from importlib import import_module import inspect import sys import os import django from django.apps import AppConfig from django.conf import settings from django.core import management from django.template import Library from django.urls import path, re_path from django.core.management.base import BaseCommand from django.core.exceptions import ImproperlyConfigured __all__ = ['command', 'configure', 'run', 'route', 'template', 'get_app_label'] register = template = Library() urlpatterns = [] _parent_module = None _app_config = None _commands = {} def _create_app(stack): parent_module = sys.modules[stack[1][0].f_locals['__name__']] parent_module_dir = os.path.dirname(os.path.abspath(parent_module.__file__)) # use parent directory of application as import root sys.path[0] = os.path.dirname(parent_module_dir) app_module = os.path.basename(parent_module_dir) entrypoint = '{}.{}'.format(app_module, os.path.basename(parent_module.__file__).split('.')[0]) # allow relative import from app.py parent_module.__package__ = app_module import_module(app_module) # allow recursive import app.py if parent_module.__name__ != app_module: sys.modules[entrypoint] = parent_module class MicroAppConfig(AppConfig): module = app_module label = app_module name = app_module def import_models(self): super().import_models() if self.models_module is None: self.models_module = parent_module globals().update( _app_config=MicroAppConfig, _parent_module=parent_module, ) def get_app_label(): if _app_config is None: raise ImproperlyConfigured( "Application label is not detected. " "Check whether the configure() was called.") return _app_config.label def route(pattern, view_func=None, regex=False, *args, **kwargs): path_func = re_path if regex else path def decorator(view_func): if hasattr(view_func, 'as_view'): view_func = view_func.as_view() urlpatterns.append(path_func(pattern, view_func, *args, **kwargs)) return view_func # allow use decorator directly # route('blog/', show_index) if view_func: return decorator(view_func) return decorator def configure(config_dict={}, django_admin=False): _create_app(inspect.stack()) # load application from parent module if 'BASE_DIR' in config_dict: config_dict.setdefault('TEMPLATE_DIRS', [os.path.join(config_dict['BASE_DIR'], 'templates')]) if django_admin: _configure_admin(config_dict) django_config = { 'INSTALLED_APPS': ['django_micro._app_config'] + config_dict.pop('INSTALLED_APPS', []), 'ROOT_URLCONF': __name__, 'TEMPLATES': [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': config_dict.pop('TEMPLATE_DIRS', []), 'APP_DIRS': True, 'OPTIONS': { 'context_processors': config_dict.pop('CONTEXT_PROCESSORS', []), 'builtins': [__name__], }, }], } django_config.update({key: val for key, val in config_dict.items() if key.isupper()}) settings.configure(**django_config) django.setup() def _configure_admin(config_dict): admin_deps = { 'INSTALLED_APPS': [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ], 'MIDDLEWARE': [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ], 'CONTEXT_PROCESSORS': [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], } for key, value in admin_deps.items(): prev_value = config_dict.get(key, []) config_dict[key] = value + prev_value config_dict.setdefault('STATIC_URL', '/static/') def _patch_get_commands(): django_get_commands = management.get_commands def patched_get_commands(): commands = django_get_commands() commands.update(_commands) return commands patched_get_commands.patched = True management.get_commands = patched_get_commands def command(name=None, command_cls=None): if not getattr(management.get_commands, 'patched', False): _patch_get_commands() if inspect.isfunction(name): # Shift arguments if decroator called without brackets command_cls = name name = None def decorator(command_cls): command_name = name if inspect.isclass(command_cls): command_instance = command_cls() else: # transform function-based command to class command_name = name or command_cls.__name__ command_instance = type('Command', (BaseCommand,), {'handle': command_cls})() if not command_name: raise DjangoMicroException("Class-based commands requires name argument.") # Hack for extracting app name from command (https://goo.gl/1c1Irj) command_instance.rpartition = lambda x: [_app_config.module] _commands[command_name] = command_instance return command_cls # allow use decorator directly # command('print_hello', PrintHelloCommand) if command_cls: return decorator(command_cls) return decorator class DjangoMicroException(Exception): pass def run(): if not settings.configured: raise ImproperlyConfigured("You should call configure() after configuration define.") if _parent_module.__name__ == '__main__': from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) else: from django.core.wsgi import get_wsgi_application return get_wsgi_application()