Skip to content

Commit 3c8461d

Browse files
committed
Don't generate symbols that shadow built-ins (fixes #13)
When generating Python names, we now check to see if that name exists in the `builtins` module. If it does, then we append an underscore. Tests are updated and I *think* they are passing but I also have some test failures from a merged branch.
1 parent 4dbaa7a commit 3c8461d

2 files changed

Lines changed: 61 additions & 46 deletions

File tree

generator/generate.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import builtins
12
from dataclasses import dataclass
23
from enum import Enum
34
import itertools
@@ -41,10 +42,12 @@
4142

4243
current_version = ''
4344

45+
4446
def indent(s: str, n: int):
4547
''' A shortcut for ``textwrap.indent`` that always uses spaces. '''
4648
return tw_indent(s, n * ' ')
4749

50+
4851
def clear_dirs(package_path: Path):
4952
''' Remove generated code. '''
5053
def rmdir(path):
@@ -82,6 +85,24 @@ def docstring(description: typing.Optional[str]) -> str:
8285
return dedent("'''\n{}\n'''").format(description)
8386

8487

88+
def is_builtin(name: str) -> bool:
89+
''' Return True if ``name`` would shadow a builtin. '''
90+
try:
91+
getattr(builtins, name)
92+
return True
93+
except AttributeError:
94+
return False
95+
96+
97+
def snake_case(name: str) -> str:
98+
''' Convert a camel case name to snake case. If the name would shadow a
99+
Python builtin, then append an underscore. '''
100+
name = inflection.underscore(name)
101+
if is_builtin(name):
102+
name += '_'
103+
return name
104+
105+
85106
def ref_to_python(ref: str) -> str:
86107
'''
87108
Convert a CDP ``$ref`` to the name of a Python type.
@@ -90,7 +111,7 @@ def ref_to_python(ref: str) -> str:
90111
'''
91112
if '.' in ref:
92113
domain, subtype = ref.split('.')
93-
ref = '{}.{}'.format(inflection.underscore(domain), subtype)
114+
ref = '{}.{}'.format(snake_case(domain), subtype)
94115
return f"{ref}"
95116

96117

@@ -148,7 +169,7 @@ class CdpProperty:
148169
@property
149170
def py_name(self) -> str:
150171
''' Get this property's Python name. '''
151-
return inflection.underscore(self.name)
172+
return snake_case(self.name)
152173

153174
@property
154175
def py_annotation(self) -> str:
@@ -333,8 +354,8 @@ def from_json(cls, json: str) -> {self.id}:
333354
if doc:
334355
code += indent(doc, 4) + '\n'
335356
for enum_member in self.enum:
336-
snake_case = inflection.underscore(enum_member).upper()
337-
enum_code = f'{snake_case} = "{enum_member}"\n'
357+
snake_name = snake_case(enum_member).upper()
358+
enum_code = f'{snake_name} = "{enum_member}"\n'
338359
code += indent(enum_code, 4)
339360
code += '\n' + indent(def_to_json, 4)
340361
code += '\n\n' + indent(def_from_json, 4)
@@ -526,7 +547,7 @@ class CdpCommand:
526547
@property
527548
def py_name(self):
528549
''' Get a Python name for this command. '''
529-
return inflection.underscore(self.name)
550+
return snake_case(self.name)
530551

531552
@classmethod
532553
def from_json(cls, command, domain) -> 'CdpCommand':
@@ -686,7 +707,7 @@ class {self.py_name}:''')
686707

687708
if self.description:
688709
desc += self.description
689-
710+
690711
code += indent(docstring(desc), 4)
691712
code += '\n'
692713
code += indent(
@@ -729,7 +750,7 @@ class CdpDomain:
729750
@property
730751
def module(self):
731752
''' The name of the Python module for this CDP domain. '''
732-
return inflection.underscore(self.domain)
753+
return snake_case(self.domain)
733754

734755
@classmethod
735756
def from_json(cls, domain: dict):
@@ -797,7 +818,7 @@ def generate_imports(self):
797818
except ValueError:
798819
continue
799820
if domain != self.domain:
800-
dependencies.add(inflection.underscore(domain))
821+
dependencies.add(snake_case(domain))
801822
code = '\n'.join(f'from . import {d}' for d in sorted(dependencies))
802823

803824
if needs_deprecation:

generator/test_generate.py

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ def test_docstring():
3434
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
3535
'''""")
3636
actual = docstring(description)
37-
print('EXPECTED:', expected)
38-
print('ACTUAL:', actual)
3937
assert expected == actual
4038

4139

@@ -62,8 +60,6 @@ def __repr__(self):
6260

6361
type = CdpType.from_json(json_type)
6462
actual = type.generate_code()
65-
print('EXPECTED:', expected)
66-
print('ACTUAL:', actual)
6763
assert expected == actual
6864

6965

@@ -93,8 +89,6 @@ def __repr__(self):
9389

9490
type = CdpType.from_json(json_type)
9591
actual = type.generate_code()
96-
print('EXPECTED:', expected)
97-
print('ACTUAL:', actual)
9892
assert expected == actual
9993

10094

@@ -133,8 +127,6 @@ def from_json(cls, json: str) -> AXValueSourceType:
133127

134128
type = CdpType.from_json(json_type)
135129
actual = type.generate_code()
136-
print('EXPECTED:', expected)
137-
print('ACTUAL:', actual)
138130
assert expected == actual
139131

140132

@@ -182,7 +174,7 @@ class AXValue:
182174
A single computed AX property.
183175
'''
184176
#: The type of this value.
185-
type: AXValueType
177+
type_: AXValueType
186178
187179
#: The computed value of this property.
188180
value: typing.Optional[typing.Any] = None
@@ -195,7 +187,7 @@ class AXValue:
195187
196188
def to_json(self) -> T_JSON_DICT:
197189
json: T_JSON_DICT = dict()
198-
json['type'] = self.type.to_json()
190+
json['type'] = self.type_.to_json()
199191
if self.value is not None:
200192
json['value'] = self.value
201193
if self.related_nodes is not None:
@@ -207,16 +199,14 @@ def to_json(self) -> T_JSON_DICT:
207199
@classmethod
208200
def from_json(cls, json: T_JSON_DICT) -> AXValue:
209201
return cls(
210-
type=AXValueType.from_json(json['type']),
202+
type_=AXValueType.from_json(json['type']),
211203
value=json['value'] if 'value' in json else None,
212204
related_nodes=[AXRelatedNode.from_json(i) for i in json['relatedNodes']] if 'relatedNodes' in json else None,
213205
sources=[AXValueSource.from_json(i) for i in json['sources']] if 'sources' in json else None,
214206
)""")
215207

216208
type = CdpType.from_json(json_type)
217209
actual = type.generate_code()
218-
print('EXPECTED:', expected)
219-
print('ACTUAL:', actual)
220210
assert expected == actual
221211

222212

@@ -297,8 +287,6 @@ def get_partial_ax_tree(
297287

298288
cmd = CdpCommand.from_json(json_cmd, 'Accessibility')
299289
actual = cmd.generate_code()
300-
print('EXPECTED:', expected)
301-
print('ACTUAL:', actual)
302290
assert expected == actual
303291

304292

@@ -319,8 +307,6 @@ def disable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
319307

320308
cmd = CdpCommand.from_json(json_cmd, 'Accessibility')
321309
actual = cmd.generate_code()
322-
print('EXPECTED:', expected)
323-
print('ACTUAL:', actual)
324310
assert expected == actual
325311

326312

@@ -345,16 +331,16 @@ def test_cdp_command_return_primitive():
345331
}
346332
expected = dedent("""\
347333
def get_current_time(
348-
id: str
334+
id_: str
349335
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,float]:
350336
'''
351337
Returns the current time of the an animation.
352338
353-
:param id: Id of animation.
339+
:param id_: Id of animation.
354340
:returns: Current time of the page.
355341
'''
356342
params: T_JSON_DICT = dict()
357-
params['id'] = id
343+
params['id'] = id_
358344
cmd_dict: T_JSON_DICT = {
359345
'method': 'Animation.getCurrentTime',
360346
'params': params,
@@ -364,8 +350,6 @@ def get_current_time(
364350

365351
cmd = CdpCommand.from_json(json_cmd, 'Animation')
366352
actual = cmd.generate_code()
367-
print('EXPECTED:', expected)
368-
print('ACTUAL:', actual)
369353
assert expected == actual
370354

371355

@@ -401,8 +385,6 @@ def get_browser_command_line() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typin
401385

402386
cmd = CdpCommand.from_json(json_cmd, 'Browser')
403387
actual = cmd.generate_code()
404-
print('EXPECTED:', expected)
405-
print('ACTUAL:', actual)
406388
assert expected == actual
407389

408390

@@ -440,8 +422,6 @@ def release_animations(
440422

441423
cmd = CdpCommand.from_json(json_cmd, 'Animation')
442424
actual = cmd.generate_code()
443-
print('EXPECTED:', expected)
444-
print('ACTUAL:', actual)
445425
assert expected == actual
446426

447427

@@ -485,8 +465,6 @@ def resolve_animation(
485465

486466
cmd = CdpCommand.from_json(json_cmd, 'Animation')
487467
actual = cmd.generate_code()
488-
print('EXPECTED:', expected)
489-
print('ACTUAL:', actual)
490468
assert expected == actual
491469

492470

@@ -582,8 +560,6 @@ def get_encoded_response(
582560

583561
cmd = CdpCommand.from_json(json_cmd, 'Audits')
584562
actual = cmd.generate_code()
585-
print('EXPECTED:', expected)
586-
print('ACTUAL:', actual)
587563
assert expected == actual
588564

589565

@@ -638,8 +614,6 @@ def grant_permissions(
638614

639615
cmd = CdpCommand.from_json(json_cmd, 'Browser')
640616
actual = cmd.generate_code()
641-
print('EXPECTED:', expected)
642-
print('ACTUAL:', actual)
643617
assert expected == actual
644618

645619

@@ -677,8 +651,6 @@ def from_json(cls, json: T_JSON_DICT) -> RecordingStateChanged:
677651

678652
cmd = CdpEvent.from_json(json_event, 'BackgroundService')
679653
actual = cmd.generate_code()
680-
print('EXPECTED:', expected)
681-
print('ACTUAL:', actual)
682654
assert expected == actual
683655

684656

@@ -740,8 +712,6 @@ def from_json(cls, json: T_JSON_DICT) -> WindowOpen:
740712

741713
cmd = CdpEvent.from_json(json_event, 'Page')
742714
actual = cmd.generate_code()
743-
print('EXPECTED:', expected)
744-
print('ACTUAL:', actual)
745715
assert expected == actual
746716

747717

@@ -846,6 +816,30 @@ def test_cdp_domain_imports():
846816

847817
domain = CdpDomain.from_json(json_domain)
848818
actual = domain.generate_imports()
849-
print('EXPECTED:', expected)
850-
print('ACTUAL:', actual)
851819
assert expected == actual
820+
821+
822+
def test_domain_shadows_builtin():
823+
''' If a domain name shadows a Python builtin, it should have an underscore
824+
appended to the module name. '''
825+
input_domain = {
826+
"domain": "Input",
827+
"types": [],
828+
"commands": [],
829+
"events": [],
830+
}
831+
domain = CdpDomain.from_json(input_domain)
832+
assert domain.module == 'input_'
833+
834+
835+
def test_domain_shadows_builtin():
836+
''' If a domain name shadows a Python builtin, it should have an underscore
837+
appended to the module name. '''
838+
input_domain = {
839+
"domain": "Input",
840+
"types": [],
841+
"commands": [],
842+
"events": [],
843+
}
844+
domain = CdpDomain.from_json(input_domain)
845+
assert domain.module == 'input_'

0 commit comments

Comments
 (0)