diff --git a/.dockerignore b/.dockerignore index d9df3712c50f92f4d98b4ec8429cfc1c8e727bfd..53642aa161d485b7debf44d9f2ee4cf3cc151e51 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,3 @@ __pycache__ storage -data/* -venv -myvenv \ No newline at end of file +data/* \ No newline at end of file diff --git a/.env.example b/.env.example index 84a3b6d36ad82dac88f277db331e14c50558eace..47c8010e0c5c0ebac2dc79217952d692eeeacaab 100644 --- a/.env.example +++ b/.env.example @@ -43,11 +43,3 @@ APP_HOST=0.0.0.0 # The port to start the backend app. APP_PORT=8000 - -# Postgres database configuration -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_DB_NAME=postgres -POSTGRES_DB_HOST=localhost -POSTGRES_DB_PORT=5432 -SQLALCHEMY_DATABASE_URL=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_DB_HOST}:${POSTGRES_DB_PORT}/${POSTGRES_DB_NAME} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 81958d8fd7408be24b4c9d0b67b0334cf05e0fc4..221ad6e0a3c1cff6a86b2e83d56100ad317c540b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ __pycache__ storage .env -venv -myvenv \ No newline at end of file +data/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b70bc8a8f4f5ceb824ff516a0571ba84c6fecce0..0000000000000000000000000000000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "jupyter.notebookFileRoot": "${workspaceFolder}", - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter", - "editor.formatOnSave": true, - }, - "ms-python.black-formatter.args": [ - "--line-length", - "119" - ] -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2c9ac9e6c015db6ec734a23aacc1cfdfe9d07041..5a03ac60c584fded0883493a4999b21636b042f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,13 +25,4 @@ COPY . . # Make port 8000 available to the world outside this container EXPOSE 8000 -ENV POSTGRES_USER codepath_project_owner -ENV POSTGRES_PASSWORD 03EdiworgCJz -ENV POSTGRES_DB_NAME codepath_project -ENV POSTGRES_DB_HOST ep-icy-cloud-a5m4mcgo.us-east-2.aws.neon.tech -ENV POSTGRES_DB_PORT 5432 - -# Run migrations -RUN alembic upgrade head - CMD ["python", "main.py"] diff --git a/Dockerfile.local.postgres b/Dockerfile.local.postgres deleted file mode 100644 index 68ab161cbead136a29c8b2c4ec5012ab4b87c0f2..0000000000000000000000000000000000000000 --- a/Dockerfile.local.postgres +++ /dev/null @@ -1,23 +0,0 @@ -# Pull the official PostgreSQL 16 image -FROM postgres:16 - -# Create a directory for the database files -RUN mkdir -p /var/lib/postgresql/data - -# Change the ownership of the data directory -RUN chown -R postgres:postgres /var/lib/postgresql/data - -# Copy the configuration files into the data directory -# NOTE: the [f] suffix is to ensure that the COPY command will not fail if the files don't exist -COPY ./pg_hba.con[f] /var/lib/postgresql/data/pg_hba.conf -COPY ./postgresql.con[f] /var/lib/postgresql/data/postgresql.conf - -# Include environment variables for the PostgreSQL user and password -ENV POSTGRES_USER=${POSTGRES_USER} -ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - -# Expose the default PostgreSQL port -EXPOSE 5432 - -# Start the PostgreSQL server -CMD ["postgres", "-c", "config_file=/var/lib/postgresql/data/postgresql.conf"] diff --git a/Makefile b/Makefile deleted file mode 100644 index a46206f9bb65523540e6e60c1fdbc3e4aa29c949..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ - -include .env - -build-postgres: - docker build -t codepath_project_postgres -f Dockerfile.local.postgres . - -run-postgres: - docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -e POSTGRES_USER=${POSTGRES_USER} --name ${POSTGRES_DB_NAME} codepath_project_postgres - -run-migrations: - alembic upgrade head - -generate-migrations: - alembic revision --autogenerate -m "$(migration_title)" \ No newline at end of file diff --git a/README.md b/README.md index f75ca8f76debc04ce7908607f7cc5f5df8062e1a..b60a862adbc38f2620fa0613157d252a54831295 100644 --- a/README.md +++ b/README.md @@ -69,36 +69,6 @@ The API allows CORS for all origins to simplify development. You can change this ENVIRONMENT=prod python main.py ``` -## Local Postgres database setup - -To setup a local postgres database, run: - -1. Build the docker image: - -```bash -make build-postgres -``` - -2. Start the docker container: - -```bash -make run-postgres -``` - -## Running Migrations - -To generate new migrations, run: - -```bash -make generate-migrations migration_title="" -``` - -To locally verify your changes, run: - -```bash -make run-migrations -``` - ## Using Docker 1. Build an image for the FastAPI app: diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index 8282a3e47a0fba55e0d58a841c206ce348eaa2b0..0000000000000000000000000000000000000000 --- a/alembic.ini +++ /dev/null @@ -1,116 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = migration - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python>=3.9 or backports.zoneinfo library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to migration/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:migration/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = driver://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = --fix REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/app/api/routers/chat.py b/app/api/routers/chat.py index 2ad735776237a4d9c28213ba46b61c0793efa983..c92ca3d425d3634b477122cb11847d09b3af4131 100644 --- a/app/api/routers/chat.py +++ b/app/api/routers/chat.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel from typing import List, Any, Optional, Dict, Tuple from fastapi import APIRouter, Depends, HTTPException, Request, status from llama_index.core.chat_engine.types import BaseChatEngine @@ -19,8 +19,9 @@ class _Message(BaseModel): class _ChatData(BaseModel): messages: List[_Message] - model_config = ConfigDict( - json_schema_extra={ + + class Config: + json_schema_extra = { "example": { "messages": [ { @@ -30,7 +31,6 @@ class _ChatData(BaseModel): ] } } - ) class _SourceNodes(BaseModel): @@ -121,7 +121,12 @@ async def chat( yield VercelStreamResponse.convert_data( { "type": "sources", - "data": {"nodes": [_SourceNodes.from_source_node(node).dict() for node in response.source_nodes]}, + "data": { + "nodes": [ + _SourceNodes.from_source_node(node).dict() + for node in response.source_nodes + ] + }, } ) diff --git a/app/api/routers/file_upload.py b/app/api/routers/file_upload.py deleted file mode 100644 index 30918367a5ce4e1943ec6b41a627f95f6a4518f7..0000000000000000000000000000000000000000 --- a/app/api/routers/file_upload.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Annotated -from fastapi import APIRouter, UploadFile, Depends -from app.categorization.file_processing import process_file, save_results -from app.schema.index import FileUploadCreate -import asyncio -import os -import csv - -from app.engine.postgresdb import get_db_session -from sqlalchemy.ext.asyncio import AsyncSession - -file_upload_router = r = APIRouter(prefix="/api/v1/file_upload", tags=["file_upload"]) - -@r.post( - "/", - responses={ - 200: {"description": "File successfully uploaded"}, - 400: {"description": "Bad request"}, - 500: {"description": "Internal server error"}, - }, -) -async def create_file(input_file: UploadFile, db: AsyncSession = Depends(get_db_session)): - try: - # Create directory to store all uploaded .csv files - file_upload_directory_path = "data/tx_data/input" - if not os.path.exists(file_upload_directory_path): - os.makedirs(file_upload_directory_path) - - # Write items of .csv filte to directory - with open(os.path.join(file_upload_directory_path, input_file.filename)) as output_file: - [output_file.write(" ".join(row)+'\n') for row in csv.reader(input_file)] - output_file.close() - - # With the newly created file and it's path, process and save it for embedding - processed_file = process_file(os.path.realpath(input_file.filename)) - result = await asyncio.gather(processed_file) - await save_results(db, result) - - except Exception: - return {"message": "There was an error uploading this file. Ensure you have a .csv file with the following columns:" - "\n transaction_date, type, category, name_description, amount"} - - return {"message": f"Successfully uploaded {input_file.filename}"} diff --git a/app/api/routers/income_statement.py b/app/api/routers/income_statement.py deleted file mode 100644 index 5976f081c3f87debe0bd977c8d02566e866624f0..0000000000000000000000000000000000000000 --- a/app/api/routers/income_statement.py +++ /dev/null @@ -1,64 +0,0 @@ -from typing import List -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.ext.asyncio import AsyncSession -from app.model.transaction import Transaction as TransactionModel -from app.model.income_statement import IncomeStatement as IncomeStatementModel -from app.schema.index import IncomeStatementCreateRequest, IncomeStatementResponse -from app.engine.postgresdb import get_db_session -from app.service.income_statement import call_llm_to_create_income_statement - -income_statement_router = r = APIRouter(prefix="/api/v1/income_statement", tags=["income_statement"]) - - -@r.post( - "/", - responses={ - 200: {"description": "New transaction created"}, - 400: {"description": "Bad request"}, - 500: {"description": "Internal server error"}, - }, -) -async def create_income_statement(payload: IncomeStatementCreateRequest, db: AsyncSession = Depends(get_db_session)) -> None: - try: - await call_llm_to_create_income_statement(payload, db) - - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@r.get( - "/user/{user_id}", - response_model=List[IncomeStatementResponse], - responses={ - 200: {"description": "New user created"}, - 400: {"description": "Bad request"}, - 204: {"description": "No content"}, - 500: {"description": "Internal server error"}, - }, -) -async def get_income_statements( - user_id: int, db: AsyncSession = Depends(get_db_session) -) -> List[IncomeStatementResponse]: - """ - Retrieve all income statements. - """ - result = await IncomeStatementModel.get_by_user(db, user_id) - if len(result) == 0: - raise HTTPException(status_code=status.HTTP_204_NO_CONTENT, detail="No income statements found for this user") - return result - - -@r.get( - "/report/{report_id}", - response_model=IncomeStatementResponse, - responses={ - 200: {"description": "Income statement found"}, - 404: {"description": "Income statement not found"}, - 500: {"description": "Internal server error"}, - }, -) -async def get_income_statement(report_id: int, db: AsyncSession = Depends(get_db_session)) -> IncomeStatementResponse: - income_statement = await IncomeStatementModel.get(db, id=report_id) - if not income_statement: - raise HTTPException(status_code=404, detail="Income statement not found") - return income_statement diff --git a/app/api/routers/transaction.py b/app/api/routers/transaction.py deleted file mode 100644 index ae4373216b2058afa3c41db1d411983b19bf5088..0000000000000000000000000000000000000000 --- a/app/api/routers/transaction.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -from typing import List -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.ext.asyncio import AsyncSession -from app.model.transaction import Transaction as TransactionModel -from app.schema.index import TransactionResponse -from app.engine.postgresdb import get_db_session - -transaction_router = r = APIRouter(prefix="/api/v1/transactions", tags=["transactions"]) - - -@r.get( - "/{user_id}", - response_model=List[TransactionResponse], - responses={ - 200: {"description": "New user created"}, - 400: {"description": "Bad request"}, - 204: {"description": "No content"}, - 500: {"description": "Internal server error"}, - }, -) -async def get_transactions(user_id: int, db: AsyncSession = Depends(get_db_session)): - """ - Retrieve all transactions. - """ - result = await TransactionModel.get_by_user(db, user_id) - all_rows = result.all() - if len(all_rows) == 0: - raise HTTPException(status_code=status.HTTP_204_NO_CONTENT, detail="No transactions found for this user") - return all_rows diff --git a/app/api/routers/user.py b/app/api/routers/user.py deleted file mode 100644 index 62abbd026aefa9c5066404bb2aa5dd2c229f5db6..0000000000000000000000000000000000000000 --- a/app/api/routers/user.py +++ /dev/null @@ -1,89 +0,0 @@ -import logging -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession - -from app.engine.postgresdb import get_db_session -from app.schema.index import UserCreate, User as UserSchema, UserResponse, UserUpdate -from app.model.user import User as UserModel - - -user_router = r = APIRouter(prefix="/api/v1/users", tags=["users"]) -logger = logging.getLogger(__name__) - - -@r.post( - "/", - response_model=UserResponse, - responses={ - 200: {"description": "New user created"}, - 400: {"description": "Bad request"}, - 409: {"description": "Conflict"}, - 500: {"description": "Internal server error"}, - }, -) -async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db_session)): - try: - db_user = await UserModel.get(db, email=user.email) - if db_user and not db_user.is_deleted: - raise HTTPException(status_code=409, detail="User already exists") - - await UserModel.create(db, **user.model_dump()) - user = await UserModel.get(db, email=user.email) - return user - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@r.get( - "/{email}", - response_model=UserResponse, - responses={ - 200: {"description": "User found"}, - 404: {"description": "User not found"}, - 500: {"description": "Internal server error"}, - }, -) -async def get_user(email: str, db: AsyncSession = Depends(get_db_session)): - user = await UserModel.get(db, email=email) - if not user: - raise HTTPException(status_code=404, detail="User not found") - return user - - -@r.put( - "/{email}", - response_model=UserResponse, - responses={ - 200: {"description": "User updated"}, - 404: {"description": "User not found"}, - 500: {"description": "Internal server error"}, - }, -) -async def update_user(email: str, user_payload: UserUpdate, db: AsyncSession = Depends(get_db_session)): - try: - user = await UserModel.get(db, email=email) - if not user: - raise HTTPException(status_code=404, detail="User not found") - await UserModel.update(db, id=user.id, **user_payload.model_dump()) - user = await UserModel.get(db, email=email) - return user - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@r.delete( - "/{email}", - response_model=UserResponse, - responses={ - 200: {"description": "User deleted"}, - 404: {"description": "User not found"}, - 500: {"description": "Internal server error"}, - }, -) -async def delete_user(email: str, db: AsyncSession = Depends(get_db_session)): - user = await UserModel.get(db, email=email) - if not user: - raise HTTPException(status_code=404, detail="User not found") - await UserModel.delete(db, email=email) - user = await UserModel.get(db, email=email) - return user diff --git a/app/categorization/__init__.py b/app/categorization/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/app/categorization/categorizer.py b/app/categorization/categorizer.py deleted file mode 100644 index 1fc76243dc6cd7b643d37fc50dad37db5182dc23..0000000000000000000000000000000000000000 --- a/app/categorization/categorizer.py +++ /dev/null @@ -1,147 +0,0 @@ -# Standard library imports -import re -import ast -import json -import logging -import os -from typing import Any, List, Tuple, Optional, Dict, Union - -# Third-party library imports -import numpy as np -import pandas as pd -import asyncio -from rapidfuzz import process -from tenacity import retry, wait_random_exponential, stop_after_attempt -from pydantic import ValidationError - -# Local application/library specific imports -from langchain_openai import ChatOpenAI -from langchain.chains import LLMChain -from langchain.output_parsers import PydanticOutputParser, OutputFixingParser -from langchain.prompts import PromptTemplate -import app.categorization.template as CATEGORY_TEMPLATE -from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE, TX_PER_LLM_RUN - - -def fuzzy_match_list_categorizer( - description: str, - descriptions: np.ndarray, - description_category_pairs: pd.DataFrame, - threshold: int = 75, -) -> Optional[str]: - """Find the most similar transaction description and return its category. - - This function uses fuzzy string matching to compare the input description - against a list of known descriptions. If a sufficient match is found, - the function returns the category associated with the matched description. - - Args: - description (str): The transaction description to categorize. - descriptions (np.ndarray): Known descriptions to compare against. - description_category_pairs (pd.DataFrame): DataFrame mapping descriptions to categories. - threshold (int): Minimum similarity score to consider a match. - - Returns: - str or None: Category of the matched description, or None if no match found. - """ - - # Fuzzy-match this description against the reference descriptions - match_results = process.extractOne( - description, descriptions, score_cutoff=threshold) - - # If a match is found, return the category of the matched description - if match_results: - return description_category_pairs.at[match_results[2], 'category'] - - return None - - -async def llm_list_categorizer(tx_list: pd.DataFrame) -> pd.DataFrame: - """Categorize a list of transactions using a language model. - - This function uses a Language Model (LLM) to categorize a list of transaction descriptions. - It splits the input DataFrame into chunks and processes each chunk asynchronously to improve performance. - - Args: - tx_list (pd.DataFrame): DataFrame containing the transaction descriptions to categorize. - - Returns: - pd.DataFrame: DataFrame mapping transaction descriptions to their inferred categories. - """ - - # Initialize language model and prompt - openai_api_key = os.environ['OPENAI_API_KEY'] - llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0125", - api_key=openai_api_key) - prompt = PromptTemplate.from_template(template=CATEGORY_TEMPLATE) - chain = LLMChain(llm=llm, prompt=prompt) - - # Iterate over the DataFrame in batches of TX_PER_LLM_RUN transactions - tasks = [llm_sublist_categorizer(tx_list.attrs['file_name'], chain=chain, tx_descriptions="\n".join(chunk['name/description']).strip()) - for chunk in np.array_split(tx_list, tx_list.shape[0] // TX_PER_LLM_RUN + 1)] - - # Gather results and extract (valid) outputs - # The results variable is a list of 'results', each 'result' being the output of a single LLM run - results = await asyncio.gather(*tasks) - - # Extract valid results (each valid result is a list of description-category pairs) - valid_results = [result['output'] for result in results if result['valid']] - - # Flatten the list of valid results to obtain a single list of description-category pairs - valid_outputs = [ - output for valid_result in valid_results for output in valid_result] - - # Return a DataFrame with the valid outputs - return pd.DataFrame(valid_outputs, columns=['name/description', 'category']) - - -@retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6)) -async def llm_sublist_categorizer( - file_name: str, - chain: LLMChain, - tx_descriptions: str, -) -> Dict[str, Union[bool, List[Tuple[str, str]]]]: - """Categorize a batch of transactions using a language model. - - This function takes a batch of transaction descriptions and passes them to a language model - for categorization. The function retries on failure, with an exponential backoff. - - Args: - file_name (str): Name of the file the transaction descriptions were extracted from. - chain (LLMChain): Language model chain to use for categorization. - tx_descriptions (str): Concatenated transaction descriptions to categorize. - - Returns: - dict: Dictionary containing a 'valid' flag and a list of categorized descriptions. - """ - - raw_result = await chain.arun(input_data=tx_descriptions) - - logger = logging.getLogger(__name__) - result = {'valid': True, 'output': []} - try: - # Create a pattern to match a list Description-Category pairs (List[Tuple[str, str]]) - pattern = r"\['([^']+)', '([^']+)'\]" - - # Use it to extract all the correctly formatted pairs from the raw result - matches = re.findall(pattern, raw_result.replace("\\'", "'")) - - # Loop over the matches, and try to parse them to ensure the content is valid - valid_outputs = [] - for match in matches: - try: - parsed_pair = ast.literal_eval(str(list(match))) - valid_outputs.append(parsed_pair) - except Exception as e: - logger.log(logging.ERROR, - f"Parsing Error: {e}\nMatch: {match}\n") - result['valid'] = False - - result['output'] = valid_outputs - - except Exception as e: - logging.log( - logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}\nRaw Result: {raw_result}") - result['valid'] = False - - return result diff --git a/app/categorization/categorizer_list.py b/app/categorization/categorizer_list.py deleted file mode 100644 index 154d58b11b9062c75d402e886cb9bdb3ce0f5baf..0000000000000000000000000000000000000000 --- a/app/categorization/categorizer_list.py +++ /dev/null @@ -1,70 +0,0 @@ -# Standard library imports -import os -from datetime import datetime - -# Third-party library imports -import pandas as pd - -# Local application/library specific imports -from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE -from app.categorization.categorizer import llm_list_categorizer, fuzzy_match_list_categorizer - - -async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame: - """Asynchronously categorize a list of transactions. - - This function categorizes a list of transactions using a combination of fuzzy matching - and a language model. It looks up new transaction descriptions in the reference file - (a combination of user input and previous executions) to minimize API calls. - Any uncategorized transactions are sent to the language model, and new description-category - pairs are added to the reference file. - - Args: - tx_list (pd.DataFrame): The list of transactions to categorize. - - Returns: - pd.DataFrame: The original DataFrame with an additional column for the category. - """ - - if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE): - # Read description-category pairs from the reference file - description_category_pairs = pd.read_csv( - CATEGORY_REFERENCE_OUTPUT_FILE, header=None, names=['name/description', 'category'] - ) - - # Extract only descriptions for faster matching - descriptions = description_category_pairs['name/description'].values - - # Use fuzzy matching to find similar descriptions and assign the category - tx_list['category'] = tx_list['name/description'].apply( - fuzzy_match_list_categorizer, - args=(descriptions, description_category_pairs), - ) - - # Filter out uncategorized transactions, deduplicate, and sort by description - uncategorized_descriptions = ( - tx_list[tx_list['category'].isnull()] - .drop_duplicates(subset=['name/description']) - .sort_values(by=['name/description']) - ) - - # Ask the language model to categorize the remaining descriptions - if not uncategorized_descriptions.empty: - categorized_descriptions = await llm_list_categorizer( - uncategorized_descriptions[['name/description', 'category']] - ) - - categorized_descriptions.dropna(inplace=True) - - # Update the category for uncategorized transactions based on the language model results - if not categorized_descriptions.empty: - tx_list['category'] = tx_list['category'].fillna( - tx_list['name/description'].map( - categorized_descriptions.set_index('name/description')['category'] - ) - ) - - # Fill remaining NaN values in 'category' with 'Other' - tx_list['category'] = tx_list['category'].fillna('Other') - - return tx_list \ No newline at end of file diff --git a/app/categorization/config.py b/app/categorization/config.py deleted file mode 100644 index 1dfccc6fcf72a955cc0624552607a6aba59c5f96..0000000000000000000000000000000000000000 --- a/app/categorization/config.py +++ /dev/null @@ -1,6 +0,0 @@ -# Data Folders -RESULT_OUTPUT_FILE = 'data/tx_data/output/result_master_data.csv' -CATEGORY_REFERENCE_OUTPUT_FILE = 'data/ref_data/category_ref_master_data.csv' - -# LLM CONFIG -TX_PER_LLM_RUN = 10 diff --git a/app/categorization/file_processing.py b/app/categorization/file_processing.py deleted file mode 100644 index 522e9fdb813c242c0943305ccc96966a85f0297a..0000000000000000000000000000000000000000 --- a/app/categorization/file_processing.py +++ /dev/null @@ -1,128 +0,0 @@ -import os -import glob -import shutil -import logging -import warnings -from typing import Optional, Union, Dict, List -from datetime import datetime - -import pandas as pd -from dateparser import parse - -from app.categorization.categorizer_list import categorize_list -from app.categorization.config import RESULT_OUTPUT_FILE, CATEGORY_REFERENCE_OUTPUT_FILE -from app.model.transaction import Transaction -from app.schema.index import TransactionCreate - -from sqlalchemy.ext.asyncio import AsyncSession - - -# Read file and process it (e.g. categorize transactions) -async def process_file(file_path: str) -> Dict[str, Union[str, pd.DataFrame]]: - """ - Process the input file by reading, cleaning, standardizing, and categorizing the transactions. - - Args: - file_path (str): Path to the input file. - - Returns: - Dict[str, Union[str, pd.DataFrame]]: Dictionary containing the file name, processed output, and error information if any - """ - - file_name = os.path.basename(file_path) - result = {"file_name": file_name, "output": pd.DataFrame(), "error": ""} - try: - # Read file into standardized tx format: source, date, type, category, description, amount - tx_list = standardize_csv_file(file_path) - - # Categorize transactions - result["output"] = await categorize_list(tx_list) - print(f"File processed sucessfully: {file_name}") - - except Exception as e: - # Return an error indicator and exception info - logging.log(logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}") - print(f"ERROR processing file {file_name}: {e}") - result["error"] = str(e) - - return result - - -def standardize_csv_file(file_path: str) -> pd.DataFrame: - """ - Read and prepare the data from the input file. - - Args: - file_path (str): Path to the input file. - - Returns: - pd.DataFrame: Prepared transaction data. - """ - - tx_list = pd.read_csv(file_path, index_col=False) - tx_list.attrs["file_name"] = file_path - tx_list.columns = tx_list.columns.str.lower().str.strip() - - # Standardize dates to YYYY/MM/DD format - tx_list["date"] = pd.to_datetime(tx_list["date"]).dt.strftime("%Y/%m/%d") - - # Add source and reindex to desired tx format; category column is new and therefore empty - tx_list.loc[:, "source"] = os.path.basename(file_path) - tx_list = tx_list.reindex(columns=["date", "expense/income", "category", "name/description", "amount"]) - - return tx_list - - -async def save_results(db: AsyncSession, results: List) -> None: - """ - Merge all interim results in the input folder and write the merged results to the output file. - - Args: - in_folder (str): Path to the input folder containing interim results. - out_file (str): Path to the output file. - - Returns: - None - """ - - # Concatenate all (valid) results into a single DataFrame - # Print errors to console - ok_files = [] - ko_files = [] - error_messages = [] - - col_list = ["transaction_date", "type", "category", "name_description", "amount"] - tx_list = pd.DataFrame(columns=col_list) - for result in results: - if not result["error"]: - ok_files.append(result["file_name"]) - result_df = result["output"] - result_df.columns = col_list - tx_list = pd.concat([tx_list, result_df], ignore_index=True) - else: - ko_files.append(result["file_name"]) - error_messages.append(f"{result['file_name']}: {result['error']}") - - # Save to database - # FIXME: get user_id from session - txn_list_to_save = [TransactionCreate(**row.to_dict(), user_id=1) for _, row in tx_list.iterrows()] - await Transaction.bulk_create(db, txn_list_to_save) - - new_ref_data = tx_list[["name/description", "category"]] - if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE): - # If it exists, add master file to interim results - old_ref_data = pd.read_csv(CATEGORY_REFERENCE_OUTPUT_FILE, names=["name/description", "category"], header=0) - new_ref_data = pd.concat([old_ref_data, new_ref_data], ignore_index=True) - - # Drop duplicates, sort, and write to create new Master File - new_ref_data.drop_duplicates(subset=["name/description"]).sort_values(by=["name/description"]).to_csv( - CATEGORY_REFERENCE_OUTPUT_FILE, mode="w", index=False, header=True - ) - - # Summarize results - print(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n") - if len(ko_files): - print(f"Errors in the following files:") - for message in error_messages: - print(f" {message}") - print("\n") diff --git a/app/categorization/template.py b/app/categorization/template.py deleted file mode 100644 index cad7d22dad3cf04da5b55bcb35ddfcc15fa9223c..0000000000000000000000000000000000000000 --- a/app/categorization/template.py +++ /dev/null @@ -1,56 +0,0 @@ -CATEGORY_TEMPLATE = """ - - Your task is to create a list of [description, category] lists, where in each list item: - - the description is exactly the same you receive as input - - the category of the transaction (choose one from the list below based on the description) - - - - - The following list contains the categories and associated keywords you often see in transaction descriptions: - -ATM: atm, cash, withdraw - -Auto: auto body - -Bars: bar, pubs, irish, brewery - -Beauty: body - -Cashback: cashback, reward, bonus, cash back - -Clothing: clothing, shoes, accessories - -Coffee Shops: coffee, cafe, tea, Starbucks, Dunkin - -Credit Card Payment: card payment, autopay - -Education: kindle, tuition - -Entertainment: event, show, movies, cinema, theater - -Fees: Fee - -Food: snack, Donalds, Burger King, KFC, Subway, Pizza, Domino, Taco Bell, Wendy, Chick-fil-A, Popeyes, Arby's, Chipotle - -Fuel: fuel, gas, petrol - -Gifts: donation, gift - -Groceries: groceries, supermarket, food, familia - -Gym: gym, fitness, yoga, pilates, crossfit - -Home: Ikea - -Housing: rent, mortgage - -Income: refund, deposit, paycheck - -Insurance: insurance - -Medical: medical, doctor, dentist, hospital, clinic - -Pets: vet, veterinary, pet, dog, cat - -Pharmacy: pharmacy, drugstore, cvs, walgreens, rite aid, duane - -Restaurants: restaurant, lunch, dinner - -Services: service, laundry, dry cleaning - -Shopping: shopping, amazon, walmart, target, safeway - -Streaming: Netflix, Spotify, Hulu, HBO - -Taxes: tax, irs - -Technology: technology, software, hardware, electronics - -Transportation: bus, train, subway, metro, airline, uber, lyft, taxi - -Travel: travel, holiday, trip, airbnb, kiwi, hotel, hostel, resort, kiwi, kayak, expedia, booking.com - -Transfer: payment from - -Utilities: electricity, water, gas, ting, verizon, comcast, sprint, t-mobile, at&t, mint - -Other: use this when very uncertain about the category - - - - Your output should be a valid list of lists (e.g. [[description1, category1], [description2, category2], ...]] parse-able by the command ast.literal_eval(output) - Don't include any kind of commentary, return carriages, spaces or other characters in the output - Fill all categories; if you don't know which one to choose, choose 'Other' - - - - - {input_data} - """ \ No newline at end of file diff --git a/app/engine/postgresdb.py b/app/engine/postgresdb.py deleted file mode 100644 index 383c3dbc5476cfbe99c2de23bf28259d2561930f..0000000000000000000000000000000000000000 --- a/app/engine/postgresdb.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging -import contextlib -from typing import Any, AsyncIterator - -from sqlalchemy.ext.asyncio import ( - AsyncConnection, - AsyncSession, - AsyncEngine, - async_sessionmaker, - create_async_engine, -) -from sqlalchemy.orm import declarative_base - -from config.index import config as env - -logger = logging.getLogger(__name__) - -Base = declarative_base() - - -class PostgresDatabase: - - def __init__(self): - self._engine: AsyncEngine | None = None - self._sessionmaker: async_sessionmaker | None = None - - def init(self, host: str, engine_kwargs: dict[str, Any] = {}): - self._engine = create_async_engine(host, **engine_kwargs) - self._sessionmaker = async_sessionmaker(autocommit=False, bind=self._engine) - - async def close(self): - if self._engine is None: - raise Exception("DatabaseSessionManager is not initialized") - await self._engine.dispose() - - self._engine = None - self._sessionmaker = None - - @contextlib.asynccontextmanager - async def connect(self) -> AsyncIterator[AsyncConnection]: - if self._engine is None: - raise Exception("DatabaseSessionManager is not initialized") - - async with self._engine.begin() as connection: - try: - yield connection - except Exception: - await connection.rollback() - raise - - @contextlib.asynccontextmanager - async def session(self) -> AsyncIterator[AsyncSession]: - if self._sessionmaker is None: - raise Exception("DatabaseSessionManager is not initialized") - - session = self._sessionmaker() - try: - yield session - except Exception: - await session.rollback() - raise - finally: - await session.close() - - def get_engine(self): - return self._engine - - # Used for testing - async def create_all(self, connection: AsyncConnection): - await connection.run_sync(Base.metadata.create_all) - - async def drop_all(self, connection: AsyncConnection): - await connection.run_sync(Base.metadata.drop_all) - - -postgresdb = PostgresDatabase() - - -async def get_db_session(): - async with postgresdb.session() as session: - yield session diff --git a/app/model/base.py b/app/model/base.py deleted file mode 100644 index 11366bdd3447529679c43f65c3a483c7139081ab..0000000000000000000000000000000000000000 --- a/app/model/base.py +++ /dev/null @@ -1,15 +0,0 @@ -from datetime import datetime, timezone -from sqlalchemy import DateTime -from sqlalchemy.orm import Mapped, mapped_column - -from app.engine.postgresdb import Base - - -class BaseModel: - id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) - created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now(timezone.utc)) - updated_at: Mapped[datetime] = mapped_column( - DateTime(timezone=True), - default=datetime.now(timezone.utc), - onupdate=datetime.now(timezone.utc), - ) diff --git a/app/model/income_statement.py b/app/model/income_statement.py deleted file mode 100644 index 74fc93f08c81dd6eac4840fe98ba145b207df178..0000000000000000000000000000000000000000 --- a/app/model/income_statement.py +++ /dev/null @@ -1,46 +0,0 @@ -from datetime import datetime -from typing import Any, Dict, List -from sqlalchemy import ForeignKey -from sqlalchemy.sql import expression as sql -from sqlalchemy.orm import relationship, Mapped, mapped_column -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.dialects.postgresql import JSON - -from app.model.base import BaseModel -from app.engine.postgresdb import Base -from app.schema.index import IncomeStatement as IncomeStatementSchema - - -class IncomeStatement(Base, BaseModel): - __tablename__ = "income_statement" - - date_from: Mapped[datetime] - date_to: Mapped[datetime] - income: Mapped[Dict] = mapped_column(JSON, nullable=True) - expenses: Mapped[Dict] = mapped_column(JSON, nullable=True) - - user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) - user = relationship("User", back_populates="income_statements") - - def __str__(self) -> str: - return f"IncomeStatement(id={self.id}, user_id={self.user_id}, date_from={self.date_from}, date_to={self.date_to}, income={self.income}, expenses={self.expenses})" - - @classmethod - async def create(cls: "type[IncomeStatement]", db: AsyncSession, **kwargs: IncomeStatementSchema) -> "IncomeStatement": - income_statement = cls(**kwargs) - db.add(income_statement) - await db.commit() - await db.refresh(income_statement) - return income_statement - - @classmethod - async def get_by_user(cls: "type[IncomeStatement]", db: AsyncSession, user_id: int) -> "List[IncomeStatement]": - query = sql.select(cls).where(cls.user_id == user_id) - income_statements = await db.scalars(query) - return income_statements.all() - - @classmethod - async def get(cls: "type[IncomeStatement]", db: AsyncSession, id: int) -> "IncomeStatement": - query = sql.select(cls).where(cls.id == id) - income_statement = await db.scalar(query) - return income_statement diff --git a/app/model/transaction.py b/app/model/transaction.py deleted file mode 100644 index 23726495014d3e6686f3f4d07bf7ecc414528e88..0000000000000000000000000000000000000000 --- a/app/model/transaction.py +++ /dev/null @@ -1,62 +0,0 @@ -from datetime import datetime -from typing import List -from sqlalchemy import ForeignKey -from sqlalchemy.orm import relationship, Mapped, mapped_column -from sqlalchemy.sql import expression as sql -from sqlalchemy.ext.asyncio import AsyncSession - -from app.model.base import BaseModel -from app.engine.postgresdb import Base -from app.schema.index import TransactionCreate - - -class Transaction(Base, BaseModel): - __tablename__ = "transactions" - transaction_date: Mapped[datetime] - category: Mapped[str] - name_description: Mapped[str] - amount: Mapped[float] - type: Mapped[str] - - user_id = mapped_column(ForeignKey("users.id")) - user = relationship("User", back_populates="transactions") - - def __str__(self) -> str: - return f"{self.transaction_date}, {self.category}, {self.name_description}, {self.amount}, {self.type}" - - @classmethod - async def create(cls: "type[Transaction]", db: AsyncSession, **kwargs) -> "Transaction": - query = sql.insert(cls).values(**kwargs) - transactions = await db.execute(query) - transaction = transactions.first() - await db.commit() - return transaction - - @classmethod - async def bulk_create(cls: "type[Transaction]", db: AsyncSession, transactions: List[TransactionCreate]) -> None: - values = [transaction.model_dump() for transaction in transactions] - query = sql.insert(cls).values(values) - await db.execute(query) - await db.commit() - - @classmethod - async def update(cls: "type[Transaction]", db: AsyncSession, id: int, **kwargs) -> "Transaction": - query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch") - transactions = await db.scalars(query) - transaction = transactions.first() - await db.commit() - return transaction - - @classmethod - async def get_by_user(cls: "type[Transaction]", db: AsyncSession, user_id: int) -> "List[Transaction]": - query = sql.select(cls).where(cls.user_id == user_id) - transactions = await db.scalars(query) - return transactions - - @classmethod - async def get_by_user_between_dates( - cls: "type[Transaction]", db: AsyncSession, user_id: int, start_date: datetime, end_date: datetime - ) -> "List[Transaction]": - query = sql.select(cls).where(cls.user_id == user_id).where(cls.transaction_date.between(start_date, end_date)) - transactions = await db.scalars(query) - return transactions.all() diff --git a/app/model/user.py b/app/model/user.py deleted file mode 100644 index a9f5e45140daf86402dccead36f3093dabc88829..0000000000000000000000000000000000000000 --- a/app/model/user.py +++ /dev/null @@ -1,110 +0,0 @@ -import logging -from sqlalchemy.orm import relationship, Mapped, mapped_column -from sqlalchemy.sql import expression as sql -from sqlalchemy.ext.asyncio import AsyncSession - -from app.model.base import BaseModel -from app.engine.postgresdb import Base -from app.schema.index import UserCreate, UserUpdate - -logger = logging.getLogger(__name__) - - -class User(Base, BaseModel): - __tablename__ = "users" - - name: Mapped[str] - email: Mapped[str] - hashed_password: Mapped[str] - is_deleted: Mapped[bool] = mapped_column(default=False) - - transactions = relationship("Transaction", back_populates="user") - income_statements = relationship("IncomeStatement", back_populates="user") - - @classmethod - async def create(cls: "type[User]", db: AsyncSession, **kwargs: UserCreate) -> "User": - """ - Creates a new user in the database. - - Args: - cls (type[User]): The class object representing the User model. - db (AsyncSession): The asynchronous session object for interacting with the database. - **kwargs (UserCreate): The keyword arguments representing the user's attributes. - - Returns: - User - """ - - print(f"Creating user: {kwargs}") - # query = sql.insert(cls).values(**kwargs) - user = cls(name=kwargs["name"], email=kwargs["email"], hashed_password=kwargs["hashed_password"]) - db.add(user) - await db.commit() - await db.refresh(user) - return user - - @classmethod - async def update(cls: "type[User]", db: AsyncSession, id: int, **kwargs: UserUpdate) -> "User": - """ - Updates a user in the database with the given ID and keyword arguments. - - Args: - cls (type[User]): The class object representing the User model. - db (AsyncSession): The asynchronous session object for interacting with the database. - id (int): The ID of the user to update. - **kwargs (UserUpdate): The keyword arguments representing the user's attributes to update. - - Returns: - User - """ - - query = sql.update(cls).where(cls.id == id).values(**kwargs).execution_options(synchronize_session="fetch") - results = await db.execute(query) - user = results.fetchone() - await db.commit() - return user - - @classmethod - async def get(cls: "type[User]", db: AsyncSession, email: str) -> "User": - """ - Retrieves a user from the database based on their email. - - Args: - cls (type[User]): The class object representing the User model. - db (AsyncSession): The asynchronous session object for interacting with the database. - email (str): The email of the user to retrieve. - - Returns: - User - """ - - logging.info(f"Getting user: {email}") - query = sql.select(cls).where(cls.email == email) - logging.info(f"Query: {query}") - users = await db.scalars(query) - logging.info(f"Users: {users}") - return users.first() - - @classmethod - async def delete(cls: "type[User]", db: AsyncSession, email: str) -> "User": - """ - Deletes a user from the database based on their email. - - Args: - cls (type[User]): The class object representing the User model. - db (AsyncSession): The asynchronous session object for interacting with the database. - email (str): The email of the user to delete. - - Returns: - User - """ - query = ( - sql.update(cls) - .where(cls.email == email) - .values(is_deleted=True) - .execution_options(synchronize_session="fetch") - ) - result = await db.execute(query) - user = result.fetchone() - await db.commit() - return user diff --git a/app/schema/base.py b/app/schema/base.py deleted file mode 100644 index b287c65376fce4c02728d44905532c62216bc474..0000000000000000000000000000000000000000 --- a/app/schema/base.py +++ /dev/null @@ -1,14 +0,0 @@ -from datetime import datetime -from pydantic import BaseModel, ConfigDict - - -class PydanticBaseModel(BaseModel): - pass - - -class BaseModel(PydanticBaseModel): - model_config = ConfigDict(from_attributes=True) - - id: int - created_at: datetime - updated_at: datetime diff --git a/app/schema/index.py b/app/schema/index.py deleted file mode 100644 index c66299485d9ff5a73135e0e83c28532adbc9feac..0000000000000000000000000000000000000000 --- a/app/schema/index.py +++ /dev/null @@ -1,85 +0,0 @@ -from enum import Enum -from datetime import datetime -from typing import Dict, List -from typing_extensions import TypedDict - -from app.schema.base import BaseModel, PydanticBaseModel - - -class TransactionType(str, Enum): - INCOME = "income" - EXPENSE = "expense" - - -class UserCreate(PydanticBaseModel): - name: str - email: str - hashed_password: str - - -class UserUpdate(PydanticBaseModel): - name: str - email: str - hashed_password: str - - -class UserResponse(PydanticBaseModel): - id: int - name: str - email: str - is_deleted: bool - - -class User(BaseModel): - name: str - email: str - hashed_password: str - is_deleted: bool = False - transactions: "List[Transaction]" = [] - - -class TransactionResponse(PydanticBaseModel): - transaction_date: datetime - category: str - name_description: str - amount: float - type: TransactionType - - -class TransactionCreate(TransactionResponse): - user_id: int - - -class Transaction(TransactionResponse): - user: User - - -class FileUploadCreate(PydanticBaseModel): - source: str - date: datetime - category: str - name_description: str - amount: float - type: str - - -class IncomeStatementCreateRequest(PydanticBaseModel): - user_id: int - date_from: datetime - date_to: datetime - - -class IncomeStatementDetail(TypedDict): - total: float - category_totals: List[Dict[str, str | float]] - -class IncomeStatementLLMResponse(PydanticBaseModel): - income: IncomeStatementDetail - expenses: IncomeStatementDetail - - -class IncomeStatement(IncomeStatementCreateRequest, IncomeStatementLLMResponse): - pass - -class IncomeStatementResponse(IncomeStatement): - id: int \ No newline at end of file diff --git a/app/service/income_statement.py b/app/service/income_statement.py deleted file mode 100644 index 0a91483cbac6d2e4e0697d630f6dd55efb4d9c24..0000000000000000000000000000000000000000 --- a/app/service/income_statement.py +++ /dev/null @@ -1,37 +0,0 @@ -import logging -from app.schema.index import IncomeStatement, IncomeStatementCreateRequest -from app.model.transaction import Transaction as TransactionModel -from app.model.income_statement import IncomeStatement as IncomeStatementModel -from sqlalchemy.ext.asyncio import AsyncSession - -from app.service.llm import call_llm - - -async def call_llm_to_create_income_statement(payload: IncomeStatementCreateRequest, db: AsyncSession) -> None: - transactions = await TransactionModel.get_by_user_between_dates( - db, payload.user_id, payload.date_from, payload.date_to - ) - - if not transactions: - print("No transactions found") - return - - response = await call_llm(transactions) - - income = response.dict()['income'] - expenses = response.dict()['expenses'] - - try: - income_statement_create_payload = IncomeStatement( - user_id=payload.user_id, - date_from=payload.date_from, - date_to=payload.date_to, - income=income, - expenses=expenses, - ) - - income_statement = await IncomeStatementModel.create(db, **income_statement_create_payload.model_dump()) - print(f"Income statement created: {income_statement}") - except Exception as e: - print(e) - raise e diff --git a/app/service/llm.py b/app/service/llm.py deleted file mode 100644 index ded36889a1eae91f6f79c6d041f01f8bded6ff53..0000000000000000000000000000000000000000 --- a/app/service/llm.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import List -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables.base import RunnableSequence -from langchain_openai import OpenAI -from langchain.globals import set_llm_cache - -from app.model.transaction import Transaction -from app.schema.index import IncomeStatementLLMResponse -from config.index import config as env - -from langchain_core.output_parsers import PydanticOutputParser - -set_llm_cache(None) - -def income_statement_prompt () -> ChatPromptTemplate: - context_str = """ - You are an accountant skilled at organizing transactions from multiple different bank - accounts and credit card statements to prepare an income statement. - - Input data is in the below csv format: - transaction_date, category, name_description, amount, type\n - {input_data_csv} - - Your task is to prepare an income statement. The output should be in the following format: {format_instructions} - - """ - prompt = ChatPromptTemplate.from_template(context_str) - return prompt - -async def call_llm(inputData: List[Transaction]) -> str: - input_data_csv = '\n'.join(str(x) for x in inputData) - - output_parser = PydanticOutputParser(pydantic_object=IncomeStatementLLMResponse) - - prompt = income_statement_prompt().partial(format_instructions=output_parser.get_format_instructions()) - - llm = OpenAI(name='Income Statement Generation Bot', - api_key=env.OPENAI_API_KEY, - # cache=True, - temperature=0.7, - verbose=True) - - try: - runnable_chain = RunnableSequence(prompt, llm, output_parser) - except Exception as e: - print(f"runnable_chain error: {str(e)}") - raise e - - try: - output_chunks = runnable_chain.invoke({"input_data_csv": input_data_csv}) - return output_chunks - - except Exception as e: - print(f"runnable_chain.invoke error: {str(e)}") - raise e diff --git a/app/transactions_rag/__init__.py b/app/transactions_rag/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/app/transactions_rag/categorize_transactions.ipynb b/app/transactions_rag/categorize_transactions.ipynb deleted file mode 100644 index 3504c5f2c151b5ee6171de834eabdfce31f70fba..0000000000000000000000000000000000000000 --- a/app/transactions_rag/categorize_transactions.ipynb +++ /dev/null @@ -1,571 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Requirement already satisfied: pandas in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (2.0.3)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2.9.0.post0)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (2024.1)\n", - "Requirement already satisfied: numpy>=1.20.3 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pandas) (1.24.4)\n", - "Requirement already satisfied: six>=1.5 in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (from python-dateutil>=2.8.2->pandas) (1.15.0)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateName / DescriptionExpense/IncomeAmount
02023-12-30Comcast InternetExpense9.96
12023-12-30Lemonade Home InsuranceExpense17.53
22023-12-30Monthly Appartment RentExpense2000.00
32023-12-30Staples Office SuppliesExpense12.46
42023-12-29Selling PaintingsIncome13.63
\n", - "
" - ], - "text/plain": [ - " Date Name / Description Expense/Income Amount\n", - "0 2023-12-30 Comcast Internet Expense 9.96\n", - "1 2023-12-30 Lemonade Home Insurance Expense 17.53\n", - "2 2023-12-30 Monthly Appartment Rent Expense 2000.00\n", - "3 2023-12-30 Staples Office Supplies Expense 12.46\n", - "4 2023-12-29 Selling Paintings Income 13.63" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Read the transactions csv\n", - "!pip3 install pandas\n", - "\n", - "import pandas as pd\n", - "df = pd.read_csv(\"transactions_2024.csv\")\n", - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array(['Lemonade Home Insurance', 'Monthly Appartment Rent',\n", - " 'Staples Office Supplies', 'Selling Paintings', 'Spotify',\n", - " 'Target', 'IT Consulting', 'Phone', 'ML Consulting'], dtype=object)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Get Unique transactions in the Name/Description column\n", - "unique_transactions = df[\"Name / Description\"].unique()\n", - "unique_transactions[1:10]" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "!python3 -m venv venv" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "!source venv/bin/activate" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Requirement already satisfied: setuptools in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (70.0.0)\n" - ] - } - ], - "source": [ - "!pip3 install --upgrade setuptools" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Defaulting to user installation because normal site-packages is not writeable',\n", - " 'Requirement already satisfied: langchain in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 1)) (0.2.1)',\n", - " 'Collecting langchain_openai (from -r requirements.txt (line 2))',\n", - " ' Downloading langchain_openai-0.1.7-py3-none-any.whl.metadata (2.5 kB)',\n", - " 'Collecting python-dotenv (from -r requirements.txt (line 3))',\n", - " ' Using cached python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)',\n", - " 'Collecting openai (from -r requirements.txt (line 4))',\n", - " ' Using cached openai-1.30.4-py3-none-any.whl.metadata (21 kB)',\n", - " 'Requirement already satisfied: tenacity in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 5)) (8.3.0)',\n", - " 'Requirement already satisfied: rapidfuzz in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 6)) (3.9.1)',\n", - " 'Requirement already satisfied: pydantic in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 7)) (2.7.1)',\n", - " 'Requirement already satisfied: dateparser in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 8)) (1.2.0)',\n", - " 'Requirement already satisfied: pandas in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from -r requirements.txt (line 9)) (2.0.3)',\n", - " 'Collecting path (from -r requirements.txt (line 10))',\n", - " ' Using cached path-16.14.0-py3-none-any.whl.metadata (6.3 kB)',\n", - " 'Collecting aiostream==0.5.2 (from -r requirements.txt (line 12))',\n", - " ' Using cached aiostream-0.5.2-py3-none-any.whl.metadata (9.9 kB)',\n", - " 'Collecting cachetools==5.3.3 (from -r requirements.txt (line 13))',\n", - " ' Using cached cachetools-5.3.3-py3-none-any.whl.metadata (5.3 kB)',\n", - " 'Collecting docx2txt==0.8 (from -r requirements.txt (line 14))',\n", - " ' Using cached docx2txt-0.8.tar.gz (2.8 kB)',\n", - " ' Preparing metadata (setup.py): started',\n", - " \" Preparing metadata (setup.py): finished with status 'done'\",\n", - " 'Collecting fastapi==0.109.1 (from -r requirements.txt (line 15))',\n", - " ' Using cached fastapi-0.109.1-py3-none-any.whl.metadata (25 kB)',\n", - " 'Collecting llama-index-agent-openai==0.2.2 (from -r requirements.txt (line 16))',\n", - " ' Using cached llama_index_agent_openai-0.2.2-py3-none-any.whl.metadata (677 bytes)',\n", - " 'Collecting llama-index-core==0.10.28 (from -r requirements.txt (line 17))',\n", - " ' Using cached llama_index_core-0.10.28-py3-none-any.whl.metadata (3.6 kB)',\n", - " 'Collecting llama-index-vector-stores-pinecone==0.1.3 (from -r requirements.txt (line 18))',\n", - " ' Using cached llama_index_vector_stores_pinecone-0.1.3-py3-none-any.whl.metadata (725 bytes)',\n", - " 'Collecting llama-index==0.10.28 (from -r requirements.txt (line 19))',\n", - " ' Using cached llama_index-0.10.28-py3-none-any.whl.metadata (11 kB)',\n", - " 'Collecting python-dotenv (from -r requirements.txt (line 3))',\n", - " ' Using cached python_dotenv-1.0.0-py3-none-any.whl.metadata (21 kB)',\n", - " '\\x1b[31mERROR: Ignored the following versions that require a different python version: 0.0.10 Requires-Python >=3.9,<4.0; 0.0.11 Requires-Python >=3.9,<4.0; 0.0.12 Requires-Python >=3.9,<4.0; 0.0.13 Requires-Python >=3.9,<4.0; 0.0.14 Requires-Python >=3.9,<4.0; 0.0.15 Requires-Python >=3.9,<4.0; 0.0.16 Requires-Python >=3.9,<4.0; 0.0.17 Requires-Python >=3.9,<4.0; 0.0.18 Requires-Python >=3.9,<4.0; 0.0.19 Requires-Python >=3.9,<4.0; 0.0.20 Requires-Python >=3.9,<4.0; 0.0.21 Requires-Python >=3.9,<4.0; 0.0.22 Requires-Python >=3.9,<4.0; 0.0.23 Requires-Python >=3.9,<4.0; 0.0.24 Requires-Python >=3.9,<4.0; 0.0.25 Requires-Python >=3.9,<4.0; 0.14.3 Requires-Python >=3.9,<4; 0.14.4 Requires-Python <4,>=3.9; 0.14.5 Requires-Python <4,>=3.9; 0.15.0 Requires-Python <4,>=3.9; 0.15.1 Requires-Python <4,>=3.9; 0.15.10 Requires-Python <4,>=3.9; 0.15.11 Requires-Python <4,>=3.9; 0.15.12 Requires-Python <4,>=3.9; 0.15.13 Requires-Python <4,>=3.9; 0.15.2 Requires-Python <4,>=3.9; 0.15.3 Requires-Python <4,>=3.9; 0.15.4 Requires-Python <4,>=3.9; 0.15.5 Requires-Python <4,>=3.9; 0.15.6 Requires-Python <4,>=3.9; 0.15.7 Requires-Python <4,>=3.9; 0.15.8 Requires-Python <4,>=3.9; 0.15.9 Requires-Python <4,>=3.9; 0.16.0 Requires-Python <4,>=3.9; 0.16.1 Requires-Python <4,>=3.9; 0.16.2 Requires-Python <4,>=3.9; 0.16.3 Requires-Python <4,>=3.9; 0.16.4 Requires-Python <4,>=3.9; 0.16.5 Requires-Python <4,>=3.9; 0.16.6 Requires-Python <4,>=3.9; 0.16.7 Requires-Python <4,>=3.9; 0.16.8 Requires-Python <4,>=3.9; 0.16.9 Requires-Python <4,>=3.9; 0.17.0 Requires-Python <4,>=3.9; 0.17.1 Requires-Python <4,>=3.9; 0.17.2 Requires-Python <4,>=3.9; 0.17.3 Requires-Python <4,>=3.9; 0.17.4 Requires-Python <4,>=3.9; 0.17.5 Requires-Python <4,>=3.9; 0.17.6 Requires-Python <4,>=3.9; 0.17.7 Requires-Python <4,>=3.9; 0.18.0 Requires-Python <4,>=3.9; 0.18.1 Requires-Python <4,>=3.9; 0.18.2 Requires-Python <4,>=3.9; 0.19.0 Requires-Python <4,>=3.9; 0.20.0 Requires-Python <4,>=3.9; 0.21.0 Requires-Python <4,>=3.9\\x1b[0m\\x1b[31m',\n", - " '\\x1b[0m\\x1b[31mERROR: Could not find a version that satisfies the requirement traceloop-sdk==0.15.11 (from versions: 0.0.26, 0.0.27b0, 0.0.27, 0.0.28, 0.0.29, 0.0.31, 0.0.32, 0.0.33, 0.0.34, 0.0.35, 0.0.36, 0.0.37, 0.0.38, 0.0.39, 0.0.40, 0.0.41, 0.0.42, 0.0.43b0, 0.0.43b2, 0.0.43b3, 0.0.43b4, 0.0.43, 0.0.44b0, 0.0.44, 0.0.46, 0.0.47, 0.0.48, 0.0.49, 0.0.50, 0.0.51, 0.0.52, 0.0.53, 0.0.54, 0.0.55, 0.0.56a0, 0.0.56a1, 0.0.56a2, 0.0.56a3, 0.0.56a4, 0.0.56a5, 0.0.56a6, 0.0.56, 0.0.57, 0.0.58, 0.0.59, 0.0.60a0, 0.0.60a1, 0.0.60, 0.0.61a0, 0.0.61, 0.0.62, 0.0.63, 0.0.64a0, 0.0.64, 0.0.65, 0.0.66, 0.0.67, 0.0.68, 0.0.69, 0.0.70, 0.1.3, 0.1.5, 0.1.6, 0.1.7, 0.1.8, 0.1.9, 0.1.10, 0.1.11, 0.1.12, 0.2.0, 0.2.1, 0.3.0, 0.3.1, 0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6, 0.4.0, 0.4.1, 0.4.2, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.6.0, 0.7.0, 0.8.0, 0.8.2, 0.9.0, 0.9.1, 0.9.2, 0.9.3, 0.9.4, 0.10.0, 0.10.1, 0.10.2, 0.10.3, 0.10.4, 0.10.5, 0.11.0, 0.11.1, 0.11.2, 0.11.3, 0.12.0, 0.12.1, 0.12.2, 0.12.3, 0.12.4, 0.12.5, 0.13.0, 0.13.1, 0.13.2, 0.13.3, 0.14.0, 0.14.1, 0.14.2)\\x1b[0m\\x1b[31m',\n", - " '\\x1b[0m\\x1b[31mERROR: No matching distribution found for traceloop-sdk==0.15.11\\x1b[0m\\x1b[31m',\n", - " '\\x1b[0m']" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "!!pip3 install -r requirements.txt" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Collecting dateparser\n", - " Using cached dateparser-1.2.0-py2.py3-none-any.whl.metadata (28 kB)\n", - "Requirement already satisfied: python-dateutil in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from dateparser) (2.9.0.post0)\n", - "Requirement already satisfied: pytz in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from dateparser) (2024.1)\n", - "Collecting regex!=2019.02.19,!=2021.8.27 (from dateparser)\n", - " Downloading regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl.metadata (40 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m40.9/40.9 kB\u001b[0m \u001b[31m95.7 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", - "\u001b[?25hCollecting tzlocal (from dateparser)\n", - " Downloading tzlocal-5.2-py3-none-any.whl.metadata (7.8 kB)\n", - "Requirement already satisfied: six>=1.5 in /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages (from python-dateutil->dateparser) (1.15.0)\n", - "Collecting backports.zoneinfo (from tzlocal->dateparser)\n", - " Downloading backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl.metadata (4.7 kB)\n", - "Downloading dateparser-1.2.0-py2.py3-none-any.whl (294 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m295.0/295.0 kB\u001b[0m \u001b[31m510.0 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", - "\u001b[?25hDownloading regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl (281 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m281.8/281.8 kB\u001b[0m \u001b[31m2.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", - "\u001b[?25hDownloading tzlocal-5.2-py3-none-any.whl (17 kB)\n", - "Downloading backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl (35 kB)\n", - "Installing collected packages: regex, backports.zoneinfo, tzlocal, dateparser\n", - "\u001b[33m WARNING: The script dateparser-download is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n", - " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", - "\u001b[0mSuccessfully installed backports.zoneinfo-0.2.1 dateparser-1.2.0 regex-2024.5.15 tzlocal-5.2\n" - ] - } - ], - "source": [ - "!pip3 install dateparser" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Collecting rapidfuzz\n", - " Using cached rapidfuzz-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl.metadata (11 kB)\n", - "Using cached rapidfuzz-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl (2.1 MB)\n", - "Installing collected packages: rapidfuzz\n", - "Successfully installed rapidfuzz-3.9.1\n" - ] - } - ], - "source": [ - "!pip3 install rapidfuzz" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Collecting dotenv\n", - " Downloading dotenv-0.0.5.tar.gz (2.4 kB)\n", - " Preparing metadata (setup.py) ... \u001b[?25lerror\n", - " \u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n", - " \n", - " \u001b[31m×\u001b[0m \u001b[32mpython setup.py egg_info\u001b[0m did not run successfully.\n", - " \u001b[31m│\u001b[0m exit code: \u001b[1;36m1\u001b[0m\n", - " \u001b[31m╰─>\u001b[0m \u001b[31m[76 lines of output]\u001b[0m\n", - " \u001b[31m \u001b[0m /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py:80: _DeprecatedInstaller: setuptools.installer and fetch_build_eggs are deprecated.\n", - " \u001b[31m \u001b[0m !!\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m ********************************************************************************\n", - " \u001b[31m \u001b[0m Requirements should be satisfied by a PEP 517 installer.\n", - " \u001b[31m \u001b[0m If you are using pip, you can try `pip install --use-pep517`.\n", - " \u001b[31m \u001b[0m ********************************************************************************\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m !!\n", - " \u001b[31m \u001b[0m dist.fetch_build_eggs(dist.setup_requires)\n", - " \u001b[31m \u001b[0m \u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m \u001b[31m×\u001b[0m \u001b[32mpython setup.py egg_info\u001b[0m did not run successfully.\n", - " \u001b[31m \u001b[0m \u001b[31m│\u001b[0m exit code: \u001b[1;36m1\u001b[0m\n", - " \u001b[31m \u001b[0m \u001b[31m╰─>\u001b[0m \u001b[31m[16 lines of output]\u001b[0m\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m Traceback (most recent call last):\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"\", line 2, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"\", line 14, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/__init__.py\", line 2, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.extension import Extension, Library\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/extension.py\", line 5, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.dist import _get_unpatched\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/dist.py\", line 7, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.command.install import install\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/command/__init__.py\", line 8, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from setuptools.command import install_scripts\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/setuptools/command/install_scripts.py\", line 3, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m from pkg_resources import Distribution, PathMetadata, ensure_directory\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-wheel-66v7g1h6/distribute_6e0b47b8750a4aa8bfff62a3f867a97f/pkg_resources.py\", line 1518, in \n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider)\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m AttributeError: module 'importlib._bootstrap' has no attribute 'SourceFileLoader'\n", - " \u001b[31m \u001b[0m \u001b[31m \u001b[0m \u001b[31m[end of output]\u001b[0m\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m \u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n", - " \u001b[31m \u001b[0m \u001b[1;31merror\u001b[0m: \u001b[1mmetadata-generation-failed\u001b[0m\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m \u001b[31m×\u001b[0m Encountered error while generating package metadata.\n", - " \u001b[31m \u001b[0m \u001b[31m╰─>\u001b[0m See above for output.\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m \u001b[1;35mnote\u001b[0m: This is an issue with the package mentioned above, not pip.\n", - " \u001b[31m \u001b[0m \u001b[1;36mhint\u001b[0m: See above for details.\n", - " \u001b[31m \u001b[0m Traceback (most recent call last):\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 101, in _fetch_build_egg_no_warn\n", - " \u001b[31m \u001b[0m subprocess.check_call(cmd)\n", - " \u001b[31m \u001b[0m File \"/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/subprocess.py\", line 364, in check_call\n", - " \u001b[31m \u001b[0m raise CalledProcessError(retcode, cmd)\n", - " \u001b[31m \u001b[0m subprocess.CalledProcessError: Command '['/Applications/Xcode.app/Contents/Developer/usr/bin/python3', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/tmpnw2yadh3', '--quiet', 'distribute']' returned non-zero exit status 1.\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m The above exception was the direct cause of the following exception:\n", - " \u001b[31m \u001b[0m \n", - " \u001b[31m \u001b[0m Traceback (most recent call last):\n", - " \u001b[31m \u001b[0m File \"\", line 2, in \n", - " \u001b[31m \u001b[0m File \"\", line 34, in \n", - " \u001b[31m \u001b[0m File \"/private/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/pip-install-ac3_ml88/dotenv_7766945cfd7d4e62965375b30e9a0c21/setup.py\", line 13, in \n", - " \u001b[31m \u001b[0m setup(name='dotenv',\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 102, in setup\n", - " \u001b[31m \u001b[0m _install_setup_requires(attrs)\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 75, in _install_setup_requires\n", - " \u001b[31m \u001b[0m _fetch_build_eggs(dist)\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/__init__.py\", line 80, in _fetch_build_eggs\n", - " \u001b[31m \u001b[0m dist.fetch_build_eggs(dist.setup_requires)\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/dist.py\", line 641, in fetch_build_eggs\n", - " \u001b[31m \u001b[0m return _fetch_build_eggs(self, requires)\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 38, in _fetch_build_eggs\n", - " \u001b[31m \u001b[0m resolved_dists = pkg_resources.working_set.resolve(\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 787, in resolve\n", - " \u001b[31m \u001b[0m dist = self._resolve_dist(\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 823, in _resolve_dist\n", - " \u001b[31m \u001b[0m dist = best[req.key] = env.best_match(\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 1093, in best_match\n", - " \u001b[31m \u001b[0m return self.obtain(req, installer)\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/pkg_resources/__init__.py\", line 1104, in obtain\n", - " \u001b[31m \u001b[0m return installer(requirement) if installer else None\n", - " \u001b[31m \u001b[0m File \"/Users/patrickalexis/Library/Python/3.8/lib/python/site-packages/setuptools/installer.py\", line 103, in _fetch_build_egg_no_warn\n", - " \u001b[31m \u001b[0m raise DistutilsError(str(e)) from e\n", - " \u001b[31m \u001b[0m distutils.errors.DistutilsError: Command '['/Applications/Xcode.app/Contents/Developer/usr/bin/python3', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/var/folders/g9/flbt409n1b7g0ry4b9ffdvnc0000gn/T/tmpnw2yadh3', '--quiet', 'distribute']' returned non-zero exit status 1.\n", - " \u001b[31m \u001b[0m \u001b[31m[end of output]\u001b[0m\n", - " \n", - " \u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n", - "\u001b[?25h\u001b[1;31merror\u001b[0m: \u001b[1mmetadata-generation-failed\u001b[0m\n", - "\n", - "\u001b[31m×\u001b[0m Encountered error while generating package metadata.\n", - "\u001b[31m╰─>\u001b[0m See above for output.\n", - "\n", - "\u001b[1;35mnote\u001b[0m: This is an issue with the package mentioned above, not pip.\n", - "\u001b[1;36mhint\u001b[0m: See above for details.\n" - ] - } - ], - "source": [ - "!pip3 install dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# Create data folder\n", - "!mkdir -p data/tx_data/output\n", - "!mkdir -p data/ref_data" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Collecting openai\n", - " Using cached openai-1.30.4-py3-none-any.whl.metadata (21 kB)\n", - "Collecting anyio<5,>=3.5.0 (from openai)\n", - " Downloading anyio-4.4.0-py3-none-any.whl.metadata (4.6 kB)\n", - "Collecting distro<2,>=1.7.0 (from openai)\n", - " Downloading distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)\n", - "Collecting httpx<1,>=0.23.0 (from openai)\n", - " Downloading httpx-0.27.0-py3-none-any.whl.metadata (7.2 kB)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (2.7.1)\n", - "Collecting sniffio (from openai)\n", - " Downloading sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)\n", - "Requirement already satisfied: tqdm>4 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (4.66.4)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from openai) (4.12.0)\n", - "Requirement already satisfied: idna>=2.8 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from anyio<5,>=3.5.0->openai) (3.7)\n", - "Collecting exceptiongroup>=1.0.2 (from anyio<5,>=3.5.0->openai)\n", - " Downloading exceptiongroup-1.2.1-py3-none-any.whl.metadata (6.6 kB)\n", - "Requirement already satisfied: certifi in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from httpx<1,>=0.23.0->openai) (2024.2.2)\n", - "Collecting httpcore==1.* (from httpx<1,>=0.23.0->openai)\n", - " Downloading httpcore-1.0.5-py3-none-any.whl.metadata (20 kB)\n", - "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->openai)\n", - " Downloading h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pydantic<3,>=1.9.0->openai) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.18.2 in /Users/patrickalexis/Library/Python/3.8/lib/python/site-packages (from pydantic<3,>=1.9.0->openai) (2.18.2)\n", - "Downloading openai-1.30.4-py3-none-any.whl (320 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m320.6/320.6 kB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m\n", - "\u001b[?25hDownloading anyio-4.4.0-py3-none-any.whl (86 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading distro-1.9.0-py3-none-any.whl (20 kB)\n", - "Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m2.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m4.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading sniffio-1.3.1-py3-none-any.whl (10 kB)\n", - "Downloading exceptiongroup-1.2.1-py3-none-any.whl (16 kB)\n", - "Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m2.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hInstalling collected packages: sniffio, h11, exceptiongroup, distro, httpcore, anyio, httpx, openai\n", - "\u001b[33m WARNING: The script distro is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n", - " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33m WARNING: The script httpx is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n", - " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33m WARNING: The script openai is installed in '/Users/patrickalexis/Library/Python/3.8/bin' which is not on PATH.\n", - " Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\u001b[0m\u001b[33m\n", - "\u001b[0mSuccessfully installed anyio-4.4.0 distro-1.9.0 exceptiongroup-1.2.1 h11-0.14.0 httpcore-1.0.5 httpx-0.27.0 openai-1.30.4 sniffio-1.3.1\n" - ] - } - ], - "source": [ - "!pip3 install openai" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Processing file\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "ERROR:root:| File: transactions_2024.csv | Unexpected Error: expected str, got module\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ERROR processing file transactions_2024.csv: expected str, got module\n", - "\n", - "Processed 1 files: 0 successful, 1 with errors\n", - "\n", - "Errors in the following files:\n", - " transactions_2024.csv: expected str, got module\n", - "\n", - "\n", - "[{'file_name': 'transactions_2024.csv', 'output': Empty DataFrame\n", - "Columns: []\n", - "Index: [], 'error': 'expected str, got module'}]\n", - "['name/description,category\\n']\n", - "None\n" - ] - } - ], - "source": [ - "# Process the transactions csv and get the results from the categorization llm utility\n", - "from app.categorization.file_processing import process_file, save_results\n", - "from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE\n", - "# from dotenv import load_dotenv\n", - "import asyncio\n", - "import os.path\n", - "\n", - "# load_dotenv()\n", - "\n", - "# relative_dir = os.path.dirname(__file__)\n", - "# abs_file_path = os.path.join(relative_dir, CATEGORY_REFERENCE_OUTPUT_FILE)\n", - "\n", - "async def apply_categorization():\n", - " processed_file = process_file(\"app/transactions_rag/transactions_2024.csv\")\n", - "\n", - " print(\"\\nProcessing file\")\n", - " result = await asyncio.gather(processed_file)\n", - "\n", - " await save_results(result)\n", - " print(result)\n", - "\n", - " output_file = open(CATEGORY_REFERENCE_OUTPUT_FILE, \"r+\")\n", - " print(output_file.readlines())\n", - "\n", - "\n", - "result = await apply_categorization()\n", - "print(result)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.8.9 64-bit", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.9" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/app/transactions_rag/transactions_2024.csv b/app/transactions_rag/transactions_2024.csv deleted file mode 100644 index 6ce4516b835091f6f9caf6ce4bbbf6e55ee83e00..0000000000000000000000000000000000000000 --- a/app/transactions_rag/transactions_2024.csv +++ /dev/null @@ -1,29 +0,0 @@ -Date,Name / Description,Expense/Income,Amount -2023-12-30,Comcast Internet,Expense,9.96 -2023-12-30,Lemonade Home Insurance,Expense,17.53 -2023-12-30,Monthly Appartment Rent,Expense,2000.0 -2023-12-30,Staples Office Supplies,Expense,12.46 -2023-12-29,Selling Paintings,Income,13.63 -2023-12-29,Spotify,Expense,12.19 -2023-12-23,Target,Expense,27.08 -2023-12-22,IT Consulting,Income,541.57 -2023-12-22,Phone,Expense,10.7 -2023-12-20,ML Consulting,Income,2641.93 -2023-12-19,Chipotle,Expense,18.9 -2023-12-18,Cold Brew Coffee,Expense,17.67 -2023-12-18,Gas,Expense,8.80 -2023-12-18,CA Property Tax,Expense,1670.34 -2022-11-26,Salary,Income,4000.36 -2022-11-26,Cellphone,Expense,19.27 -2022-11-26,Gym Membership,Expense,24.71 -2022-11-25,Wholefoods,Expense,17.35 -2022-11-24,Freelancing,Income,2409.55 -2022-11-19,Spotify,Expense,20.76 -2022-10-25,Blogging,Income,4044.27 -2022-10-24,Uber Taxi,Expense,18.9 -2022-10-23,Uber Taxi,Expense,27.54 -2022-10-22,Apple Services,Expense,41.25 -2022-10-21,Netflix,Expense,22.8 -2022-01-16,Amazon Lux,Expense,24.11 -2022-01-15,Burger King,Expense,30.08 -2022-01-14,Amazon,Expense,11.0 diff --git a/config/index.py b/config/index.py deleted file mode 100644 index 6481806567fc2f7c5cedc13eff562f4026501d4e..0000000000000000000000000000000000000000 --- a/config/index.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from dotenv import load_dotenv - -load_dotenv() - - -class Config: - DEBUG = os.getenv("DEBUG", False) - SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI") - SQLALCHEMY_TRACK_MODIFICATIONS = os.getenv("SQLALCHEMY_TRACK_MODIFICATIONS", False) - ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") - APP_HOST = os.getenv("APP_HOST", "0.0.0.0") - APP_PORT = int(os.getenv("APP_PORT", "8000")) - PINECONE_API_KEY = os.getenv("PINECONE_API_KEY") - PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME") - PINECONE_ENVIRONMENT = os.getenv("PINECONE_ENVIRONMENT") - OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") - OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION") - MODEL = os.getenv("MODEL", "gpt-3.5-turbo") - EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-ada-002") - EMBEDDING_DIM = os.getenv("EMBEDDING_DIM", "1536") - CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1024")) - CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "20")) - MAX_TOKENS = int(os.getenv("MAX_TOKENS", "4000")) - - POSTGRES_USER = os.getenv("POSTGRES_USER") - POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") - POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME") - POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST") - POSTGRES_DB_PORT = int(os.getenv("POSTGRES_DB_PORT", 5432)) - SQLALCHEMY_DATABASE_URL = f"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_DB_HOST}:{POSTGRES_DB_PORT}/{POSTGRES_DB_NAME}" - - -config = Config diff --git a/data/.gitkeep b/data/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/data/tx_data/input/transactions_2024.csv b/data/tx_data/input/transactions_2024.csv deleted file mode 100644 index a5f69ddd9284e1df17b945adc32c16c65a2e3534..0000000000000000000000000000000000000000 --- a/data/tx_data/input/transactions_2024.csv +++ /dev/null @@ -1,28 +0,0 @@ -2023-12-30,Comcast Internet,Expense,9.96 -2023-12-30,Lemonade Home Insurance,Expense,17.53 -2023-12-30,Monthly Appartment Rent,Expense,2000.0 -2023-12-30,Staples Office Supplies,Expense,12.46 -2023-12-29,Selling Paintings,Income,13.63 -2023-12-29,Spotify,Expense,12.19 -2023-12-23,Target,Expense,27.08 -2023-12-22,IT Consulting,Income,541.57 -2023-12-22,Phone,Expense,10.7 -2023-12-20,ML Consulting,Income,2641.93 -2023-12-19,Chipotle,Expense,18.9 -2023-12-18,Cold Brew Coffee,Expense,17.67 -2023-12-18,Gas,Expense,8.80 -2023-12-18,CA Property Tax,Expense,1670.34 -2022-11-26,Salary,Income,4000.36 -2022-11-26,Cellphone,Expense,19.27 -2022-11-26,Gym Membership,Expense,24.71 -2022-11-25,Wholefoods,Expense,17.35 -2022-11-24,Freelancing,Income,2409.55 -2022-11-19,Spotify,Expense,20.76 -2022-10-25,Blogging,Income,4044.27 -2022-10-24,Uber Taxi,Expense,18.9 -2022-10-23,Uber Taxi,Expense,27.54 -2022-10-22,Apple Services,Expense,41.25 -2022-10-21,Netflix,Expense,22.8 -2022-01-16,Amazon Lux,Expense,24.11 -2022-01-15,Burger King,Expense,30.08 -2022-01-14,Amazon,Expense,11.0 \ No newline at end of file diff --git a/main.py b/main.py index 9353c9e80b51898739ea01f4c46288dac42e0fd5..c053fd6d28f9b12718f9e5562c3e5880ffb9ba98 100644 --- a/main.py +++ b/main.py @@ -1,74 +1,51 @@ +from dotenv import load_dotenv + +load_dotenv() + import logging import os import uvicorn -from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import RedirectResponse from app.api.routers.chat import chat_router -from app.api.routers.user import user_router -from app.api.routers.transaction import transaction_router -from app.api.routers.file_upload import file_upload_router -from app.api.routers.income_statement import income_statement_router from app.settings import init_settings +from app.observability import init_observability from fastapi.staticfiles import StaticFiles -from alembic.config import Config -from alembic import command - -from app.engine.postgresdb import postgresdb -from config.index import config as env - -logger = logging.getLogger("uvicorn") +app = FastAPI() init_settings() -# init_observability() - - -def init_app(init_db: bool = True) -> FastAPI: - lifespan = None +init_observability() - if init_db: - postgresdb.init(env.SQLALCHEMY_DATABASE_URL, {"echo": True, "future": True}) +environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set - @asynccontextmanager - async def lifespan(app: FastAPI): - yield - if postgresdb.get_engine() is not None: - await postgresdb.close() +if environment == "dev": + logger = logging.getLogger("uvicorn") + logger.warning("Running in development mode - allowing CORS for all origins") + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) - app = FastAPI(lifespan=lifespan) + # Redirect to documentation page when accessing base URL + @app.get("/") + async def redirect_to_docs(): + return RedirectResponse(url="/docs") - if env.ENVIRONMENT == "dev": - logger.warning("Running in development mode - allowing CORS for all origins") - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - if os.path.exists("data"): - app.mount("/api/data", StaticFiles(directory="data"), name="static") - app.include_router(chat_router, prefix="/api/chat") - app.include_router(user_router) - app.include_router(transaction_router) - app.include_router(file_upload_router) - app.include_router(income_statement_router) - - return app - - -app = init_app() - - -# Redirect to documentation page when accessing base URL -@app.get("/") -async def redirect_to_docs(): - return RedirectResponse(url="/docs") +if os.path.exists("data"): + app.mount("/api/data", StaticFiles(directory="data"), name="static") +app.include_router(chat_router, prefix="/api/chat") if __name__ == "__main__": - uvicorn.run(app="main:app", host=env.APP_HOST, port=env.APP_PORT, reload=(env.ENVIRONMENT == "dev")) + app_host = os.getenv("APP_HOST", "0.0.0.0") + app_port = int(os.getenv("APP_PORT", "8000")) + reload = True if environment == "dev" else False + + uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload) diff --git a/migration/README b/migration/README deleted file mode 100644 index 98e4f9c44effe479ed38c66ba922e7bcc672916f..0000000000000000000000000000000000000000 --- a/migration/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/migration/env.py b/migration/env.py deleted file mode 100644 index 58e59d62988a4b06b0e048e00331e9611adb47ee..0000000000000000000000000000000000000000 --- a/migration/env.py +++ /dev/null @@ -1,91 +0,0 @@ -import asyncio -from logging.config import fileConfig - -from app.engine.postgresdb import Base -from sqlalchemy import Connection -from sqlalchemy import pool -from sqlalchemy.ext.asyncio import async_engine_from_config - -from config.index import config as env - -from alembic import context - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -config.set_main_option("sqlalchemy.url", env.SQLALCHEMY_DATABASE_URL) - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# Import all models so they're registered with SQLAlchemy. -import app.model.user -import app.model.transaction -import app.model.income_statement - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = Base.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - Calls to context.execute() here emit the given string to the - script output. - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def do_run_migrations(connection: Connection) -> None: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() - - -async def run_migrations_online(): - """Run migrations in 'online' mode. - In this scenario we need to create an Engine - and associate a connection with the context. - """ - configuration = config.get_section(config.config_ini_section) - configuration["sqlalchemy.url"] = env.SQLALCHEMY_DATABASE_URL - connectable = async_engine_from_config( - configuration, - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - async with connectable.connect() as connection: - await connection.run_sync(do_run_migrations) - - await connectable.dispose() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - asyncio.run(run_migrations_online()) diff --git a/migration/script.py.mako b/migration/script.py.mako deleted file mode 100644 index fbc4b07dcef98b20c6f96b642097f35e8433258e..0000000000000000000000000000000000000000 --- a/migration/script.py.mako +++ /dev/null @@ -1,26 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/migration/versions/4e76691ab103_add_income_statement_table.py b/migration/versions/4e76691ab103_add_income_statement_table.py deleted file mode 100644 index 82098bbee99b26d992fc78c0efa9edb05d68b69d..0000000000000000000000000000000000000000 --- a/migration/versions/4e76691ab103_add_income_statement_table.py +++ /dev/null @@ -1,43 +0,0 @@ -"""add_income_statement_table - -Revision ID: 4e76691ab103 -Revises: 7ea44cbc5b1f -Create Date: 2024-06-03 23:59:03.717778 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = '4e76691ab103' -down_revision: Union[str, None] = '7ea44cbc5b1f' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('income_statement', - sa.Column('income', postgresql.JSON(astext_type=sa.Text()), nullable=True), - sa.Column('expenses', postgresql.JSON(astext_type=sa.Text()), nullable=True), - sa.Column('date_from', sa.DateTime(), nullable=False), - sa.Column('date_to', sa.DateTime(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_income_statement_id'), 'income_statement', ['id'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_income_statement_id'), table_name='income_statement') - op.drop_table('income_statement') - # ### end Alembic commands ### diff --git a/migration/versions/7ea44cbc5b1f_default_timezone.py b/migration/versions/7ea44cbc5b1f_default_timezone.py deleted file mode 100644 index e37670e6fafd6e639bfbb0de65f947909bb9625a..0000000000000000000000000000000000000000 --- a/migration/versions/7ea44cbc5b1f_default_timezone.py +++ /dev/null @@ -1,69 +0,0 @@ -"""default_timezone - -Revision ID: 7ea44cbc5b1f -Revises: 8feaedca36f9 -Create Date: 2024-06-02 14:36:01.552518 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = "7ea44cbc5b1f" -down_revision: Union[str, None] = "8feaedca36f9" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "transactions", - "created_at", - existing_type=postgresql.TIMESTAMP(), - type_=sa.DateTime(timezone=True), - nullable=False, - ) - op.alter_column( - "transactions", - "updated_at", - existing_type=postgresql.TIMESTAMP(), - type_=sa.DateTime(timezone=True), - nullable=False, - ) - op.alter_column( - "users", "created_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False - ) - op.alter_column( - "users", "updated_at", existing_type=postgresql.TIMESTAMP(), type_=sa.DateTime(timezone=True), nullable=False - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column( - "users", "updated_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True - ) - op.alter_column( - "users", "created_at", existing_type=sa.DateTime(timezone=True), type_=postgresql.TIMESTAMP(), nullable=True - ) - op.alter_column( - "transactions", - "updated_at", - existing_type=sa.DateTime(timezone=True), - type_=postgresql.TIMESTAMP(), - nullable=True, - ) - op.alter_column( - "transactions", - "created_at", - existing_type=sa.DateTime(timezone=True), - type_=postgresql.TIMESTAMP(), - nullable=True, - ) - # ### end Alembic commands ### diff --git a/migration/versions/8feaedca36f9_users_datatype_updates.py b/migration/versions/8feaedca36f9_users_datatype_updates.py deleted file mode 100644 index 61f6cedd1b29552e5815679a3d8ec04ca6b81a05..0000000000000000000000000000000000000000 --- a/migration/versions/8feaedca36f9_users_datatype_updates.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Remove_Transactions_From_Users - -Revision ID: 8feaedca36f9 -Revises: cd515c44401d -Create Date: 2024-06-02 01:26:54.731002 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision: str = '8feaedca36f9' -down_revision: Union[str, None] = 'cd515c44401d' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('transactions', 'transaction_date', - existing_type=postgresql.TIMESTAMP(), - nullable=False) - op.alter_column('transactions', 'category', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('transactions', 'name_description', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('transactions', 'amount', - existing_type=sa.DOUBLE_PRECISION(precision=53), - nullable=False) - op.alter_column('transactions', 'type', - existing_type=sa.VARCHAR(), - nullable=False) - op.add_column('users', sa.Column('hashed_password', sa.String(), nullable=False)) - op.add_column('users', sa.Column('is_deleted', sa.Boolean(), nullable=False)) - op.alter_column('users', 'name', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('users', 'email', - existing_type=sa.VARCHAR(), - nullable=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('users', 'email', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('users', 'name', - existing_type=sa.VARCHAR(), - nullable=True) - op.drop_column('users', 'is_deleted') - op.drop_column('users', 'hashed_password') - op.alter_column('transactions', 'type', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('transactions', 'amount', - existing_type=sa.DOUBLE_PRECISION(precision=53), - nullable=True) - op.alter_column('transactions', 'name_description', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('transactions', 'category', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('transactions', 'transaction_date', - existing_type=postgresql.TIMESTAMP(), - nullable=True) - # ### end Alembic commands ### diff --git a/migration/versions/cd515c44401d_add_users_transactions_tables.py b/migration/versions/cd515c44401d_add_users_transactions_tables.py deleted file mode 100644 index 93fe88a5dba2ec7ba6f71c2ce5055dde8d844913..0000000000000000000000000000000000000000 --- a/migration/versions/cd515c44401d_add_users_transactions_tables.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Add users, transactions tables - -Revision ID: cd515c44401d -Revises: -Create Date: 2024-06-01 01:32:42.500524 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'cd515c44401d' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('users', - sa.Column('name', sa.String(), nullable=True), - sa.Column('email', sa.String(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=True), - sa.Column('updated_at', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False) - op.create_table('transactions', - sa.Column('transaction_date', sa.DateTime(), nullable=True), - sa.Column('category', sa.String(), nullable=True), - sa.Column('name_description', sa.String(), nullable=True), - sa.Column('amount', sa.Float(), nullable=True), - sa.Column('type', sa.String(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=True), - sa.Column('updated_at', sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_transactions_id'), table_name='transactions') - op.drop_table('transactions') - op.drop_index(op.f('ix_users_id'), table_name='users') - op.drop_table('users') - # ### end Alembic commands ### diff --git a/poetry.lock b/poetry.lock index 021ec2ca69ae688d71dae0be44e2d911ebd13bf6..3ba03a81b32caa156e20327ea060304b23fbc571 100644 --- a/poetry.lock +++ b/poetry.lock @@ -123,25 +123,6 @@ files = [ [package.dependencies] typing-extensions = "*" -[[package]] -name = "alembic" -version = "1.13.1" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.8" -files = [ - {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, - {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" - -[package.extras] -tz = ["backports.zoneinfo"] - [[package]] name = "annotated-types" version = "0.7.0" @@ -198,87 +179,6 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "asyncio" -version = "3.4.3" -description = "reference implementation of PEP 3156" -optional = false -python-versions = "*" -files = [ - {file = "asyncio-3.4.3-cp33-none-win32.whl", hash = "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de"}, - {file = "asyncio-3.4.3-cp33-none-win_amd64.whl", hash = "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c"}, - {file = "asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d"}, - {file = "asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41"}, -] - -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} - -[package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] - [[package]] name = "attrs" version = "23.2.0" @@ -480,28 +380,6 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" -[[package]] -name = "dateparser" -version = "1.2.0" -description = "Date parsing library designed to parse dates from HTML pages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"}, - {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"}, -] - -[package.dependencies] -python-dateutil = "*" -pytz = "*" -regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" -tzlocal = "*" - -[package.extras] -calendars = ["convertdate", "hijri-converter"] -fasttext = ["fasttext"] -langdetect = ["langdetect"] - [[package]] name = "deprecated" version = "1.2.14" @@ -541,20 +419,6 @@ files = [ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - [[package]] name = "fastapi" version = "0.109.2" @@ -1039,17 +903,6 @@ files = [ {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, ] -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - [[package]] name = "jinja2" version = "3.1.4" @@ -1148,139 +1001,6 @@ files = [ {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, ] -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] - -[[package]] -name = "langchain" -version = "0.2.1" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langchain-0.2.1-py3-none-any.whl", hash = "sha256:3e13bf97c5717bce2c281f5117e8778823e8ccf62d949e73d3869448962b1c97"}, - {file = "langchain-0.2.1.tar.gz", hash = "sha256:5758a315e1ac92eb26dafec5ad0fafa03cafa686aba197d5bb0b1dd28cc03ebe"}, -] - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -langchain-core = ">=0.2.0,<0.3.0" -langchain-text-splitters = ">=0.2.0,<0.3.0" -langsmith = ">=0.1.17,<0.2.0" -numpy = ">=1,<2" -pydantic = ">=1,<3" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<9.0.0" - -[package.extras] -azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"] -clarifai = ["clarifai (>=9.1.0)"] -cli = ["typer (>=0.9.0,<0.10.0)"] -cohere = ["cohere (>=4,<6)"] -docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] -embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.1,<0.2)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] -javascript = ["esprima (>=4.0.1,<5.0.0)"] -llms = ["clarifai (>=9.1.0)", "cohere (>=4,<6)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] -openai = ["openai (<2)", "tiktoken (>=0.7,<1.0)"] -qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] -text-helpers = ["chardet (>=5.1.0,<6.0.0)"] - -[[package]] -name = "langchain-core" -version = "0.2.3" -description = "Building applications with LLMs through composability" -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langchain_core-0.2.3-py3-none-any.whl", hash = "sha256:22189b5a3a30bfd65eb995f95e627f7c2c3acb322feb89f5f5f2fb7df21833a7"}, - {file = "langchain_core-0.2.3.tar.gz", hash = "sha256:fbc75a64b9c0b7655d96ca57a707df1e6c09efc1539c36adbd73260612549810"}, -] - -[package.dependencies] -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.65,<0.2.0" -packaging = ">=23.2,<24.0" -pydantic = ">=1,<3" -PyYAML = ">=5.3" -tenacity = ">=8.1.0,<9.0.0" - -[package.extras] -extended-testing = ["jinja2 (>=3,<4)"] - -[[package]] -name = "langchain-openai" -version = "0.1.8" -description = "An integration package connecting OpenAI and LangChain" -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langchain_openai-0.1.8-py3-none-any.whl", hash = "sha256:8125c84223e9f43b05defbca64eedbcf362fd78a680de6c25e64f973b34a8063"}, - {file = "langchain_openai-0.1.8.tar.gz", hash = "sha256:a11fcce15def7917c44232abda6baaa63dfc79fe44be1531eea650d39a44cd95"}, -] - -[package.dependencies] -langchain-core = ">=0.2.2,<0.3" -openai = ">=1.26.0,<2.0.0" -tiktoken = ">=0.7,<1" - -[[package]] -name = "langchain-text-splitters" -version = "0.2.0" -description = "LangChain text splitting utilities" -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langchain_text_splitters-0.2.0-py3-none-any.whl", hash = "sha256:7b4c6a45f8471630a882b321e138329b6897102a5bc62f4c12be1c0b05bb9199"}, - {file = "langchain_text_splitters-0.2.0.tar.gz", hash = "sha256:b32ab4f7397f7d42c1fa3283fefc2547ba356bd63a68ee9092865e5ad83c82f9"}, -] - -[package.dependencies] -langchain-core = ">=0.2.0,<0.3.0" - -[package.extras] -extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"] - -[[package]] -name = "langsmith" -version = "0.1.65" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = "<4.0,>=3.8.1" -files = [ - {file = "langsmith-0.1.65-py3-none-any.whl", hash = "sha256:ab4487029240e69cca30da1065f1e9138e5a7ca2bbe8c697f0bd7d5839f71cf7"}, - {file = "langsmith-0.1.65.tar.gz", hash = "sha256:d3c2eb2391478bd79989f02652cf66e29a7959d677614b6993a47cef43f7f43b"}, -] - -[package.dependencies] -orjson = ">=3.9.14,<4.0.0" -pydantic = ">=1,<3" -requests = ">=2,<3" - [[package]] name = "llama-index" version = "0.10.28" @@ -1590,25 +1310,6 @@ files = [ httpx = ">=0.20.0" pydantic = ">=1.10" -[[package]] -name = "mako" -version = "1.3.5" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - [[package]] name = "markupsafe" version = "2.1.5" @@ -1697,20 +1398,6 @@ dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] -[[package]] -name = "mirakuru" -version = "2.5.2" -description = "Process executor (not only) for tests." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mirakuru-2.5.2-py3-none-any.whl", hash = "sha256:90c2d90a8cf14349b2f33e6db30a16acd855499811e0312e56cf80ceacf2d3e5"}, - {file = "mirakuru-2.5.2.tar.gz", hash = "sha256:41ca583d355eb7a6cfdc21c1aea549979d685c27b57239b88725434f115a7132"}, -] - -[package.dependencies] -psutil = {version = ">=4.0.0", markers = "sys_platform != \"cygwin\""} - [[package]] name = "monotonic" version = "1.6" @@ -1933,13 +1620,13 @@ files = [ [[package]] name = "openai" -version = "1.30.5" +version = "1.30.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.30.5-py3-none-any.whl", hash = "sha256:2ad95e926de0d2e09cde632a9204b0a6dca4a03c2cdcc84329b01f355784355a"}, - {file = "openai-1.30.5.tar.gz", hash = "sha256:5366562eb2c5917e6116ae0391b7ae6e3acd62b0ae3f565ada32b35d8fcfa106"}, + {file = "openai-1.30.1-py3-none-any.whl", hash = "sha256:c9fb3c3545c118bbce8deb824397b9433a66d0d0ede6a96f7009c95b76de4a46"}, + {file = "openai-1.30.1.tar.gz", hash = "sha256:4f85190e577cba0b066e1950b8eb9b11d25bc7ebcc43a86b326ce1bfa564ec74"}, ] [package.dependencies] @@ -2442,70 +2129,15 @@ files = [ {file = "opentelemetry_util_http-0.45b0.tar.gz", hash = "sha256:4ce08b6a7d52dd7c96b7705b5b4f06fdb6aa3eac1233b3b0bfef8a0cab9a92cd"}, ] -[[package]] -name = "orjson" -version = "3.10.3" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"}, - {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"}, - {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"}, - {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"}, - {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"}, - {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"}, - {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"}, - {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"}, - {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"}, - {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"}, - {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"}, - {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"}, - {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"}, - {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"}, - {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"}, - {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"}, - {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"}, - {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"}, - {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"}, - {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"}, - {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"}, - {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"}, - {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"}, - {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"}, - {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"}, - {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"}, -] - [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -2577,21 +2209,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] -[[package]] -name = "path" -version = "16.14.0" -description = "A module wrapper for os.path" -optional = false -python-versions = ">=3.8" -files = [ - {file = "path-16.14.0-py3-none-any.whl", hash = "sha256:8ee37703cbdc7cc83835ed4ecc6b638226fb2b43b7b45f26b620589981a109a5"}, - {file = "path-16.14.0.tar.gz", hash = "sha256:dbaaa7efd4602fd6ba8d82890dc7823d69e5de740a6e842d9919b0faaf2b6a8e"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "pywin32"] - [[package]] name = "pillow" version = "10.3.0" @@ -2698,32 +2315,6 @@ urllib3 = {version = ">=1.26.0", markers = "python_version >= \"3.8\" and python [package.extras] 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)"] -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "port-for" -version = "0.7.2" -description = "Utility that helps with local TCP ports management. It can find an unused TCP localhost port and remember the association." -optional = false -python-versions = ">=3.8" -files = [ - {file = "port-for-0.7.2.tar.gz", hash = "sha256:074f29335130578aa42fef3726985e57d01c15189e509633a8a1b0b7f9226349"}, - {file = "port_for-0.7.2-py3-none-any.whl", hash = "sha256:16b279ab4f210bad33515c45bd9af0c6e048ab24c3b6bbd9cfc7e451782617df"}, -] - [[package]] name = "posthog" version = "3.5.0" @@ -2767,143 +2358,20 @@ files = [ {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] -[[package]] -name = "psutil" -version = "5.9.8" -description = "Cross-platform lib for process and system monitoring in Python." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, -] - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "psycopg" -version = "3.1.19" -description = "PostgreSQL database adapter for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731"}, - {file = "psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961"}, -] - -[package.dependencies] -typing-extensions = ">=4.1" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.1.19)"] -c = ["psycopg-c (==3.1.19)"] -dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg-binary" -version = "3.1.19" -description = "PostgreSQL database adapter for Python -- C optimisation distribution" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7204818f05151dd08f8f851defb01972ec9d2cc925608eb0de232563f203f354"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4e67fd86758dbeac85641419a54f84d74495a8683b58ad5dfad08b7fc37a8f"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12173e34b176e93ad2da913de30f774d5119c2d4d4640c6858d2d77dfa6c9bf"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052f5193304066318853b4b2e248f523c8f52b371fc4e95d4ef63baee3f30955"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29008f3f8977f600b8a7fb07c2e041b01645b08121760609cc45e861a0364dc9"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6a9a651a08d876303ed059c9553df18b3c13c3406584a70a8f37f1a1fe2709"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:91a645e6468c4f064b7f4f3b81074bdd68fe5aa2b8c5107de15dcd85ba6141be"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5c6956808fd5cf0576de5a602243af8e04594b25b9a28675feddc71c5526410a"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:1622ca27d5a7a98f7d8f35e8b146dc7efda4a4b6241d2edf7e076bd6bcecbeb4"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a100482950a55228f648bd382bb71bfaff520002f29845274fccbbf02e28bd52"}, - {file = "psycopg_binary-3.1.19-cp310-cp310-win_amd64.whl", hash = "sha256:955ca8905c0251fc4af7ce0a20999e824a25652f53a558ab548b60969f1f368e"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cf49e91dcf699b8a449944ed898ef1466b39b92720613838791a551bc8f587a"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964c307e400c5f33fa762ba1e19853e048814fcfbd9679cc923431adb7a2ead2"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433924e1b14074798331dc2bfae2af452ed7888067f2fc145835704d8981b15"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00879d4c6be4b3afc510073f48a5e960f797200e261ab3d9bd9b7746a08c669d"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a6997c80f86d3dd80a4f078bb3b200079c47eeda4fd409d8899b883c90d2ac"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0106e42b481677c41caa69474fe530f786dcef88b11b70000f0e45a03534bc8f"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81efe09ba27533e35709905c3061db4dc9fb814f637360578d065e2061fbb116"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d312d6dddc18d9c164e1893706269c293cba1923118349d375962b1188dafb01"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:bfd2c734da9950f7afaad5f132088e0e1478f32f042881fca6651bb0c8d14206"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8a732610a5a6b4f06dadcf9288688a8ff202fd556d971436a123b7adb85596e2"}, - {file = "psycopg_binary-3.1.19-cp311-cp311-win_amd64.whl", hash = "sha256:321814a9a3ad785855a821b842aba08ca1b7de7dfb2979a2f0492dca9ec4ae70"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4aa0ca13bb8a725bb6d12c13999217fd5bc8b86a12589f28a74b93e076fbb959"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:469424e354ebcec949aa6aa30e5a9edc352a899d9a68ad7a48f97df83cc914cf"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04f5349313529ae1f1c42fe1aa0443faaf50fdf12d13866c2cc49683bfa53d0"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959feabddc7fffac89b054d6f23f3b3c62d7d3c90cd414a02e3747495597f150"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9da624a6ca4bc5f7fa1f03f8485446b5b81d5787b6beea2b4f8d9dbef878ad7"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1823221a6b96e38b15686170d4fc5b36073efcb87cce7d3da660440b50077f6"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:866db42f986298f0cf15d805225eb8df2228bf19f7997d7f1cb5f388cbfc6a0f"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:738c34657305b5973af6dbb6711b07b179dfdd21196d60039ca30a74bafe9648"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb9758473200384a04374d0e0cac6f451218ff6945a024f65a1526802c34e56e"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e991632777e217953ac960726158987da684086dd813ac85038c595e7382c91"}, - {file = "psycopg_binary-3.1.19-cp312-cp312-win_amd64.whl", hash = "sha256:1d87484dd42c8783c44a30400949efb3d81ef2487eaa7d64d1c54df90cf8b97a"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d1d1723d7449c12bb61aca7eb6e0c6ab2863cd8dc0019273cc4d4a1982f84bdb"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538a8671005641fa195eab962f85cf0504defbd3b548c4c8fc27102a59f687b"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c50592bc8517092f40979e4a5d934f96a1737a77724bb1d121eb78b614b30fc8"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95f16ae82bc242b76cd3c3e5156441e2bd85ff9ec3a9869d750aad443e46073c"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebd1e98e865e9a28ce0cb2c25b7dfd752f0d1f0a423165b55cd32a431dcc0f4"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:49cd7af7d49e438a39593d1dd8cab106a1912536c2b78a4d814ebdff2786094e"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:affebd61aa3b7a8880fd4ac3ee94722940125ff83ff485e1a7c76be9adaabb38"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d1bac282f140fa092f2bbb6c36ed82270b4a21a6fc55d4b16748ed9f55e50fdb"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1285aa54449e362b1d30d92b2dc042ad3ee80f479cc4e323448d0a0a8a1641fa"}, - {file = "psycopg_binary-3.1.19-cp37-cp37m-win_amd64.whl", hash = "sha256:6cff31af8155dc9ee364098a328bab688c887c732c66b8d027e5b03818ca0287"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9b689c4a17dd3130791dcbb8c30dbf05602f7c2d56c792e193fb49adc7bf5f8"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017518bd2de4851adc826a224fb105411e148ad845e11355edd6786ba3dfedf5"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c35fd811f339a3cbe7f9b54b2d9a5e592e57426c6cc1051632a62c59c4810208"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38ed45ec9673709bfa5bc17f140e71dd4cca56d4e58ef7fd50d5a5043a4f55c6"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:433f1c256108f9e26f480a8cd6ddb0fb37dbc87d7f5a97e4540a9da9b881f23f"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ed61e43bf5dc8d0936daf03a19fef3168d64191dbe66483f7ad08c4cea0bc36b"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ae8109ff9fdf1fa0cb87ab6645298693fdd2666a7f5f85660df88f6965e0bb7"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a53809ee02e3952fae7977c19b30fd828bd117b8f5edf17a3a94212feb57faaf"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9d39d5ffc151fb33bcd55b99b0e8957299c0b1b3e5a1a5f4399c1287ef0051a9"}, - {file = "psycopg_binary-3.1.19-cp38-cp38-win_amd64.whl", hash = "sha256:e14bc8250000921fcccd53722f86b3b3d1b57db901e206e49e2ab2afc5919c2d"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd88c5cea4efe614d5004fb5f5dcdea3d7d59422be796689e779e03363102d24"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621a814e60825162d38760c66351b4df679fd422c848b7c2f86ad399bff27145"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46e50c05952b59a214e27d3606f6d510aaa429daed898e16b8a37bfbacc81acc"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03354a9db667c27946e70162cb0042c3929154167f3678a30d23cebfe0ad55b5"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c2f3b79037581afec7baa2bdbcb0a1787f1758744a7662099b0eca2d721cb"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6469ebd9e93327e9f5f36dcf8692fb1e7aeaf70087c1c15d4f2c020e0be3a891"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:85bca9765c04b6be90cb46e7566ffe0faa2d7480ff5c8d5e055ac427f039fd24"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a836610d5c75e9cff98b9fdb3559c007c785c09eaa84a60d5d10ef6f85f671e8"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8de7a1d9fb3518cc6b58e3c80b75a824209ad52b90c542686c912db8553dad"}, - {file = "psycopg_binary-3.1.19-cp39-cp39-win_amd64.whl", hash = "sha256:76fcd33342f38e35cd6b5408f1bc117d55ab8b16e5019d99b6d3ce0356c51717"}, -] - [[package]] name = "pydantic" -version = "2.7.2" +version = "2.7.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.2-py3-none-any.whl", hash = "sha256:834ab954175f94e6e68258537dc49402c4a5e9d0409b9f1b86b7e934a8372de7"}, - {file = "pydantic-2.7.2.tar.gz", hash = "sha256:71b2945998f9c9b7919a45bde9a50397b289937d215ae141c1d0903ba7149fd7"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.3" +pydantic-core = "2.18.2" typing-extensions = ">=4.6.1" [package.extras] @@ -2911,90 +2379,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.3" +version = "2.18.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:744697428fcdec6be5670460b578161d1ffe34743a5c15656be7ea82b008197c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b40c05ced1ba4218b14986fe6f283d22e1ae2ff4c8e28881a70fb81fbfcda7"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a9a75622357076efb6b311983ff190fbfb3c12fc3a853122b34d3d358126c"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2e253af04ceaebde8eb201eb3f3e3e7e390f2d275a88300d6a1959d710539e2"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855ec66589c68aa367d989da5c4755bb74ee92ccad4fdb6af942c3612c067e34"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3e42bb54e7e9d72c13ce112e02eb1b3b55681ee948d748842171201a03a98a"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6ac9ffccc9d2e69d9fba841441d4259cb668ac180e51b30d3632cd7abca2b9b"}, - {file = "pydantic_core-2.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c56eca1686539fa0c9bda992e7bd6a37583f20083c37590413381acfc5f192d6"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17954d784bf8abfc0ec2a633108207ebc4fa2df1a0e4c0c3ccbaa9bb01d2c426"}, - {file = "pydantic_core-2.18.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98ed737567d8f2ecd54f7c8d4f8572ca7c7921ede93a2e52939416170d357812"}, - {file = "pydantic_core-2.18.3-cp310-none-win32.whl", hash = "sha256:9f9e04afebd3ed8c15d67a564ed0a34b54e52136c6d40d14c5547b238390e779"}, - {file = "pydantic_core-2.18.3-cp310-none-win_amd64.whl", hash = "sha256:45e4ffbae34f7ae30d0047697e724e534a7ec0a82ef9994b7913a412c21462a0"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b9ebe8231726c49518b16b237b9fe0d7d361dd221302af511a83d4ada01183ab"}, - {file = "pydantic_core-2.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b8e20e15d18bf7dbb453be78a2d858f946f5cdf06c5072453dace00ab652e2b2"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d9ff283cd3459fa0bf9b0256a2b6f01ac1ff9ffb034e24457b9035f75587cb"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f7ef5f0ebb77ba24c9970da18b771711edc5feaf00c10b18461e0f5f5949231"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73038d66614d2e5cde30435b5afdced2b473b4c77d4ca3a8624dd3e41a9c19be"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6afd5c867a74c4d314c557b5ea9520183fadfbd1df4c2d6e09fd0d990ce412cd"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd7df92f28d351bb9f12470f4c533cf03d1b52ec5a6e5c58c65b183055a60106"}, - {file = "pydantic_core-2.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:80aea0ffeb1049336043d07799eace1c9602519fb3192916ff525b0287b2b1e4"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaee40f25bba38132e655ffa3d1998a6d576ba7cf81deff8bfa189fb43fd2bbe"}, - {file = "pydantic_core-2.18.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9128089da8f4fe73f7a91973895ebf2502539d627891a14034e45fb9e707e26d"}, - {file = "pydantic_core-2.18.3-cp311-none-win32.whl", hash = "sha256:fec02527e1e03257aa25b1a4dcbe697b40a22f1229f5d026503e8b7ff6d2eda7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_amd64.whl", hash = "sha256:58ff8631dbab6c7c982e6425da8347108449321f61fe427c52ddfadd66642af7"}, - {file = "pydantic_core-2.18.3-cp311-none-win_arm64.whl", hash = "sha256:3fc1c7f67f34c6c2ef9c213e0f2a351797cda98249d9ca56a70ce4ebcaba45f4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0928cde2ae416a2d1ebe6dee324709c6f73e93494d8c7aea92df99aab1fc40f"}, - {file = "pydantic_core-2.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bee9bb305a562f8b9271855afb6ce00223f545de3d68560b3c1649c7c5295e9"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e862823be114387257dacbfa7d78547165a85d7add33b446ca4f4fae92c7ff5c"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a36f78674cbddc165abab0df961b5f96b14461d05feec5e1f78da58808b97e7"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba905d184f62e7ddbb7a5a751d8a5c805463511c7b08d1aca4a3e8c11f2e5048"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fdd362f6a586e681ff86550b2379e532fee63c52def1c666887956748eaa326"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24b214b7ee3bd3b865e963dbed0f8bc5375f49449d70e8d407b567af3222aae4"}, - {file = "pydantic_core-2.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:691018785779766127f531674fa82bb368df5b36b461622b12e176c18e119022"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60e4c625e6f7155d7d0dcac151edf5858102bc61bf959d04469ca6ee4e8381bd"}, - {file = "pydantic_core-2.18.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4e651e47d981c1b701dcc74ab8fec5a60a5b004650416b4abbef13db23bc7be"}, - {file = "pydantic_core-2.18.3-cp312-none-win32.whl", hash = "sha256:ffecbb5edb7f5ffae13599aec33b735e9e4c7676ca1633c60f2c606beb17efc5"}, - {file = "pydantic_core-2.18.3-cp312-none-win_amd64.whl", hash = "sha256:2c8333f6e934733483c7eddffdb094c143b9463d2af7e6bd85ebcb2d4a1b82c6"}, - {file = "pydantic_core-2.18.3-cp312-none-win_arm64.whl", hash = "sha256:7a20dded653e516a4655f4c98e97ccafb13753987434fe7cf044aa25f5b7d417"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:eecf63195be644b0396f972c82598cd15693550f0ff236dcf7ab92e2eb6d3522"}, - {file = "pydantic_core-2.18.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c44efdd3b6125419c28821590d7ec891c9cb0dff33a7a78d9d5c8b6f66b9702"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e59fca51ffbdd1638b3856779342ed69bcecb8484c1d4b8bdb237d0eb5a45e2"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70cf099197d6b98953468461d753563b28e73cf1eade2ffe069675d2657ed1d5"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63081a49dddc6124754b32a3774331467bfc3d2bd5ff8f10df36a95602560361"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370059b7883485c9edb9655355ff46d912f4b03b009d929220d9294c7fd9fd60"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a64faeedfd8254f05f5cf6fc755023a7e1606af3959cfc1a9285744cc711044"}, - {file = "pydantic_core-2.18.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d2e725de0f90d8671f89e420d36c3dd97639b98145e42fcc0e1f6d492a46dc"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:67bc078025d70ec5aefe6200ef094576c9d86bd36982df1301c758a9fff7d7f4"}, - {file = "pydantic_core-2.18.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf952c3f4100e203cbaf8e0c907c835d3e28f9041474e52b651761dc248a3c0"}, - {file = "pydantic_core-2.18.3-cp38-none-win32.whl", hash = "sha256:9a46795b1f3beb167eaee91736d5d17ac3a994bf2215a996aed825a45f897558"}, - {file = "pydantic_core-2.18.3-cp38-none-win_amd64.whl", hash = "sha256:200ad4e3133cb99ed82342a101a5abf3d924722e71cd581cc113fe828f727fbc"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:304378b7bf92206036c8ddd83a2ba7b7d1a5b425acafff637172a3aa72ad7083"}, - {file = "pydantic_core-2.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c826870b277143e701c9ccf34ebc33ddb4d072612683a044e7cce2d52f6c3fef"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e201935d282707394f3668380e41ccf25b5794d1b131cdd96b07f615a33ca4b1"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5560dda746c44b48bf82b3d191d74fe8efc5686a9ef18e69bdabccbbb9ad9442"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b32c2a1f8032570842257e4c19288eba9a2bba4712af542327de9a1204faff8"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:929c24e9dea3990bc8bcd27c5f2d3916c0c86f5511d2caa69e0d5290115344a9"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a8376fef60790152564b0eab376b3e23dd6e54f29d84aad46f7b264ecca943"}, - {file = "pydantic_core-2.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dccf3ef1400390ddd1fb55bf0632209d39140552d068ee5ac45553b556780e06"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:41dbdcb0c7252b58fa931fec47937edb422c9cb22528f41cb8963665c372caf6"}, - {file = "pydantic_core-2.18.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:666e45cf071669fde468886654742fa10b0e74cd0fa0430a46ba6056b24fb0af"}, - {file = "pydantic_core-2.18.3-cp39-none-win32.whl", hash = "sha256:f9c08cabff68704a1b4667d33f534d544b8a07b8e5d039c37067fceb18789e78"}, - {file = "pydantic_core-2.18.3-cp39-none-win_amd64.whl", hash = "sha256:4afa5f5973e8572b5c0dcb4e2d4fda7890e7cd63329bd5cc3263a25c92ef0026"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:77319771a026f7c7d29c6ebc623de889e9563b7087911b46fd06c044a12aa5e9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:df11fa992e9f576473038510d66dd305bcd51d7dd508c163a8c8fe148454e059"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d531076bdfb65af593326ffd567e6ab3da145020dafb9187a1d131064a55f97c"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33ce258e4e6e6038f2b9e8b8a631d17d017567db43483314993b3ca345dcbbb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1f9cd7f5635b719939019be9bda47ecb56e165e51dd26c9a217a433e3d0d59a9"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cd4a032bb65cc132cae1fe3e52877daecc2097965cd3914e44fbd12b00dae7c5"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f2718430098bcdf60402136c845e4126a189959d103900ebabb6774a5d9fdb"}, - {file = "pydantic_core-2.18.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c0037a92cf0c580ed14e10953cdd26528e8796307bb8bb312dc65f71547df04d"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b95a0972fac2b1ff3c94629fc9081b16371dad870959f1408cc33b2f78ad347a"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a62e437d687cc148381bdd5f51e3e81f5b20a735c55f690c5be94e05da2b0d5c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b367a73a414bbb08507da102dc2cde0fa7afe57d09b3240ce82a16d608a7679c"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ecce4b2360aa3f008da3327d652e74a0e743908eac306198b47e1c58b03dd2b"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd4435b8d83f0c9561a2a9585b1de78f1abb17cb0cef5f39bf6a4b47d19bafe3"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:616221a6d473c5b9aa83fa8982745441f6a4a62a66436be9445c65f241b86c94"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7e6382ce89a92bc1d0c0c5edd51e931432202b9080dc921d8d003e616402efd1"}, - {file = "pydantic_core-2.18.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff58f379345603d940e461eae474b6bbb6dab66ed9a851ecd3cb3709bf4dcf6a"}, - {file = "pydantic_core-2.18.3.tar.gz", hash = "sha256:432e999088d85c8f36b9a3f769a8e2b57aabd817bbb729a90d1fe7f18f6f1f39"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] [package.dependencies] @@ -3018,82 +2486,6 @@ docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"] image = ["Pillow (>=8.0.0)"] -[[package]] -name = "pytest" -version = "8.2.1" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, - {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2.0" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.7" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, - {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-postgresql" -version = "6.0.0" -description = "Postgresql fixtures and fixture factories for Pytest." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-postgresql-6.0.0.tar.gz", hash = "sha256:6a4d8e600a2eef273f3c0e846cd0b2ea577282e252de29b4ca854bfb929bb682"}, - {file = "pytest_postgresql-6.0.0-py3-none-any.whl", hash = "sha256:f14272bffad16a74d9a63f4cc828f243a12ae92995e236b68fd53154760e6a5a"}, -] - -[package.dependencies] -mirakuru = "*" -port-for = ">=0.6.0" -psycopg = ">=3.0.0" -pytest = ">=6.2" -setuptools = "*" - -[[package]] -name = "pytest-xdist" -version = "3.6.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3122,20 +2514,6 @@ files = [ [package.extras] cli = ["click (>=5.0)"] -[[package]] -name = "python-multipart" -version = "0.0.9" -description = "A streaming multipart parser for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, -] - -[package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - [[package]] name = "pytz" version = "2024.1" @@ -3207,111 +2585,6 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] -[[package]] -name = "rapidfuzz" -version = "3.9.2" -description = "rapid fuzzy string matching" -optional = false -python-versions = ">=3.8" -files = [ - {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45e0c3e279e70589381f47ad410de7211bac943e827eb09eb8339d2124abca90"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:280ef2f3066df9c486ffd3874d2489978fb8021044c47c006eb96be8d47917d7"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe128ac0e05ca3a71d8ff18e70884a64fde00b6fbd2b4d9f59f7a4d798257c55"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fbc0f6e1b6f4063b937d0edcf0a56cbc1d7179ade9b7d6c849c94e44a7b20f6"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df19455c2fb85e86a721111b84ac8dd3685194f0edc9faefb226731ad3e134a7"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:801a5d97c465a3467b3cdf50cdcdadec129ddca582b24430f5d24c715c80be9b"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f218524596d261a6cb33cda965687e62dd30def478d39f0befa243642c3985"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5c61d53f293b4e3286919b0e081513367afabcb5aef0b6f899d006117778e558"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0ed70fc6627ae37319f822e5d8d21d561044e0b3331b6f0e6904476faa8d8ed7"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96fa229d06ee005d2f46374fb2af65590a590a6fa2fd56e66474829f5fa9adfe"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6609e881b57cabb40d515cc226bbf570e32e768bd2cc688ba026a45ffbc60875"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:204fd4d293ef4d409c4142ddf830b7613924b998670f67e512ab1f880a60218a"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-win32.whl", hash = "sha256:5b331a09446bc8f8971cf488c9e6c0f7dbf2739828588e063cf08fd400638a24"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:01a9975984953fe549649e6a4c3f0d9c60707acf458184ec09678d6a57560112"}, - {file = "rapidfuzz-3.9.2-cp310-cp310-win_arm64.whl", hash = "sha256:ca4af5d7fc9c17bdc498aa1cab9ecf5140c8535c9cedeba1990bbe4b8be75098"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:300ab53981a5d6831fe7e0f30c407c79520ad0f0ab51b2cece8717689026f495"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f4828642acdb075154ce2ff3260f8afb6a17b5b0c8a437efbadac06e9995dd7b"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b262883c3ce93dee1a9a974992961c8098e96b8142e2e01cabdb15ea8105c4a"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf8582d85e35641734d6c1f43eb37c1f2a5eda338d3cfa8e651e078246b9ec58"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e33b61ef87e1876d216c479fa2256233b3bb0424465ab2db1d94ab7b8649ae1c"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fa1b3eb21756003a6a3977847dd4e0e9a26e2e02731d9daa5e92a9258e7f0db"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923ae0301a56356364f1159e3005fbeb2191e7a0e8705d5cc1b481d9eea27b97"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8e4041cfd87f0a022aa8a9a187d3b0824e35be2bd9b3bceada11578ddd9ad65"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1f832b430f976727bdbba009ee64acda25412602976fbfb2113d41e765d81849"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6ce5e57e0c6acf5a98ffbdfaf8bccb6e41fbddb9eda3e041f4cc69b7cade5fa0"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d65f34e71102d9cbe733d4ba1c645e7623eef850562501bab1ac79d217831436"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dd9ba4df0db46b9f909289e4687cc7721c622985c4cd169969005dd30fc1e24"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-win32.whl", hash = "sha256:34c8bca3fef33d7e71f290de68be2184fac7a9e136fa0ed22b17ec597e181406"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e1a8872c0b8aef95c33db86d25e8bdea6f557b9cdf683123c25035b2bcfb8e"}, - {file = "rapidfuzz-3.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:ed02d73e46b7a4604d2bc1e0364b25f204862d40dd162f6b36ee22b9bf6d9df2"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ae6c4ba2778b097397968130f2b0cb795cdc415c115539a49ce798f606152ad5"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7270556ddebaa98fb777f493f17ed6a733b3527de16c43342bce1db109042845"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4625273447bdd94f2ab06b2951cd8b74356c3a48552208279a3ec2947ceee141"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5107b5ec8821453f7cac70b2d0bc4866699b25bff4819ada8b28bf2b11e87f65"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b04c851d309df8261ed42951444db657936234ceddf4032f4409b0214c95ecbe"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aeefff80f3f5d6841c30ffe0cdc84d62874de5a64cff509ae26fbd7478297af8"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cdc106b5a99edd46443449c767287dbb5d4464a7536475a365e368e7ee4d651"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce253a2b7a71a01a4abac71ac31fd05f6ac1f1cd2af2d98fa80fe5c402175e54"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5c30407cadbfe99753b7a996f0dd6da490b1e27d318c01db227e8f49770a01ec"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3fc387783f70387a91aababd8a5faeb230931b655ad99bcf838cd72404ba66"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c409852a89535ec8720301a847bab198c1c14d0f34ed07dfabbb90b1dbfc506d"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8603050e547249c1cc8a8dc6a49917076572ea69b04bc51eb1748c403cfc9f46"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-win32.whl", hash = "sha256:77bdb96e82d8831f0dd6db83e2ff0d4a731cff53e926d029c65a1dc3ae0f160a"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:09f354fa28e0fd170c6e4eea5e97eea0dba43761067df93109f49a5414ca8584"}, - {file = "rapidfuzz-3.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:168299c9a2b4f20f10c1bb96d8da0bb05bf1f3b9957be3a0bae5db65ce9f095f"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d87621d60078f87cb52082b1cbf9849afeaa1cb6d0a2b072fce25fe21c8675b4"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c447d0e534418ef3eaabcd890d85c7e9f289c1c6ef6e060a0b1f239799781747"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7161b205f25eff5f88ab809fb05a2a102634e06f452c0deb9535c9f41cd7b0a"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f13a6bbadba8fdd42676c1213ebc692bba9fac00f7db0ae92acc06bb734294c4"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54534743820a15bd0dc30a0a0010825be337973236550fd63587700a7950bbca"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea61851a4c2f93148aa2779458fb3f70a62342d77c9ec3d9d08445c8485b738"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e941f81a60351a842976fea208e6a6701a5899eb8a80b907e57d7c3099337900"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1bbfaf439e48efe3a48cada946cf7678b09c818ce9668e09dac40d05b772f6f8"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:574f464da18d660712e9776072572d462cf6a26144c833d18d9c93778286e023"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8a56c494246d29aacf5ac93ca3cf338d79588a1a5c05d8f496c3f4d7127e9031"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2943b0f17195c000948a7668bb11979ea0e50079a3d3db9d139e51b68c3a7c26"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:27214f93555d4f9b7b1baf107a6ba13e9daee21f1ec6e36418556d04a7ee4d9b"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-win32.whl", hash = "sha256:876c6628fec6241262c27f8fda3c73bab88e205e9b9394c8868361e2eda59048"}, - {file = "rapidfuzz-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf1952b486589ffcfbde2015ca9be15e0f4b0e63d1e2c25f3daec0263fda4e69"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ca9a135060ee4d887d6af86493c3e0eb1c99ca205bca943fe5994dc93e648d5"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:723518c9a18e8bda996d77aa9307b6f8b0e77905702b2772b020adf24191073a"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65eb9aeae73ac60e53a9d6c509daaa217ea256a5e184eb8920c9b15295c48677"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef2964f4eb9a37487c96e5e32167a3c4fa51bf8e899853d0ac67e0465a27702c"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c64a252c96f29667c206726903bb9705c5195f01850360c9b9268de92ac878dc"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b32b03398517b5e33c7f36d625a00fcb1c955b9fe3c939325688175fb21730"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec5f7b1bac77439b624f5acbd8bfe61e7b833678701068b43f7a489c151427c0"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5fd1b49fba8b4b9172eed5b131c1e9864d4d76bebea34359274f16a3591e5f44"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c05b033fc3ff043f48e744f67038af7fd34003047c7810f24bec7c01ce7da05b"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c3bea20db89b510d78d017b349b9d87159c32418693ddf091d9035dbe20b4dc0"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:77226a77590f83ee073f4f8cc86a1232da88e24d19d349361faa169fb17ba1cd"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:83ed8bc2c942dc61ab739bbca1ead791143b4639dc92156d3060bd0b6f4541ea"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-win32.whl", hash = "sha256:2db70f64974c10b76ae37d5cff6124dce791def815d4fdf5ac16fe60be88d905"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:bdead23114206dea4a22ed3aad6565b99a9e4b3fff9837c423afc556d2814b1a"}, - {file = "rapidfuzz-3.9.2-cp39-cp39-win_arm64.whl", hash = "sha256:0ec69ad076cfc7c88323d671613e40bb8754ba95a203556d9a7759e60f0544e8"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:018360654881e75131b227aa96cdaba543c438da881c70a12ca0c86e2c4083b2"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:eaa8178ec9238f32f15b6e49f70b852accda0a848448c4e30bce77c6624ebaba"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dd79b0f90ce609df96d0d48ef4327cf1f0415b9274588a466d3610a775d2f9"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04a1c38a72a50f3e6d346a33d53fa51ba390552b3592fca64a07e54d749b439b"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ca96eec40e815f0cf10b00008f295fd26ca43792a844cf62588a8ea614e160"}, - {file = "rapidfuzz-3.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c01c515a928f295f49d588b6523f44b474f047f9f2de0079bc57bcd00b870778"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07e14ef260b6f4ee03dff07a0ac95a16aff1ddbc7e6171e07e49d2d61526f3be"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:64f3480bddc12b89969930f12a50a1aeb53e09aad41cf8b27694d83ca1cc7864"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3c9e33ec21755bda1878095537cb84848e9cf6510d4837d22144ba04e33df29"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a70045e84225697ddf67d656aa25b70d6802e2ff339d51f9545fca5b9b13fb8c"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ec1fd328518c33adb9171afe8735137cb7b492e4a81cddc23568f9980c235c"}, - {file = "rapidfuzz-3.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1fd8458fdac232766d55593c1228c70968f382fdc376c25685273f99b5d1d921"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a373748fddb5403b562b6d682082de360bb08395f44e3cb7e74819461e39a16c"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:45f80856db3e22cb5f96ad1572aa1d004714514625ed4668144661d8a7c7e61f"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e52cf878e0ccbbad0744eb3e2bb83a784645b146f15611bac225bc218f19b"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbe4d3034a8cfe59a2b477375ad7d739b3e5935f10af08abdf64aae55780cad"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd38abfda97e42b30093f207108dcba944beab1edf6624ba757cf57354063177"}, - {file = "rapidfuzz-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:16b41fe360387283a3184ce72d4d26d1928e7ce809268a88e8491a776dd770af"}, - {file = "rapidfuzz-3.9.2.tar.gz", hash = "sha256:c899d78709f8d4bd0059784fa27a9f6c53d04fc4aeaa21de7c0c8e34a7154e88"}, -] - -[package.extras] -full = ["numpy"] - [[package]] name = "regex" version = "2024.5.15" @@ -3919,23 +3192,6 @@ files = [ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] -[[package]] -name = "tzlocal" -version = "5.2" -description = "tzinfo object for the local timezone" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, - {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, -] - -[package.dependencies] -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] - [[package]] name = "urllib3" version = "2.2.1" @@ -4390,4 +3646,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = "^3.11,<3.12" -content-hash = "99d9141afef8d1d15a2e26cd82d141146ba630edd99b3adc82803f50c2682091" +content-hash = "e1009d6d5a3112c225a6916b387f78d5e01bbda9edbd6ee1b78759540da5e72e" diff --git a/pyproject.toml b/pyproject.toml index 172d2b627c2c08250936562a4f4dd0ed3b9335ea..518ce9f4b05c5a25b04f21dd87d5668128a2386c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "app" version = "0.1.0" description = "" -authors = [ "Praneeth Yerrapragada, Patrick Alexis" ] +authors = [ "Marcus Schiesser " ] readme = "README.md" package-mode = false @@ -19,42 +19,14 @@ llama-index = "0.10.28" llama-index-core = "0.10.28" llama-index-vector-stores-pinecone = "^0.1.7" traceloop-sdk = "^0.19.0" -langchain = "^0.2.1" -langchain-openai = "^0.1.8" -openai = "^1.30.5" -tenacity = "^8.3.0" -rapidfuzz = "^3.9.2" -pydantic = "^2.7.2" -dateparser = "^1.2.0" -pandas = "^2.2.2" -path = "^16.14.0" -pytest-xdist = "^3.6.1" -pytest-asyncio = "^0.23.7" -httpx = "^0.27.0" -alembic = "^1.13.1" -asyncpg = "^0.29.0" -asyncio = "^3.4.3" -pytest-postgresql = "^6.0.0" -psycopg-binary = "^3.1.19" -python-multipart = "^0.0.9" [tool.poetry.dependencies.uvicorn] extras = [ "standard" ] version = "^0.23.2" -[tool.poetry.dependencies.sqlalchemy] -extras = [ "asyncio" ] -version = "^2.0.30" - [tool.poetry.dependencies.llama-index-agent-openai] version = "0.2.2" -[tool.black] -line-length = 119 - -[tool.pytest.ini_options] -asyncio_mode = "auto" - [build-system] requires = [ "poetry-core" ] build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..877d611001363315f0852bd652c4a4550afb96b0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +aiostream==0.5.2 +cachetools==5.3.3 +docx2txt==0.8 +fastapi==0.109.1 +llama-index-agent-openai==0.2.2 +llama-index-core==0.10.28 +llama-index-vector-stores-pinecone==0.1.3 +llama-index==0.10.28 +python-dotenv==1.0.0 +traceloop-sdk==0.15.11 +uvicorn==0.23.2 \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 6a6c4040393f8c1d1f131a97a5f0b8771e13cd85..0000000000000000000000000000000000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,83 +0,0 @@ -# Credits: https://github.com/ThomasAitken/demo-fastapi-async-sqlalchemy/blob/main/backend/app/conftest.py - -import asyncio -from contextlib import ExitStack - -import pytest -from alembic.config import Config -from alembic.migration import MigrationContext -from alembic.operations import Operations -from alembic.script import ScriptDirectory -from config.index import config as settings -from app.engine.postgresdb import Base, get_db_session, postgresdb as sessionmanager -from main import init_app -from asyncpg import Connection -from fastapi.testclient import TestClient -from pytest_postgresql import factories -from pytest_postgresql.factories.noprocess import postgresql_noproc -from pytest_postgresql.janitor import DatabaseJanitor -from sqlalchemy.testing.entities import ComparableEntity - -from config.index import config as env - -test_db = factories.postgresql_proc(dbname="test_db", port=5433) - - -@pytest.fixture(autouse=True) -def app(): - with ExitStack(): - # Don't initialize database connection. - # This is because we want to initialize the database connection manually, so that we can create the test database. - yield init_app(init_db=False) - - -@pytest.fixture -def client(app): - with TestClient(app) as c: - yield c - - -@pytest.fixture(scope="session") -def event_loop(request): - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - - -@pytest.fixture(scope="function", autouse=True) -async def connection_test(test_db, event_loop): - pg_host = test_db.host - pg_port = test_db.port - pg_user = test_db.user - pg_db = test_db.dbname - pg_password = test_db.password - - with DatabaseJanitor(user=pg_user, host=pg_host, port=pg_port, dbname=pg_db, version=test_db.version, password=pg_password): - connection_str = f"postgresql+psycopg://{pg_user}:@{pg_host}:{pg_port}/{pg_db}" - sessionmanager.init(connection_str, - # {"echo": True, "future": True} - ) - yield - await sessionmanager.close() - - -@pytest.fixture(scope="function", autouse=True) -async def create_tables(connection_test): - async with sessionmanager.connect() as connection: - await sessionmanager.drop_all(connection) - await sessionmanager.create_all(connection) - - -@pytest.fixture(scope="function", autouse=True) -async def session_override(app, connection_test): - async def get_db_session_override(): - async with sessionmanager.session() as session: - yield session - - app.dependency_overrides[get_db_session] = get_db_session_override - - -@pytest.fixture(scope="function", autouse=True) -async def get_db_session_fixture(): - async with sessionmanager.session() as session: - yield session diff --git a/tests/pytest.ini b/tests/pytest.ini deleted file mode 100644 index 718d9b6aa85be28cf5fab4dcf754d27be8472e97..0000000000000000000000000000000000000000 --- a/tests/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -asyncio_mode = auto -timeout = 60 -addopts = -vv --disable-warnings --durations=10 --durations-min=1.0 \ No newline at end of file diff --git a/tests/test_income_statement.py b/tests/test_income_statement.py deleted file mode 100644 index f8b7d1ebc290008e4d992352ef65c6900e4fb4b5..0000000000000000000000000000000000000000 --- a/tests/test_income_statement.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi.testclient import TestClient -import pytest - -from sqlalchemy.ext.asyncio import AsyncSession -from app.model.transaction import Transaction -from app.model.user import User -from tests.utils import get_fake_transactions - -@pytest.mark.asyncio -async def test_income_statement(client: TestClient, get_db_session_fixture: AsyncSession) -> None: - session_override = get_db_session_fixture - - # 1. Create a user - user = await User.create(session_override, name="user", email="email", hashed_password="password") - - # 2. Create a bunch of transactions - fake_transactions = get_fake_transactions(user.id) - await Transaction.bulk_create(session_override, fake_transactions) - - # 3. Create an income statement - min_date = min(t.transaction_date for t in fake_transactions) - max_date = max(t.transaction_date for t in fake_transactions) - - print(f"min_date: {min_date}, max_date: {max_date}") - response = client.post( - "/api/v1/income_statement", - json={ - "user_id": 1, - "date_from": str(min_date), - "date_to": str(max_date), - }, - ) - - assert response.status_code == 200 - - # 4. Verify that the income statement matches the transactions - response = client.get(f"/api/v1/income_statement/user/1") - print(response.json()) - assert response.status_code == 200 - assert response.json()[0].get("income") - assert response.json()[0].get("expenses") - - report_id = response.json()[0].get("id") - - # # 5. Verify that the income statement can be retrieved - if report_id is not None: - response = client.get(f"/api/v1/income_statement/report/{report_id}") - assert response.status_code == 200 - assert response.json().get("income") - assert response.json().get("expenses") - assert response.json().get("id") == report_id \ No newline at end of file diff --git a/tests/test_transactions.py b/tests/test_transactions.py deleted file mode 100644 index 16f9fe36105693874b4362f6034915336ade0e9d..0000000000000000000000000000000000000000 --- a/tests/test_transactions.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi.testclient import TestClient -import pytest - -from app.model.transaction import Transaction -from app.model.user import User -from sqlalchemy.ext.asyncio import AsyncSession -from tests.utils import get_fake_transactions - - -@pytest.mark.asyncio -async def test_transactions(client: TestClient, get_db_session_fixture: AsyncSession) -> None: - - session_override = get_db_session_fixture - - # 1. Create a user - user = await User.create(session_override, name="user", email="email", hashed_password="password") - - # 2. Create a bunch of transactions - fake_transactions = get_fake_transactions(user.id) - await Transaction.bulk_create(session_override, fake_transactions) - - # 3. Verify that the transactions are returned - response = client.get("/api/v1/transactions/1") - assert response.status_code == 200 - assert len(response.json()) == 10 \ No newline at end of file diff --git a/tests/test_users.py b/tests/test_users.py deleted file mode 100644 index 531aa1087db0ee8e367161b88bd71d053b42875a..0000000000000000000000000000000000000000 --- a/tests/test_users.py +++ /dev/null @@ -1,40 +0,0 @@ -from random import randint - -from fastapi.testclient import TestClient - - -def test_users(client: TestClient) -> None: - rand_int = randint(0, 1000) - email = f"test{rand_int}@example.com" - response = client.get(f"/api/v1/users/{email}") - assert response.status_code == 404 - - response = client.post( - "/api/v1/users", - json={"email": email, "name": "Full Name Test", "hashed_password": "test"}, - ) - assert response.status_code == 200 - assert response.json().get("email") == email - assert response.json().get("name") == "Full Name Test" - assert response.json().get("is_deleted") == False - - response = client.get(f"/api/v1/users/{email}") - assert response.status_code == 200 - assert response.json().get("email") == email - assert response.json().get("name") == "Full Name Test" - assert response.json().get("is_deleted") == False - - response = client.put( - f"/api/v1/users/{email}", - json={"email": email, "name": "Full Name Test 2", "hashed_password": "test"}, - ) - assert response.status_code == 200 - assert response.json().get("email") == email - assert response.json().get("name") == "Full Name Test 2" - assert response.json().get("is_deleted") == False - - response = client.delete(f"/api/v1/users/{email}") - assert response.status_code == 200 - assert response.json().get("email") == email - assert response.json().get("name") == "Full Name Test 2" - assert response.json().get("is_deleted") == True diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 770eea71ecabcdc498b9505e722a788ec5b4ce2b..0000000000000000000000000000000000000000 --- a/tests/utils.py +++ /dev/null @@ -1,88 +0,0 @@ -from datetime import datetime -from typing import List -from app.schema.index import TransactionCreate, TransactionType - - -def get_fake_transactions(user_id: int) -> List[TransactionCreate]: - return [ - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 1), - category="category1", - name_description="name_description", - amount=1.0, - type=TransactionType.EXPENSE, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 2), - category="category2", - name_description="name_description", - amount=2.0, - type=TransactionType.EXPENSE, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 3), - category="category3", - name_description="name_description", - amount=3.0, - type=TransactionType.INCOME, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 4), - category="category1", - name_description="name_description", - amount=4.0, - type=TransactionType.INCOME, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 5), - category="category2", - name_description="name_description", - amount=5.0, - type=TransactionType.EXPENSE, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 6), - category="category3", - name_description="name_description", - amount=6.0, - type=TransactionType.EXPENSE, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 7), - category="category1", - name_description="name_description", - amount=7.0, - type=TransactionType.INCOME, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 8), - category="category2", - name_description="name_description", - amount=8.0, - type=TransactionType.INCOME, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 9), - category="category3", - name_description="name_description", - amount=9.0, - type=TransactionType.EXPENSE, - ), - TransactionCreate( - user_id=user_id, - transaction_date=datetime(2022, 1, 10), - category="category1", - name_description="name_description", - amount=10.0, - type=TransactionType.EXPENSE, - ), - ] \ No newline at end of file