diff --git a/README.md b/README.md index c8bb610..0c07c0b 100644 --- a/README.md +++ b/README.md @@ -13,51 +13,12 @@ After you that you are going to see an interactive screen like this: ![create-app-terminal](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746180/Allient/create-fastapi-project/demo-create-fastapi-final_fyirob.gif) -## Getting Started - -The commands in this documentation can be customized on the **Makefile**. It can be started with and without docker. - -After your project is created. First, make sure you have all packages installed: - -```bash -make install -``` - -Run the server: - -```bash -# Run locally without docker -make run-app -# or -# Run locally with docker in dev mode and force build -make run-dev-build -# or -# Run locally with docker in dev mode -make run-dev-build -# or -# Run locally with docker in prod mode -make run-prod -``` - -## Learn More - -To learn more about Fastapi, take a look at the following resources: - -- [Fastapi Documentation](https://fastapi.tiangolo.com/). -- [fastapi-alembic-sqlmodel-async](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async). -- [full-stack-fastapi-postgresql](https://github.com/tiangolo/full-stack-fastapi-postgresql). -- [sqlmodel-tutorial](https://sqlmodel.tiangolo.com/tutorial/fastapi/). -- [asyncer-tutorial](https://asyncer.tiangolo.com/tutorial/). -- [fastapi-pagination](https://github.com/uriyyo/fastapi-pagination). -- [fastapi-best-practices](https://github.com/zhanymkanov/fastapi-best-practices). -- [awesome-fastapi](https://github.com/mjhea0/awesome-fastapi). - ## Templates ### Basic
- Click me + See More We're excited to introduce you to our FastAPI Basic Project Template, carefully designed to jumpstart your FastAPI development journey. This template offers you a pre-configured project with a fundamental FastAPI setup and an organized folder structure, allowing you to hit the ground running. @@ -81,7 +42,7 @@ To learn more about Fastapi, take a look at the following resources: ### Langchain Basic
- Click me + See More We're thrilled to introduce you to the LangChain project template, designed to accelerate your development process. This template serves as a solid foundation for your project, complete with essential features and an organized folder structure, all thoughtfully configured and ready for use. @@ -103,6 +64,27 @@ app └───test ``` +## Containers Architecture + +![langchain-architecture](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1693340056/Allient/create-fastapi-project/image_2_mraj02.png) +As this project uses [Caddy](https://caddyserver.com/) as a reverse proxy, which uses namespaces routing, you can access the documentation with the following path [http://fastapi.localhost/docs](http://fastapi.localhost/docs) + +## ENV Variables + +```bash +PROJECT_NAME= +OPENAI_API_KEY= +UNSPLASH_API_KEY= # Optional +SERP_API_KEY= # Optional + +############################################# +# Caddy variables +############################################# +EXT_ENDPOINT1=127.0.0.1 +LOCAL_1=localhost +LOCAL_2=127.0.0.1 +``` + ## Tools - Search weather tool ![weather-tool](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746086/Allient/create-fastapi-project/weather-tool-demo_lgqtwu.gif) @@ -114,7 +96,7 @@ app ### Full
- Click me + See More This is a project template which uses [FastAPI](https://fastapi.tiangolo.com/), [Alembic](https://alembic.sqlalchemy.org/en/latest/) and async [SQLModel](https://sqlmodel.tiangolo.com/) as ORM. It shows a complete async CRUD template using authentication. Our implementation utilizes the newest version of FastAPI and incorporates typing hints that are fully compatible with **Python 3.10** and later versions. If you're looking to build modern and efficient web applications with Python, this template will provide you with the necessary tools to get started quickly. You can read a short article with the motivations for starting this sample project [here](https://medium.com/allient/our-journey-using-async-fastapi-to-harnessing-the-power-of-modern-web-apis-90301827f14c?source=friends_link&sk=9006b3f2a4137a28a8576a69546c8c18). @@ -182,6 +164,45 @@ Developing web applications can be a challenging process, especially when dealin
+## Getting Started + +The commands in this documentation can be customized on the **Makefile**. It can be started with and without docker. + +After your project is created. First, make sure you have all packages installed: + +```bash +make install +``` + +Run the server: + +```bash +# Run locally without docker +make run-app +# or +# Run locally with docker in dev mode and force build +make run-dev-build +# or +# Run locally with docker in dev mode +make run-dev-build +# or +# Run locally with docker in prod mode +make run-prod +``` + +## Learn More + +To learn more about Fastapi, take a look at the following resources: + +- [Fastapi Documentation](https://fastapi.tiangolo.com/). +- [fastapi-alembic-sqlmodel-async](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async). +- [full-stack-fastapi-postgresql](https://github.com/tiangolo/full-stack-fastapi-postgresql). +- [sqlmodel-tutorial](https://sqlmodel.tiangolo.com/tutorial/fastapi/). +- [asyncer-tutorial](https://asyncer.tiangolo.com/tutorial/). +- [fastapi-pagination](https://github.com/uriyyo/fastapi-pagination). +- [fastapi-best-practices](https://github.com/zhanymkanov/fastapi-best-practices). +- [awesome-fastapi](https://github.com/mjhea0/awesome-fastapi). + ## Why use Create FastAPI Project? `create-fastapi-project` provides a streamlined way to kickstart your FastAPI projects. Here are some compelling reasons to choose it for your project setup: diff --git a/create_fastapi_project/main.py b/create_fastapi_project/main.py index 79a3be3..de9d704 100644 --- a/create_fastapi_project/main.py +++ b/create_fastapi_project/main.py @@ -40,11 +40,8 @@ def create_project(): langchain_basic = ITemplate.langchain_basic.value full = ITemplate.full.value template_type: str = questionary.select( - "Choose a template", choices=[ - basic, - langchain_basic, - questionary.Choice(full, disabled=disabled_message) - ] + "Choose a template", + choices=[basic, langchain_basic, full], ).ask() if template_type == ITemplate.full: questionary.select( @@ -79,9 +76,7 @@ def create_project(): panel = Panel(styled_message, title="Project Initialization") console.print(panel) - confirmation: bool = questionary.confirm( - "Are you sure you want to continue?" - ).ask() + confirmation: bool = questionary.confirm("Are you sure you want to continue?").ask() if not confirmation: print("Not created") raise typer.Abort() diff --git a/create_fastapi_project/templates/__init__.py b/create_fastapi_project/templates/__init__.py index 163f136..48da552 100644 --- a/create_fastapi_project/templates/__init__.py +++ b/create_fastapi_project/templates/__init__.py @@ -2,12 +2,17 @@ import shutil from enum import Enum from dotenv import dotenv_values -from create_fastapi_project.helpers.install import add_configuration_to_pyproject, install_dependencies +from create_fastapi_project.helpers.install import ( + add_configuration_to_pyproject, + install_dependencies, +) + class ITemplate(str, Enum): basic = "basic" langchain_basic = "langchain_basic" - full = "full" + full = "full" + def install_template(root: str, template: ITemplate, app_name: str): print(f"Initializing project with template: {template}") @@ -32,45 +37,107 @@ def install_template(root: str, template: ITemplate, app_name: str): dirs_exist_ok=True, ) - poetry_path = "" if template == ITemplate.full or template == ITemplate.langchain_basic: # TODO: CHECK PATHS IN MACOS AND WINDOWS | (os.path.join) poetry_path = os.path.join(root, "backend", "app") + poetry_frontend_path = os.path.join(root, "frontend", "app") else: poetry_path = os.path.join(root, "app") - + has_pyproject = add_configuration_to_pyproject(poetry_path) if has_pyproject: - dependencies = ["fastapi[all]", "fastapi-pagination[sqlalchemy]@^0.12.7", "asyncer@^0.0.2", "httpx@^0.24.1"] + dependencies = [ + "fastapi[all]", + "fastapi-pagination[sqlalchemy]@^0.12.7", + "asyncer@^0.0.2", + "httpx@^0.24.1", + ] + dev_dependencies = [ + "pytest@^7.4.0", + "mypy@^1.5.0", + "ruff@^0.0.284", + "black@^23.7.0", + ] if template == ITemplate.langchain_basic: langchain_dependencies = [ "langchain@^0.0.265", "openai@^0.27.8", "adaptive-cards-py@^0.0.7", - "google-search-results@^2.4.2" + "google-search-results@^2.4.2", + ] + frontend_dependencies = [ + "streamlit", + "websockets", ] dependencies[0] = "fastapi[all]@^0.99.1" dependencies.extend(langchain_dependencies) - dev_dependencies = ["pytest@^5.2", "mypy@^1.5.0", "ruff@^0.0.284", "black@^23.7.0"] + if template == ITemplate.full: + full_dependencies = [ + "alembic@^1.10.2", + "asyncpg@^0.27.0", + "sqlmodel@^0.0.8", + "python-jose@^3.3.0", + "cryptography@^38.0.3", + "passlib@^1.7.4", + "SQLAlchemy-Utils@^0.38.3", + "SQLAlchemy@^1.4.40", + "minio@^7.1.13", + "Pillow@^9.4.0", + "watchfiles@^0.18.1", + "asyncer@^0.0.2", + "httpx@^0.23.1", + "pandas@^1.5.3", + "openpyxl@^3.0.10", + "redis@^4.5.1", + "fastapi-async-sqlalchemy@^0.3.12", + "oso@^0.26.4", + "celery@^5.2.7", + "transformers@^4.28.1", + "requests@^2.29.0", + "wheel@^0.40.0", + "setuptools@^67.7.2", + "langchain@^0.0.262", + "openai@^0.27.5", + "celery-sqlalchemy-scheduler@^0.3.0", + "psycopg2-binary@^2.9.5", + "fastapi-limiter@^0.1.5 ", + "fastapi-pagination[sqlalchemy]@^0.11.4 ", + "fastapi-cache2[redis]@^0.2.1 ", + ] + full_dev_dependencies = [ + "pytest-asyncio@^0.21.1", + ] + dependencies[0] = "fastapi[all]@^0.95.2" + dependencies.extend(full_dependencies) + dev_dependencies.extend(full_dev_dependencies) + print("- Installing main packages. This might take a couple of minutes.") install_dependencies(poetry_path, dependencies) print("- Installing development packages. This might take a couple of minutes.") install_dependencies(poetry_path, dev_dependencies, dev=True) + + if template == ITemplate.langchain_basic: + add_configuration_to_pyproject(poetry_frontend_path) + print( + "- Installing frontend packages. This might take a couple of minutes." + ) + install_dependencies(poetry_frontend_path, frontend_dependencies) + # Set your dynamic environment variables - + # Load variables from .env.example example_env = dotenv_values(".env.example") example_env["PROJECT_NAME"] = app_name # Write modified environment variables to .env and .env.example file - with open(".env", "w") as env_file, open(".env.example", "w") as example_env_file: + with open(".env", "w") as env_file, open( + ".env.example", "w" + ) as example_env_file: for key, value in example_env.items(): env_file.write(f"{key}={value}\n") example_env_file.write(f"{key}={value}\n") return has_pyproject - - diff --git a/create_fastapi_project/templates/full/README.md b/create_fastapi_project/templates/full/README.md index f4da3c4..8c7b8d0 100644 --- a/create_fastapi_project/templates/full/README.md +++ b/create_fastapi_project/templates/full/README.md @@ -1,34 +1,421 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Async configuration for FastAPI and SQLModel -## Getting Started +This is a project template which uses [FastAPI](https://fastapi.tiangolo.com/), [Alembic](https://alembic.sqlalchemy.org/en/latest/) and async [SQLModel](https://sqlmodel.tiangolo.com/) as ORM. It shows a complete async CRUD template using authentication. Our implementation utilizes the newest version of FastAPI and incorporates typing hints that are fully compatible with **Python 3.10** and later versions. If you're looking to build modern and efficient web applications with Python, this template will provide you with the necessary tools to get started quickly. You can read a short article with the motivations for starting this sample project [here](https://medium.com/allient/our-journey-using-async-fastapi-to-harnessing-the-power-of-modern-web-apis-90301827f14c?source=friends_link&sk=9006b3f2a4137a28a8576a69546c8c18). -First, run the development server: +## Why Use This Template? -```bash -npm run dev -# or -yarn dev -# or -pnpm dev +Developing web applications can be a challenging process, especially when dealing with databases, authentication, asynchronous tasks, and other complex components. Our template is designed to simplify this process and offer you a solid starting point. Some of the highlights of this template include: + +- FastAPI Integration: FastAPI is a modern and efficient web framework that allows you to quickly and easily create APIs. This template uses the latest features of FastAPI and offers type hints that are compatible with **Python 3.10** and later versions. +- Asynchronous Database Management: We use SQLModel, an asynchronous ORM library, to interact with the database efficiently and securely. +- Asynchronous Tasks with Celery: This template includes examples of how to execute asynchronous and scheduled tasks using Celery, which is ideal for operations that require significant time or resources. +- Authentication and Authorization: We implement JWT-based authentication and role-based access control to ensure that your APIs are secure and protected. +- Documentation and Automated Testing: The template is configured to automatically generate interactive documentation for your APIs. It also includes automated tests using pytest to ensure code quality. +- Development Best Practices: We apply code formatting, type checking, and static analysis tools to ensure that the code is readable, robust, and reliable. + +## Table of Contents + +1. [Set environment variables](#set-environment-variables) +2. [Run the project using Docker containers and forcing build containers](#run-the-project-using-docker-containers-and-forcing-build-containers) +3. [Run project using Docker containers](#run-project-using-docker-containers) +4. [Setup database with initial data](#setup-database-with-initial-data) +5. [ERD Database model](#erd-database-model) +6. [Containers architecture](#containers-architecture) +7. [Preview](#preview) +8. [Static files](#static-files) +9. [Minio server](#minio-server) +10. [Celery](#celery) +11. [Run Alembic migrations (Only if you change the DB model)](#run-alembic-migrations-only-if-you-change-the-db-model) +12. [Production Deployment](#production-deployment) +13. [Database unique IDs](#database-unique-ids) +14. [Code Style](#code-style) +15. [SonarQube static analysis](#sonarqube-static-analysis) +16. [Testing](#testing) +17. [Type checker](#type-checker) +18. [Basic chatbot example with Langchain and OpenAI](#basic-chatbot-example-with-langchain-and-openai) +19. [Inspiration and References](#inspiration-and-references) +20. [TODO List](#todo-list) +21. [License](#license) + +## Set environment variables + +Create an **.env** file on root folder and copy the content from **.env.example**. Feel free to change it according to your own configuration. + +## Run the project using Docker containers and forcing build containers + +_Using docker compose command_ + +```sh +docker compose -f docker-compose-dev.yml up --build +``` + +_Using Makefile command_ + +```sh +make run-dev-build +``` + +## Run project using Docker containers + +_Using docker compose command_ + +```sh +docker compose -f docker-compose-dev.yml up +``` + +_Using Makefile command_ + +```sh +make run-dev +``` + +## Setup database with initial data + +This creates sample users on database. + +_Using docker compose command_ + +``` +docker compose -f docker-compose-dev.yml exec fastapi_server python app/initial_data.py +``` + +_Using Makefile command_ + +```sh +make init-db +``` + +Any of the above commands creates three users with the following passwords: + +- **Admin credentials ->** _username:_ admin@admin.com and _password:_ admin +- **Manager credentials ->** _username:_ manager@example.com and _password:_ admin +- **User credentials ->** _username:_ user@example.com and _password:_ admin + +You can connect to the Database using pgAdmin4 and use the credentials from .env file. Database port on local machine has been configured to **5454** on docker-compose-dev.yml file + +(Optional) If you prefer you can run pgAdmin4 on a docker container using the following commands, they should executed on different terminals: + +_Starts pgadmin_ + +```sh +make run-pgadmin ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +_Load server configuration (It is required just the first time)_ + +```sh +make load-server-pgadmin +``` + +This starts pgamin in [http://localhost:15432](http://localhost:15432). + +

+ +

+ +## ERD Database model + +

+ +

+ +## Containers architecture + +

+ +

+ +As this project uses [Caddy](https://caddyserver.com/) as a reverse proxy, which uses namespaces routing, you can access the documentation with the following path [http://fastapi.localhost/docs](http://fastapi.localhost/docs) + +## Preview + +

+ +

+

+ +

+ +## Static files + +All files on static folder will be served by Caddy container as static files. You can check it with this link [http://static.localhost](http://static.localhost) + +## Minio server + +This template allows users can upload their photos. The images are stored using the open source Object Storage Service (OSS) [minio](https://min.io/), which provides storage of images using buckets in a secure way through presigned URLs. + +- **Minio credentials ->** _username:_ minioadmin and _password:_ minioadmin + +

+ +

+ +## Celery + +[Celery](https://docs.celeryq.dev/en/stable/getting-started/introduction.html) is a distributed task queue that allows developers to run asynchronous tasks in their applications. It is particularly useful for tasks that are time-consuming, require heavy computation or access external services, and can be run independently of the main application. It also offers features such as task scheduling, task prioritization, and retries in case of failure. + +[Celery Beat](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html) is an additional component of Celery that allows developers to schedule periodic tasks and intervals for their Celery workers. It provides an easy-to-use interface for defining task schedules and supports several scheduling options such as crontab, interval, and relative. + +You can see the architecture used in this project which uses Redis as celery broker and the current postgres database as celery backend. It also uses [celery-sqlalchemy-scheduler](https://github.com/AngelLiang/celery-sqlalchemy-scheduler) to store celery beats task into database so they can mutated. + +Within the **natural_language** endpoints, you can access a sample application that demonstrates not only synchronous prediction of machine learning models but also batch prediction. Additionally, there are examples of how to schedule periodic tasks using Celery Beat in the **periodic_tasks** endpoints. + +

+ +

+ +## Run Alembic migrations (Only if you change the DB model) + +_Using docker compose command_ + +```sh +docker compose -f docker-compose-dev.yml exec fastapi_server alembic revision --autogenerate +docker compose -f docker-compose-dev.yml exec fastapi_server alembic upgrade head +``` + +_Using Makefile command_ + +```sh +make add-dev-migration +``` + +## Production Deployment + +Remember to use a persistant PostgreSQL database, update the new credentials on .env file and use this command to run the project in a production environment. For testing this configuration on localhost you can uncomment the database container and +depends_on of fastapi container otherwise it will not work on a local environment. + +_Using docker compose command_ + +```sh +docker compose up --build +``` + +## Database unique IDs + +Generating and using unique IDs is a really important desicion when starting a new project and its most common use is as primary keys for database tables. This project uses a custom [UUID7 Draft04 implementation](https://github.com/oittaa/uuid6-python) to make it simple to use and take advantage of UUID type of PostgreSQL. UUID7 combines timestamp with random data in order to help to convert data into time-stamped sequencially. If you are looking for another alternatives for tables IDs like [Snowflakes](https://betterprogramming.pub/uuid-generation-snowflake-identifiers-unique-2aed8b1771bc), [ULID](https://github.com/ulid/spec), [KSUID](https://github.com/segmentio/ksuid), [pushID](https://firebase.blog/posts/2015/02/the-2120-ways-to-ensure-unique_68#how-push-ids-are-generated), [xid](https://github.com/rs/xid) among others you can check [these references](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async/issues/12#issuecomment-1272425109). + +## Code Style + +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +To ensure a standardized code style this project uses [black](https://github.com/ambv/black) and [ruff](https://github.com/charliermarsh/ruff). If you want to change the config rules you can edit both ruff and black rules in the _pyproject.toml_ file. + +To reformat files execute the following command + +```sh +make formatter +``` + +To run lint, you can run the following command: + +```sh +make lint +``` + +To run lint in watch mode, you can run the following command: + +```sh +make lint-watch +``` + +To run lint and try to fix the errors, you can run the following command: + +```sh +make lint-fix +``` + +## SonarQube static analysis + +[SonarQube](https://www.sonarqube.org/) is an automatic code review tool that detects bugs, vulnerabilities, and code smells in a project. You can read [this post](https://medium.com/jrtec/static-analysis-using-sonarqube-in-a-react-webapp-dd4b335d6062) in order to have a better understanding about what SonarQube can do. + +The following steps can help you to run a local static code analysis + +1. Start SonarQube container + +```sh +make run-sonarqube +``` + +The above code starts SonarQube at [localhost:9000](http://localhost:9000/). You can login using this **credentials ->** _username:_ admin and _password:_ admin, after that it should requiere you change your password. + +2. Add new project +

+ +

+ +

+ +

+ +

+ +

+ +5. Copy **projectKey** and **login** and replace on _backend/sonar-project.properties_ file. +

+ +

+ +_backend/sonar-project.properties_ file + +```sh +# Organization and project keys are displayed in the right sidebar of the project homepage +sonar.organization=my_organization +sonar.projectKey=fastapi-alembic-sqlmodel-async +sonar.host.url=http://host.docker.internal:9000 +sonar.login=157cc42f5b2702f470af3466610eebf38551fdd7 + +# --- optional properties --- + +# defaults to project key +sonar.projectName=fastapi-alembic-sqlmodel-async +# defaults to 'not provided' +sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Defaults to . +sonar.sources=app + +# Encoding of the source code. Default is default system encoding +sonar.sourceEncoding=UTF-8 +``` + +6. Run the following command to execute a new code scan + +```sh +make run-sonar-scanner +``` + +

+ +

+ +When the build is successful, you can see the SonarQube screen automatically refreshed with the analysis. If you want to export a report, you can check this [this post](https://medium.com/jrtec/static-analysis-using-sonarqube-in-a-react-webapp-dd4b335d6062). + +## Testing + +Testing in FastAPI with pytest involves creating test functions that simulate HTTP requests to the API endpoints and verifying the responses. This approach allows us to conduct both unit tests for individual functions and integration tests for the entire application. + +To perform tests in this project, we utilize two essential libraries: [pytest](https://github.com/pytest-dev/pytest) and [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio). + +However, when testing FastAPI endpoints that utilize async connections with the database and a pool strategy, there is a trick to be aware of. The recommended approach is to create an isolated testing environment that connects to the database using the "poolclass": NullPool parameter on the engine. This helps to avoid potential issues related to tasks being attached to different loops. For more details on this, you can refer to the following references: [Fastapi testing RuntimeError: Task attached to a different loop](https://stackoverflow.com/questions/75252097/fastapi-testing-runtimeerror-task-attached-to-a-different-loop/75444607#75444607) and [Connection Pooling](https://docs.sqlalchemy.org/en/20/core/pooling.html#api-documentation-available-pool-implementations). + +To execute the tests, follow these steps: + +1. Start the testing environment using the command: + +```sh +make run-test +``` + +2. Once the testing environment is up and running, open another terminal and run the tests with the following command: + +```sh +make pytest +``` + +## Type checker + +Python's type hints, introduced in PEP 484 and fully embraced in later versions of Python, allow you to specify the expected types of variables, function parameters, and return values. It is really good how fastapi documentation promotes type hints so this code base tryies to use this tool the most posible because type hints make the code more self-documenting by providing clear information about what types of values a function or variable can hold and they catch type-related errors at compile time, before the code is executed. + +This project uses [mypy](https://mypy-lang.org/) a popular static type checker for Python. If you want to change the config rules you can edit the rules in the _pyproject.toml_ file. + +To execute Type checking, run this command: + +```sh +make mypy +``` + +## Basic chatbot example with Langchain and OpenAI + +In addition to its core features, this project template demonstrates how to integrate an basic chatbot powered by Langchain and OpenAI through websockets. + +To begin experimenting with the basic chatbot, follow these steps: + +1. **Obtain an OpenAI API Key**: You'll need to set the `OPENAI_API_KEY` environment variable, which you can obtain from [OpenAI's platform](https://platform.openai.com/). + +2. **Test Websocket Connection**: You can test the websocket connection by using the following URL: [ws://fastapi.localhost/chat/\](ws://fastapi.localhost/chat/). Replace `` with a user identifier of your choice. + +3. **Sending and Receiving Messages**: You should be able to send messages to the chatbot using the provided websocket connection. To do this, use the following message structure: + + ```json + { "message": "Hello world" } + ``` + + Once you send a message, the chatbot will respond with generated responses based on the content of your input. + +## Inspiration and References -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +- [full-stack-fastapi-postgresql](https://github.com/tiangolo/full-stack-fastapi-postgresql). +- [fastapi-sqlmodel-alembic](https://github.com/testdrivenio/fastapi-sqlmodel-alembic). +- [sqlmodel-tutorial](https://sqlmodel.tiangolo.com/tutorial/fastapi/). +- [asyncer-tutorial](https://asyncer.tiangolo.com/tutorial/). +- [fastapi-pagination](https://github.com/uriyyo/fastapi-pagination). +- [fastapi-cache](https://github.com/long2ice/fastapi-cache). +- [fastapi-keycloak](https://github.com/code-specialist/fastapi-keycloak). +- [fastapi-async-sqlalchemy](https://github.com/h0rn3t/fastapi-async-sqlalchemy). +- [fastapi-minio](https://github.com/Longdh57/fastapi-minio). +- [fastapi-best-practices](https://github.com/zhanymkanov/fastapi-best-practices). +- [pgadmin Makefile](https://gist.github.com/alldevic/b2a0573e5464fe91fd118024f33bcbaa). +- [Styling and makefiles](https://github.com/RasaHQ/rasa). +- [awesome-fastapi](https://github.com/mjhea0/awesome-fastapi). +- [Serving ML Models in Production with FastAPI and Celery](https://towardsdatascience.com/deploying-ml-models-in-production-with-fastapi-and-celery-7063e539a5db) +- [Database detup](https://christophergs.com/tutorials/ultimate-fastapi-tutorial-pt-7-sqlalchemy-database-setup/) +- [Dispatch](https://github.com/Netflix/dispatch) -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +## TODO List: -## Learn More +- [x] Add Custom Response model +- [x] Create sample one to many relationship +- [x] Create sample many to many relationship +- [x] Add JWT authentication +- [x] Add Pagination +- [x] Add User birthday field with timezone +- [x] Add static server +- [x] Add basic RBAC (Role base access control) +- [x] Add sample heroes, teams and groups on init db +- [x] Add cache configuration using fastapi-cache2 and redis +- [x] Create a global database pool of sessions to avoid to pass the session as dependency injection on each handle +- [x] Refactor tablename to Pascal case +- [x] Add one to one relationship sample +- [x] Add sample to upload images and store them using minio +- [x] Invalidate access and refresh tokens when the password is changed using Redis +- [x] Add shortcuts using a Makefile +- [x] Add sample async, sync and concurrent functions using asyncer +- [x] Add Black formatter and flake8 lint (Rasa as reference) +- [x] Add static code analysis using SonarQube +- [x] Function return type annotations to declare the response_model (fastapi > 0.89.0) +- [x] Add export report api in csv/xlsx files using StreamingResponse +- [x] Add production deployment orchestation using terraform + Elastic Beanstalk - AWS +- [x] Add Github actions automation for deploy on Elastic Beanstalk - AWS +- [x] Database query optimization. Many-Many use "selectin" and One-One and One-Many use "joined" [issue](https://github.com/jonra1993/fastapi-alembic-sqlmodel-async/issues/20) +- [x] Add Enum sample column +- [x] Add docstrings +- [x] Install pg_trgm by code and add a query for smart search of users by name +- [x] Upgrade typing (Compatible just with python > 3.10) +- [x] Add sample transformers NLP models and use them globally +- [x] Add Celery samples for tasks, and schedule tasks +- [x] Migrate from traefik reverse proxy to Caddy reverse proxy for automatic ssl +- [x] Add fastapi limiter to natural language endpoints +- [x] Add websocket conneting with chatgpt +- [x] Setup testing configuracion +- [x] Add sample composition using pydantic +- [ ] Add a nextjs sample frontend +- [ ] Add testing +- [ ] Add jsonb field on table sample +- [ ] Make that celery-sqlalchemy-scheduler works async +- [ ] Add AuthZ using oso +- [ ] Add SSL to reverse proxy on prod +- [ ] Add instructions on doc for production deployment using github actions and dockerhub (CI/CD) +- [ ] Convert repo into template using cookiecutter -To learn more about Next.js, take a look at the following resources: +### Support and Maintenance -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +`fastapi-alembic-sqlmodel-async` is supported by the [Allient development team](https://www.allient.io/). Our team is composed by a experienced professionals specializing in FastAPI projects and NLP. If you need assistance or support for your project, please don't hesitate to get in touch with us at [info@allient.io](mailto:info@allient.io) or schedule a meeting with us [here](https://calendly.com/jonathanvargas). -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +PR are welcome ❤️ -## Deploy on Vercel +## License -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +[![License](http://img.shields.io/:license-mit-blue.svg?style=flat-square)](http://badges.mit-license.org) -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +- This project is licensed under the terms of the **[MIT license](LICENSE)** diff --git a/create_fastapi_project/templates/full/backend/app/app/api/celery_task.py b/create_fastapi_project/templates/full/backend/app/app/api/celery_task.py index c02a234..beb1b08 100644 --- a/create_fastapi_project/templates/full/backend/app/app/api/celery_task.py +++ b/create_fastapi_project/templates/full/backend/app/app/api/celery_task.py @@ -6,50 +6,6 @@ from app.models.hero_model import Hero from app.db.session import SessionLocal from asyncer import runnify -import logging -from celery import Task -from transformers import pipeline - - -class PredictTransformersPipelineTask(Task): - """ - Abstraction of Celery's Task class to support loading transformers model. - """ - - task_name = "" - model_name = "" - abstract = True - - def __init__(self): - super().__init__() - self.pipeline = None - - def __call__(self, *args, **kwargs): - """ - Load pipeline on first call (i.e. first task processed) - Avoids the need to load pipeline on each task request - """ - if not self.pipeline: - logging.info("Loading pipeline...") - self.pipeline = pipeline(self.task_name, model=self.model_name) - logging.info("Pipeline loaded") - return self.run(*args, **kwargs) - - -@celery.task( - ignore_result=False, - bind=True, - base=PredictTransformersPipelineTask, - task_name="text-generation", - model_name="gpt2", - name="tasks.predict_transformers_pipeline", -) -def predict_transformers_pipeline(self, prompt: str): - """ - Essentially the run method of PredictTask - """ - result = self.pipeline(prompt) - return result @celery.task(name="tasks.increment") diff --git a/create_fastapi_project/templates/full/backend/app/app/api/v1/api.py b/create_fastapi_project/templates/full/backend/app/app/api/v1/api.py index db291d9..d8af806 100644 --- a/create_fastapi_project/templates/full/backend/app/app/api/v1/api.py +++ b/create_fastapi_project/templates/full/backend/app/app/api/v1/api.py @@ -1,6 +1,5 @@ from fastapi import APIRouter from app.api.v1.endpoints import ( - natural_language, user, hero, team, @@ -23,9 +22,6 @@ api_router.include_router(cache.router, prefix="/cache", tags=["cache"]) api_router.include_router(weather.router, prefix="/weather", tags=["weather"]) api_router.include_router(report.router, prefix="/report", tags=["report"]) -api_router.include_router( - natural_language.router, prefix="/natural_language", tags=["natural_language"] -) api_router.include_router( periodic_tasks.router, prefix="/periodic_tasks", tags=["periodic_tasks"] ) diff --git a/create_fastapi_project/templates/full/backend/app/app/api/v1/endpoints/natural_language.py b/create_fastapi_project/templates/full/backend/app/app/api/v1/endpoints/natural_language.py deleted file mode 100644 index 0c56d8e..0000000 --- a/create_fastapi_project/templates/full/backend/app/app/api/v1/endpoints/natural_language.py +++ /dev/null @@ -1,101 +0,0 @@ -from datetime import datetime, timedelta -from app.api import deps -from app.api.celery_task import predict_transformers_pipeline -from app.models.user_model import User -from fastapi import APIRouter, Depends, HTTPException -from app.utils.fastapi_globals import g -from app.schemas.response_schema import IPostResponseBase, create_response -from app.core.celery import celery -from fastapi_limiter.depends import RateLimiter - -router = APIRouter() - - -@router.post( - "/sentiment_analysis", - dependencies=[ - Depends(RateLimiter(times=10, hours=24)), - ], -) -async def sentiment_analysis_prediction( - prompt: str = "Fastapi is awesome", - current_user: User = Depends(deps.get_current_user()), -) -> IPostResponseBase: - """ - Gets a sentimental analysis predition using a NLP model from transformers libray - """ - sentiment_model = g.sentiment_model - prediction = sentiment_model(prompt) - return create_response(message="Prediction got succesfully", data=prediction) - - -@router.post( - "/text_generation_prediction_batch_task", - dependencies=[ - Depends(RateLimiter(times=10, hours=24)), - ], -) -async def text_generation_prediction_batch_task( - prompt: str = "Batman is awesome because", -) -> IPostResponseBase: - """ - Async batch task for text generation using a NLP model from transformers libray - """ - prection_task = predict_transformers_pipeline.delay(prompt) - return create_response( - message="Prediction got succesfully", data={"task_id": prection_task.task_id} - ) - - -@router.post( - "/text_generation_prediction_batch_task_after_some_seconds", - dependencies=[ - Depends(RateLimiter(times=10, hours=24)), - ], -) -async def text_generation_prediction_batch_task_after_some_seconds( - prompt: str = "Batman is awesome because", seconds: float = 5 -) -> IPostResponseBase: - """ - Async batch task for text generation using a NLP model from transformers libray - - It is executed after x number of seconds - """ - delay_elapsed = datetime.utcnow() + timedelta(seconds=seconds) - prection_task = predict_transformers_pipeline.apply_async( - args=[prompt], eta=delay_elapsed - ) - return create_response( - message="Prediction got succesfully", data={"task_id": prection_task.task_id} - ) - - -@router.get( - "/get_result_from_batch_task", - dependencies=[ - Depends(RateLimiter(times=10, minutes=1)), - ], -) -async def get_result_from_batch_task(task_id: str) -> IPostResponseBase: - """ - Get result from batch task using task_id - """ - async_result = celery.AsyncResult(task_id) - - if async_result.ready(): - if not async_result.successful(): - raise HTTPException( - status_code=404, - detail=f"Task {task_id} with state {async_result.state}.", - ) - - result = async_result.get(timeout=1.0) - return create_response( - message="Prediction got succesfully", - data={"task_id": task_id, "result": result}, - ) - else: - raise HTTPException( - status_code=404, - detail=f"Task {task_id} does not exist or is still running.", - ) diff --git a/create_fastapi_project/templates/full/backend/app/app/main.py b/create_fastapi_project/templates/full/backend/app/app/main.py index 75ac04c..18e4ff5 100644 --- a/create_fastapi_project/templates/full/backend/app/app/main.py +++ b/create_fastapi_project/templates/full/backend/app/app/main.py @@ -76,21 +76,11 @@ async def lifespan(app: FastAPI): FastAPICache.init(RedisBackend(redis_client), prefix="fastapi-cache") await FastAPILimiter.init(redis_client, identifier=user_id_identifier) - # Load a pre-trained sentiment analysis model as a dictionary to an easy cleanup - models: dict[str, Any] = { - "sentiment_model": pipeline( - "sentiment-analysis", - model="distilbert-base-uncased-finetuned-sst-2-english", - ), - } - g.set_default("sentiment_model", models["sentiment_model"]) print("startup fastapi") yield # shutdown await FastAPICache.clear() await FastAPILimiter.close() - models.clear() - g.cleanup() gc.collect() diff --git a/create_fastapi_project/templates/full/backend/app/pyproject.toml b/create_fastapi_project/templates/full/backend/app/pyproject.toml deleted file mode 100644 index d757ce5..0000000 --- a/create_fastapi_project/templates/full/backend/app/pyproject.toml +++ /dev/null @@ -1,88 +0,0 @@ -[tool.poetry] -name = "app" -version = "0.1.0" -description = "" -authors = ["username "] - -[tool.black] -line-length = 88 -target-version = [ "py310", "py311" ] -exclude = "((.eggs | .git | .pytest_cache | build | dist))" - -[tool.ruff] -line-length = 88 -exclude = [".git", "__pycache__", ".mypy_cache", ".pytest_cache"] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - # "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear -] -ignore = [ - "B904", - "B006", - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "C901", # too complex -] - -[tool.ruff.per-file-ignores] -"__init__.py" = ["F401"] - -[tool.mypy] -warn_return_any = true -warn_unused_configs = true -ignore_missing_imports = true -exclude = ["alembic", "__pycache__"] - - -[tool.poetry.dependencies] -python = ">3.9,<3.12" -alembic = "^1.10.2" -asyncpg = "^0.27.0" -fastapi = {extras = ["all"], version = "^0.95.2"} -sqlmodel = "^0.0.8" -python-jose = "^3.3.0" -cryptography = "^38.0.3" -passlib = "^1.7.4" -SQLAlchemy-Utils = "^0.38.3" -SQLAlchemy = "^1.4.40" -fastapi-pagination = {extras = ["sqlalchemy"], version = "^0.11.4"} -fastapi-cache2 = {extras = ["redis"], version = "^0.2.1"} -minio = "^7.1.13" -Pillow = "^9.4.0" -watchfiles = "^0.18.1" -asyncer = "^0.0.2" -httpx = "^0.23.1" -pandas = "^1.5.3" -openpyxl = "^3.0.10" -redis = "^4.5.1" -fastapi-async-sqlalchemy = "^0.3.12" -oso = "^0.26.4" -celery = "^5.2.7" -celery-sqlalchemy-scheduler = "^0.3.0" -psycopg2-binary = "^2.9.5" -transformers = "^4.28.1" -torch = [ - {url = "https://download.pytorch.org/whl/cpu/torch-2.0.0%2Bcpu-cp310-cp310-linux_x86_64.whl", markers = "sys_platform == 'linux'"}, - {url = "https://download.pytorch.org/whl/cpu/torch-2.0.0%2Bcpu-cp310-cp310-win_amd64.whl", markers = "sys_platform == 'win32'"} -] -requests = "^2.29.0" -wheel = "^0.40.0" -setuptools = "^67.7.2" -langchain = "^0.0.262" -openai = "^0.27.5" -fastapi-limiter = "^0.1.5" - -[tool.poetry.group.dev.dependencies] -black = "^23.1.0" -ruff = "^0.0.256" -pytest = "^7.4.0" -pytest-asyncio = "^0.21.1" -mypy = "^1.5.0" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" diff --git a/create_fastapi_project/templates/langchain_basic/README.md b/create_fastapi_project/templates/langchain_basic/README.md index 5f29819..50b973f 100644 --- a/create_fastapi_project/templates/langchain_basic/README.md +++ b/create_fastapi_project/templates/langchain_basic/README.md @@ -25,6 +25,10 @@ This is a FastAPI project initialized using [`create-fastapi-project`](https://g 2. Get your API key from [API Key - SerpApi](https://serpapi.com/manage-api-key). 3. Set your API key as an environment variable named `SERP_API_KEY`. +## Containers Architecture + +As this project uses [Caddy](https://caddyserver.com/) as a reverse proxy, which uses namespaces routing, you can access the documentation with the following path [http://fastapi.localhost/docs](http://fastapi.localhost/docs) + ## Getting Started The commands in this documentation can be customized on the **Makefile**. It can be started with and without docker. @@ -56,21 +60,40 @@ make install make run-app ``` -Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the result. +Open [http://fastapi.localhost/docs](http://fastapi.localhost/docs) with your browser to see the result. You can start editing the server by modifying `app/main.py`. ## Demo Langchain Tools -This template includes some tools to help you get started with your project: - -- Search pokemon by name or id -- Search weather by city name -- Search images by keyword -- Search videos by keyword +- Search weather by city name ![weather-tool](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746086/Allient/create-fastapi-project/weather-tool-demo_lgqtwu.gif) +- Search images by keyword ![images-tool](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746086/Allient/create-fastapi-project/search-images-demo_mkorzv.gif) +- Search videos by keyword ![videos-tool](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746087/Allient/create-fastapi-project/search-videos-demo_wikzn1.gif) +- Search pokemon by name or id ![pokemon-tool](https://res.cloudinary.com/dnv0qwkrk/image/upload/v1692746086/Allient/create-fastapi-project/pokemon-tool-demo_ggsc63.gif) And also includes a agent that uses the tools to answer your questions. -You can access the agent by opening [http://localhost:8000/chat](http://localhost:8000/chat) with your browser. +You can access the agent by opening [http://fastapi.localhost/chat](http://fastapi.localhost/chat) with your browser or you can use the frontend server. + +## Frontend server + +This project includes a frontend server that uses [Streamlit](https://streamlit.io/) to display a chatbot that uses the langchain tools to answer your questions. + +- Run the frontend server (Recommended using docker): + +```bash +make run-streamlit +``` + +Now you can access the frontend server by opening [frontend.localhost](http://frontend.localhost) with your browser. + +if you want to run the frontend server without docker, you need to install the dependencies in the frontend folder. + +```bash +cd frontend +cd app +poetry install +streamlit run streamlit_app.py +``` ## Learn More diff --git a/create_fastapi_project/templates/langchain_basic/backend/app/app/api/v1/endpoints/chat.py b/create_fastapi_project/templates/langchain_basic/backend/app/app/api/v1/endpoints/chat.py index 831f03a..155079e 100644 --- a/create_fastapi_project/templates/langchain_basic/backend/app/app/api/v1/endpoints/chat.py +++ b/create_fastapi_project/templates/langchain_basic/backend/app/app/api/v1/endpoints/chat.py @@ -1,6 +1,6 @@ +from app.core.config import settings from app.schemas.message_schema import ( IChatResponse, - IUserMessage, ) import logging from app.utils.adaptive_cards.cards import create_adaptive_card @@ -15,7 +15,7 @@ YoutubeSearchTool, GeneralWeatherTool, ) -from fastapi import APIRouter, WebSocket +from fastapi import APIRouter, WebSocket, WebSocketDisconnect from app.utils.uuid6 import uuid7 from langchain.chat_models import ChatOpenAI from langchain.schema import SystemMessage @@ -37,6 +37,10 @@ @router.websocket("") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() + if not settings.OPENAI_API_KEY.startswith("sk-"): + await websocket.send_json({"error": "OPENAI_API_KEY is not set"}) + return + while True: data = await websocket.receive_json() user_message = data["message"] @@ -86,6 +90,10 @@ async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket): await websocket.accept() + if not settings.OPENAI_API_KEY.startswith("sk-"): + await websocket.send_json({"error": "OPENAI_API_KEY is not set"}) + return + while True: try: data = await websocket.receive_json() diff --git a/create_fastapi_project/templates/langchain_basic/backend/app/app/main.py b/create_fastapi_project/templates/langchain_basic/backend/app/app/main.py index 6746094..cf488fc 100644 --- a/create_fastapi_project/templates/langchain_basic/backend/app/app/main.py +++ b/create_fastapi_project/templates/langchain_basic/backend/app/app/main.py @@ -1,6 +1,4 @@ -from fastapi import ( - FastAPI, -) +from fastapi import FastAPI, HTTPException from fastapi.responses import HTMLResponse from app.api.v1.api import api_router as api_router_v1 from app.core.config import settings @@ -37,6 +35,11 @@ async def root(): @app.get("/chat", response_class=HTMLResponse) async def chat(): + if not settings.OPENAI_API_KEY.startswith("sk-"): + raise HTTPException( + status_code=500, detail="OPENAI_API_KEY is not set or not start with sk-" + ) + return chat_html diff --git a/create_fastapi_project/templates/langchain_basic/caddy/Caddyfile b/create_fastapi_project/templates/langchain_basic/caddy/Caddyfile index e80f60d..e17bb59 100644 --- a/create_fastapi_project/templates/langchain_basic/caddy/Caddyfile +++ b/create_fastapi_project/templates/langchain_basic/caddy/Caddyfile @@ -2,7 +2,7 @@ email custom@domain.com } -fastapi.{$EXT_ENDPOINT1}:80, fastapi.{$LOCAL_1}:80, fastapi.{$LOCAL_2}:80, :80 { +fastapi.{$EXT_ENDPOINT1}:80, fastapi.{$LOCAL_1}:80, fastapi.{$LOCAL_2}:80 { reverse_proxy fastapi_server:8000 } diff --git a/create_fastapi_project/templates/langchain_basic/docker-compose-dev.yml b/create_fastapi_project/templates/langchain_basic/docker-compose-dev.yml index de77b97..057696c 100644 --- a/create_fastapi_project/templates/langchain_basic/docker-compose-dev.yml +++ b/create_fastapi_project/templates/langchain_basic/docker-compose-dev.yml @@ -10,21 +10,17 @@ services: - ./backend/app:/code expose: - 8000 - ports: - - 8000:8000 env_file: ".env" streamlit_frontend: container_name: streamlit_frontend build: ./frontend restart: always - # command: "sh -c 'streamlit run app.py --server.port 8501'" volumes: - - ./frontend:/code + - ./frontend/app:/code expose: - 8501 - ports: - - 8501:8501 + env_file: ".env" caddy_reverse_proxy: diff --git a/create_fastapi_project/templates/langchain_basic/docker-compose.yml b/create_fastapi_project/templates/langchain_basic/docker-compose.yml index e95aa43..4d2c81b 100644 --- a/create_fastapi_project/templates/langchain_basic/docker-compose.yml +++ b/create_fastapi_project/templates/langchain_basic/docker-compose.yml @@ -6,9 +6,41 @@ services: restart: always command: "sh -c 'gunicorn -w 3 -k uvicorn.workers.UvicornWorker app.main:app --bind 0.0.0.0:8000 --preload --log-level=debug --timeout 120'" volumes: - - ./app:/code + - ./backend/app:/code expose: - 8000 ports: - 8000:8000 env_file: ".env" + + streamlit_frontend: + container_name: streamlit_frontend + build: ./frontend + restart: always + volumes: + - ./frontend/app:/code + expose: + - 8501 + + env_file: ".env" + + caddy_reverse_proxy: + container_name: caddy_reverse_proxy + image: caddy:alpine + restart: always + ports: + - 80:80 + - 443:443 + environment: + - EXT_ENDPOINT1=${EXT_ENDPOINT1} + - LOCAL_1=${LOCAL_1} + - LOCAL_2=${LOCAL_2} + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile + - ./static:/code/static + - caddy_data:/data + - caddy_config:/config + +volumes: + caddy_data: + caddy_config: diff --git a/create_fastapi_project/templates/langchain_basic/frontend/Dockerfile b/create_fastapi_project/templates/langchain_basic/frontend/Dockerfile index def7146..fc9d1cb 100644 --- a/create_fastapi_project/templates/langchain_basic/frontend/Dockerfile +++ b/create_fastapi_project/templates/langchain_basic/frontend/Dockerfile @@ -1,24 +1,21 @@ -# app/Dockerfile - -FROM python:3.9-slim - -WORKDIR /app - -RUN apt-get update && apt-get install -y \ - build-essential \ - curl \ - software-properties-common \ - git \ - && rm -rf /var/lib/apt/lists/* - - -# Copiar la carpeta app al directorio de trabajo del contenedor -COPY app /app - -RUN pip3 install -r /app/requirements.txt - +FROM python:3.10-slim +ENV PYTHONUNBUFFERED=1 +WORKDIR /code +# Install Poetry +RUN apt clean && apt update && apt install curl -y +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \ + cd /usr/local/bin && \ + ln -s /opt/poetry/bin/poetry && \ + poetry config virtualenvs.create false + +# Copy poetry.lock* in case it doesn't exist in the repo +COPY app/pyproject.toml app/poetry.lock* /code/ + +ENV PYTHONPATH=/code EXPOSE 8501 -HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health +ARG INSTALL_DEV=false +RUN bash -c "if [ $INSTALL_DEV == 'true' ] ; then poetry install --no-root ; else poetry install --no-root --no-dev ; fi" + -ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] +ENTRYPOINT ["streamlit", "run", "main.py", "--server.port=8501", "--server.address=0.0.0.0"] diff --git a/create_fastapi_project/templates/langchain_basic/frontend/app/streamlit_app.py b/create_fastapi_project/templates/langchain_basic/frontend/app/main.py similarity index 94% rename from create_fastapi_project/templates/langchain_basic/frontend/app/streamlit_app.py rename to create_fastapi_project/templates/langchain_basic/frontend/app/main.py index 03f17ab..3b576c6 100644 --- a/create_fastapi_project/templates/langchain_basic/frontend/app/streamlit_app.py +++ b/create_fastapi_project/templates/langchain_basic/frontend/app/main.py @@ -21,6 +21,10 @@ async def retrieve_bot_response(text): response = await asyncio.wait_for(websocket.recv(), timeout=20) response = json.loads(response) + if "error" in response: + stream_data = response["error"] + break + if response["sender"] == "bot": stream_data = ( response["message"]["body"][0]["items"][0]["text"] diff --git a/create_fastapi_project/templates/langchain_basic/frontend/app/requirements.txt b/create_fastapi_project/templates/langchain_basic/frontend/app/requirements.txt deleted file mode 100644 index 03dc771..0000000 --- a/create_fastapi_project/templates/langchain_basic/frontend/app/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -streamlit -websockets \ No newline at end of file diff --git a/create_fastapi_project/templates/langchain_basic/streamlit.yml b/create_fastapi_project/templates/langchain_basic/streamlit.yml new file mode 100644 index 0000000..acc49c7 --- /dev/null +++ b/create_fastapi_project/templates/langchain_basic/streamlit.yml @@ -0,0 +1,15 @@ +version: "3.8" + +services: + streamlit_frontend: + container_name: streamlit_frontend + build: ./frontend + restart: always + # command: "sh -c 'streamlit run app.py --server.port 8501'" + volumes: + - ./frontend:/code + expose: + - 8501 + ports: + - 8501:8501 + env_file: ".env" diff --git a/poetry.lock b/poetry.lock index 9730e72..ac7c3b7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -50,7 +49,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "click" version = "8.1.6" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -65,7 +63,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -77,7 +74,6 @@ files = [ name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -102,7 +98,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -114,7 +109,6 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -156,7 +150,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -168,7 +161,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -180,7 +172,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -192,7 +183,6 @@ files = [ name = "platformdirs" version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -208,7 +198,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "prompt-toolkit" version = "3.0.36" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.6.2" files = [ @@ -223,7 +212,6 @@ wcwidth = "*" name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -238,7 +226,6 @@ plugins = ["importlib-metadata"] name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -253,7 +240,6 @@ cli = ["click (>=5.0)"] name = "questionary" version = "2.0.0" description = "Python library to build pretty command line user prompts ⭐️" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -268,7 +254,6 @@ prompt_toolkit = ">=2.0,<=3.0.36" name = "rich" version = "13.5.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -287,7 +272,6 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "ruff" version = "0.0.284" description = "An extremely fast Python linter, written in Rust." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -314,7 +298,6 @@ files = [ name = "shellingham" version = "1.5.0.post1" description = "Tool to Detect Surrounding Shell" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -326,7 +309,6 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -338,7 +320,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -350,7 +331,6 @@ files = [ name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -375,7 +355,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -387,7 +366,6 @@ files = [ name = "wcwidth" version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ diff --git a/pyproject.toml b/pyproject.toml index 800b6c1..0857936 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "create-fastapi-project" -version = "0.1.5" +version = "0.1.8" description = "" authors = ["jonra1993 "] readme = "README.md"