WinterFramework / winter

Web Framework with focus on python typing, dataclasses and modular design
MIT License
17 stars 18 forks source link

Simplify initial setup #230

Open mofr opened 2 years ago

mofr commented 2 years ago

Pain points (as shown by the winter-getting-started project):

Django-related:

Not related to Django:

mofr commented 2 years ago

I'm trying to incapsulate django usage within winter_django package by creating a winter_django.create_wsgi function which sets up the application and returns ready-to-work WSGI instance. https://github.com/WinterFramework/winter/tree/winter-django-wsgi https://github.com/WinterFramework/winter-getting-started/tree/winter-django-wsgi

Temporarily missing functionality:

mofr commented 2 years ago

OpenAPI resolution plan:

EDIT: Moved to a separate issue https://github.com/WinterFramework/winter/issues/241

mofr commented 1 year ago

Better version of WinterWebApplication:

import winter.core
from injector import Injector

class WinterWebApplication:
    """
    Generic class to be incorporated into Winter framework.
    Performs basic autodiscovery and injector setup.
    Hides drf_yasg usage and simplifies the application setup.
    It's also supposed to absorb all global configurations from winter except annotations which are static.
    """

    class DjangoURLConf:
        def __init__(self):
            self.urlpatterns = []

    def __init__(self, routes_package: str, injector_modules):
        """
        :param routes_package: TODO Make it optional, discover the current directory by default
        :param injector_modules:
        """

        # It's public for the only purpose - to integrate it with existing Django app
        self.django_urlconf = self.DjangoURLConf()
        self._setup_django(self.django_urlconf)

        winter.core.set_injector(Injector(injector_modules))
        winter.web.setup()

        self._autodiscover_routes(routes_package)

    def _setup_django(self, urlconf: DjangoURLConf):
        from django.conf import settings

        settings.configure(
            ROOT_URLCONF=urlconf,
            REST_FRAMEWORK={
                'DEFAULT_RENDERER_CLASSES': ('winter_django.renderers.JSONRenderer',),
                'UNAUTHENTICATED_USER': object,
            },
            INSTALLED_APPS=(
                # Hack for making module discovery working
                'django.contrib.admin',
                'django.contrib.contenttypes',
                # End hack
            ),
            SWAGGER_SETTINGS={
                'DEFAULT_AUTO_SCHEMA_CLASS': 'winter_openapi.SwaggerAutoSchema',
            }
        )

        import django
        import winter_django
        import winter_openapi

        winter_django.setup()
        winter_openapi.setup(allow_missing_raises_annotation=True)
        django.setup()

    def _autodiscover_routes(self, package: str):
        import winter_django.autodiscovery
        self.django_urlconf.urlpatterns = winter_django.autodiscovery.create_django_urls_for_package(package)

    def get_wsgi(self):
        from django.core.wsgi import get_wsgi_application
        return get_wsgi_application()

    def get_openapi(self, title: str, format: str = 'json') -> str:
        from drf_yasg.generators import OpenAPISchemaGenerator
        from drf_yasg import openapi

        api_info = openapi.Info(title=title, default_version='v1')
        generator = OpenAPISchemaGenerator(api_info, urlconf=self.django_urlconf)
        schema = generator.get_schema()

        if format == 'json':
            from drf_yasg.codecs import OpenAPICodecJson
            codec = OpenAPICodecJson(validators=[], pretty=True)
            swagger_json = codec.encode(schema).decode('utf-8')
            return swagger_json
        elif format == 'yaml':
            from drf_yasg.codecs import OpenAPICodecYaml
            codec = OpenAPICodecYaml(validators=[])
            swagger_yaml = codec.encode(schema).decode('utf-8')
            return swagger_yaml

And example usage:

from injector_modules.sqlalchemy_engine import SQLAlchemyEngineModule
from injector_modules.database_url import DatabaseUrlModule

# injector modules to be automatically discovered as well

app = WinterWebApplication('api', injector_modules=[
    SQLAlchemyEngineModule,
    DatabaseUrlModule,
])
wsgi = app.get_wsgi()
openapi = app.get_openapi(title='My API', format='json')