# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.aicrisk.com/example-python-application.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
