Repository: vitalik/django-ninja
Pull request #715 (2023-03-26 14:10:03)

Allow to extend additional info key, value to OpenAPI Info section
Hey @vitalik! Thanks for your previous feedback and I totally get not being locked into an engine.
I wonder what do you think about this solution?
adding `additional_info` as optional to initialize the API, so users can pass whatever they need to extend the OpenAPI 'info' section.
 
for example:
```Python
api = NinjaAPI(
               version="1.0.0",
               additional_info={
                   "termsOfService": "https://example.com/terms/",
                   'x-logo': {'url': 'https://example.com/logo.svg'}
               },
               title="Demo API",
               description="This is a demo API with dynamic OpenAPI info section")
```


Patch:
diff --git a/docs/docs/guides/api-docs.md b/docs/docs/guides/api-docs.md
index 50f0d47..238dd59 100644
--- a/docs/docs/guides/api-docs.md
+++ b/docs/docs/guides/api-docs.md
@@ -41,6 +41,20 @@ from django.contrib.admin.views.decorators import staff_member_required
 api = NinjaAPI(docs_decorator=staff_member_required)
 ```
 
+## Extending OpenAPI Spec with custom attributes
+
+You can extend OpenAPI spec with custom attributes, for example to add `termsOfService`
+
+```Python
+api = NinjaAPI(
+   openapi_info={
+       "termsOfService": "https://example.com/terms/",
+   },
+   title="Demo API",
+   description="This is a demo API with dynamic OpenAPI info section"
+)
+```
+
 ## Resolving the doc's url
 
 The url for the api's documentation view can be reversed by referencing the view's name `openapi-view`.
@@ -58,5 +72,5 @@ In a Django template, for example:
 ```Html
 <a href="{% url 'api-1.0.0:openapi-view' %}">API Docs</a>
 
->>> <a href="/api/docs">API Docs</a>
+<a href="/api/docs">API Docs</a>
 ```
diff --git a/ninja/main.py b/ninja/main.py
index 15de28e..66af4d1 100644
--- a/ninja/main.py
+++ b/ninja/main.py
@@ -58,6 +58,7 @@ class NinjaAPI:
         renderer: Optional[BaseRenderer] = None,
         parser: Optional[Parser] = None,
         default_router: Optional[Router] = None,
+        openapi_info: Optional[Dict[str, Any]] = None,
     ):
         """
         Args:
@@ -66,6 +67,7 @@ class NinjaAPI:
             version: The API version.
             urls_namespace: The Django URL namespace for the API. If not provided, the namespace will be ``"api-" + self.version``.
             openapi_url: The relative URL to serve the openAPI spec.
+            openapi_info: Additional info for the openAPI spec.
             docs_url: The relative URL to serve the API docs.
             servers: List of target hosts used in openAPI spec.
             csrf: Require a CSRF token for unsafe request types. See <a href="../csrf">CSRF</a> docs.
@@ -84,6 +86,7 @@ class NinjaAPI:
         self.csrf = csrf
         self.renderer = renderer or JSONRenderer()
         self.parser = parser or Parser()
+        self.openapi_info = openapi_info or {}
 
         self._exception_handlers: Dict[Exc, ExcHandler] = {}
         self.set_default_exception_handlers()
diff --git a/ninja/openapi/schema.py b/ninja/openapi/schema.py
index 123e948..bfea39e 100644
--- a/ninja/openapi/schema.py
+++ b/ninja/openapi/schema.py
@@ -57,6 +57,7 @@ class OpenAPISchema(dict):
                         "title": api.title,
                         "version": api.version,
                         "description": api.description,
+                        **api.openapi_info,
                     },
                 ),
                 ("paths", self.get_paths()),


Test patch:
diff --git a/tests/test_openapi_schema_info.py b/tests/test_openapi_schema_info.py
new file mode 100644
index 0000000..edd5fc1
--- /dev/null
+++ b/tests/test_openapi_schema_info.py
@@ -0,0 +1,22 @@
+from ninja import NinjaAPI
+
+
+class TestOpenAPIInfo:
+    def test_openapi_info_defined(self):
+        openapi_info = {
+            "termsOfService": "https://example.com/terms/",
+            "title": "Test API",
+        }
+        api = NinjaAPI(openapi_info=openapi_info, version="1.0.0")
+        schema = api.get_openapi_schema()
+
+        assert schema["info"]["termsOfService"] == "https://example.com/terms/"
+        assert schema["info"]["title"] == "Test API"
+        assert schema["info"]["version"] == "1.0.0"
+
+    def test_openapi_no_additional_info(self):
+        api = NinjaAPI(title="Test API")
+        schema = api.get_openapi_schema()
+
+        assert schema["info"]["title"] == "Test API"
+        assert "termsOfService" not in schema["info"]