Python - Dynamic class import - python

I have the following folder structure:
- MyProject
- App1
- some_module1.py
- some_module2.py
- App2
- some_other_module1.py
- some_other_module2.py
Inside each of the modules (some_module1.py for example) there is a class that extends from a base class, in my case, Producer.
What I am trying to do is dynamically load in this class. To do that, I have a list of "installed apps" that looks like this:
INSTALLED_APPS = (
'App1',
'App2',
)
I am trying to write a function that will check each "app" package for a particular producer class and ensure it extends from the producer base class. Something like this:
module_class = 'some_module1.SomeClass'
# Loop through each package in the INSTALLED_APPS tuple:
for app in INSTALL_APPS:
try:
#is the module_class found in this app?
#App1.some_module1.SomeClass - Yes
#App2.some_module1.SomeClass - No
# is the class we found a subclass of Producer?
exception ImportError:
pass
I've tried experimenting with imp and importlib, but it doesn't seem to handle this kind of import. Is there anyway for me to be able to achieve this?

You may want to have a look at:
__import__() to import modules knowing their name as string;
dir() to get the names of all the objects of a module (attributes, functions, etc);
inspect.isclass(getattr(<module ref>, <object name>)) to identify classes among the objects of a module;
issubclass() to identify sub-classes from a given class, as explained here.
With these tools, you can identify all classes in a given module, that are inheriting a given class.
I'm using this mechanism to dynamically instantiate classes from given module, so that their updates are automatically taken into account at an upper level.

Related

Is it a bad practice (anti-pattern) to provide a module as an argument when initialising a class?

I've a implemented a BaseScanner inside common/base/scanner.py that is subclasses by Scanner inside stash/scanner.py, jira/scanner.py, etc.
Now, the problem is that BaseScanner needs to access the ORM models in e.g. stash/db/models.py, depending on where it's subclasses (in stash, jira, etc.):
# common package
common/base/scanner.py
# stash package
stash/scanner.py
stash/db/models.py
# jira package
jira/scanner.py
jira/db/models.py
...
Is it an anti-pattern to provide a module as an argument to a class when instantiating it, like I do here in main.py?
import stash, jira
...
if args.command == 'stash':
import stash.db.models as models
scanner = jira.Scanner(args, models, ...)
scanner.run()
and then to access the different ORM models from inside BaseScanner, like self.models.Scan, self.models.Match, etc.?
If it's an anti-pattern, what could an alternative solution be?

Creating Custom SessionStore for a django project

If I want to subclass a module such as django.contrib.sessions.backends.db.SessionStore so that I can override a lot of its default behaviour, what is the standard way of doing this in Django?
Suppose I create a file called mydb.py:
from django.contrib.sessions.backends.db import SessionStore as DBSessionStore
class SessionStore(DBSessionStore):
...
If my project has this structure below, is it best practice to put, mydb.py in a backends directory under project's folder?
myproject
myproject/manage.py
myproject/myproject
myproject/myproject/wsgi.py
myproject/myproject/__init__.py
myproject/myproject/settings.py
myproject/myproject/urls.py
myproject/db.sqlite3
myproject/myapp
myproject/myapp/tests.py
myproject/myapp/admin.py
myproject/myapp/__init__.py
myproject/myapp/models.py
myproject/myapp/apps.py
myproject/myapp/migrations
myproject/myapp/migrations/__init__.py
myproject/myapp/views.py
myproject/myapp/urls.py
myproject/backends
myproject/backends/__init__.py
myproject/backends/mydb.py
myproject/__init__.py
Is settings.SESSION_ENGINE='backends.db' a reasonable standard to avoid namespace collisions? Is it a general rule of djamgo configurations that the current project is included in the python search path?
You should refer to it as to the file:
SESSION_ENGINE = 'python.path.mydb'
Django docs is missing this little detail. In
https://docs.djangoproject.com/en/2.2/_modules/django/contrib/sessions/middleware/ line 12 (django 1.9), you can find this:
self.SessionStore = engine.SessionStore
So it's literally taking the SessionStore class from engine you provided in settings. SESSION_ENGINE.

Same imports across different files

Is it possible to include libraries/packages in only one location?
class Sample( db.Model ):
randomText = db.StringProperty( multiline = True )
--
from google.appengine.ext import db
from project.models import Sample
class SampleHandler( ):
def get( self ):
xamp = Sample.all( )
Since the handler already imports db from the google.appengine.ext library/package, and then imports the model i'd assume you don't have to include it again in the model itself. However, it looks like I have to, any way?
Anyone care to explain?
You need to import modules where they are used.
If your models module uses the google.appengine.ext.db module, you need to import it there, not in your handler module.
Importing things creates a reference to that 'thing' in your module namespace, so that the code there can find it when using it. db is the local name by which you get to use the object defined in google.appengine.ext.
If your handler uses the same object, it needs to import that still. If by importing models all names used by models suddenly where available in your handler module too, you'd end up with name conflicts and hard-to-debug errors all over the place.
Vice versa, if only importing google.appengine.ext.db in your handler module and not in your models module were to work, you'd need to import all the dependencies of given module together with the module itself. This quickly becomes unworkable, as you'd need to document all the things your models module requires just to be able to use it.

Django: tests.py adds project name to class

In my views.py I have a piece of code that checks if a model inherits from an other model.
def inherits_from_animal(some_animal):
return Animal in inspect.getmro(some_animal)
Now in my views.py this works but in my tests it's failing because the name of the project gets added to the class name.
So if I have a project named zoo and an app named animals it looks like:
views.py:
<class 'animals.models.Animal'>
tests.py:
<class 'zoo.animals.models.Animal'>
Could anybody explain why this is happening and how I could fix it?
Update:
Seems to only be happening to an abstract model.
The problem is manage.py. If you put django apps inside other packages you should use their fully qualified module name for imports and as a corrolary in INSTALLED_APPS.
It still works if you omit the parent package name, because manage.py temporarily hacks sys.path to import the module that contains the settings module. Then nose does its own import magic and is unable to reproduce the manage.py hack.
I'd recommend to use django-admin.py instead of manage.py as it does not do any import tricks.
There is no concept of a project in django. Just treat everything as plain python packages and control your PYTHONPATH.
the inspect module is doing probably something hinky.
Python has the builtin function issubclass() for just this occasion!
def inherits_from_animal(some_animal):
return issubclass(some_animal, Animal)
although i dont see why you couldnt just use issubclass wherever you are using inherits_from_animal now ;)

How do I get the filepath for a class in Python?

Given a class C in Python, how can I determine which file the class was defined in? I need something that can work from either the class C, or from an instance off C.
The reason I am doing this, is because I am generally a fan off putting files that belong together in the same folder. I want to create a class that uses a Django template to render itself as HTML. The base implementation should infer the filename for the template based on the filename that the class is defined in.
Say I put a class LocationArtifact in the file "base/artifacts.py", then I want the default behaviour to be that the template name is "base/LocationArtifact.html".
You can use the inspect module, like this:
import inspect
inspect.getfile(C.__class__)
try:
import sys, os
os.path.abspath(sys.modules[LocationArtifact.__module__].__file__)
This is the wrong approach for Django and really forcing things.
The typical Django app pattern is:
/project
/appname
models.py
views.py
/templates
index.html
etc.

Categories

Resources