Spaces:
Sleeping
Sleeping
postgres database integration
#3
by
praneethys
- opened
- .dockerignore +2 -1
- .env.example +8 -0
- .gitignore +2 -1
- .vscode/settings.json +9 -1
- Dockerfile.local.postgres +23 -0
- Makefile +8 -0
- README.md +30 -0
- alembic.ini +116 -0
- app/api/routers/user.py +92 -0
- app/engine/postgresdb.py +35 -0
- app/model/base.py +9 -0
- app/model/transaction.py +16 -0
- app/model/user.py +16 -0
- app/schema/base.py +11 -0
- app/schema/index.py +33 -0
- main.py +41 -3
- migration/README +1 -0
- migration/env.py +89 -0
- migration/script.py.mako +26 -0
- migration/versions/cd515c44401d_add_users_transactions_tables.py +55 -0
- poetry.lock +218 -1
- pyproject.toml +8 -0
- tests/test_main.py +9 -0
- tests/test_postgresdb_connection.py +15 -0
.dockerignore
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
__pycache__
|
2 |
storage
|
3 |
-
data/*
|
|
|
|
1 |
__pycache__
|
2 |
storage
|
3 |
+
data/*
|
4 |
+
venv
|
.env.example
CHANGED
@@ -43,3 +43,11 @@ APP_HOST=0.0.0.0
|
|
43 |
|
44 |
# The port to start the backend app.
|
45 |
APP_PORT=8000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
# The port to start the backend app.
|
45 |
APP_PORT=8000
|
46 |
+
|
47 |
+
# Postgres database configuration
|
48 |
+
POSTGRES_USER=postgres
|
49 |
+
POSTGRES_PASSWORD=postgres
|
50 |
+
POSTGRES_DB_NAME=codepath_project_postgres_local
|
51 |
+
POSTGRES_DB_HOST=localhost
|
52 |
+
POSTGRES_DB_PORT=5432
|
53 |
+
SQLALCHEMY_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_DB_HOST}:${POSTGRES_DB_PORT}/${POSTGRES_DB_NAME}
|
.gitignore
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
__pycache__
|
2 |
storage
|
3 |
.env
|
4 |
-
data/*
|
|
|
|
1 |
__pycache__
|
2 |
storage
|
3 |
.env
|
4 |
+
data/*
|
5 |
+
venv
|
.vscode/settings.json
CHANGED
@@ -1,3 +1,11 @@
|
|
1 |
{
|
2 |
-
"jupyter.notebookFileRoot": "${workspaceFolder}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
}
|
|
|
1 |
{
|
2 |
+
"jupyter.notebookFileRoot": "${workspaceFolder}",
|
3 |
+
"[python]": {
|
4 |
+
"editor.defaultFormatter": "ms-python.black-formatter",
|
5 |
+
"editor.formatOnSave": true,
|
6 |
+
},
|
7 |
+
"ms-python.black-formatter.args": [
|
8 |
+
"--line-length",
|
9 |
+
"119"
|
10 |
+
]
|
11 |
}
|
Dockerfile.local.postgres
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Pull the official PostgreSQL 16 image
|
2 |
+
FROM postgres:16
|
3 |
+
|
4 |
+
# Create a directory for the database files
|
5 |
+
RUN mkdir -p /var/lib/postgresql/data
|
6 |
+
|
7 |
+
# Change the ownership of the data directory
|
8 |
+
RUN chown -R postgres:postgres /var/lib/postgresql/data
|
9 |
+
|
10 |
+
# Copy the configuration files into the data directory
|
11 |
+
# NOTE: the [f] suffix is to ensure that the COPY command will not fail if the files don't exist
|
12 |
+
COPY ./pg_hba.con[f] /var/lib/postgresql/data/pg_hba.conf
|
13 |
+
COPY ./postgresql.con[f] /var/lib/postgresql/data/postgresql.conf
|
14 |
+
|
15 |
+
# Include environment variables for the PostgreSQL user and password
|
16 |
+
ENV POSTGRES_USER=${POSTGRES_USER}
|
17 |
+
ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
18 |
+
|
19 |
+
# Expose the default PostgreSQL port
|
20 |
+
EXPOSE 5432
|
21 |
+
|
22 |
+
# Start the PostgreSQL server
|
23 |
+
CMD ["postgres", "-c", "config_file=/var/lib/postgresql/data/postgresql.conf"]
|
Makefile
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
include .env
|
3 |
+
|
4 |
+
build-postgres:
|
5 |
+
docker build -t codepath_project_postgres -f Dockerfile.local.postgres .
|
6 |
+
|
7 |
+
run-postgres:
|
8 |
+
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -e POSTGRES_USER=${POSTGRES_USER} --name ${POSTGRES_DB_NAME} codepath_project_postgres
|
README.md
CHANGED
@@ -69,6 +69,36 @@ The API allows CORS for all origins to simplify development. You can change this
|
|
69 |
ENVIRONMENT=prod python main.py
|
70 |
```
|
71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
## Using Docker
|
73 |
|
74 |
1. Build an image for the FastAPI app:
|
|
|
69 |
ENVIRONMENT=prod python main.py
|
70 |
```
|
71 |
|
72 |
+
## Local Postgres database setup
|
73 |
+
|
74 |
+
To setup a local postgres database, run:
|
75 |
+
|
76 |
+
1. Build the docker image:
|
77 |
+
|
78 |
+
```bash
|
79 |
+
make build-postgres
|
80 |
+
```
|
81 |
+
|
82 |
+
2. Start the docker container:
|
83 |
+
|
84 |
+
```bash
|
85 |
+
make run-postgres
|
86 |
+
```
|
87 |
+
|
88 |
+
## Running Migrations
|
89 |
+
|
90 |
+
To generate new migrations, run:
|
91 |
+
|
92 |
+
```bash
|
93 |
+
alembic revision --autogenerate -m "<your_comment>"
|
94 |
+
```
|
95 |
+
|
96 |
+
To locally verify your changes, run:
|
97 |
+
|
98 |
+
```bash
|
99 |
+
alembic upgrade head
|
100 |
+
```
|
101 |
+
|
102 |
## Using Docker
|
103 |
|
104 |
1. Build an image for the FastAPI app:
|
alembic.ini
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# A generic, single database configuration.
|
2 |
+
|
3 |
+
[alembic]
|
4 |
+
# path to migration scripts
|
5 |
+
script_location = migration
|
6 |
+
|
7 |
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
8 |
+
# Uncomment the line below if you want the files to be prepended with date and time
|
9 |
+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
10 |
+
# for all available tokens
|
11 |
+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
12 |
+
|
13 |
+
# sys.path path, will be prepended to sys.path if present.
|
14 |
+
# defaults to the current working directory.
|
15 |
+
prepend_sys_path = .
|
16 |
+
|
17 |
+
# timezone to use when rendering the date within the migration file
|
18 |
+
# as well as the filename.
|
19 |
+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
|
20 |
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
21 |
+
# string value is passed to ZoneInfo()
|
22 |
+
# leave blank for localtime
|
23 |
+
# timezone =
|
24 |
+
|
25 |
+
# max length of characters to apply to the
|
26 |
+
# "slug" field
|
27 |
+
# truncate_slug_length = 40
|
28 |
+
|
29 |
+
# set to 'true' to run the environment during
|
30 |
+
# the 'revision' command, regardless of autogenerate
|
31 |
+
# revision_environment = false
|
32 |
+
|
33 |
+
# set to 'true' to allow .pyc and .pyo files without
|
34 |
+
# a source .py file to be detected as revisions in the
|
35 |
+
# versions/ directory
|
36 |
+
# sourceless = false
|
37 |
+
|
38 |
+
# version location specification; This defaults
|
39 |
+
# to migration/versions. When using multiple version
|
40 |
+
# directories, initial revisions must be specified with --version-path.
|
41 |
+
# The path separator used here should be the separator specified by "version_path_separator" below.
|
42 |
+
# version_locations = %(here)s/bar:%(here)s/bat:migration/versions
|
43 |
+
|
44 |
+
# version path separator; As mentioned above, this is the character used to split
|
45 |
+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
46 |
+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
47 |
+
# Valid values for version_path_separator are:
|
48 |
+
#
|
49 |
+
# version_path_separator = :
|
50 |
+
# version_path_separator = ;
|
51 |
+
# version_path_separator = space
|
52 |
+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
53 |
+
|
54 |
+
# set to 'true' to search source files recursively
|
55 |
+
# in each "version_locations" directory
|
56 |
+
# new in Alembic version 1.10
|
57 |
+
# recursive_version_locations = false
|
58 |
+
|
59 |
+
# the output encoding used when revision files
|
60 |
+
# are written from script.py.mako
|
61 |
+
# output_encoding = utf-8
|
62 |
+
|
63 |
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
64 |
+
|
65 |
+
|
66 |
+
[post_write_hooks]
|
67 |
+
# post_write_hooks defines scripts or Python functions that are run
|
68 |
+
# on newly generated revision scripts. See the documentation for further
|
69 |
+
# detail and examples
|
70 |
+
|
71 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
72 |
+
# hooks = black
|
73 |
+
# black.type = console_scripts
|
74 |
+
# black.entrypoint = black
|
75 |
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
76 |
+
|
77 |
+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
78 |
+
# hooks = ruff
|
79 |
+
# ruff.type = exec
|
80 |
+
# ruff.executable = %(here)s/.venv/bin/ruff
|
81 |
+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
82 |
+
|
83 |
+
# Logging configuration
|
84 |
+
[loggers]
|
85 |
+
keys = root,sqlalchemy,alembic
|
86 |
+
|
87 |
+
[handlers]
|
88 |
+
keys = console
|
89 |
+
|
90 |
+
[formatters]
|
91 |
+
keys = generic
|
92 |
+
|
93 |
+
[logger_root]
|
94 |
+
level = WARN
|
95 |
+
handlers = console
|
96 |
+
qualname =
|
97 |
+
|
98 |
+
[logger_sqlalchemy]
|
99 |
+
level = WARN
|
100 |
+
handlers =
|
101 |
+
qualname = sqlalchemy.engine
|
102 |
+
|
103 |
+
[logger_alembic]
|
104 |
+
level = INFO
|
105 |
+
handlers =
|
106 |
+
qualname = alembic
|
107 |
+
|
108 |
+
[handler_console]
|
109 |
+
class = StreamHandler
|
110 |
+
args = (sys.stderr,)
|
111 |
+
level = NOTSET
|
112 |
+
formatter = generic
|
113 |
+
|
114 |
+
[formatter_generic]
|
115 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
116 |
+
datefmt = %H:%M:%S
|
app/api/routers/user.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List
|
2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
3 |
+
from sqlalchemy.orm import Session
|
4 |
+
|
5 |
+
from app.engine.postgresdb import get_db
|
6 |
+
from app.schema.index import UserCreate, User as UserSchema
|
7 |
+
from app.model.user import User as UserModel
|
8 |
+
|
9 |
+
|
10 |
+
user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"])
|
11 |
+
|
12 |
+
|
13 |
+
@r.post(
|
14 |
+
"/",
|
15 |
+
response_model=UserSchema,
|
16 |
+
responses={
|
17 |
+
200: {"description": "New user created"},
|
18 |
+
400: {"description": "Bad request"},
|
19 |
+
409: {"description": "Conflict"},
|
20 |
+
500: {"description": "Internal server error"},
|
21 |
+
},
|
22 |
+
)
|
23 |
+
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
|
24 |
+
try:
|
25 |
+
db_user = (
|
26 |
+
db.query(UserModel).filter(UserModel.email == user.email).sort_by(UserModel.updated_at, desc=True).first()
|
27 |
+
)
|
28 |
+
if db_user and not db_user.is_deleted:
|
29 |
+
raise HTTPException(status_code=409, detail="User already exists")
|
30 |
+
|
31 |
+
db_user = UserModel(**user.dict())
|
32 |
+
db.add(db_user)
|
33 |
+
db.commit()
|
34 |
+
db.refresh(db_user)
|
35 |
+
return db_user
|
36 |
+
except Exception as e:
|
37 |
+
raise HTTPException(status_code=500, detail=str(e))
|
38 |
+
|
39 |
+
|
40 |
+
@r.get(
|
41 |
+
"/{user_id}",
|
42 |
+
response_model=UserSchema,
|
43 |
+
responses={
|
44 |
+
200: {"description": "User found"},
|
45 |
+
404: {"description": "User not found"},
|
46 |
+
500: {"description": "Internal server error"},
|
47 |
+
},
|
48 |
+
)
|
49 |
+
async def get_user(user_id: int, db: Session = Depends(get_db)):
|
50 |
+
user = db.query(UserModel).get(user_id)
|
51 |
+
if not user:
|
52 |
+
raise HTTPException(status_code=404, detail="User not found")
|
53 |
+
return user
|
54 |
+
|
55 |
+
|
56 |
+
@r.put(
|
57 |
+
"/{user_id}",
|
58 |
+
response_model=UserSchema,
|
59 |
+
responses={
|
60 |
+
200: {"description": "User updated"},
|
61 |
+
404: {"description": "User not found"},
|
62 |
+
500: {"description": "Internal server error"},
|
63 |
+
},
|
64 |
+
)
|
65 |
+
async def update_user(user_id: int, user_payload: UserCreate, db: Session = Depends(get_db)):
|
66 |
+
user = db.query(UserModel).get(user_id)
|
67 |
+
if not user:
|
68 |
+
raise HTTPException(status_code=404, detail="User not found")
|
69 |
+
user.name = user_payload.name
|
70 |
+
user.email = user_payload.email
|
71 |
+
db.commit()
|
72 |
+
db.refresh(user)
|
73 |
+
return user
|
74 |
+
|
75 |
+
|
76 |
+
@r.delete(
|
77 |
+
"/{user_id}",
|
78 |
+
response_model=UserSchema,
|
79 |
+
responses={
|
80 |
+
200: {"description": "User deleted"},
|
81 |
+
404: {"description": "User not found"},
|
82 |
+
500: {"description": "Internal server error"},
|
83 |
+
},
|
84 |
+
)
|
85 |
+
async def delete_user(user_id: int, db: Session = Depends(get_db)):
|
86 |
+
user = db.query(UserModel).get(user_id)
|
87 |
+
if not user:
|
88 |
+
raise HTTPException(status_code=404, detail="User not found")
|
89 |
+
user.is_deleted = True
|
90 |
+
db.commit()
|
91 |
+
db.refresh(user)
|
92 |
+
return user
|
app/engine/postgresdb.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import logging
|
3 |
+
from sqlalchemy import create_engine
|
4 |
+
from sqlalchemy.orm import sessionmaker, declarative_base, Session
|
5 |
+
from fastapi import Request
|
6 |
+
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
load_dotenv()
|
10 |
+
logger = logging.getLogger(__name__)
|
11 |
+
|
12 |
+
SQLALCHEMY_DATABASE_URL = os.getenv("SQLALCHEMY_DATABASE_URL")
|
13 |
+
assert SQLALCHEMY_DATABASE_URL, f"SQLALCHEMY_DATABASE_URL is not set"
|
14 |
+
logger.info(f"SQLALCHEMY_DATABASE_URL: {SQLALCHEMY_DATABASE_URL}")
|
15 |
+
|
16 |
+
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
17 |
+
|
18 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
19 |
+
|
20 |
+
Base = declarative_base()
|
21 |
+
|
22 |
+
|
23 |
+
# def get_db():
|
24 |
+
# session = SessionLocal()
|
25 |
+
# logger.info("Postgres Session created successfully!")
|
26 |
+
# try:
|
27 |
+
# yield session
|
28 |
+
# finally:
|
29 |
+
# session.close()
|
30 |
+
# logger.info("Postgres Session closed successfully!")
|
31 |
+
|
32 |
+
|
33 |
+
# Dependency
|
34 |
+
def get_db(request: Request):
|
35 |
+
return request.state.db
|
app/model/base.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from sqlalchemy import Column, DateTime, Integer
|
3 |
+
|
4 |
+
from app.engine.postgresdb import Base
|
5 |
+
|
6 |
+
class BaseModel():
|
7 |
+
id = Column(Integer, primary_key=True, index=True)
|
8 |
+
created_at = Column(DateTime, default=datetime.now())
|
9 |
+
updated_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now())
|
app/model/transaction.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String
|
2 |
+
from sqlalchemy.orm import relationship
|
3 |
+
|
4 |
+
from app.model.base import BaseModel
|
5 |
+
from app.engine.postgresdb import Base
|
6 |
+
|
7 |
+
class Transaction(Base, BaseModel):
|
8 |
+
__tablename__ = "transactions"
|
9 |
+
transaction_date = Column(DateTime)
|
10 |
+
category = Column(String)
|
11 |
+
name_description = Column(String)
|
12 |
+
amount = Column(Float)
|
13 |
+
type = Column(String)
|
14 |
+
|
15 |
+
user_id = Column(Integer, ForeignKey("users.id"))
|
16 |
+
user = relationship("User", back_populates="transactions")
|
app/model/user.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, String, Boolean
|
2 |
+
from sqlalchemy.orm import relationship
|
3 |
+
|
4 |
+
from app.model.base import BaseModel
|
5 |
+
from app.engine.postgresdb import Base
|
6 |
+
|
7 |
+
|
8 |
+
class User(Base, BaseModel):
|
9 |
+
__tablename__ = "users"
|
10 |
+
|
11 |
+
name = Column(String)
|
12 |
+
email = Column(String)
|
13 |
+
hashed_password = Column(String)
|
14 |
+
is_deleted = Column(Boolean, default=False)
|
15 |
+
|
16 |
+
transactions = relationship("Transaction", back_populates="user")
|
app/schema/base.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from pydantic import BaseModel
|
3 |
+
|
4 |
+
|
5 |
+
class BaseModel(BaseModel):
|
6 |
+
id: int
|
7 |
+
created_at: datetime
|
8 |
+
updated_at: datetime
|
9 |
+
|
10 |
+
class Config:
|
11 |
+
from_attributes = True
|
app/schema/index.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from enum import Enum
|
2 |
+
from datetime import datetime
|
3 |
+
from typing import List
|
4 |
+
|
5 |
+
from app.schema.base import BaseModel
|
6 |
+
|
7 |
+
|
8 |
+
class TransactionType(str, Enum):
|
9 |
+
INCOME = "income"
|
10 |
+
EXPENSE = "expense"
|
11 |
+
|
12 |
+
|
13 |
+
class UserCreate(BaseModel):
|
14 |
+
name: str
|
15 |
+
email: str
|
16 |
+
hashed_password: str
|
17 |
+
|
18 |
+
|
19 |
+
class User(BaseModel):
|
20 |
+
name: str
|
21 |
+
email: str
|
22 |
+
hashed_password: str
|
23 |
+
is_deleted: bool = False
|
24 |
+
transactions: "List[Transaction]"
|
25 |
+
|
26 |
+
|
27 |
+
class Transaction(BaseModel):
|
28 |
+
transaction_date: datetime
|
29 |
+
category: str
|
30 |
+
name_description: str
|
31 |
+
amount: float
|
32 |
+
type: TransactionType
|
33 |
+
user: User
|
main.py
CHANGED
@@ -5,16 +5,54 @@ load_dotenv()
|
|
5 |
import logging
|
6 |
import os
|
7 |
import uvicorn
|
8 |
-
from
|
|
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
10 |
from fastapi.responses import RedirectResponse
|
11 |
from app.api.routers.chat import chat_router
|
|
|
12 |
from app.settings import init_settings
|
13 |
from app.observability import init_observability
|
14 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
15 |
|
16 |
|
17 |
-
app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
init_settings()
|
20 |
init_observability()
|
@@ -22,7 +60,6 @@ init_observability()
|
|
22 |
environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set
|
23 |
|
24 |
if environment == "dev":
|
25 |
-
logger = logging.getLogger("uvicorn")
|
26 |
logger.warning("Running in development mode - allowing CORS for all origins")
|
27 |
app.add_middleware(
|
28 |
CORSMiddleware,
|
@@ -41,6 +78,7 @@ if environment == "dev":
|
|
41 |
if os.path.exists("data"):
|
42 |
app.mount("/api/data", StaticFiles(directory="data"), name="static")
|
43 |
app.include_router(chat_router, prefix="/api/chat")
|
|
|
44 |
|
45 |
|
46 |
if __name__ == "__main__":
|
|
|
5 |
import logging
|
6 |
import os
|
7 |
import uvicorn
|
8 |
+
from contextlib import asynccontextmanager
|
9 |
+
from fastapi import FastAPI, Request, Response
|
10 |
from fastapi.middleware.cors import CORSMiddleware
|
11 |
from fastapi.responses import RedirectResponse
|
12 |
from app.api.routers.chat import chat_router
|
13 |
+
from app.api.routers.user import user_router
|
14 |
from app.settings import init_settings
|
15 |
from app.observability import init_observability
|
16 |
from fastapi.staticfiles import StaticFiles
|
17 |
+
from alembic.config import Config
|
18 |
+
from alembic import command
|
19 |
|
20 |
|
21 |
+
from app.engine.postgresdb import engine, Base, SessionLocal
|
22 |
+
|
23 |
+
logger = logging.getLogger("uvicorn")
|
24 |
+
|
25 |
+
# Create all tables in the database
|
26 |
+
Base.metadata.create_all(bind=engine)
|
27 |
+
|
28 |
+
|
29 |
+
def run_migrations():
|
30 |
+
alembic_cfg = Config("alembic.ini")
|
31 |
+
command.upgrade(alembic_cfg, "head")
|
32 |
+
|
33 |
+
|
34 |
+
@asynccontextmanager
|
35 |
+
async def lifespan(app_: FastAPI):
|
36 |
+
logger.info("Starting up...")
|
37 |
+
logger.info("Run 'alembic upgrade head' to apply migrations...")
|
38 |
+
run_migrations()
|
39 |
+
yield
|
40 |
+
logger.info("Shutting down...")
|
41 |
+
|
42 |
+
|
43 |
+
app = FastAPI(lifespan=lifespan)
|
44 |
+
|
45 |
+
|
46 |
+
@app.middleware("http")
|
47 |
+
async def db_session_middleware(request: Request, call_next):
|
48 |
+
response = Response("Internal server error", status_code=500)
|
49 |
+
try:
|
50 |
+
request.state.db = SessionLocal()
|
51 |
+
response = await call_next(request)
|
52 |
+
finally:
|
53 |
+
request.state.db.close()
|
54 |
+
return response
|
55 |
+
|
56 |
|
57 |
init_settings()
|
58 |
init_observability()
|
|
|
60 |
environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set
|
61 |
|
62 |
if environment == "dev":
|
|
|
63 |
logger.warning("Running in development mode - allowing CORS for all origins")
|
64 |
app.add_middleware(
|
65 |
CORSMiddleware,
|
|
|
78 |
if os.path.exists("data"):
|
79 |
app.mount("/api/data", StaticFiles(directory="data"), name="static")
|
80 |
app.include_router(chat_router, prefix="/api/chat")
|
81 |
+
app.include_router(user_router)
|
82 |
|
83 |
|
84 |
if __name__ == "__main__":
|
migration/README
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Generic single-database configuration.
|
migration/env.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from logging.config import fileConfig
|
3 |
+
|
4 |
+
from app.engine.postgresdb import Base
|
5 |
+
from sqlalchemy import engine_from_config
|
6 |
+
from sqlalchemy import pool
|
7 |
+
|
8 |
+
from alembic import context
|
9 |
+
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
# this is the Alembic Config object, which provides
|
15 |
+
# access to the values within the .ini file in use.
|
16 |
+
config = context.config
|
17 |
+
|
18 |
+
config.set_main_option("sqlalchemy.url", os.getenv("SQLALCHEMY_DATABASE_URL"))
|
19 |
+
|
20 |
+
# Interpret the config file for Python logging.
|
21 |
+
# This line sets up loggers basically.
|
22 |
+
if config.config_file_name is not None:
|
23 |
+
fileConfig(config.config_file_name)
|
24 |
+
|
25 |
+
# Import all models so they're registered with SQLAlchemy.
|
26 |
+
from app.model import *
|
27 |
+
|
28 |
+
# add your model's MetaData object here
|
29 |
+
# for 'autogenerate' support
|
30 |
+
# from myapp import mymodel
|
31 |
+
# target_metadata = mymodel.Base.metadata
|
32 |
+
target_metadata = Base.metadata
|
33 |
+
|
34 |
+
# other values from the config, defined by the needs of env.py,
|
35 |
+
# can be acquired:
|
36 |
+
# my_important_option = config.get_main_option("my_important_option")
|
37 |
+
# ... etc.
|
38 |
+
|
39 |
+
|
40 |
+
def run_migrations_offline() -> None:
|
41 |
+
"""Run migrations in 'offline' mode.
|
42 |
+
|
43 |
+
This configures the context with just a URL
|
44 |
+
and not an Engine, though an Engine is acceptable
|
45 |
+
here as well. By skipping the Engine creation
|
46 |
+
we don't even need a DBAPI to be available.
|
47 |
+
|
48 |
+
Calls to context.execute() here emit the given string to the
|
49 |
+
script output.
|
50 |
+
|
51 |
+
"""
|
52 |
+
url = config.get_main_option("sqlalchemy.url")
|
53 |
+
context.configure(
|
54 |
+
url=url,
|
55 |
+
target_metadata=target_metadata,
|
56 |
+
literal_binds=True,
|
57 |
+
dialect_opts={"paramstyle": "named"},
|
58 |
+
)
|
59 |
+
|
60 |
+
with context.begin_transaction():
|
61 |
+
context.run_migrations()
|
62 |
+
|
63 |
+
|
64 |
+
def run_migrations_online() -> None:
|
65 |
+
"""Run migrations in 'online' mode.
|
66 |
+
|
67 |
+
In this scenario we need to create an Engine
|
68 |
+
and associate a connection with the context.
|
69 |
+
|
70 |
+
"""
|
71 |
+
connectable = engine_from_config(
|
72 |
+
config.get_section(config.config_ini_section, {}),
|
73 |
+
prefix="sqlalchemy.",
|
74 |
+
poolclass=pool.NullPool,
|
75 |
+
)
|
76 |
+
|
77 |
+
with connectable.connect() as connection:
|
78 |
+
context.configure(
|
79 |
+
connection=connection, target_metadata=target_metadata, compare_type=True
|
80 |
+
)
|
81 |
+
|
82 |
+
with context.begin_transaction():
|
83 |
+
context.run_migrations()
|
84 |
+
|
85 |
+
|
86 |
+
if context.is_offline_mode():
|
87 |
+
run_migrations_offline()
|
88 |
+
else:
|
89 |
+
run_migrations_online()
|
migration/script.py.mako
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""${message}
|
2 |
+
|
3 |
+
Revision ID: ${up_revision}
|
4 |
+
Revises: ${down_revision | comma,n}
|
5 |
+
Create Date: ${create_date}
|
6 |
+
|
7 |
+
"""
|
8 |
+
from typing import Sequence, Union
|
9 |
+
|
10 |
+
from alembic import op
|
11 |
+
import sqlalchemy as sa
|
12 |
+
${imports if imports else ""}
|
13 |
+
|
14 |
+
# revision identifiers, used by Alembic.
|
15 |
+
revision: str = ${repr(up_revision)}
|
16 |
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
17 |
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
18 |
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
19 |
+
|
20 |
+
|
21 |
+
def upgrade() -> None:
|
22 |
+
${upgrades if upgrades else "pass"}
|
23 |
+
|
24 |
+
|
25 |
+
def downgrade() -> None:
|
26 |
+
${downgrades if downgrades else "pass"}
|
migration/versions/cd515c44401d_add_users_transactions_tables.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Add users, transactions tables
|
2 |
+
|
3 |
+
Revision ID: cd515c44401d
|
4 |
+
Revises:
|
5 |
+
Create Date: 2024-06-01 01:32:42.500524
|
6 |
+
|
7 |
+
"""
|
8 |
+
from typing import Sequence, Union
|
9 |
+
|
10 |
+
from alembic import op
|
11 |
+
import sqlalchemy as sa
|
12 |
+
|
13 |
+
|
14 |
+
# revision identifiers, used by Alembic.
|
15 |
+
revision: str = 'cd515c44401d'
|
16 |
+
down_revision: Union[str, None] = None
|
17 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
18 |
+
depends_on: Union[str, Sequence[str], None] = None
|
19 |
+
|
20 |
+
|
21 |
+
def upgrade() -> None:
|
22 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
23 |
+
op.create_table('users',
|
24 |
+
sa.Column('name', sa.String(), nullable=True),
|
25 |
+
sa.Column('email', sa.String(), nullable=True),
|
26 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
27 |
+
sa.Column('created_at', sa.DateTime(), nullable=True),
|
28 |
+
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
29 |
+
sa.PrimaryKeyConstraint('id')
|
30 |
+
)
|
31 |
+
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
32 |
+
op.create_table('transactions',
|
33 |
+
sa.Column('transaction_date', sa.DateTime(), nullable=True),
|
34 |
+
sa.Column('category', sa.String(), nullable=True),
|
35 |
+
sa.Column('name_description', sa.String(), nullable=True),
|
36 |
+
sa.Column('amount', sa.Float(), nullable=True),
|
37 |
+
sa.Column('type', sa.String(), nullable=True),
|
38 |
+
sa.Column('user_id', sa.Integer(), nullable=True),
|
39 |
+
sa.Column('id', sa.Integer(), nullable=False),
|
40 |
+
sa.Column('created_at', sa.DateTime(), nullable=True),
|
41 |
+
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
42 |
+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
43 |
+
sa.PrimaryKeyConstraint('id')
|
44 |
+
)
|
45 |
+
op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False)
|
46 |
+
# ### end Alembic commands ###
|
47 |
+
|
48 |
+
|
49 |
+
def downgrade() -> None:
|
50 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
51 |
+
op.drop_index(op.f('ix_transactions_id'), table_name='transactions')
|
52 |
+
op.drop_table('transactions')
|
53 |
+
op.drop_index(op.f('ix_users_id'), table_name='users')
|
54 |
+
op.drop_table('users')
|
55 |
+
# ### end Alembic commands ###
|
poetry.lock
CHANGED
@@ -123,6 +123,25 @@ files = [
|
|
123 |
[package.dependencies]
|
124 |
typing-extensions = "*"
|
125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
[[package]]
|
127 |
name = "annotated-types"
|
128 |
version = "0.7.0"
|
@@ -441,6 +460,20 @@ files = [
|
|
441 |
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
|
442 |
]
|
443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
444 |
[[package]]
|
445 |
name = "fastapi"
|
446 |
version = "0.109.2"
|
@@ -925,6 +958,17 @@ files = [
|
|
925 |
{file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
|
926 |
]
|
927 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
928 |
[[package]]
|
929 |
name = "jinja2"
|
930 |
version = "3.1.4"
|
@@ -1465,6 +1509,25 @@ files = [
|
|
1465 |
httpx = ">=0.20.0"
|
1466 |
pydantic = ">=1.10"
|
1467 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1468 |
[[package]]
|
1469 |
name = "markupsafe"
|
1470 |
version = "2.1.5"
|
@@ -2540,6 +2603,21 @@ urllib3 = {version = ">=1.26.0", markers = "python_version >= \"3.8\" and python
|
|
2540 |
[package.extras]
|
2541 |
grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv2 (==0.1.0)", "grpcio (>=1.44.0)", "grpcio (>=1.59.0)", "lz4 (>=3.1.3)", "protobuf (>=3.20.0,<3.21.0)"]
|
2542 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2543 |
[[package]]
|
2544 |
name = "posthog"
|
2545 |
version = "3.5.0"
|
@@ -2583,6 +2661,87 @@ files = [
|
|
2583 |
{file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
|
2584 |
]
|
2585 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2586 |
[[package]]
|
2587 |
name = "pydantic"
|
2588 |
version = "2.7.2"
|
@@ -2711,6 +2870,64 @@ docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"]
|
|
2711 |
full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"]
|
2712 |
image = ["Pillow (>=8.0.0)"]
|
2713 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2714 |
[[package]]
|
2715 |
name = "python-dateutil"
|
2716 |
version = "2.9.0.post0"
|
@@ -3993,4 +4210,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more
|
|
3993 |
[metadata]
|
3994 |
lock-version = "2.0"
|
3995 |
python-versions = "^3.11,<3.12"
|
3996 |
-
content-hash = "
|
|
|
123 |
[package.dependencies]
|
124 |
typing-extensions = "*"
|
125 |
|
126 |
+
[[package]]
|
127 |
+
name = "alembic"
|
128 |
+
version = "1.13.1"
|
129 |
+
description = "A database migration tool for SQLAlchemy."
|
130 |
+
optional = false
|
131 |
+
python-versions = ">=3.8"
|
132 |
+
files = [
|
133 |
+
{file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
|
134 |
+
{file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
|
135 |
+
]
|
136 |
+
|
137 |
+
[package.dependencies]
|
138 |
+
Mako = "*"
|
139 |
+
SQLAlchemy = ">=1.3.0"
|
140 |
+
typing-extensions = ">=4"
|
141 |
+
|
142 |
+
[package.extras]
|
143 |
+
tz = ["backports.zoneinfo"]
|
144 |
+
|
145 |
[[package]]
|
146 |
name = "annotated-types"
|
147 |
version = "0.7.0"
|
|
|
460 |
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
|
461 |
]
|
462 |
|
463 |
+
[[package]]
|
464 |
+
name = "execnet"
|
465 |
+
version = "2.1.1"
|
466 |
+
description = "execnet: rapid multi-Python deployment"
|
467 |
+
optional = false
|
468 |
+
python-versions = ">=3.8"
|
469 |
+
files = [
|
470 |
+
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
471 |
+
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
472 |
+
]
|
473 |
+
|
474 |
+
[package.extras]
|
475 |
+
testing = ["hatch", "pre-commit", "pytest", "tox"]
|
476 |
+
|
477 |
[[package]]
|
478 |
name = "fastapi"
|
479 |
version = "0.109.2"
|
|
|
958 |
{file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
|
959 |
]
|
960 |
|
961 |
+
[[package]]
|
962 |
+
name = "iniconfig"
|
963 |
+
version = "2.0.0"
|
964 |
+
description = "brain-dead simple config-ini parsing"
|
965 |
+
optional = false
|
966 |
+
python-versions = ">=3.7"
|
967 |
+
files = [
|
968 |
+
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
969 |
+
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
970 |
+
]
|
971 |
+
|
972 |
[[package]]
|
973 |
name = "jinja2"
|
974 |
version = "3.1.4"
|
|
|
1509 |
httpx = ">=0.20.0"
|
1510 |
pydantic = ">=1.10"
|
1511 |
|
1512 |
+
[[package]]
|
1513 |
+
name = "mako"
|
1514 |
+
version = "1.3.5"
|
1515 |
+
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
1516 |
+
optional = false
|
1517 |
+
python-versions = ">=3.8"
|
1518 |
+
files = [
|
1519 |
+
{file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
|
1520 |
+
{file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
|
1521 |
+
]
|
1522 |
+
|
1523 |
+
[package.dependencies]
|
1524 |
+
MarkupSafe = ">=0.9.2"
|
1525 |
+
|
1526 |
+
[package.extras]
|
1527 |
+
babel = ["Babel"]
|
1528 |
+
lingua = ["lingua"]
|
1529 |
+
testing = ["pytest"]
|
1530 |
+
|
1531 |
[[package]]
|
1532 |
name = "markupsafe"
|
1533 |
version = "2.1.5"
|
|
|
2603 |
[package.extras]
|
2604 |
grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv2 (==0.1.0)", "grpcio (>=1.44.0)", "grpcio (>=1.59.0)", "lz4 (>=3.1.3)", "protobuf (>=3.20.0,<3.21.0)"]
|
2605 |
|
2606 |
+
[[package]]
|
2607 |
+
name = "pluggy"
|
2608 |
+
version = "1.5.0"
|
2609 |
+
description = "plugin and hook calling mechanisms for python"
|
2610 |
+
optional = false
|
2611 |
+
python-versions = ">=3.8"
|
2612 |
+
files = [
|
2613 |
+
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
2614 |
+
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
2615 |
+
]
|
2616 |
+
|
2617 |
+
[package.extras]
|
2618 |
+
dev = ["pre-commit", "tox"]
|
2619 |
+
testing = ["pytest", "pytest-benchmark"]
|
2620 |
+
|
2621 |
[[package]]
|
2622 |
name = "posthog"
|
2623 |
version = "3.5.0"
|
|
|
2661 |
{file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
|
2662 |
]
|
2663 |
|
2664 |
+
[[package]]
|
2665 |
+
name = "psycopg2-binary"
|
2666 |
+
version = "2.9.9"
|
2667 |
+
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
2668 |
+
optional = false
|
2669 |
+
python-versions = ">=3.7"
|
2670 |
+
files = [
|
2671 |
+
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
|
2672 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
|
2673 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
|
2674 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
|
2675 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
|
2676 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
|
2677 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
|
2678 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
|
2679 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
|
2680 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
|
2681 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
|
2682 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
|
2683 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
|
2684 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
|
2685 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
|
2686 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
|
2687 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
|
2688 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
|
2689 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
|
2690 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
|
2691 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
|
2692 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
|
2693 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
|
2694 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
2695 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
2696 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
2697 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
|
2698 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
2699 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
2700 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
2701 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
|
2702 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
|
2703 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
2704 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
2705 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
2706 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
|
2707 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
|
2708 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
2709 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
2710 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
2711 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
|
2712 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
|
2713 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
|
2714 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
|
2715 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
|
2716 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
|
2717 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
|
2718 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
|
2719 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
|
2720 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
|
2721 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
|
2722 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
|
2723 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
|
2724 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
|
2725 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
|
2726 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
|
2727 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
|
2728 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
|
2729 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
|
2730 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
|
2731 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
|
2732 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
|
2733 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
|
2734 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
|
2735 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
|
2736 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
|
2737 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
|
2738 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
|
2739 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
|
2740 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
|
2741 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
|
2742 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
|
2743 |
+
]
|
2744 |
+
|
2745 |
[[package]]
|
2746 |
name = "pydantic"
|
2747 |
version = "2.7.2"
|
|
|
2870 |
full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"]
|
2871 |
image = ["Pillow (>=8.0.0)"]
|
2872 |
|
2873 |
+
[[package]]
|
2874 |
+
name = "pytest"
|
2875 |
+
version = "8.2.1"
|
2876 |
+
description = "pytest: simple powerful testing with Python"
|
2877 |
+
optional = false
|
2878 |
+
python-versions = ">=3.8"
|
2879 |
+
files = [
|
2880 |
+
{file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"},
|
2881 |
+
{file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"},
|
2882 |
+
]
|
2883 |
+
|
2884 |
+
[package.dependencies]
|
2885 |
+
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
2886 |
+
iniconfig = "*"
|
2887 |
+
packaging = "*"
|
2888 |
+
pluggy = ">=1.5,<2.0"
|
2889 |
+
|
2890 |
+
[package.extras]
|
2891 |
+
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
2892 |
+
|
2893 |
+
[[package]]
|
2894 |
+
name = "pytest-asyncio"
|
2895 |
+
version = "0.23.7"
|
2896 |
+
description = "Pytest support for asyncio"
|
2897 |
+
optional = false
|
2898 |
+
python-versions = ">=3.8"
|
2899 |
+
files = [
|
2900 |
+
{file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
|
2901 |
+
{file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
|
2902 |
+
]
|
2903 |
+
|
2904 |
+
[package.dependencies]
|
2905 |
+
pytest = ">=7.0.0,<9"
|
2906 |
+
|
2907 |
+
[package.extras]
|
2908 |
+
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
2909 |
+
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
2910 |
+
|
2911 |
+
[[package]]
|
2912 |
+
name = "pytest-xdist"
|
2913 |
+
version = "3.6.1"
|
2914 |
+
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
2915 |
+
optional = false
|
2916 |
+
python-versions = ">=3.8"
|
2917 |
+
files = [
|
2918 |
+
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
2919 |
+
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
2920 |
+
]
|
2921 |
+
|
2922 |
+
[package.dependencies]
|
2923 |
+
execnet = ">=2.1"
|
2924 |
+
pytest = ">=7.0.0"
|
2925 |
+
|
2926 |
+
[package.extras]
|
2927 |
+
psutil = ["psutil (>=3.0)"]
|
2928 |
+
setproctitle = ["setproctitle"]
|
2929 |
+
testing = ["filelock"]
|
2930 |
+
|
2931 |
[[package]]
|
2932 |
name = "python-dateutil"
|
2933 |
version = "2.9.0.post0"
|
|
|
4210 |
[metadata]
|
4211 |
lock-version = "2.0"
|
4212 |
python-versions = "^3.11,<3.12"
|
4213 |
+
content-hash = "527fd744fa60d7638490e59133486e40e1a5303635e9f0fd7286ce5ec97ad4d1"
|
pyproject.toml
CHANGED
@@ -28,6 +28,11 @@ pydantic = "^2.7.2"
|
|
28 |
dateparser = "^1.2.0"
|
29 |
pandas = "^2.2.2"
|
30 |
path = "^16.14.0"
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
[tool.poetry.dependencies.uvicorn]
|
33 |
extras = [ "standard" ]
|
@@ -36,6 +41,9 @@ version = "^0.23.2"
|
|
36 |
[tool.poetry.dependencies.llama-index-agent-openai]
|
37 |
version = "0.2.2"
|
38 |
|
|
|
|
|
|
|
39 |
[build-system]
|
40 |
requires = [ "poetry-core" ]
|
41 |
build-backend = "poetry.core.masonry.api"
|
|
|
28 |
dateparser = "^1.2.0"
|
29 |
pandas = "^2.2.2"
|
30 |
path = "^16.14.0"
|
31 |
+
psycopg2-binary = "^2.9.9"
|
32 |
+
pytest-xdist = "^3.6.1"
|
33 |
+
pytest-asyncio = "^0.23.7"
|
34 |
+
httpx = "^0.27.0"
|
35 |
+
alembic = "^1.13.1"
|
36 |
|
37 |
[tool.poetry.dependencies.uvicorn]
|
38 |
extras = [ "standard" ]
|
|
|
41 |
[tool.poetry.dependencies.llama-index-agent-openai]
|
42 |
version = "0.2.2"
|
43 |
|
44 |
+
[tool.black]
|
45 |
+
line-length = 119
|
46 |
+
|
47 |
[build-system]
|
48 |
requires = [ "poetry-core" ]
|
49 |
build-backend = "poetry.core.masonry.api"
|
tests/test_main.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from main import app
|
2 |
+
from fastapi.testclient import TestClient
|
3 |
+
|
4 |
+
client = TestClient(app)
|
5 |
+
|
6 |
+
|
7 |
+
def test_read_main():
|
8 |
+
response = client.get("/")
|
9 |
+
assert response.status_code == 200
|
tests/test_postgresdb_connection.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pytest
|
2 |
+
from app.engine.postgresdb import get_db
|
3 |
+
|
4 |
+
|
5 |
+
@pytest.mark.skip
|
6 |
+
def test_postgres_db_connection():
|
7 |
+
"""
|
8 |
+
Tests the connection to the postgres db
|
9 |
+
"""
|
10 |
+
with get_db() as session:
|
11 |
+
# Execute a query to test the connection
|
12 |
+
result = session.execute(f"SELECT 1").scalar()
|
13 |
+
|
14 |
+
# Assert that the result is equal to 1
|
15 |
+
assert result == 1
|