Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to configure backend API and frontend client #101

Merged
merged 7 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions django_ai_assistant/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,30 @@
ThreadSchema,
ThreadSchemaIn,
)
from django_ai_assistant.conf import app_settings
from django_ai_assistant.exceptions import AIUserNotAllowedError
from django_ai_assistant.helpers import use_cases
from django_ai_assistant.models import Message, Thread


class API(NinjaAPI):
# Force "operationId" to be like "django_ai_assistant_delete_thread"
def get_openapi_operation_id(self, operation: Operation) -> str:
name = operation.view_func.__name__
return (package_name + "_" + name).replace(".", "_")
def init_api():
class API(NinjaAPI):
# Force "operationId" to be like "django_ai_assistant_delete_thread"
def get_openapi_operation_id(self, operation: Operation) -> str:
name = operation.view_func.__name__
return (package_name + "_" + name).replace(".", "_")
pamella marked this conversation as resolved.
Show resolved Hide resolved

return API(
title=package_name,
version=version,
urls_namespace="django_ai_assistant",
# Add auth to all endpoints
auth=django_auth,
csrf=True,
)


api = API(
title=package_name,
version=version,
urls_namespace="django_ai_assistant",
# Add auth to all endpoints
auth=django_auth,
csrf=True,
)
api = app_settings.call_fn("INIT_API_FN")


@api.exception_handler(AIUserNotAllowedError)
Expand Down
1 change: 1 addition & 0 deletions django_ai_assistant/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


DEFAULTS = {
"INIT_API_FN": "django_ai_assistant.api.views.init_api",
"CAN_CREATE_THREAD_FN": "django_ai_assistant.permissions.allow_all",
"CAN_VIEW_THREAD_FN": "django_ai_assistant.permissions.owns_thread",
"CAN_UPDATE_THREAD_FN": "django_ai_assistant.permissions.owns_thread",
Expand Down
25 changes: 25 additions & 0 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ urlpatterns = [
The built-in API supports retrieval of Assistants info, as well as CRUD for Threads and Messages.
It has a OpenAPI schema that you can explore at `ai-assistant/docs/`.


#### Configuring the API

The built-in API is implemented using [Django Ninja](https://django-ninja.dev/reference/api/). By default, it is initialized with the following setting:

```python title="myproject/settings.py"
AI_ASSISTANT_INIT_API_FN = "django_ai_assistant.api.views.init_api"
```

You can override this setting in your Django project's `settings.py` to customize the API, such as using a different authentication method or modifying other configurations.

The method signature for `AI_ASSISTANT_INIT_API_FN` is as follows:

```python
from ninja import NinjaAPI


pamella marked this conversation as resolved.
Show resolved Hide resolved
def init_api():
return NinjaAPI(
...
)
pamella marked this conversation as resolved.
Show resolved Hide resolved
```

By providing your own implementation of init_api, you can tailor the API setup to better fit your project's requirements.
pamella marked this conversation as resolved.
Show resolved Hide resolved

### Configuring permissions

The API uses the helpers from the `django_ai_assistant.use_cases` module, which have permission checks
Expand Down
2 changes: 1 addition & 1 deletion example/assets/js/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const theme = createTheme({});

// Relates to path("ai-assistant/", include("django_ai_assistant.urls"))
// which can be found at example/demo/urls.py)
configAIAssistant({ baseURL: "ai-assistant" });
configAIAssistant({ BASE: "ai-assistant" });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this has changed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we are now using the OpenAPIConfig as props to make override the client config easier.

export const OpenAPI: OpenAPIConfig = {
BASE: '',
CREDENTIALS: 'include',
ENCODE_PATH: undefined,
HEADERS: undefined,
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
VERSION: '0.0.1',
WITH_CREDENTIALS: false,
interceptors: {
request: new Interceptors(),
response: new Interceptors(),
},
};


const ExampleIndex = () => {
return (
Expand Down
1 change: 1 addition & 0 deletions example/example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@

# django-ai-assistant

AI_ASSISTANT_INIT_API_FN = "django_ai_assistant.api.views.init_api"
AI_ASSISTANT_CAN_CREATE_THREAD_FN = "django_ai_assistant.permissions.allow_all"
AI_ASSISTANT_CAN_VIEW_THREAD_FN = "django_ai_assistant.permissions.owns_thread"
AI_ASSISTANT_CAN_UPDATE_THREAD_FN = "django_ai_assistant.permissions.owns_thread"
Expand Down
33 changes: 22 additions & 11 deletions frontend/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import cookie from "cookie";

import { OpenAPI } from "./client";
import { OpenAPI, OpenAPIConfig } from "./client";
import { AxiosRequestConfig } from "axios";

/**
* Configures the base URL for the AI Assistant API which is path associated with
* the Django include.
* Configures the AI Assistant client, such as setting the base URL (which is
* associated with the Django path include) and request interceptors.
*
* Configures the Axios request to include the CSRF token if it exists.
* By default, this function will add a request interceptor to include the CSRF token
* in the request headers if it exists. You can override the default request interceptor
* by providing your own request interceptor function in the configuration object.
*
* @param baseURL Base URL of the AI Assistant API.
* NOTE: This function must be called in the root of your application before any
* requests are made to the AI Assistant API.
*
* @param props An `OpenAPIConfig` object containing configuration options for the OpenAPI client.
*
* @example
* configAIAssistant({ baseURL: "ai-assistant" });
* configAIAssistant({ BASE: "ai-assistant" });
*/
export function configAIAssistant({ baseURL }: { baseURL: string }) {
OpenAPI.BASE = baseURL;

OpenAPI.interceptors.request.use((request: AxiosRequestConfig) => {
export function configAIAssistant(props: OpenAPIConfig): OpenAPIConfig {
function defaultRequestInterceptor(request: AxiosRequestConfig) {
const { csrftoken } = cookie.parse(document.cookie);
if (request.headers && csrftoken) {
request.headers["X-CSRFTOKEN"] = csrftoken;
}
return request;
});
}

OpenAPI.interceptors.request.use(defaultRequestInterceptor);

// Apply the configuration options to the OpenAPI client, and allow the user
// to override the default request interceptor.
Object.assign(OpenAPI, props);

return OpenAPI;
}
1 change: 1 addition & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
# django-ai-assistant

# NOTE: set a OPENAI_API_KEY on .env.tests file at root when updating the VCRs.
AI_ASSISTANT_INIT_API_FN = "django_ai_assistant.api.views.init_api"
AI_ASSISTANT_CAN_CREATE_THREAD_FN = "django_ai_assistant.permissions.allow_all"
AI_ASSISTANT_CAN_VIEW_THREAD_FN = "django_ai_assistant.permissions.owns_thread"
AI_ASSISTANT_CAN_UPDATE_THREAD_FN = "django_ai_assistant.permissions.owns_thread"
Expand Down