Use inspect module to grab the name of inherited object - python

I want to look at a file and get the names of classes and check if the "Runconfig" name is inherited. So if a file has
class some_function(RunConfig):
I want to return true.
My code looks like this right now:
for file in list_of_files:
if file in ['some_file.py']:
for name,obj in inspect.getmembers(file):
if inspect.isclass(obj):
print("NAME",name,"obj",obj)
This returns objects but I don't see anything that says 'RunConfig' on it.
What am I missing here?
Thank you so much in advance!

You can do something like:
import importlib
import inspect
def is_class_inherited_in_file(file_name, class_ref):
module = importlib.import_module(file_name.split('.')[0])
module_members = inspect.getmembers(module)
for member in module_members:
if type(member[1]) == type and issubclass(member[1], class_ref):
return True
return False
>>> is_class_inherited_in_file('some_file.py', RunConfig)
True
Assumption:
The filename is in the working directory. If you would like to import from any directory, then do something like: How to import a module given the full path?

Related

Python: always import the last revision in the directory

Imagine that we have the following Data Base structure with the data stored in python files ready to be imported:
data_base/
foo_data/
rev_1.py
rev_2.py
bar_data/
rev_1.py
rev_2.py
rev_3.py
In my main script, I would like to import the last revision of the data available in the folder. For example, instead of doing this:
from data_base.foo_data.rev_2 import foofoo
from data_base.bar_data.rev_3 import barbar
I want to call a method:
import_from_db(path='data_base.foo_data', attr='foofoo', rev='last')
import_from_db(path='data_base.bar_data', attr='barbar', rev='last')
I could take a relative path to the Data Base and use glob.glob to search the last revision, but for this, I should know the path to the data_base folder, which complicates things (imagine that the parent folder of the data_base is in sys.path so the from data_base.*** import will work)
Is there an efficient way to maybe retrieve a full path knowing only part of it (data_base.foo_data)? Other ideas?
I think it's better to install the last version.
but going on with your flow, you may use getattr on the module:
from data_base import foo_data
i = 0
while True:
try:
your_module = getattr(foo_data, f'rev_{i}')
except AttributeError:
break
i += 1
# Now your_module is the latest rev
#JohnDoriaN 's idea led me to a quite simple solution:
import os, glob
def import_from_db(import_path, attr, rev_id=None):
"""
"""
# Get all the modules/folders names
dir_list = import_path.split('.')
# Import the last module
exec(f"from {'.'.join(dir_list[:-1])} import {dir_list[-1]}")
db_parent = locals()[dir_list[-1]]
# Get an absolute path to corresponding to the db_parent folder
abs_path = db_parent.__path__._path[0]
rev_path = os.path.join(abs_path, 'rev_*.py')
rev_names = [os.path.basename(x) for x in glob.glob(rev_path)]
if rev_id is None:
revision = rev_names[-1]
else:
revision = rev_names[rev_id]
revision = revision.split('.')[0]
# import attribute
exec(f'from {import_path}.{revision} import {attr}', globals())
Some explanations:
Apparently (I didn't know this), we can import a folder as a module; this module has a __path__ attribute (found out using the built-in dir method).
glob.glob allows us to use regex expressions to search for a required pattern for files in the directory.
using exec without parameters will import only in the local namespace (namespace of the method) so without polluting the global namespace.
using exec with globals() allows us to import in the global namespace.

Patching a function in a file where it is defined

I am trying to learn unittest patching. I have a single file that both defines a function, then later uses that function. When I try to patch this function, its return value is giving me the real return value, not the patched return value.
How do I patch a function that is both defined and used in the same file? Note: I did try to follow the advice given here, but it didn't seem to solve my problem.
walk_dir.py
from os.path import dirname, join
from os import walk
from json import load
def get_config():
current_path =dirname(__file__)
with open(join(current_path, 'config', 'json', 'folder.json')) as json_file:
json_data = load(json_file)
return json_data['parent_dir']
def get_all_folders():
dir_to_walk = get_config()
for root, dir, _ in walk(dir_to_walk):
return [join(root, name) for name in dir]
test_walk_dir.py
from hello_world.walk_dir import get_all_folders
from unittest.mock import patch
#patch('walk_dir.get_config')
def test_get_all_folders(mock_get_config):
mock_get_config.return_value = 'C:\\temp\\test\\'
result = get_all_folders()
assert set(result) == set('C:\\temp\\test\\test_walk_dir')
Try declaring the patch in such way:
#patch('hello_world.walk_dir.get_config')
As you can see this answer to the question you linked, it's recommended that your import statements match your patch statements. In your case from hello_world.walk_dir import get_all_folders and #patch('walk_dir.get_config') doesn't match.

Possible to generate auto-complete from a namespace object?

Imagine I have a .env file that looks like this.
EARTH_SYNONYM1 = "World"
EARTH_SYNONYM2 = "Planet"
EARTH_SYNONYM3 = "Globe"
I've managed to load it into a namespace.
import json
from pathlib import Path
from types import SimpleNamespace
from dotenv.main import dotenv_values # dotenv package needs to be installed.
def json_to_python(json_str):
return json.loads(json_str, object_hook=lambda d: SimpleNamespace(**d))
dotenv_path = Path(".") / ".env"
dotenv_vars_list = dotenv_values(dotenv_path)
dotenv_vars_as_json = json.dumps(dotenv_vars_list)
dotenv_vars = json_to_python(dotenv_vars_as_json)
print(dotenv_vars)
Which prints
namespace(EARTH_SYNONYM1='World', EARTH_SYNONYM2='Planet', EARTH_SYNONYM3='Globe')
Now I can do things like
print(f"Hello {dotenv_vars.EARTH_SYNONYM1}")
Which prints
Hello World
What I would love to accomplish is turning this into a class module (I think).
I'd like to
import dotenv_vars
And then type
dotenv_vars.
and be presented with a list of auto-complete options.
Continuing this example, each of the EARTH_SYNONYMx would show as auto-complete options.
How can I make the namespace object provide auto-complete?
I always thought dotenv is a pretty silly library and overcomplicating something that is so simple. Load your vars into a module namespace with Python's import system.
import imp
dotenv_vars = imp.load_source('dotenv_vars', '.env')

Getting attributes from a function in another file

I have a main file which is looking at files within a /modules/ folder, it needs to look at every .py file and find all functions that have a specific attribute.
An example module will be like this:
def Command1_1():
True
Command1_1.command = ['cmd1']
def Command1_2():
True
The code I am currently using to look through each file and function is this:
for module in glob.glob('modules/*.py'):
print(module)
tree = ast.parse(open(module, "rt").read(), filename=PyBot.msggrp + module)
for item in [x.name for x in ast.walk(tree) if isinstance(x, ast.FunctionDef)]:
if item is not None:
print(str(item))
Below is what the code produces but I cannot find a way to show if a function has a ".command" attribute:
modules/Placeholder001.py
Command1_1
Command1_2
modules/Placeholder002.py
Command2_1
Command2_2
Command2_3
The easiest way is to import each file and then look for functions in its global scope. Functions can be identified with the use of callable. Checking if a function has an attribute can be done with hasattr.
The code to import a module from a path is taken from this answer.
from pathlib import Path
import importlib.util
def import_from_path(path):
spec = importlib.util.spec_from_file_location(path.stem, str(path))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
for module_path in Path('modules').glob('*.py'):
module = import_from_path(module_path)
for name, value in vars(module).items():
if callable(value):
has_attribute = hasattr(value, 'command')
print(name, has_attribute)
Output:
Command1_1 True
Command1_2 False

Django - importing dynamically

I'm trying to do a dynamic import of a python module in django. I have two different apps that I want to import from, and I want to replace these import statements:
from app1.forms import App1ProfileForm
from app2.forms import App2ProfileForm
I am dynamically able to create the strings App1ProfileForm and App2ProfileForm and then instantiate them like so:
globals()[form]()
I tried following some of the instructions in this post: Dynamically import class by name for static access
and so I tried doing this:
theModule = __import__("app1.forms.App1ProfileForm")
but I'm getting an error that says No module named App1ProfileForm
EDIT:::
Ok I tried this code:
theModule = __import__("app1")
print theModule
theClass = getattr(theModule,'forms')
print theClass
theForm = getattr(theClass,'App1ProfileForm')
print theForm
theForm.initialize()
but I get an error that type object 'App1ProfileForm' has no attribute 'initialize'
You don't want to do this. Imports are done when the relevant code is first executed - in the case of module-level imports, it's when the module itself is imported. If you're depending on something in the request, or some other run-time element, to determine what class you want, then this will not work.
Instead, just import them both, and get the code to choose which one you need:
from app1.forms import App1ProfileForm
from app2.forms import App2ProfileForm
forms = {'app1': App1ProfileForm,
'app2': App2ProfileForm}
relevant_form = forms[whatever_the_dependent_value_is]
I don't quite know how you're generting the string to import. I'll assume you generate the whole "path". Try this:
def import_from_strings(paths):
ret = []
for path in paths:
module_name, class_name = path.rsplit('.', 1)
module = __import__(module_name, globals(), locals(), [class_name], -1)
ret.append(getattr(module, class_name))
return ret
Aren't you trying to import a class, and not a module ? I'm not an expert, but I think you must import the module using __import__, then select it's App1ProfileForm class with something like yourmodule.App1ProfileForm
I figured it out. Here's how to do it:
theModule = __import__(module_name+".forms") # for some reason need the .forms part
theClass = getattr(theModule,'forms')
theForm = getattr(theClass,form_name)
then to initialize:
theForm() or theForm(request.POST)

Categories