Skip to content
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

detect instance variables in constructor #7

Closed
lucsorel opened this issue Jul 1, 2020 · 7 comments · Fixed by #14
Closed

detect instance variables in constructor #7

lucsorel opened this issue Jul 1, 2020 · 7 comments · Fixed by #14
Assignees
Labels
enhancement New feature or request fixed

Comments

@lucsorel
Copy link
Owner

lucsorel commented Jul 1, 2020

The current approach detects class variables (which is how dataclass works). But non-dataclass classes should be handled as well.

@doyou89
Copy link
Contributor

doyou89 commented Jan 29, 2021

To detect instance variables, The class should be instantiated first.
Create the class with it's parameters as mock.
Initial parameters can be detected from init using #3.

For example,

from inspect import signature, Parameter
from unittest import mock

def instantiate_class(class_type):
    params = []
    for p, v in signature(class_type.__init__).parameters.items():
        if p == 'self': continue
        if v.kind == Parameter.POSITIONAL_ONLY or v.kind == Parameter.POSITIONAL_OR_KEYWORD:
            if v.default is not Parameter.empty:    # default value exists
                if v.default is not None:                   # and not None
                    params.append(v.default)
            else:
                if v.annotation == Parameter.empty:    # value type is not specified
                    spec = None
                else:
                    spec = find_class_type(v.annotation)
                params.append(mock.MagicMock(spec=spec))

    obj = class_type(*params)
    return obj

find_class_type need and have to recommend class type for initialization.
If the class_type is List[str], find_class_type returns str.

@lucsorel lucsorel self-assigned this Feb 28, 2021
@lucsorel lucsorel added enhancement New feature or request in progress Somebody is working on it labels Feb 28, 2021
@lucsorel
Copy link
Owner Author

yes it is an approach that can be followed. The one I am following in my current work is to process the AST of the constructor and detect self.x = x assignment.

@MMartin09
Copy link

Sry but I don't understand what you mean. If I have a class

class Test:
  string_1: str = "String 1"
  string_2: str = "String 2"

  def __init__(self):
    pass

How can I create the correct UML?

@lucsorel
Copy link
Owner Author

lucsorel commented Mar 1, 2021

in this case, the class test has 2 class attributes and no instance attribute. The value of string_1 and string_2 are shared for all instances of the Test class.

If you want instance attributes, the code should be:

class Test:
  def __init__(self, string_1: str, string_2: str):
    self.string_1 = string_1
    self.string_2 = string_2

And if you want instance attributes with default values:

class Test:
  def __init__(self, string_1: str = 'String 1', string_2: str = 'String 2'):
    self.string_1 = string_1
    self.string_2 = string_2

When I started this library, it was mainly to create a class diagram of dataclasses. The @dataclass decorator actually use the definition of class attributes to generate a constructor with instance attributes (among other things). This issue aims at handling instance attributes defined in the constructor of a class which is not a dataclass.

@MMartin09
Copy link

Okay, I understand. But I think this could be a really great tool.

@doyou89
Copy link
Contributor

doyou89 commented Jun 24, 2021

Followings are the code that gets instance variables from the class definition code.
This may be helpful for this issue.

class Address(object):
    street: str
    zipcode: str
    city: str

class Worker:
    name: str
    # forward refs are skipped for now
    colleagues: List['Worker']

    def __init__(self):
        self.addr: Address
        self.address: Address = Address()
        self.old_addr = Address()
        self.cons = []
        self.cons[0] = 0
        self.age = self.max_age = 0
        flag = 0
        self.age += 1
        addr = Address()
    
    def func(self, age):
        self.your_age = age
        return self.age
"""

import ast
import pprint
p=ast.parse(code)
for cls in p.body:
    if isinstance(cls, ast.ClassDef):
        print(cls.name)
        for m in cls.body:
            if isinstance(m, ast.FunctionDef):
                print(m.name)
                for a in m.body:
                    if isinstance(a, ast.AnnAssign):
                        if isinstance(a.target, ast.Attribute):
                            if a.target.value.id == 'self':
                                print(f'{a.target.value.id}.{a.target.attr}: {a.annotation.id}')
                    elif isinstance(a, ast.AugAssign):
                        pass
                    elif isinstance(a, ast.Assign):
                        for t in a.targets:
                            if isinstance(t, ast.Attribute):
                                if t.value.id == 'self':
                                    print(f'{t.value.id}.{t.attr}')

This will generate output:

Address
Worker
__init__
self.addr: Address
self.address: Address
self.old_addr
self.cons
self.age
self.max_age
func
self.your_age

@lucsorel
Copy link
Owner Author

lucsorel commented Jul 2, 2021

hi @doyou89 😃

Thank you for your suggestion. I am coming up with some similar code, but following the visitor pattern so that:

  • the underlying code does not rely on the code structure: the self.x = ... assignments could be within several layers of if conditions
  • it can handle different assignment spellings (self.x = x, self.x, self.y = x, y, etc.)

Thank you for pointing out some cases like self.cons[0] = 0.

@lucsorel lucsorel removed the in progress Somebody is working on it label Sep 3, 2021
@lucsorel lucsorel added the fixed label Oct 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request fixed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants