-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add public variables #5
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
5f8c2b4
Fix setModel() accepting None
bluebird75 2909d5b
WIP for testing qt dbus interface
bluebird75 354ca04
WIP: annotate public variables
bluebird75 11a03c7
Work in progress: detect and add public variables
bluebird75 fd067ce
Annotate public variables with qualified names and modules
boldar99 fc8c25b
Merge pull request #6 from boldar99/add_public_variables
bluebird75 50b1010
Work in progress of adding all public variables
bluebird75 6aaa503
Adding public variables for all modules
bluebird75 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
from typing import Dict, Optional, Union | ||
|
||
import pathlib, json | ||
|
||
import libcst | ||
import libcst as cst | ||
import libcst.matchers as matchers | ||
|
||
JSON_INPUT_FNAME = pathlib.Path(__file__).parent / 'public-variables.json' | ||
|
||
class TypingTransformer(cst.CSTTransformer): | ||
"""TypingTransformer that visits classes and methods.""" | ||
|
||
def __init__(self, mod_name: str, d: Dict[str, str]) -> None: | ||
super().__init__() | ||
self.mod_name = mod_name | ||
self.full_name_stack = [mod_name] | ||
self.fqn_class_pub_var = d | ||
self.visited_attributes = [] | ||
|
||
|
||
def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]: | ||
"""Put a class on top of the stack when visiting.""" | ||
self.full_name_stack.append( node.name.value ) | ||
return True | ||
|
||
|
||
def leave_AnnAssign(self, original_node: cst.AnnAssign, updated_node: cst.AnnAssign ) \ | ||
-> cst.AnnAssign: | ||
fqn_class = '.'.join(self.full_name_stack) | ||
if not fqn_class in self.fqn_class_pub_var: | ||
return updated_node | ||
|
||
|
||
attr_ann_type_dict = self.fqn_class_pub_var[fqn_class] | ||
attr_name = original_node.target.value | ||
self.visited_attributes.append(attr_name) | ||
if attr_name not in attr_ann_type_dict: | ||
# we have no info about this attribute | ||
return updated_node | ||
|
||
ann_value = original_node.annotation.annotation.value | ||
if ann_value == attr_ann_type_dict[attr_name]: | ||
# we agree with annotation | ||
return updated_node | ||
|
||
# let's update the annotation | ||
print(f'Fixing {fqn_class}.{attr_name} from annotation "{ann_value}" to "{attr_ann_type_dict[attr_name]}"') | ||
return updated_node.with_changes( | ||
annotation=updated_node.annotation.with_changes( | ||
annotation=updated_node.annotation.annotation.with_changes( | ||
value=attr_ann_type_dict[attr_name] | ||
) ) ) | ||
|
||
def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) \ | ||
-> Union[cst.BaseStatement, cst.FlattenSentinel[cst.BaseStatement], cst.RemovalSentinel, ]: | ||
fqn_class = '.'.join(self.full_name_stack) | ||
self.full_name_stack.pop() | ||
|
||
# no variables to adjust | ||
if not fqn_class in self.fqn_class_pub_var: | ||
return updated_node | ||
|
||
attr_ann_type_dict = self.fqn_class_pub_var[fqn_class] | ||
|
||
nonAnnotatedAttributes = set() | ||
|
||
for class_content in updated_node.body.body: | ||
if matchers.matches(class_content, matchers.SimpleStatementLine(body=[matchers.Assign()])): | ||
nonAnnotatedAttributes.add(class_content.body[0].targets[0].target.value) | ||
|
||
|
||
missingPubVar = sorted(set(attr_ann_type_dict.keys()) - nonAnnotatedAttributes - set(self.visited_attributes)) | ||
|
||
if not missingPubVar: | ||
# all public variables are already there | ||
return updated_node | ||
|
||
pre_body = [] | ||
for pub_var in missingPubVar: | ||
print(f'Class {fqn_class}: adding public variable {pub_var}: {attr_ann_type_dict[pub_var]}') | ||
pre_body.append(libcst.parse_statement(f'{pub_var}: {attr_ann_type_dict[pub_var]}')) | ||
pre_body.insert(0, libcst.EmptyLine(indent=False, newline=libcst.Newline())) | ||
pre_body.append(libcst.EmptyLine(indent=False, newline=libcst.Newline())) | ||
|
||
if isinstance(updated_node.body, libcst.SimpleStatementSuite): | ||
# the class is a single ellipsis, we need to create a full indented body | ||
return updated_node.with_changes( | ||
body=libcst.IndentedBlock(body=pre_body) | ||
) | ||
|
||
# regular class | ||
return updated_node.with_changes( | ||
body=updated_node.body.with_changes( | ||
body=tuple(pre_body) + updated_node.body.body | ||
) | ||
) | ||
|
||
|
||
def apply_public_variables_for_module(module_path: str, d: Dict[str, str]) -> None: | ||
if module_path.name.startswith('_'): | ||
return | ||
|
||
module_name = module_path.stem | ||
|
||
print('Fixing ', module_name) | ||
with open(module_path, "r", encoding="utf-8") as fhandle: | ||
stub_tree = cst.parse_module(fhandle.read()) | ||
|
||
transformer = TypingTransformer(module_name, d) | ||
modified_tree = stub_tree.visit(transformer) | ||
|
||
with open(module_path, "w", encoding="utf-8") as fhandle: | ||
fhandle.write(modified_tree.code) | ||
|
||
|
||
def main(): | ||
with open(JSON_INPUT_FNAME, 'r') as f: | ||
d = json.load(f) | ||
|
||
for fpath in (pathlib.Path(__file__).parent.parent / 'PySide6-stubs').glob('QtWidgets.pyi'): | ||
apply_public_variables_for_module(fpath, d) | ||
|
||
|
||
|
||
if __name__ == '__main__': | ||
# auto_test() | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from typing import Dict, Type | ||
|
||
import importlib, json, pathlib | ||
|
||
from PySide6.QtWidgets import QApplication | ||
|
||
JSON_OUTPUT_FNAME = pathlib.Path(__file__).parent / 'public-variables.json' | ||
|
||
def collect_public_variables_for_module(module_name: str, d: Dict[str, str]) -> None: | ||
'''Load module, inspect all attribute types and fill dict with information''' | ||
if module_name.startswith('_'): | ||
return | ||
|
||
print('Processing %s' % module_name) | ||
try: | ||
m = importlib.import_module(f'PySide6.{module_name}') | ||
except ModuleNotFoundError: | ||
print('... Module not available!') | ||
# platform-specific modules can not be imported for example on other platforms | ||
return | ||
|
||
for class_name, class_type in m.__dict__.items(): | ||
if class_name.startswith('_'): | ||
continue | ||
|
||
collect_public_variables_for_class(f'{module_name}.{class_name}', class_type, d) | ||
|
||
def collect_public_variables_for_class(class_fqn: str, class_type: Type, d: Dict[str, str]) -> None: | ||
# we only care about classes | ||
try: | ||
class_members = class_type.__dict__.items() | ||
except AttributeError: | ||
# this is not a class | ||
return | ||
|
||
instance = None | ||
for class_attr_name, class_attr_value in class_members: | ||
if class_attr_name.startswith('_'): | ||
continue | ||
|
||
if class_attr_value.__class__.__name__ == 'getset_descriptor': | ||
|
||
# create the instance on-demand | ||
if instance is None: | ||
try: | ||
instance = class_type() | ||
except Exception: | ||
# we can not work without the instance | ||
return | ||
|
||
attr_of_instance = getattr(instance, class_attr_name) | ||
if attr_of_instance == None: | ||
continue | ||
typename = attr_of_instance.__class__.__name__ | ||
|
||
try: | ||
pub_var_dict = d[class_fqn] | ||
except KeyError: | ||
pub_var_dict = {} | ||
d[class_fqn] = pub_var_dict | ||
pub_var_dict[class_attr_name] = typename | ||
else: | ||
# try if it is a subclass | ||
collect_public_variables_for_class(f'{class_fqn}.{class_attr_name}', class_attr_value, d) | ||
|
||
|
||
def main(): | ||
application = QApplication(['-platform', 'minimal']) # needed for instancing QWidgets | ||
d = {} | ||
for fpath in (pathlib.Path(__file__).parent.parent / 'PySide6-stubs').glob('*.pyi'): | ||
module_name = fpath.stem | ||
collect_public_variables_for_module(module_name, d) | ||
|
||
with open(JSON_OUTPUT_FNAME, 'w') as f: | ||
json.dump(d, f, indent=4) | ||
|
||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do something like this to extend the types with modules:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, I did not know about that one. It will solve several cases, but not nested class. So, all enums can not be typed for example, because they always lie within another class.
Still, it's better than nothing, I'll have an attempt at it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe using
__class__.__qualname__
could solve this problem?