SQLAlchemy backrefs tend to cause circular imports as they get complex, so I came up with a way to "re-open" a Python class like in Ruby:
def reopen(cls):
"""
Moves the contents of the decorated class into an existing class and returns that.
Usage::
from .other_module import ExistingClass
@reopen(ExistingClass)
class ExistingClass:
@property
def new_property(self):
pass
This is equivalent to::
def new_property(self):
pass
ExistingClass.new_property = property(new_property)
"""
def decorator(temp_cls):
for attr, value in temp_cls.__dict__.items():
# Skip the standard Python attributes, process the rest
if attr not in ('__dict__', '__doc__', '__module__', '__weakref__'):
setattr(cls, attr, value)
return cls
return decorator
This is a simplified version; the full implementation has more safety checks and tests.
This code works fairly well for inserting bi-directional SQLAlchemy relationships and helper methods wherever SQLAlchemy's existing relationship+backref mechanism doesn't suffice.
However:
- mypy raises error "name 'ExistingClass' already defined (possibly by an import)"
- pylint raises E0102 "class already defined"
I could ignore both errors by having an annotation on the line (# type: ignore
and # skipcq
), but overuse of those annotations can let bugs slip through. It'll be nice to tell mypy and pylint that this use is okay.
How can I do that?