diff --git a/docs/developer/DJANGO_API.md b/docs/developer/DJANGO_API.md index b3294055b..6172074cf 100644 --- a/docs/developer/DJANGO_API.md +++ b/docs/developer/DJANGO_API.md @@ -82,7 +82,9 @@ python manage.py runserver_plus curl -s http://localhost:8000/hlo/v1/hello | jq curl -s http://localhost:8000/hlo/v1/hello/1 | jq -open -a Safari http://localhost:8000 +open -a "Google Chrome" http://localhost:8000/hlo/v1/ +open -a "Google Chrome" http://localhost:8000/swagger-ui/ +open -a "Google Chrome" http://localhost:8000/schema/openapi.json (CRTL+C to stop the server) ``` @@ -124,10 +126,14 @@ python manage.py showmigrations At this point, you may rename the source code and continue developing the app or simply delete it. -Go back to project root: +Go back to project root and clean up like so: + ``` +make down cd ../../../../../ rm -rf lib/workload/stateless/stacks/hello-manager +conda deactivate +conda env remove -n hello-manager ``` ## CDK diff --git a/skel/django-api/README.md b/skel/django-api/README.md index 6682cafff..90db37a25 100644 --- a/skel/django-api/README.md +++ b/skel/django-api/README.md @@ -1,5 +1,8 @@ # Hello Manager Service +> FIXME: The following is just an example README as template. You should update it to adapt your service. +--- + ``` Namespace: orcabus.hlo ``` @@ -120,21 +123,8 @@ Or visit in browser: ### API Doc -#### Swagger - -- http://localhost:8000/swagger -- http://localhost:8000/swagger.json -- http://localhost:8000/swagger.yaml - -#### Redoc - -- http://localhost:8000/redoc/ - -#### OpenAPI v3 - -``` -python manage.py generateschema > orcabus.hlo.openapi.yaml -``` +- http://localhost:8000/swagger-ui/ +- http://localhost:8000/schema/openapi.json ## Testing diff --git a/skel/django-api/compose.yml b/skel/django-api/compose.yml index 271c25b71..0ce88711a 100644 --- a/skel/django-api/compose.yml +++ b/skel/django-api/compose.yml @@ -1,7 +1,5 @@ include: - path: -# - ../../../../../shared/mock-ica.yml - - ../../../../../shared/mock-aws.yml - ../../../../../shared/mock-db.yml #services: diff --git a/skel/django-api/deps/requirements-dev.txt b/skel/django-api/deps/requirements-dev.txt index c5c7f1546..3cc2e8d70 100644 --- a/skel/django-api/deps/requirements-dev.txt +++ b/skel/django-api/deps/requirements-dev.txt @@ -1,4 +1,3 @@ -r requirements-test.txt -django_extensions==3.2.3 -drf_yasg==1.21.7 +django_extensions openapi-spec-validator diff --git a/skel/django-api/deps/requirements-test.txt b/skel/django-api/deps/requirements-test.txt index 0e21e7935..627567288 100644 --- a/skel/django-api/deps/requirements-test.txt +++ b/skel/django-api/deps/requirements-test.txt @@ -1,8 +1,14 @@ -r requirements.txt -pytest==8.2.0 -factory_boy==3.3.0 -pytz==2024.1 -mockito==1.5.0 -# Intentionally leave out the boto3 version here. They bump like daily and latest is always fine. + +# Intentionally leave out the boto3 version here. They bump like daily and latest is always fine for test scope. boto3 -coverage==7.5.1 + +## +# FIXME You should pin exact version for following packages. Remove this comment after done so. +## + +pytest +factory_boy +pytz +mockito +coverage diff --git a/skel/django-api/deps/requirements.txt b/skel/django-api/deps/requirements.txt index ee2b1cf3c..b9416a7dd 100644 --- a/skel/django-api/deps/requirements.txt +++ b/skel/django-api/deps/requirements.txt @@ -2,19 +2,19 @@ aws-xray-sdk # Intentionally commented boto3. We can just leverage the AWS Lambda Python Runtime cache for boto3 and botocore. #boto3 -Django==5.0.6 -djangorestframework==3.15.1 -django-cors-headers==4.3.1 -django-environ==0.11.2 -djangorestframework-camel-case==1.4.2 -# See psycopg[binary] or psycopg[c] impl https://www.psycopg.org/psycopg3/docs/basic/install.html -psycopg[binary]==3.1.18 -Werkzeug==3.0.3 -# libica==2.2.1 -libumccr==0.4.0rc1 -cachetools==5.3.3 -serverless-wsgi==3.0.3 -# six and regex required by automatically generated EventBridge code binding -# for sequencerunstatechange package -six==1.16.0 -regex==2024.4.28 + +## +# FIXME You should pin exact version for following packages. Remove this comment after done so. +## + +Django +djangorestframework +django-cors-headers +django-environ +djangorestframework-camel-case +drf-spectacular +psycopg[binary] +Werkzeug +serverless-wsgi +cachetools +libumccr diff --git a/skel/django-api/project_name/pagination.py b/skel/django-api/project_name/pagination.py index 3892ddde9..b3c0f990e 100644 --- a/skel/django-api/project_name/pagination.py +++ b/skel/django-api/project_name/pagination.py @@ -31,3 +31,31 @@ def get_paginated_response(self, data): "results": data, } ) + + def get_paginated_response_schema(self, schema): + return { + "type": "object", + 'required': ['links', 'pagination', 'results'], + "properties": { + "links": { + "type": "object", + "properties": { + "next": {"type": "string", "format": "uri", "nullable": True, + 'example': 'http://api.example.org/accounts/?{page_query_param}=4'.format( + page_query_param=self.page_query_param)}, + "previous": {"type": "string", "format": "uri", "nullable": True, + 'example': 'http://api.example.org/accounts/?{page_query_param}=2'.format( + page_query_param=self.page_query_param)}, + }, + }, + "pagination": { + "type": "object", + "properties": { + PaginationConstant.COUNT: {"type": "integer"}, + PaginationConstant.PAGE: {"type": "integer"}, + PaginationConstant.ROWS_PER_PAGE: {"type": "integer"}, + }, + }, + "results": schema + }, + } diff --git a/skel/django-api/project_name/settings/base.py b/skel/django-api/project_name/settings/base.py index 0aa10c348..263c9f21f 100644 --- a/skel/django-api/project_name/settings/base.py +++ b/skel/django-api/project_name/settings/base.py @@ -6,6 +6,8 @@ import aws_xray_sdk from corsheaders.defaults import default_headers +API_VERSION = "v1" + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/skel/django-api/project_name/settings/local.py b/skel/django-api/project_name/settings/local.py index ec1e552ca..15453b54d 100644 --- a/skel/django-api/project_name/settings/local.py +++ b/skel/django-api/project_name/settings/local.py @@ -10,31 +10,51 @@ from .base import * # noqa -db_conn_cfg = Env.db_url_config( - # pragma: allowlist nextline secret - os.getenv("DB_URL", "postgresql://orcabus:orcabus@localhost:5432/orcabus") -) - -DATABASES = {"default": db_conn_cfg} +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'orcabus', # FIXME perhaps change it to '{{project_name}}' + 'USER': 'orcabus', + 'PASSWORD': 'orcabus', # pragma: allowlist-secret + 'HOST': os.getenv('DB_HOSTNAME', 'localhost'), + 'PORT': os.getenv('DB_PORT', 5432), + } +} INSTALLED_APPS += ( "django_extensions", - "drf_yasg", + "drf_spectacular", ) ROOT_URLCONF = "{{project_name}}.urls.local" RUNSERVER_PLUS_PRINT_SQL_TRUNCATE = sys.maxsize -# --- drf_yasg swagger and redoc settings - -SWAGGER_SETTINGS = { - "SECURITY_DEFINITIONS": { - "Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"} +# --- drf-spectacular settings + +REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'drf_spectacular.openapi.AutoSchema' + +SPECTACULAR_SETTINGS = { + 'TITLE': 'UMCCR OrcaBus {{project_name}} API', + 'DESCRIPTION': 'UMCCR OrcaBus {{project_name}} API', + 'VERSION': API_VERSION, + 'SERVE_INCLUDE_SCHEMA': False, + 'SECURITY': [ + { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + } + ], + 'CONTACT': { + 'name': 'UMCCR', + 'email': 'services@umccr.org' + }, + "LICENSE": { + "name": "MIT License", + }, + "EXTERNAL_DOCS": { + "description": "Terms of service", + "url": "https://umccr.org/", }, - "USE_SESSION_AUTH": False, -} - -REDOC_SETTINGS = { - "LAZY_RENDERING": False, } diff --git a/skel/django-api/project_name/urls/base.py b/skel/django-api/project_name/urls/base.py index b398575d0..ed355a0ab 100644 --- a/skel/django-api/project_name/urls/base.py +++ b/skel/django-api/project_name/urls/base.py @@ -2,9 +2,10 @@ from {{project_name}}.routers import OptionalSlashDefaultRouter from {{project_name}}.viewsets.helloworld import HelloWorldViewSet +from {{project_name}}.settings.base import API_VERSION api_namespace = "hlo" -api_version = "v1" +api_version = API_VERSION api_base = f"{api_namespace}/{api_version}/" router = OptionalSlashDefaultRouter() diff --git a/skel/django-api/project_name/urls/local.py b/skel/django-api/project_name/urls/local.py index 6ea496aa2..562934128 100644 --- a/skel/django-api/project_name/urls/local.py +++ b/skel/django-api/project_name/urls/local.py @@ -1,26 +1,10 @@ -from django.urls import path, include, re_path -from drf_yasg import openapi -from drf_yasg.views import get_schema_view -from rest_framework import permissions +from django.urls import path +from drf_spectacular.views import SpectacularJSONAPIView, SpectacularSwaggerView -from .base import urlpatterns as base_urlpatterns, router, api_version, api_base - -schema_view = get_schema_view( - openapi.Info( - title="UMCCR OrcaBus {{project_name}} API", - default_version=f"{api_version}", - description="UMCCR OrcaBus {{project_name}} API", - terms_of_service="https://umccr.org/", - contact=openapi.Contact(email="services@umccr.org"), - license=openapi.License(name="MIT License"), - ), - public=True, - permission_classes=[permissions.AllowAny, ], - patterns=[path(f"{api_base}", include(router.urls)), ], -) +from .base import urlpatterns as base_urlpatterns urlpatterns = base_urlpatterns + [ - re_path(r"^swagger(?P\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"), - re_path(r"^swagger/$", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), - re_path(r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), + path('schema/openapi.json', SpectacularJSONAPIView.as_view(), name='schema'), + path('swagger-ui/', + SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), ]