> For the complete documentation index, see [llms.txt](https://docs.aicrisk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aicrisk.com/example-python-application.md).

# Example Python Application

{% file src="/files/ECu3OkXq4qvO7SlnWjag" %}
Now with Leave-Feedback Functionality!
{% endfile %}

## Understanding the project structure

Poetry used for package management

* Non-src-based layout
  * I.e., the main package is adjacent to the `pyproject.toml` file (since the source code is important, when usually one would use poetry to build the package and send a wheel of it)
* Why Poetry?
  * Automatically and cleanly handles cross-package dependencies, installing valid versions for cross-compatibility if possible
  * Virtual Environment Management<br>

#### Resources

1. Swagger Page: `https://demo.aicrisk.com/Swagger/index.html`
   1. NOTE: Outside of the demo server, replace `https://demo.aicrisk.com` with your deployed endpoint
2. [Article on running flask with HTTPS](https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https)

## NOTES:

1. Setup: Rename `example.env` to `.env` (Or if there is already a `.env file`, edit that) and fill in `CLIENT_ID` and `CLIENT_SECRET` and any other configuration
   1. For Example, set `USER_ID` if wanting the app to automatically select you (and even sign you in automatically if `AUTO_LOG_ME_IN` is True) as the user to authenticate
   2. Ensure proper url to deployment in `API_BASE_URL`

### Installation

1. [Install Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)
2. cd into directory containing `pyproject.toml`
3. [Activate your poetry environment ](https://python-poetry.org/docs/managing-environments/#bash-csh-zsh)
4. `poetry install`

## Commands & Usage

CD into `airisk_python_demo/airisk_python_demo`<br>

* Run Dev: `poetry run flask run --port 7221 --cert=adhoc`
* Run with debugger: `poetry run flask run --port 7221 --cert=adhoc --debugger --debug`

## The UI

1. When Signed in as User, `Impersonate User` button allows impersonation of another user without losing initial user token
2. `Exit Impersonation` button when impersonating someone will remove active impersonation and will sign in as initial user.
3. `Data Endpoints` Section
   * View the literal JSON response data from corresponding remote endpoints
4. `UI Endpoints`
   * Examples of using remote data in ones own UI
5. `Select Navigator` section
   * View remote data on things relative to other things
     * Ex. Given current authenticated users available agents, go to conversation data for one agent (the one selected in the dropdown)
6. `Endpoint with Form Link`
   * Example of integrating ones own form and flask view to handle the data input, and to communicate with the remote API with some sore of creation action. Ex. Creating a conversation.
7. `Forms`
   * ~~`Ask Form Page`~~ (Deprecated, replaced with "Option Name" section)
     * ~~Rough example of chat-less interface for using the remote `/api/Chat/Ask` endpoint~~
   * `Single Object Navigator Util`
     * `List Conversations JSON` Button
       * JSON data view for all authenticated conversations
     * `Option Name ...` section
       * Navigate to a custom chat page with which communicates with the remote server, providing a ChatGPT-like interface for an entire conversation.
         * Note, since this example app isn't utilizing streaming, one could either adapt this app to do so, or could create an illusion of streaming.

## NOTES:

1. Since in `demo.aicrisk.com`, the callback URLs allowed are HTTPS and not HTTP (e.g. `https://localhost:7221/callback`), **therefore it is necessary to make flask run with https**
   * needed `pyopenssl` and to append `--cert=adhoc` to flask cli (or equiv to `app.run`)
2. Like #1, the callback URLs specify a port `7221`.
   * Need to run flask with `--port 7221` or change the setting
3. Using Jinja2 as a template language for rendering the front-end HTML based UI using data coming from python

## Components

1. `.env`
   * Contains config variables for a more dynamic approach
2. `const.py`
   * loads from `.env` into env variables also defining defaults.
   * Also defines `TokenInput` class for persistence of tokens with support for auto-saving, auto-loading, expiration-aware auth, etc.
3. `decorators.py`
   * `@login_required`
     * Custom decorator to redirect client to `/` url (where auth is invoked) for any views accessing the remote api and therefore needing authentication.
       * If running server in debug mode, making a change & saving and then refreshing the page on a non-root URL, the page will fail since it would be obnoxious to implement authentication logic in every view and since these views require a token.
4. `authentication/`
   * Module containing the flask Blueprint encapsulating the app logic (to show how to separate concerns.)
     * Could have set custom url prefix for this as well but didn't since didn't want to change aicrisk callback urls
     * contains view functions and data retrieval utilities along with some state management therein
5. `templates/`
   * Directory containing the jinja2 templates used across the project
   * `base.html` is the root template (all others 'extend' it)
     * i.e. when extending a template, you inherit everything by default, including blocks. If defining an extisting block in the child, the whole block from the parent is overridden (unless doing super call therein)
     * Added CDN for tailwind css for easier style prototyping

## Before Running

* Make sure to set variables in .env properly. Noteably the `CLIENT_ID` and `CLIENT_SECRET`. Also the REDIRECT\_URI if on a non-demo instance of AICRisk
* Ensure callback urls are set in the remote server admin
  * If running locally, `https://localhost:{PORT}` (+`/callback`, ...)
  * Otherwise, set the exact IP or domain in which the local server will be run on
* If wanting to automatically sign in as oneself (assuming `USER_ID` is set), also set `AUTO_LOG_ME_IN=True`

## How it Works

1. On start, the variables in `.env` are loaded into environment variables with fallbacks in `const.py`
2. flask runs app.py (using configured variables) which in turn uses the app factory pattern to register the auth\_bp blueprint and its routes to the app
3. User opens browser to page
   * If the page is the auth\_index page
     * If `AUTO_RESTORE_SESSION` is True and if the file at the path of `TOKEN_INFO_FILE` exists and contains a valid, not-expired token, then that token is read, validated, and set within the session as "access\_token"
     * Otherwise
       * If `AUTO_RESTORE_SESSION` is False but the **token file exists** and contains a valid, non-expired token
         * The page is rendered with links to
           1. Go through the auth process from scratch, invoked by redirecting to `auth` view
           2. Read & load token from the file (see `TokenInfo.load`)
       * Otherwise (token non-existent or expired)
         * The page is rendered with a link to go through the authentication process
   * Otherwise (any other page that also isn't the authentication view (`auth`))
     * The client is redirected to `auth_index` page and is provided same means of authenticating as above accordingly
4. Once `auth` is invoked
   * Starts by passing `client_id` (from env) and redirect\_uri (`https://{HOST|localhost}:{PORT|7221}/callback`) to the remote authorize endpoint via a GET request (`/api/oauth2`) with encoded params placed in the url directly
   * The remote server then does its thing internally and redirects the client browser to the passed `redirect_url` (url for `oauth_callback` view) with a `code` parameter passed along therefrom
   * The redirect urls corresponding view function (`oauth_callback`) then issues a POST request to the remote token endpoint (`/api/oauth2/Token`) with parameters for the `client_id`, `client_secret`, `grant_type="authorization_code"`, `redirect_uri`, and `code` with the value of code being that value received from the remote authorize endpoint
   * If successful and yielding a valid response from that POST request, a variable is then pulled from the returned json for `access_token` and is set in the state and saved. This is the JWT/Bearer Token we needed for the rest of the apps views to work
   * Custom UI components are rendered to simplify navigation and to illustrate how to somewhat dynamically set up linking to the other pages
     * For the list views, a given remote endpoint (ex user list) has
       1. a corresponding local url for retrieving the data.
       2. (Optionally) Some also have a corresponding "\*\_ui" view which renders the data in a nice user-centric fashion
     * Also illustrate how one may set up form field choices based on the existing remote data fetched. Ex. selecting conversations only for a particular agent

possible states when in app (non-linear)

* Not Authenticated - \[client\_id, client\_secret]
* Authenticated as Generic
  * has token
* Authenticated as Generic then User
* User Authenticated directly without generic
* User Authenticated somehow, and impersonating another user
