Skip to content

Commit fee3baf

Browse files
author
hartsantler
committed
made __getattr__ work with parent and grandparent classes,
fixed class attribute lookup on grandparents, made closure compatible again.
1 parent 5efddb3 commit fee3baf

2 files changed

Lines changed: 127 additions & 14 deletions

File tree

pythonscript/python_to_pythonjs.py

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,26 @@ def get_pythonjs_function_name(self, name):
104104
assert name in self.methods
105105
return '__%s_%s' %(self.name, name) ## class name
106106

107+
def check_for_parent_with(self, method=None, property=None, operator=None, class_attribute=None):
108+
for parent_name in self.parents:
109+
typedef = self.compiler.get_typedef( class_name=parent_name )
110+
if method and method in typedef.methods:
111+
return typedef
112+
elif property and property in typedef.properties:
113+
return typedef
114+
elif operator and typedef.operators:
115+
return typedef
116+
elif class_attribute in typedef.class_attributes:
117+
return typedef
118+
elif typedef.parents:
119+
res = typedef.check_for_parent_with(
120+
method=method,
121+
property=property,
122+
operator=operator,
123+
class_attribute=class_attribute
124+
)
125+
if res:
126+
return res
107127

108128
class PythonToPythonJS(NodeVisitor):
109129

@@ -112,6 +132,7 @@ class PythonToPythonJS(NodeVisitor):
112132
def __init__(self, module=None, module_path=None):
113133
super(PythonToPythonJS, self).__init__()
114134
self._classes = dict() ## class name : [method names]
135+
self._class_parents = dict() ## class name : parents
115136
self._instance_attributes = dict() ## class name : [attribute names]
116137
self._class_attributes = dict()
117138
self._catch_attributes = None
@@ -124,21 +145,27 @@ def __init__(self, module=None, module_path=None):
124145
self._module = module
125146
self._module_path = module_path
126147

148+
self._typedefs = dict() ## class name : typedef (not pickled)
149+
127150
def get_typedef(self, instance=None, class_name=None):
128151
assert instance or class_name
129152
if isinstance(instance, Name) and instance.id in self._instances:
130153
class_name = self._instances[ instance.id ]
131154

132155
if class_name:
133156
assert class_name in self._classes
134-
typedef = Typedef(
135-
name = class_name,
136-
methods = self._classes[ class_name ],
137-
properties = self._decorator_class_props[ class_name ],
138-
attributes = self._instance_attributes[ class_name ],
139-
class_attributes = self._class_attributes[ class_name ],
140-
)
141-
return typedef
157+
158+
if class_name not in self._typedefs:
159+
self._typedefs[ class_name ] = Typedef(
160+
name = class_name,
161+
methods = self._classes[ class_name ],
162+
properties = self._decorator_class_props[ class_name ],
163+
attributes = self._instance_attributes[ class_name ],
164+
class_attributes = self._class_attributes[ class_name ],
165+
parents = self._class_parents[ class_name ],
166+
compiler = self,
167+
)
168+
return self._typedefs[ class_name ]
142169

143170
def save_module(self):
144171
if self._module and self._module_path:
@@ -148,6 +175,7 @@ def save_module(self):
148175
class_attributes = self._class_attributes,
149176
decorator_class_props = self._decorator_class_props,
150177
function_return_types = self._function_return_types,
178+
class_parents = self._class_parents,
151179
)
152180
pickle.dump( a, open(os.path.join(self._module_path, self._module+'.module'), 'wb') )
153181

@@ -165,6 +193,7 @@ def visit_ImportFrom(self, node):
165193
self._instance_attributes.update( a['instance_attributes'] )
166194
self._decorator_class_props.update( a['decorator_class_props'] )
167195
self._function_return_types.update( a['function_return_types'] )
196+
self._class_parents.update( a['class_parents'] )
168197

169198
def visit_Assert(self, node):
170199
## hijacking "assert isinstance(a,A)" as a type system ##
@@ -200,7 +229,7 @@ def visit_AugAssign(self, node):
200229
def visit_Yield(self, node):
201230
return 'yield %s' % self.visit(node.value)
202231

203-
def _gen_getattr_helper(self, class_name, func_name):
232+
def _gen_getattr_helper(self, class_name, func_name): ## DEPRECATED
204233
'''
205234
This helper is used to emulate how Python works, __getattr__ is only supposed
206235
to be called when the attribute is not found on the instance.
@@ -222,6 +251,7 @@ def _gen_getattr_helper(self, class_name, func_name):
222251
def visit_ClassDef(self, node):
223252
name = node.name
224253
self._classes[ name ] = list() ## method names
254+
self._class_parents[ name ] = set()
225255
self._class_attributes[ name ] = set()
226256
self._catch_attributes = None
227257
self._decorator_properties = dict() ## property names : {'get':func, 'set':func}
@@ -237,11 +267,16 @@ def visit_ClassDef(self, node):
237267

238268

239269
writer.write('var(%s, __%s_attrs, __%s_parents)' % (name, name, name))
240-
writer.write('__%s_attrs = JSObject()' % name)
241-
writer.write('__%s_parents = JSArray()' % name)
270+
writer.write('window["__%s_attrs"] = JSObject()' % name)
271+
writer.write('window["__%s_parents"] = JSArray()' % name)
242272
for base in node.bases:
243273
code = '__%s_parents.push(%s)' % (name, self.visit(base))
244274
writer.write(code)
275+
if isinstance(base, Name):
276+
self._class_parents[ name ] = base.id
277+
else:
278+
raise NotImplementedError
279+
245280
for item in node.body:
246281
if isinstance(item, FunctionDef):
247282
self._classes[ name ].append( item.name )
@@ -254,7 +289,7 @@ def visit_ClassDef(self, node):
254289
if item_name in self._decorator_properties:
255290
pass
256291
else:
257-
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
292+
writer.write('window["__%s_attrs"]["%s"] = %s' % (name, item_name, item.name))
258293

259294
#if item_name == '__getattr__':
260295
# writer.write( self._gen_getattr_helper(name, item.name) )
@@ -263,7 +298,7 @@ def visit_ClassDef(self, node):
263298
item_name = item.targets[0].id
264299
item.targets[0].id = '__%s_%s' % (name, item_name)
265300
self.visit(item) # this will output the code for the assign
266-
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.targets[0].id))
301+
writer.write('window["__%s_attrs"]["%s"] = %s' % (name, item_name, item.targets[0].id))
267302
self._class_attributes[ name ].add( item_name ) ## should this come before self.visit(item) ??
268303
else:
269304
raise NotImplementedError
@@ -275,7 +310,7 @@ def visit_ClassDef(self, node):
275310
self._decorator_properties = None
276311
self._instances.pop('self')
277312

278-
writer.write('%s = create_class("%s", __%s_parents, __%s_attrs)' % (name, name, name, name))
313+
writer.write('%s = create_class("%s", window["__%s_parents"], window["__%s_attrs"])' % (name, name, name, name))
279314

280315
def visit_If(self, node):
281316
writer.write('if %s:' % self.visit(node.test))
@@ -427,6 +462,22 @@ def visit_Attribute(self, node):
427462
elif '__getattr__' in typedef.methods:
428463
func = typedef.get_pythonjs_function_name( '__getattr__' )
429464
return '%s([%s, "%s"])' %(func, node_value, node.attr)
465+
466+
elif typedef.check_for_parent_with( property=node.attr ):
467+
parent = typedef.check_for_parent_with( property=node.attr )
468+
getter = parent.properties[ node.attr ]['get']
469+
if getter in self._function_return_types:
470+
node.returns_type = self._function_return_types[getter]
471+
return '%s( [%s] )' %(getter, node_value)
472+
elif typedef.check_for_parent_with( class_attribute=node.attr ):
473+
#return 'get_attribute(%s, "%s")' % (node_value, node.attr) ## get_attribute is broken with grandparent class attributes
474+
parent = typedef.check_for_parent_with( class_attribute=node.attr )
475+
return "window['__%s_attrs']['%s']" %(parent.name, node.attr)
476+
elif typedef.check_for_parent_with( method='__getattr__' ):
477+
parent = typedef.check_for_parent_with( method='__getattr__' )
478+
func = parent.get_pythonjs_function_name( '__getattr__' )
479+
return '%s([%s, "%s"])' %(func, node_value, node.attr)
480+
430481
else:
431482
return 'get_attribute(%s, "%s")' % (node_value, node.attr)
432483
else:
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<html>
2+
<head>
3+
<script src="pythonscript.js"></script>
4+
5+
<script type="text/python" closure="true">
6+
7+
class A:
8+
X = 'root parent class attribute'
9+
10+
def __getattr__(self, name):
11+
if name == 'hello':
12+
return 100
13+
elif name == 'world':
14+
return 200
15+
else:
16+
return 300
17+
18+
class B(A):
19+
Y = 'parent class attribute'
20+
21+
@property
22+
def y(self):
23+
return self._y
24+
25+
class C( B ):
26+
Z = 'my class attribute'
27+
28+
def __init__(self, x,y,z):
29+
self.x = x
30+
self._y = y
31+
self._z = z
32+
33+
@property
34+
def z(self):
35+
return self._z
36+
37+
38+
39+
def test():
40+
a = C(1,2,3)
41+
print('--testing root class level attribute')
42+
print( a.X )
43+
print('--testing parent class level attribute')
44+
print( a.Y )
45+
print('--testing class level attribute')
46+
print( a.Z )
47+
print('--testing normal attributes and @property')
48+
print( a.x )
49+
print( a.y )
50+
print( a.z )
51+
print('--testing __getattr__')
52+
print( a.hello )
53+
print( a.world )
54+
print( a.XXX )
55+
56+
</script>
57+
</head>
58+
59+
<body>
60+
<button onclick="test()">click me</button>
61+
</body>
62+
</html>

0 commit comments

Comments
 (0)