Create a class with static functions? - Python - python

I need to have this import specifically in the class to prevent my tests having to include the imports for each test (they can't be declared outside because of circular dependency issues with the entire codebase).
I am trying to declare my imports in a class, and access what I need with functions but not too sure how to make this work so that any test can call the functions and get what we need.
I have this at the moment:
class KTestHelper:
""" Small helper class for retrieving our hook and constants"""
from fd.fdee import (
IMOLDER,
KDER,
)
p3 = P3Hook()
#staticmethod
def get_imolder(self) -> str:
return self.IMOLDER
#staticmethod
def get_kder(self) -> str:
return self.KDER
#staticmethod
def get_p3_hook(self) -> P3Hook:
return self.p3
self obviously no longer exists as i added #staticmethod but now i'm not sure how to get it to work properly.
I need to be able to do KTestHelper.get_imolder() on every test function / some test functions that need it.

This strategy isn't enough to prevent the module import. The class will be constructed during the module load and the class attributes get evaluated. Example:
test/
__init__.py
a.py
b.py
c.py
a.py
print("loading module a")
class A:
pass
b.py
print("loading module b")
class B:
from .a import A
c.py
from test.b import B
This will output:
loading module b
loading module a
If you want this to work you'll need to put the import line in the class methods themselves.
print("loading module b")
class B:
#classmethod
def a(cls):
from .a import A
return A

Related

Registering classes to factory with classes in different files

I have a factory as shown in the following code:
class ClassFactory:
registry = {}
#classmethod
def register(cls, name):
def inner_wrapper(wrapped_class):
if name in cls.registry:
print(f'Class {name} already exists. Will replace it')
cls.registry[name] = wrapped_class
return wrapped_class
return inner_wrapper
#classmethod
def create_type(cls, name):
exec_class = cls.registry[name]
type = exec_class()
return type
#ClassFactory.register('Class 1')
class M1():
def __init__(self):
print ("Starting Class 1")
#ClassFactory.register('Class 2')
class M2():
def __init__(self):
print("Starting Class 2")
This works fine and when I do
if __name__ == '__main__':
print(ClassFactory.registry.keys())
foo = ClassFactory.create_type("Class 2")
I get the expected result of dict_keys(['Class 1', 'Class 2']) Starting Class 2
Now the problem is that I want to isolate classes M1 and M2 to their own files m1.py and m2.py, and in the future add other classes using their own files in a plugin manner.
However, simply placing it in their own file
m2.py
from test_ import ClassFactory
#MethodFactory.register('Class 2')
class M2():
def __init__(self):
print("Starting Class 2")
gives the result dict_keys(['Class 1']) since it never gets to register the class.
So my question is: How can I ensure that the class is registered when placed in a file different from the factory, without making changes to the factory file whenever I want to add a new class? How to self register in this way? Also, is this decorator way a good way to do this kind of thing, or are there better practices?
Thanks
How can I ensure that the class is registered when placed in a file different from the factory, without making changes to the factory file whenever I want to add a new class?
I'm playing around with a similar problem, and I've found a possible solution. It seems too much of a 'hack' though, so set your critical thinking levels to 'high' when reading my suggestion below :)
As you've mentioned in one of your comments above, the trick is to force the loading of the individual *.py files that contain individual class definitions.
Applying this to your example, this would involve:
Keeping all class implementations in a specific folders, e.g., structuring the files as follows:
.
└- factory.py # file with the ClassFactory class
└─ classes/
└- __init__.py
└- m1.py # file with M1 class
└- m2.py # file with M2 class
Adding the following statement to the end of your factory.py file, which will take care of loading and registering each individual class:
from classes import *
Add a piece of code like the snippet below to your __init__.py within the classes/ foder, so that to dynamically load all classes [1]:
from inspect import isclass
from pkgutil import iter_modules
from pathlib import Path
from importlib import import_module
# iterate through the modules in the current package
package_dir = Path(__file__).resolve().parent
for (_, module_name, _) in iter_modules([package_dir]):
# import the module and iterate through its attributes
module = import_module(f"{__name__}.{module_name}")
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if isclass(attribute):
# Add the class to this package's variables
globals()[attribute_name] = attribute
If I then run your test code, I get the desired result:
# test.py
from factory import ClassFactory
if __name__ == "__main__":
print(ClassFactory.registry.keys())
foo = ClassFactory.create_type("Class 2")
$ python test.py
dict_keys(['Class 1', 'Class 2'])
Starting Class 2
Also, is this decorator way a good way to do this kind of thing, or are there better practices?
Unfortunately, I'm not experienced enough to answer this question. However, when searching for answers to this problem, I've came across the following sources that may be helpful to you:
[2] : this presents a method for registering class existence based on Python Metaclasses. As far as I understand, it relies on the registering of subclasses, so I don't know how well it applies to your case. I did not follow this approach, as I've noticed that the new edition of the book suggests the use of another technique (see bullet below).
[3], item 49 : this is the 'current' suggestion for subclass registering, which relies on the definition of the __init_subclass__() function in a base class.
If I had to apply the __init_subclass__() approach to your case, I'd do the following:
Add a Registrable base class to your factory.py (and slightly re-factor ClassFactory), like this:
class Registrable:
def __init_subclass__(cls, name:str):
ClassFactory.register(name, cls)
class ClassFactory:
registry = {}
#classmethod
def register(cls, name:str, sub_class:Registrable):
if name in cls.registry:
print(f'Class {name} already exists. Will replace it')
cls.registry[name] = sub_class
#classmethod
def create_type(cls, name):
exec_class = cls.registry[name]
type = exec_class()
return type
from classes import *
Slightly modify your concrete classes to inherit from the Registrable base class, e.g.:
from factory import Registrable
class M2(Registrable, name='Class 2'):
def __init__(self):
print ("Starting Class 2")

Redundant classname references in Python

My python program has the following structure
projectfolder
|--classes
|--__init__.py
|--Exampleclass.py
|--singletons
|--__init__.py
|--Examplesingleton.py
__init__.py
Main.py
Basically, in my main class, I want to create several instances of Exampleclass.py (and other classes from the folder classes) and put them inside an instance of Examplesingleton.py (basically the "World" that is populated by the instances of classes).
The problem is, I always have to call Examplesingleton and Exampleclass like this:
Main.py
import singletons.Examplesingleton as Examplesingleton
import classes.Exampleclass as Exampleclass
test = Exampleclass.Exampleclass("test")
Examplesingleton.Examplesingleton.Exampleclasses.append(test)
Is there a way in Python to reference these classes without typing the Class name twice?
Instead of
Exampleclass.Exampleclass()
I want to write just
Exampleclass()
I tried using
from classes.Exampleclass import Exampleclass
seems to enable that but leads to circular import error messages.
Exampleclass.py:
class Exampleclass:
Somevariable = 0
def __init__(self, somevariable):
self.Somevariable = somevariable
Examplesingleton.py:
class Examplesingleton(object):
Exampleclasses = []
#classmethod
def __init__(cls):
pass

"from module import class" importing other classes from same module

Given the following files:
a.py
-----
class CommonClass(object):
def do_thing(self):
pass
b.py
-----
from a import CommonClass
class SubClassA(CommonClass):
def do_thing(self):
print("I am A")
class SubClassB(CommonClass):
def do_thing(self):
print("I am B")
c.py
-----
from a import CommonClass
from b import SubClassA
if __name__ == "__main__":
for member in CommonClass.__subclasses__():
member().do_thing()
I would expect only SubClassA is imported, and visible when looping through subclasses of CommonClass, but it seems SubClassB is imported as well.
I am running Python 3.8.5 and this is the output of python3 c.py:
$ python3 c.py
I am A
I am B
How can I only import the classes I want?
You did import only SubClassA to c.py. This can be tested by doing
x = SubClassB()
or
x = b.SubClassB()
Both will result in a NameError.
The thing is, that when you import a file, it is actually being ran, even when using from x import y!
This can be easily seen by adding a print("I'm from b.py") at the end of b.py and then running c.py.
This makes both SubClassA and SubClassB be subclasses of CommonClass, which you imported. So, while you don't have access to the SubClassB name, it is still a subclass of CommonClass and you can access it from there.
In general you don't have to import a module to be able to use its objects. Importing expands your namespace to include that module so you can create an object directly. You can still use this module's objects even without importing it if you acquired them in some other way (like importing a third module which returns objects from the second one).
Anyway right now, you are not really using even the imported SubClassA. If you want to "allow" certain classes to be considered only from an external source, you can create an allowed set of classes:
from a import CommonClass
from b import SubClassA
allowed_classes = {SubClassA}
if __name__ == "__main__":
for member in CommonClass.__subclasses__():
if member in allowed_classes:
member().do_thing()
Which only prints I am A
from a import CommonClass
from b import SubClassA
if __name__ == "__main__":
h = CommonClass.__subclasses__()[0]()
h.do_thing()
You can do this.

How to import one class from many classes on one python file

Here is is the directory structure:
->src/dir/classes.py
->src/run.py
# classes.py
class A():
def methA():
# class A
class B():
def MethB():
# class B
class C():
def methC():
# class C
then i need to import Class A in run.py file.
from dir.classes import A
A.methA()
i already tried with using from dir.classes import A but it gives me
ModuleNotFoundError: No module named 'dir.classes'; 'classes' is not a package error
So how can i do that?
You need to put__init__.py file on your dir folder.
This way dir will be recognized as python package.
First, you must have __init__.py in each directory for Python to recognize them as packages.
Then, you should use from dir.classes import A. A is the name of the class, you shouldn't use Class A

How to mock a class in an instance attribute using patch

I'm fairly new to python and currently attempting to write a unit test for a class, but am having some problems with mocking out dependencies. I have 2 classes, one of which (ClassB) is a dependency of the other (ClassC). The goal is to mock out ClassB and the ArgumentParser classes in the test case for ClassC. ClassB looks as follows:
# defined in a.b.b
class ClassB:
def doStuff(self) -> None:
# do stuff
pass
def doSomethingElse(self) -> None:
# do something else
pass
ClassC:
# defined in a.b.c
from .b import ClassB
from argparse import ArgumentParser
class ClassC:
b
def __init__(self) -> None:
arguments = self.parseArguments()
self.b = ClassB()
self.b.doStuff()
def close(self) -> None:
self.b.doSomethingElse()
def parseArguments(self) -> dict:
c = ArgumentParser()
return return parser.parse_args()
And finally, the test case for ClassC:
# inside a.b.test
from unittest import TestCase
from unittest.mock import patch, MagicMock
from a.b.c import ClassC
class ClassCTest(TestCase):
#patch('a.b.c.ClassB')
#patch('a.b.c.ArgumentParser')
def test__init__(self, mock_ArgumentParser, mock_ClassB):
c = ClassC()
print(isinstance(c.b, MagicMock)) # outputs False
# for reference
print(isinstance(mock_ClassB, MagicMock)) # outputs True
I read in the patch docs that it's important to mock the class in the namespace it is used not where it is defined. So that's what I did, I mocked: a.b.c.classB instead of a.b.b.classB, have tried both though. I also tried importing ClassC inside the test__init__ method body, but this also didn't work.
I prefer not mocking methods of ClassB but rather the entire class to keep the test as isolated as possible.
Environment info:
Python 3.6.1
Any help would be greatly appreciated!
Since i'm new to python i didn't know about class attributes. I had a class attribute in ClassC that held ClassB and an instance attribute in init that shadowed the class attribute.

Categories