Skip to content

Commit 5454ec9

Browse files
update with comments
1 parent 652940d commit 5454ec9

File tree

1 file changed

+162
-122
lines changed

1 file changed

+162
-122
lines changed
Lines changed: 162 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,192 @@
11
package com.regnosys.rosetta.generator.python.util;
22

3-
import java.util.HashSet;
3+
import java.util.Map;
44
import java.util.Set;
55

66
import com.regnosys.rosetta.types.RAttribute;
77
import com.regnosys.rosetta.types.REnumType;
88
import com.regnosys.rosetta.types.RType;
99

10-
public class RuneToPythonMapper {
11-
private static final Set<String> PYTHON_KEYWORDS = new HashSet<>();
12-
13-
static {
14-
// Initialize the set with Python keywords and soft keywords
15-
PYTHON_KEYWORDS.add("False");
16-
PYTHON_KEYWORDS.add("await");
17-
PYTHON_KEYWORDS.add("else");
18-
PYTHON_KEYWORDS.add("import");
19-
PYTHON_KEYWORDS.add("pass");
20-
PYTHON_KEYWORDS.add("None");
21-
PYTHON_KEYWORDS.add("break");
22-
PYTHON_KEYWORDS.add("except");
23-
PYTHON_KEYWORDS.add("in");
24-
PYTHON_KEYWORDS.add("raise");
25-
PYTHON_KEYWORDS.add("True");
26-
PYTHON_KEYWORDS.add("class");
27-
PYTHON_KEYWORDS.add("finally");
28-
PYTHON_KEYWORDS.add("is");
29-
PYTHON_KEYWORDS.add("return");
30-
PYTHON_KEYWORDS.add("and");
31-
PYTHON_KEYWORDS.add("continue");
32-
PYTHON_KEYWORDS.add("for");
33-
PYTHON_KEYWORDS.add("lambda");
34-
PYTHON_KEYWORDS.add("try");
35-
PYTHON_KEYWORDS.add("as");
36-
PYTHON_KEYWORDS.add("def");
37-
PYTHON_KEYWORDS.add("from");
38-
PYTHON_KEYWORDS.add("nonlocal");
39-
PYTHON_KEYWORDS.add("while");
40-
PYTHON_KEYWORDS.add("assert");
41-
PYTHON_KEYWORDS.add("del");
42-
PYTHON_KEYWORDS.add("global");
43-
PYTHON_KEYWORDS.add("not");
44-
PYTHON_KEYWORDS.add("with");
45-
PYTHON_KEYWORDS.add("async");
46-
PYTHON_KEYWORDS.add("elif");
47-
PYTHON_KEYWORDS.add("if");
48-
PYTHON_KEYWORDS.add("or");
49-
PYTHON_KEYWORDS.add("yield");
50-
PYTHON_KEYWORDS.add("match");
51-
PYTHON_KEYWORDS.add("case");
52-
PYTHON_KEYWORDS.add("type");
53-
PYTHON_KEYWORDS.add("_");
10+
/**
11+
* Utility for mapping Rune/Rosetta types and identifiers to Python-friendly names and types.
12+
*
13+
* Note: Keep the Python keyword/soft-keyword list aligned with the Python version you target.
14+
* You can regenerate a canonical list via:
15+
* ```python
16+
* import keyword; print(sorted(keyword.kwlist))
17+
* ```
18+
* and add any soft keywords you wish to avoid (e.g., "match", "case") for your generator context.
19+
*/
20+
21+
public final class RuneToPythonMapper {
22+
23+
private RuneToPythonMapper() {
24+
// Prevent instantiation (utility class)
5425
}
5526

56-
// Define the set of Python types as a static final field
27+
// Prefix used to avoid collisions with Python reserved words or leading underscores.
28+
private static final String GENERATED_ATTR_PREFIX = "rune_attr_";
29+
30+
// Python keywords and soft keywords to avoid as identifiers (case-sensitive, as in Python).
31+
// Sorted alphabetically for maintainability.
32+
private static final Set<String> PYTHON_KEYWORDS = Set.of(
33+
"False", "None", "True", "_",
34+
"and", "as", "assert", "async", "await",
35+
"break",
36+
"case", "class", "continue",
37+
"def", "del",
38+
"elif", "else", "except",
39+
"finally", "for", "from",
40+
"global",
41+
"if", "import", "in", "is",
42+
"lambda",
43+
"match",
44+
"nonlocal", "not",
45+
"or",
46+
"pass",
47+
"raise", "return",
48+
"try", "type",
49+
"while", "with",
50+
"yield"
51+
);
52+
53+
// Python basic types recognized by the generator. Includes both qualified and unqualified forms
54+
// as they may appear in generated output or user-provided annotations.
5755
private static final Set<String> PYTHON_TYPES = Set.of(
58-
"int", "str", "Decimal", "date", "datetime", "datetime.datetime",
59-
"datetime.date", "datetime.time", "time", "bool"
56+
"Decimal", "bool", "date", "datetime", "datetime.date",
57+
"datetime.datetime", "datetime.time", "int", "str", "time"
6058
);
6159

62-
public static String mangleName(String attrib) {
63-
// Check if the attribute is a Python keyword or starts with an underscore
64-
if (PYTHON_KEYWORDS.contains(attrib) || attrib.charAt(0) == '_') {
65-
return "rune_attr_" + attrib;
66-
}
67-
return attrib;
68-
}
69-
70-
private static String toPythonBasicTypeInnerFunction(String rosettaType) {
71-
// inner private function to convert from Rosetta type to Python type
72-
// returns null if no matching type
73-
switch (rosettaType) {
74-
case "string":
75-
case "eventType":
76-
case "calculation":
77-
case "productType":
78-
return "str";
79-
case "time":
80-
return "datetime.time";
81-
case "date":
82-
return "datetime.date";
83-
case "dateTime":
84-
case "zonedDateTime":
85-
return "datetime.datetime";
86-
case "number":
87-
return "Decimal";
88-
case "boolean":
89-
return "bool";
90-
case "int":
91-
return "int";
92-
default:
93-
return null;
60+
// Mapping from Rosetta scalar types to Python basic types used by the generator.
61+
// Keys are the Rosetta/Rune type names.
62+
private static final Map<String, String> ROSETTA_TO_PY_BASIC = Map.ofEntries(
63+
Map.entry("boolean", "bool"),
64+
Map.entry("calculation", "str"),
65+
Map.entry("date", "datetime.date"),
66+
Map.entry("dateTime", "datetime.datetime"),
67+
Map.entry("eventType", "str"),
68+
Map.entry("int", "int"),
69+
Map.entry("number", "Decimal"),
70+
Map.entry("productType", "str"),
71+
Map.entry("string", "str"),
72+
Map.entry("time", "datetime.time"),
73+
Map.entry("zonedDateTime", "datetime.datetime")
74+
);
75+
76+
// Mapping from Python basic types to their "WithMeta" counterparts used by the generator.
77+
private static final Map<String, String> PY_BASIC_TO_META = Map.ofEntries(
78+
Map.entry("Decimal", "NumberWithMeta"),
79+
Map.entry("bool", "BoolWithMeta"),
80+
Map.entry("datetime.date", "DateWithMeta"),
81+
Map.entry("datetime.datetime", "DateTimeWithMeta"),
82+
Map.entry("datetime.time", "TimeWithMeta"),
83+
Map.entry("int", "IntWithMeta"),
84+
Map.entry("str", "StrWithMeta")
85+
);
86+
87+
/**
88+
* Mangles an identifier to avoid clashes with Python reserved words and names starting with "_".
89+
*
90+
* Null handling:
91+
* - Returns null if the input is null (caller-friendly for streaming/pipelines).
92+
*
93+
* @param name original identifier (may be null)
94+
* @return either the original name or the prefixed name if mangling is required
95+
*/
96+
public static String mangleName(String name) {
97+
if (name == null) return null;
98+
if (PYTHON_KEYWORDS.contains(name) || name.startsWith("_")) {
99+
return GENERATED_ATTR_PREFIX + name;
94100
}
101+
return name;
95102
}
96103

97-
public static String getAttributeTypeWithMeta (String attributeType) {
98-
// inner private function to convert from Rosetta type to Python type
99-
// returns null if no matching type
100-
switch (attributeType) {
101-
case "str":
102-
return "StrWithMeta";
103-
case "datetime.time":
104-
return "TimeWithMeta";
105-
case "datetime.date":
106-
return "DateWithMeta";
107-
case "datetime.datetime":
108-
return "DateTimeWithMeta";
109-
case "Decimal":
110-
return "NumberWithMeta";
111-
case "bool":
112-
return "BoolWithMeta";
113-
case "int":
114-
return "IntWithMeta";
115-
default:
116-
return attributeType;
117-
}
104+
/**
105+
* Maps a Python basic type to its WithMeta wrapper type used by the generator.
106+
*
107+
* @param pythonType Python basic type name (may be null)
108+
* @return the WithMeta type if known; otherwise returns the input unchanged; returns null if input is null
109+
*/
110+
public static String getAttributeTypeWithMeta(String pythonType) {
111+
if (pythonType == null) return null;
112+
return PY_BASIC_TO_META.getOrDefault(pythonType, pythonType);
118113
}
114+
115+
/**
116+
* Maps a Rosetta/Rune scalar type name to a Python basic type.
117+
*
118+
* @param rosettaType Rosetta type name (may be null)
119+
* @return Python basic type if mapped; otherwise the original input; null if input is null
120+
*/
119121
public static String toPythonBasicType(String rosettaType) {
120-
String pythonType = toPythonBasicTypeInnerFunction(rosettaType);
121-
return (pythonType == null) ? rosettaType : pythonType;
122+
if (rosettaType == null) return null;
123+
return ROSETTA_TO_PY_BASIC.getOrDefault(rosettaType, rosettaType);
122124
}
123125

126+
/**
127+
* Maps an RType to its Python type representation.
128+
* - If it's a Rosetta basic type, returns the mapped Python basic type.
129+
* - Otherwise returns a qualified name "namespace.TypeName".
130+
* - For enums, appends ".TypeName" to reference the enum class within its module/package.
131+
*
132+
* @param rt Rosetta type (may be null)
133+
* @return Python type name (possibly qualified), or null if input is null
134+
*/
124135
public static String toPythonType(RType rt) {
125-
if (rt == null)
126-
return null;
127-
var pythonType = toPythonBasicTypeInnerFunction(rt.getName());
128-
if (pythonType == null) {
129-
String rtName = rt.getName();
130-
pythonType = rt.getNamespace().toString() + "." + rtName;
131-
pythonType = (rt instanceof REnumType) ? pythonType + "." + rtName : pythonType;
132-
}
133-
return pythonType;
136+
if (rt == null) return null;
137+
138+
// Prefer basic mapping when available
139+
String basic = ROSETTA_TO_PY_BASIC.get(rt.getName());
140+
if (basic != null) return basic;
141+
142+
String rtName = rt.getName();
143+
String namespace = rt.getNamespace() != null ? rt.getNamespace().toString() : "";
144+
String qualified = namespace.isEmpty() ? rtName : namespace + "." + rtName;
145+
146+
// For enums, retain original behavior: append ".<TypeName>"
147+
return (rt instanceof REnumType) ? qualified + "." + rtName : qualified;
134148
}
149+
150+
/**
151+
* Checks whether a Rosetta type name corresponds to a Python basic type.
152+
*
153+
* @param rtName Rosetta type name (may be null)
154+
* @return true if the name maps to a Python basic type; false otherwise
155+
*/
135156
public static boolean isRosettaBasicType(String rtName) {
136-
return (toPythonBasicTypeInnerFunction(rtName) != null);
157+
return rtName != null && ROSETTA_TO_PY_BASIC.containsKey(rtName);
137158
}
159+
160+
/**
161+
* Checks whether a Rosetta RType corresponds to a Python basic type.
162+
*
163+
* @param rt Rosetta type (may be null)
164+
* @return true if the type maps to a Python basic type; false otherwise
165+
*/
138166
public static boolean isRosettaBasicType(RType rt) {
139-
return (toPythonBasicTypeInnerFunction(rt.getName()) != null);
167+
return rt != null && isRosettaBasicType(rt.getName());
140168
}
169+
170+
/**
171+
* Safely checks if an attribute's type maps to a Python basic type.
172+
* Defensive null checks avoid NullPointerExceptions on partially-populated models.
173+
*
174+
* @param ra Rosetta attribute (may be null)
175+
* @return true if the attribute's type maps to a Python basic type; false otherwise
176+
*/
141177
public static boolean isRosettaBasicType(RAttribute ra) {
142-
if (ra == null) {
143-
return false;
144-
}
178+
if (ra == null || ra.getRMetaAnnotatedType() == null) return false;
145179
RType rt = ra.getRMetaAnnotatedType().getRType();
146-
return (rt != null) ? isRosettaBasicType(rt.getName()) : false;
180+
return rt != null && isRosettaBasicType(rt.getName());
147181
}
148-
public static boolean isPythonBasicType(final String pythonType) {
149-
// Check if the given type is in the set of Python types
150-
return PYTHON_TYPES.contains(pythonType);
182+
183+
/**
184+
* Checks whether a Python type name is one of the generator's recognized basic types.
185+
*
186+
* @param pythonType Python type name (may be null)
187+
* @return true if the type is recognized as a basic Python type by the generator; false otherwise
188+
*/
189+
public static boolean isPythonBasicType(String pythonType) {
190+
return pythonType != null && PYTHON_TYPES.contains(pythonType);
151191
}
152-
}
192+
}

0 commit comments

Comments
 (0)