Skip to content

Instantly share code, notes, and snippets.

@manikos
Created March 22, 2018 14:05
Show Gist options
  • Save manikos/9ac107a1cfba602f2f021ad15863f815 to your computer and use it in GitHub Desktop.
Save manikos/9ac107a1cfba602f2f021ad15863f815 to your computer and use it in GitHub Desktop.
Various helper functions
from importlib.util import find_spec
from keyword import iskeyword
from django.apps import apps
from django.db.models.fields.files import ImageFileDescriptor
from pack.validation.const import CONFIG_NAME
class DictAttr(dict):
def __getattr__(self, item):
return self.__getitem__(item)
def __setattr__(self, key, value):
self.__setitem__(key, value)
def split_the_path(dotted_path):
return dotted_path.split('.')
def _dotted_path_is_str(path):
"""
Whether the path is a string or not.
:param str path: a dotted path
:return: boolean
"""
return isinstance(path, str)
def _dotted_path_valid_syntax(path):
"""
Whether path's name is a syntactically correct python path or not.
Correct: 'path.to.a.package.or.module'
Wrong: 'path/to//module', 'path,to,,module', 'path,to..module'
:param str path: a dotted path to a package, module, class etc
:return: boolean
"""
split_path = split_the_path(dotted_path=path)
return all([
True
if word.isidentifier() and not iskeyword(word)
else False
for word in split_path
])
def _dotted_path_is_valid(path):
"""
Whether the path given, is a valid python (dotted) path.
Does not check if this path can be imported or not.
:param str path: a dotted python path
:return: boolean
"""
return _dotted_path_is_str(path) and _dotted_path_valid_syntax(path)
def smart_find_spec(dotted_path):
"""
Returns the doted path, including the module, regardless the size of the
dotted path.
Example:
myapp/
my_models/
models.py
# models.py
class MyModel(models.Model):
img = models.ImageField()
If the given dotted path is "myapp.my_models.models.MyModel.img"
the function will return "myapp.my_models.models" (until the module).
Not the class (MyModel) or the class attr (MyModel.img1).
:param str dotted_path: a dotted path to a package/module/class etc
:return: str or None
"""
if not _dotted_path_is_valid(dotted_path):
msg = f'"{CONFIG_NAME}" setting key {dotted_path} is not a ' \
f'valid python path. '
raise ValueError(msg)
split_path = split_the_path(dotted_path=dotted_path)
spec = None
for i, word in enumerate(split_path, start=1):
try:
spec = find_spec(SEPARATOR.join(split_path[:i]))
if spec is None:
break
except (ModuleNotFoundError, AttributeError, ValueError):
break
# return spec.name only if it's a module, otherwise None
if spec is not None:
return spec
# if spec is not None and \
# not spec.submodule_search_locations:
# return spec.name
def examine_path(dotted_path):
app_label = model_module_name = model_name = field = None
d = DictAttr({
'app_label': app_label,
'model_module_name': model_module_name,
'model_name': model_name,
'field': field
})
spec = smart_find_spec(dotted_path)
if spec is not None:
if spec.submodule_search_locations:
# spec refers to a package
d.app_label = spec.name
return d
# spec refers to a module
app_config = apps.get_app_config(app_label=spec.parent)
# if other fields declared in the path (apart from package and
# module), examine them here.
search_string = f'{spec.name}.'
new_path = dotted_path.replace(search_string, '')
new_path_split = split_the_path(new_path)
if len(new_path_split) > 2:
msg = f'The correct pattern for a key after the models module ' \
f'is "MyModel.image_field". ' \
f'The key {dotted_path} does not seem to fit the pattern!'
raise ValueError(msg)
if new_path:
model_name = new_path_split[0]
try:
model = app_config.get_model(model_name)
except LookupError as e:
msg = f'The model {model_name} defined in {dotted_path} is ' \
f'not a valid one. Please check again!'
raise ValueError(msg) from e
else:
if len(new_path_split) == 2:
field_name = new_path_split[1]
field = getattr(model, field_name)
if not isinstance(field, ImageFileDescriptor):
msg = f'The field {field} is not an ImageField!'
raise ValueError(msg)
d.app_label = spec.parent
d.model_module_name = app_config.models_module.__name__
d.model_name = model_name
d.field = field.field.name
return d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment