Reputation: 16249
I'm currently working on a CLI abstraction layer, which abstracts CLI programs as classes in Python. Such CLI programs offer a structured way to enable and configure CLI parameters. It helps checking for faulty inputs and generated properly escaped arguments (e.g. adding double quotes).
Note: The following example is using Git, while in my target application, it will be commercial tools, that don't offer any Python API or similar.
Basic Ideas:
Git
class, which derives from class Program
.Git
class.CLIOption
derived from Attribute
Git
is used to enabled / configure CLI parameters and helps to assemble a list of correctly encoded strings that can be used e.g. in subprocess.Popen(...)
tool = Git()
tool[tool.FlagVersion] = True
print(tool.ToArgumentList())
Some Python Code:
from pyAttributes import Attribute
class CLIOption(Attribute): ... # see pyAttributes for more details
class Argument:
_name: ClassVar[str]
def __init_subclass__(cls, *args, name: str = "", **kwargs):
super().__init_subclass__(*args, **kwargs)
cls._name = name
class FlagArgument(Argument): ...
class CommandArgument(Argument): ...
class Program:
__cliOptions__: Dict[Type[Argument], Optional[Argument]]
def __init_subclass__(cls, *args, **kwargs):
"""Hook into subclass creation to register all marked CLI options in ``__cliOptions__``.
super().__init_subclass__(*args, **kwargs)
# get all marked options and
cls.__cliOptions__ = {}
for option in CLIOption.GetClasses():
cls.__cliOptions__[option] = None
class Git(Program):
@CLIOption()
class FlagVersion(FlagArgument, name="--version"): ...
@CLIOption()
class FlagHelp(FlagArgument, name="--help"): ...
@CLIOption()
class CmdCommit(CommandArgument, name="commit"): ...
Observations:
FlagVersion
flag. Some as -v
, but others as --version
.Primary Questions:
FlagVersion
is a nested class of class Git
?What I investigated so far:
isinstance(...)
or issubclass(...)
are offering.__module__
reference to the outer scope, nested classes have no "pointer" to the next outer scope.__module__
values.__qualname__
includes the names of parent classes.Git.FlagVersion
So I see a possible "ugly" solution using __qualname__
and string operations to check if a class is nested and if it's nested in a certain outer scope.
Algorithm:
__module__
and __qualname__
.This gets even more complicated if one nested class is defined in a parent class and another nested class is defined in a derived class. Then I also need to look into MRO ... oOo
Secondary Questions:
Upvotes: 0
Views: 97
Reputation: 16249
Following the proposed approaches by iterating __dict__
works quite good.
So this was the first solution developed based on the given ideas:
def isnestedclass(cls: Type, scope: Type) -> bool:
for memberName in scope.__dict__:
member = getattr(scope, memberName)
if type(member) is type:
if cls is member:
return True
return False
That solution doesn't work on members inherited from parent classes.
So I extended it with searching through the inheritance graph via mro()
.
This is my current and final solution for a isnestedclass
helper function.
def isnestedclass(cls: Type, scope: Type) -> bool:
for mroClass in scope.mro():
for memberName in mroClass.__dict__:
member = getattr(mroClass, memberName)
if type(member) is type:
if cls is member:
return True
return False
The function is available within the pyTooling package.
Upvotes: 0
Reputation: 504
Because I don’t understand English very well, I understand by translating is that you need to find out how to get the embedded class decorated by CLIOption
in the subclass of Program
(Git
here). If so, the following methods may help you.
I read some codes of some pyAttributes
from pyAttributes import Attribute
class Program(object):
__cliOptions__: Dict[Type[Argument], Optional[Argument]]
def __init_subclass__(cls, *args, **kwargs):
cls.__cliOptions__ = {}
for obj in cls.__dict__.values():
if hasattr(obj, Attribute.__AttributesMemberName__):
print(obj)
# for option in CLIOption.GetClasses():
# cls.__cliOptions__[option] = None
class Git(Program):
a = 1
b = 2
@CLIOption()
class FlagVersion(FlagArgument, name="--version"):
...
@CLIOption()
class FlagHelp(FlagArgument, name="--help"):
...
Of course, the above can’t work directly. Later I found that there was of course an error in the Attribute._AppendAttribute
method, as follows, I modified it
class CLIOption(Attribute):
... # see pyAttributes for more details
@staticmethod
def _AppendAttribute(func: Callable, attribute: 'Attribute') -> None:
# inherit attributes and prepend myself or create a new attributes list
if Attribute.__AttributesMemberName__ in func.__dict__:
func.__dict__[Attribute.__AttributesMemberName__].insert(0, attribute)
else:
# The original func.__setattr__(Attribute.__AttributesMemberName__, [attribute]) has an error
# Because __setattr__ of class FlagVersion is object.__setattr__
setattr(func, Attribute.__AttributesMemberName__, [attribute])
# or object.__setattr__(func, Attribute.__AttributesMemberName__, [attribute])
Upvotes: 1