Created
March 22, 2018 14:05
-
-
Save manikos/9ac107a1cfba602f2f021ad15863f815 to your computer and use it in GitHub Desktop.
Various helper functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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