Skip to content

Commit

Permalink
style: replace yapf by ruff formatter (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucsorel authored Nov 9, 2023
1 parent 4b8f785 commit c0369fe
Show file tree
Hide file tree
Showing 32 changed files with 329 additions and 267 deletions.
12 changes: 2 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ repos:
- id: trailing-whitespace
# ensures that all files end in a newline and only a newline
- id: end-of-file-fixer
# replace "double quotes" by 'single quotes' unless "it's impossible"
- id: double-quote-string-fixer
# prevents large files from being committed (>100kb)
- id: check-added-large-files
args: [--maxkb=100]
Expand All @@ -32,14 +30,8 @@ repos:
- id: isort
additional_dependencies: [toml]

- repo: https://github.com/google/yapf
rev: v0.40.2
hooks:
- id: yapf
name: Yapf
additional_dependencies: [toml]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
rev: v0.1.5
hooks:
- id: ruff
- id: ruff-format
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ To save time (my writing time and your reading time), I tried to make it short s

To homogenize code style consistency and enforce code quality, this project uses:

- the `yapf` formatter and the `ruff` linter
- the `ruff` linter and formatter
- the `isort` formatter for imports, because it handles the 'tests' sections in the imports
- `pre-commit` hooks that are triggered on the Github CI (in pull-requests) and should be activated locally when contributing to the project:

```sh
Expand All @@ -75,6 +76,9 @@ poetry install

# activates the pre-commit hooks
poetry run pre-commit install --hook-type pre-commit --hook-type commit-msg

# runs the code quality hooks on the codebase
poetry run pre-commit run --all-files
```

## Unit tests
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ poetry run pytest -v --cov=py2puml --cov-branch --cov-report term-missing --cov-

# Changelog

* `upcoming`: replaced yapf by the ruff formatter
* `0.8.1`: delegated the grouping of nested namespaces (see `0.7.0`) to the PlantUML binary, which handles it natively
* `0.8.0`: added support for union types, and github actions (pre-commit hooks + automated tests)
* `0.7.2`: added the current working directory to the import path to make py2puml work in any directory or in native virtual environment (not handled by poetry)
Expand Down
8 changes: 5 additions & 3 deletions py2puml/asserts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ def assert_py2puml_is_file_content(domain_path: str, domain_module: str, diagram


def normalize_lines_with_returns(lines_with_returns: Iterable[str]) -> List[str]:
'''
"""
When comparing contents, each piece of contents can either be:
- a formatted string block output by the py2puml command containg line returns
- a single line of contents read from a file, each line ending with a line return
This function normalizes each sequence of contents as a list of string lines,
each one finishing without a line return to ease comparison.
'''
"""
return ''.join(lines_with_returns).split('\n')


Expand All @@ -34,6 +34,8 @@ def assert_multilines(actual_multilines: List[str], expected_multilines: List[st
line_index = 0
for line_index, (actual_line, expected_line) in enumerate(zip(actual_multilines, expected_multilines)):
# print(f'{actual_line=}\n{expected_line=}')
assert actual_line == expected_line, f'actual and expected contents have changed at line {line_index + 1}: {actual_line=}, {expected_line=}'
assert (
actual_line == expected_line
), f'actual and expected contents have changed at line {line_index + 1}: {actual_line=}, {expected_line=}'

assert line_index + 1 == len(actual_multilines), f'actual and expected diagrams have {line_index + 1} lines'
30 changes: 15 additions & 15 deletions py2puml/export/puml.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
from py2puml.domain.umlitem import UmlItem
from py2puml.domain.umlrelation import UmlRelation

PUML_FILE_START = '''@startuml {diagram_name}
PUML_FILE_START = """@startuml {diagram_name}
!pragma useIntermediatePackages false
'''
PUML_FILE_FOOTER = '''footer Generated by //py2puml//
'''
PUML_FILE_END = '''@enduml
'''
PUML_ITEM_START_TPL = '''{item_type} {item_fqn} {{
'''
PUML_ATTR_TPL = ''' {attr_name}: {attr_type}{staticity}
'''
PUML_ITEM_END = '''}
'''
PUML_RELATION_TPL = '''{source_fqn} {rel_type}-- {target_fqn}
'''
"""
PUML_FILE_FOOTER = """footer Generated by //py2puml//
"""
PUML_FILE_END = """@enduml
"""
PUML_ITEM_START_TPL = """{item_type} {item_fqn} {{
"""
PUML_ATTR_TPL = """ {attr_name}: {attr_type}{staticity}
"""
PUML_ITEM_END = """}
"""
PUML_RELATION_TPL = """{source_fqn} {rel_type}-- {target_fqn}
"""

FEATURE_STATIC = ' {static}'
FEATURE_INSTANCE = ''
Expand All @@ -46,7 +46,7 @@ def to_puml_content(diagram_name: str, uml_items: List[UmlItem], uml_relations:
yield PUML_ATTR_TPL.format(
attr_name=uml_attr.name,
attr_type=uml_attr.type,
staticity=FEATURE_STATIC if uml_attr.static else FEATURE_INSTANCE
staticity=FEATURE_STATIC if uml_attr.static else FEATURE_INSTANCE,
)
yield PUML_ITEM_END
else:
Expand Down
30 changes: 20 additions & 10 deletions py2puml/inspection/inspectclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ def handle_inheritance_relation(


def inspect_static_attributes(
class_type: Type, class_type_fqn: str, root_module_name: str, domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation]
class_type: Type,
class_type_fqn: str,
root_module_name: str,
domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation],
) -> List[UmlAttribute]:
'''
"""
Adds the definitions:
- of the inspected type
- of its static attributes from the class annotations (type and relation)
'''
"""
# defines the class being inspected
definition_attrs: List[UmlAttribute] = []
uml_class = UmlClass(
Expand Down Expand Up @@ -85,8 +88,11 @@ def inspect_static_attributes(


def inspect_class_type(
class_type: Type, class_type_fqn: str, root_module_name: str, domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation]
class_type: Type,
class_type_fqn: str,
root_module_name: str,
domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation],
):
attributes = inspect_static_attributes(
class_type, class_type_fqn, root_module_name, domain_items_by_fqn, domain_relations
Expand All @@ -99,11 +105,15 @@ def inspect_class_type(


def inspect_dataclass_type(
class_type: Type[dataclass], class_type_fqn: str, root_module_name: str, domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation]
class_type: Type[dataclass],
class_type_fqn: str,
root_module_name: str,
domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation],
):
for attribute in inspect_static_attributes(class_type, class_type_fqn, root_module_name, domain_items_by_fqn,
domain_relations):
for attribute in inspect_static_attributes(
class_type, class_type_fqn, root_module_name, domain_items_by_fqn, domain_relations
):
attribute.static = False

handle_inheritance_relation(class_type, class_type_fqn, root_module_name, domain_relations)
2 changes: 1 addition & 1 deletion py2puml/inspection/inspectenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ def inspect_enum_type(enum_type: Type[Enum], enum_type_fqn: str, domain_items_by
domain_items_by_fqn[enum_type_fqn] = UmlEnum(
name=enum_type.__name__,
fqn=enum_type_fqn,
members=[Member(name=enum_member.name, value=enum_member.value) for enum_member in enum_type]
members=[Member(name=enum_member.name, value=enum_member.value) for enum_member in enum_type],
)
17 changes: 11 additions & 6 deletions py2puml/inspection/inspectmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@ def filter_domain_definitions(module: ModuleType, root_module_name: str) -> Iter
definition_members = getmembers(definition_type)
definition_module_member = next(
(
member for member in definition_members
member
for member in definition_members
# ensures that the type belongs to the module being parsed
if member[0] == '__module__' and member[1].startswith(root_module_name)
),
None
None,
)
if definition_module_member is not None:
yield definition_type


def inspect_domain_definition(
definition_type: Type, root_module_name: str, domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation]
definition_type: Type,
root_module_name: str,
domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation],
):
definition_type_fqn = f'{definition_type.__module__}.{definition_type.__name__}'
if definition_type_fqn not in domain_items_by_fqn:
Expand All @@ -49,8 +52,10 @@ def inspect_domain_definition(


def inspect_module(
domain_item_module: ModuleType, root_module_name: str, domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation]
domain_item_module: ModuleType,
root_module_name: str,
domain_items_by_fqn: Dict[str, UmlItem],
domain_relations: List[UmlRelation],
):
# processes only the definitions declared or imported within the given root module
for definition_type in filter_domain_definitions(domain_item_module, root_module_name):
Expand Down
2 changes: 1 addition & 1 deletion py2puml/inspection/inspectnamedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ def inspect_namedtuple_type(namedtuple_type: Type, namedtuple_type_fqn: str, dom
domain_items_by_fqn[namedtuple_type_fqn] = UmlClass(
name=namedtuple_type.__name__,
fqn=namedtuple_type_fqn,
attributes=[UmlAttribute(tuple_field, 'Any', False) for tuple_field in namedtuple_type._fields]
attributes=[UmlAttribute(tuple_field, 'Any', False) for tuple_field in namedtuple_type._fields],
)
47 changes: 30 additions & 17 deletions py2puml/parsing/astvisitors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
from ast import (
AnnAssign, Assign, Attribute, BinOp, FunctionDef, Name, NodeVisitor, Subscript, arg, expr, get_source_segment
AnnAssign,
Assign,
Attribute,
BinOp,
FunctionDef,
Name,
NodeVisitor,
Subscript,
arg,
expr,
get_source_segment,
)
from collections import namedtuple
from typing import Dict, List, Tuple
Expand All @@ -13,9 +23,10 @@


class SignatureVariablesCollector(NodeVisitor):
'''
"""
Collects the variables and their type annotations from the signature of a constructor method
'''
"""

def __init__(self, constructor_source: str, *args, **kwargs):
super().__init__(*args, **kwargs)
self.constructor_source = constructor_source
Expand All @@ -34,38 +45,40 @@ def visit_arg(self, node: arg):


class AssignedVariablesCollector(NodeVisitor):
'''Parses the target of an assignment statement to detect whether the value is assigned to a variable or an instance attribute'''
"""Parses the target of an assignment statement to detect whether the value is assigned to a variable or an instance attribute"""

def __init__(self, class_self_id: str, annotation: expr):
self.class_self_id: str = class_self_id
self.annotation: expr = annotation
self.variables: List[Variable] = []
self.self_attributes: List[Variable] = []

def visit_Name(self, node: Name):
'''
"""
Detects declarations of new variables
'''
"""
if node.id != self.class_self_id:
self.variables.append(Variable(node.id, self.annotation))

def visit_Attribute(self, node: Attribute):
'''
"""
Detects declarations of new attributes on 'self'
'''
"""
if isinstance(node.value, Name) and node.value.id == self.class_self_id:
self.self_attributes.append(Variable(node.attr, self.annotation))

def visit_Subscript(self, node: Subscript):
'''
"""
Assigns a value to a subscript of an existing variable: must be skipped
'''
"""
pass


class ConstructorVisitor(NodeVisitor):
'''
"""
Identifies the attributes (and infer their type) assigned to self in the body of a constructor method
'''
"""

def __init__(
self, constructor_source: str, class_name: str, root_fqn: str, module_resolver: ModuleResolver, *args, **kwargs
):
Expand Down Expand Up @@ -97,7 +110,7 @@ def get_from_namespace(self, variable_id: str) -> Variable:
for variable in self.variables_namespace[::-1]
if variable.id == variable_id
),
None
None,
)

def generic_visit(self, node):
Expand Down Expand Up @@ -154,11 +167,11 @@ def visit_Assign(self, node: Assign):
self.variables_namespace.extend(variables_collector.variables)

def derive_type_annotation_details(self, annotation: expr) -> Tuple[str, List[str]]:
'''
"""
From a type annotation, derives:
- a short version of the type (withenum.TimeUnit -> TimeUnit, Tuple[withenum.TimeUnit] -> Tuple[TimeUnit])
- a list of the full-namespaced definitions involved in the type annotation (in order to build the relationships)
'''
"""
if annotation is None:
return None, []

Expand All @@ -182,13 +195,13 @@ def derive_type_annotation_details(self, annotation: expr) -> Tuple[str, List[st


def shorten_compound_type_annotation(type_annotation: str, module_resolver: ModuleResolver) -> Tuple[str, List[str]]:
'''
"""
In the string representation of a compound type annotation, the elementary types can be prefixed by their packages or sub-packages.
Like in 'Dict[datetime.datetime,typing.List[Worker]]'. This function returns a tuple of 2 values:
- a string representation with shortened types for display purposes in the PlantUML documentation: 'Dict[datetime, List[Worker]]'
(note: a space is inserted after each coma for readability sake)
- a list of the fully-qualified types involved in the annotation: ['typing.Dict', 'datetime.datetime', 'typing.List', 'mymodule.Worker']
'''
"""
compound_type_parts: List[str] = CompoundTypeSplitter(type_annotation, module_resolver.module.__name__).get_parts()
compound_short_type_parts: List[str] = []
associated_types: List[str] = []
Expand Down
Loading

0 comments on commit c0369fe

Please sign in to comment.