diff --git a/.gitignore b/.gitignore index bb20a1fbf..484d4bd89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,52 @@ +# General +docker-compose.yml + # Python -backend/__pycache__/ -backend/*/__pycache__/ -backend/*/*/__pycache__/ -backend/*.pyc +backend/app/__pycache__/ +backend/app/*/__pycache__/ +backend/app/*/*/__pycache__/ +backend/app/*.pyc # Logs -backend/logs/*.log -backend/*.log +backend/app/logs/*.log +backend/app/*.log # user image folder images -backend/user_images/*.jpeg -backend/user_images/*.png -backend/user_images/*.jpg +backend/app/user_images/*.jpeg +backend/app/user_images/*.png +backend/app/user_images/*.jpg # Frontend -frontend/img/users_img/*.* +frontend/app/img/users_img/*.* # Logs -frontend/.gitignore -frontend/logs -frontend/*.log -frontend/npm-debug.log* -frontend/yarn-debug.log* -frontend/yarn-error.log* -frontend/pnpm-debug.log* -frontend/lerna-debug.log* +frontend/app/.gitignore +frontend/app/logs +frontend/app/*.log +frontend/app/npm-debug.log* +frontend/app/yarn-debug.log* +frontend/app/yarn-error.log* +frontend/app/pnpm-debug.log* +frontend/app/lerna-debug.log* -frontend/node_modules -frontend/.DS_Store -frontend/dist -frontend/dist-ssr -frontend/coverage -frontend/*.local -frontend/README.md +frontend/app/node_modules +frontend/app/.DS_Store +frontend/app/dist +frontend/app/dist-ssr +frontend/app/coverage +frontend/app/*.local +frontend/app/README.md -frontend/cypress/videos/ -frontend/cypress/screenshots/ +frontend/app/cypress/videos/ +frontend/app/cypress/screenshots/ # Editor directories and files -frontend/.vscode/* -frontend/!.vscode/extensions.json -frontend/.idea -frontend/*.suo -frontend/*.ntvs* -frontend/*.njsproj -frontend/*.sln -frontend/*.sw? +frontend/app/.vscode/* +frontend/app/!.vscode/extensions.json +frontend/app/.idea +frontend/app/*.suo +frontend/app/*.ntvs* +frontend/app/*.njsproj +frontend/app/*.sln +frontend/app/*.sw? -frontend/*.tsbuildinfo \ No newline at end of file +frontend/app/*.tsbuildinfo \ No newline at end of file diff --git a/Dockerfile_backend b/Dockerfile_backend deleted file mode 100644 index 162f2a603..000000000 --- a/Dockerfile_backend +++ /dev/null @@ -1,50 +0,0 @@ -FROM python:3.11 - -# Links Docker image with repository -LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endurain - -# Set the working directory -WORKDIR /app - -# Copy the requirements.txt file to /app -COPY requirements.txt /app - -# Install any needed packages specified in requirements.txt and then remove requirements.txt file -# Remove any temporary files or directories created during the build process -# Cache Cleanup -RUN pip install --no-cache-dir --upgrade -r requirements.txt \ - && rm requirements.txt \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Copy the directory backend contents to /app -COPY backend /app - -# Make port 80 available to the world outside this container -EXPOSE 80 - -# Define environment variables -ENV DB_HOST="mariadb" -ENV DB_PORT=3306 -ENV DB_USER="endurain" -ENV DB_PASSWORD="changeme" -ENV DB_DATABASE="endurain" -ENV SECRET_KEY="changeme" -ENV ALGORITHM="HS256" -ENV ACCESS_TOKEN_EXPIRE_MINUTES=30 -ENV REFRESH_TOKEN_EXPIRE_DAYS=7 -ENV STRAVA_CLIENT_ID="changeme" -ENV STRAVA_CLIENT_SECRET="changeme" -ENV STRAVA_AUTH_CODE="changeme" -ENV JAEGER_ENABLED="false" -ENV JAEGER_HOST="jaeger" -ENV JAEGER_PROTOCOL="http" -ENV JAGGER_PORT=4317 -ENV STRAVA_DAYS_ACTIVITIES_ONLINK=30 -ENV FRONTEND_PROTOCOL="http" -ENV FRONTEND_HOST="frontend" -ENV FRONTEND_PORT=8080 -ENV GEOCODES_MAPS_API="changeme" - -# Run main.py when the container launches -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 000000000..f2dbc11ad --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,119 @@ +# FROM python:3.12 + +# # Links Docker image with repository +# LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endurain + +# # Set the working directory +# WORKDIR /app + +# # Copy pyproject.toml and poetry.lock* files +# COPY pyproject.toml poetry.lock* /app/ + +# # Install Poetry +# RUN pip install poetry + +# # Install dependencies using poetry +# # Remove any temporary files or directories created during the build process +# # Cache Cleanup +# RUN poetry config virtualenvs.create false \ +# && poetry install --only main --no-root --no-directory --no-interaction --no-ansi \ +# && apt-get clean \ +# && rm -rf /var/lib/apt/lists/* \ +# && rm -rf /tmp/poetry_cache + +# # Copy the directory app contents to /app +# COPY app /app + +# # Make port 80 available to the world outside this container +# EXPOSE 80 + +# # Define environment variables +# ENV DB_HOST="mariadb" +# ENV DB_PORT=3306 +# ENV DB_USER="endurain" +# ENV DB_PASSWORD="changeme" +# ENV DB_DATABASE="endurain" +# ENV SECRET_KEY="changeme" +# ENV ALGORITHM="HS256" +# ENV ACCESS_TOKEN_EXPIRE_MINUTES=30 +# ENV REFRESH_TOKEN_EXPIRE_DAYS=7 +# ENV STRAVA_CLIENT_ID="changeme" +# ENV STRAVA_CLIENT_SECRET="changeme" +# ENV STRAVA_AUTH_CODE="changeme" +# ENV JAEGER_ENABLED="false" +# ENV JAEGER_HOST="jaeger" +# ENV JAEGER_PROTOCOL="http" +# ENV JAGGER_PORT=4317 +# ENV STRAVA_DAYS_ACTIVITIES_ONLINK=30 +# ENV FRONTEND_PROTOCOL="http" +# ENV FRONTEND_HOST="frontend" +# ENV FRONTEND_PORT=8080 +# ENV GEOCODES_MAPS_API="changeme" + +# # Run main.py when the container launches +# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] + + +# Set the base image to Python 3.11 and create a new stage +FROM python:3.11 as requirements-stage + +# Set the working directory +WORKDIR /tmp + +# Install Poetry +RUN pip install poetry + +# Copy pyproject.toml and poetry.lock* files +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +# Install dependencies using poetry and export them to requirements.txt +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + +# Set the base image to Python 3.11 +FROM python:3.11 + +# Links Docker image with repository +LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endurain + +# Set the working directory +WORKDIR /app + +# Copy requirements.txt from requirements-stage to /app +COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt + +# Install dependencies +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +# Copy the directory app contents to /app +COPY app /app + +# Make port 80 available to the world outside this container +EXPOSE 80 + +# Define environment variables +ENV DB_HOST="mariadb" +ENV DB_PORT=3306 +ENV DB_USER="endurain" +ENV DB_PASSWORD="changeme" +ENV DB_DATABASE="endurain" +ENV SECRET_KEY="changeme" +ENV ALGORITHM="HS256" +ENV ACCESS_TOKEN_EXPIRE_MINUTES=30 +ENV REFRESH_TOKEN_EXPIRE_DAYS=7 +ENV STRAVA_CLIENT_ID="changeme" +ENV STRAVA_CLIENT_SECRET="changeme" +ENV STRAVA_AUTH_CODE="changeme" +ENV JAEGER_ENABLED="false" +ENV JAEGER_HOST="jaeger" +ENV JAEGER_PROTOCOL="http" +ENV JAGGER_PORT=4317 +ENV STRAVA_DAYS_ACTIVITIES_ONLINK=30 +ENV FRONTEND_PROTOCOL="http" +ENV FRONTEND_HOST="frontend" +ENV FRONTEND_PORT=8080 +ENV GEOCODES_MAPS_API="changeme" + +# Run main.py when the container launches +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +# +#CMD ["fastapi", "run", "main.py", "--port", "80"] \ No newline at end of file diff --git a/backend/__init__.py b/backend/app/__init__.py similarity index 100% rename from backend/__init__.py rename to backend/app/__init__.py diff --git a/backend/dependencies/__init__.py b/backend/app/activities/__init__.py similarity index 100% rename from backend/dependencies/__init__.py rename to backend/app/activities/__init__.py diff --git a/backend/crud/crud_activities.py b/backend/app/activities/crud.py similarity index 95% rename from backend/crud/crud_activities.py rename to backend/app/activities/crud.py index 6bb27fea9..980fd5dd3 100644 --- a/backend/crud/crud_activities.py +++ b/backend/app/activities/crud.py @@ -8,7 +8,8 @@ from sqlalchemy.orm import Session, joinedload from urllib.parse import unquote import models -from schemas import schema_activities + +import activities.schema as activities_schema # Define a loggger created on main.py logger = logging.getLogger("myLogger") @@ -188,7 +189,7 @@ def get_user_following_activities_with_pagination( .filter( and_( models.Follower.follower_id == user_id, - models.Follower.is_accepted == True, + models.Follower.is_accepted, ), models.Activity.visibility.in_([0, 1]), ) @@ -232,7 +233,7 @@ def get_user_following_activities(user_id, db): .filter( and_( models.Follower.follower_id == user_id, - models.Follower.is_accepted == True, + models.Follower.is_accepted, ), models.Activity.visibility.in_([0, 1]), ) @@ -336,7 +337,9 @@ def get_activity_by_id_from_user_id_or_has_visibility( ) from err -def get_activity_by_id_from_user_id(activity_id: int, user_id: int, db: Session) -> schema_activities.Activity: +def get_activity_by_id_from_user_id( + activity_id: int, user_id: int, db: Session +) -> activities_schema.Activity: try: # Get the activities from the database activity = ( @@ -405,9 +408,7 @@ def get_activity_by_strava_id_from_user_id( ) from err -def get_activities_if_contains_name( - name: str, user_id: int, db: Session -): +def get_activities_if_contains_name(name: str, user_id: int, db: Session): try: # Define a search term partial_name = unquote(name).replace("+", " ") @@ -445,7 +446,7 @@ def get_activities_if_contains_name( ) from err -def create_activity(activity: schema_activities.Activity, db: Session): +def create_activity(activity: activities_schema.Activity, db: Session): try: # Create a new activity db_activity = models.Activity( @@ -476,8 +477,13 @@ def create_activity(activity: schema_activities.Activity, db: Session): db.commit() db.refresh(db_activity) - # Return the user - return db_activity + activity.id = db_activity.id + activity.created_at = db_activity.created_at.strftime("%Y-%m-%d %H:%M:%S") + activity.end_time = activity.end_time.strftime("%Y-%m-%d %H:%M:%S") + activity.created_at = activity.created_at.strftime("%Y-%m-%d %H:%M:%S") + + # Return the activity + return activity except Exception as err: # Rollback the transaction db.rollback() @@ -514,12 +520,14 @@ def add_gear_to_activity(activity_id: int, gear_id: int, db: Session): ) from err -def edit_multiple_activities_gear_id(activities: [schema_activities.Activity], user_id: int, db: Session): +def edit_multiple_activities_gear_id( + activities: list[activities_schema.Activity], user_id: int, db: Session +): try: for activity in activities: # Get the activity from the database db_activity = get_activity_by_id_from_user_id(activity.id, user_id, db) - + # Update the activity db_activity.gear_id = activity.gear_id @@ -577,7 +585,7 @@ def delete_all_strava_activities_for_user(user_id: int, db: Session): db.query(models.Activity) .filter( models.Activity.user_id == user_id, - models.Activity.strava_activity_id != None, + models.Activity.strava_activity_id is not None, ) .delete() ) diff --git a/backend/dependencies/dependencies_activities.py b/backend/app/activities/dependencies.py similarity index 100% rename from backend/dependencies/dependencies_activities.py rename to backend/app/activities/dependencies.py diff --git a/backend/routers/router_activities.py b/backend/app/activities/router.py similarity index 60% rename from backend/routers/router_activities.py rename to backend/app/activities/router.py index dc2772533..2b63d8fa7 100644 --- a/backend/routers/router_activities.py +++ b/backend/app/activities/router.py @@ -4,26 +4,36 @@ import calendar from typing import Annotated, Callable -from fastapi import APIRouter, Depends, HTTPException, status, UploadFile -from datetime import datetime, timedelta -from fastapi.security import OAuth2PasswordBearer +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, Security from sqlalchemy.orm import Session +from datetime import datetime, timedelta + +import activities.schema as activities_schema +import activities.utils as activies_utils +import activities.crud as activities_crud +import activities.dependencies as activities_dependencies + +import session.security as session_security + +import dependencies.dependencies_global as dependencies_global + +import gears.crud as gears_crud +import gears.dependencies as gears_dependencies + +import users.dependencies as users_dependencies + +import activity_streams.crud as activity_streams_crud + +import gpx.utils as gpx_utils + +import database -from schemas import schema_activities -from crud import crud_activities, crud_activity_streams, crud_gear from dependencies import ( - dependencies_database, dependencies_session, - dependencies_users, - dependencies_activities, - dependencies_gear, dependencies_global, - dependencies_security, + # dependencies_security, ) -from processors import gpx_processor, fit_processor - -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") +from processors import fit_processor # Define the API router router = APIRouter() @@ -33,24 +43,27 @@ logger = logging.getLogger("myLogger") @router.get( - "/activities/user/{user_id}/week/{week_number}", - response_model=list[schema_activities.Activity] | None, - tags=["activities"], + "/user/{user_id}/week/{week_number}", + response_model=list[activities_schema.Activity] | None, ) async def read_activities_useractivities_week( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], week_number: int, validate_week_number: Annotated[ - Callable, Depends(dependencies_activities.validate_week_number) + Callable, Depends(activities_dependencies.validate_week_number) + ], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) ], token_user_id: Annotated[ Callable, - Depends( - dependencies_session.validate_access_token_and_get_authenticated_user_id - ), + Depends(session_security.get_user_id_from_access_token), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Calculate the start of the requested week today = datetime.utcnow().date() @@ -59,12 +72,12 @@ async def read_activities_useractivities_week( if user_id == token_user_id: # Get all user activities for the requested week if the user is the owner of the token - activities = crud_activities.get_user_activities_per_timeframe( + activities = activities_crud.get_user_activities_per_timeframe( user_id, start_of_week, end_of_week, db ) else: # Get user following activities for the requested week if the user is not the owner of the token - activities = crud_activities.get_user_following_activities_per_timeframe( + activities = activities_crud.get_user_following_activities_per_timeframe( user_id, start_of_week, end_of_week, db ) @@ -78,20 +91,23 @@ async def read_activities_useractivities_week( @router.get( - "/activities/user/{user_id}/thisweek/distances", - response_model=schema_activities.ActivityDistances | None, - tags=["activities"], + "/user/{user_id}/thisweek/distances", + response_model=activities_schema.ActivityDistances | None, ) async def read_activities_useractivities_thisweek_distances( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], token_user_id: Annotated[ Callable, - Depends( - dependencies_session.validate_access_token_and_get_authenticated_user_id - ), + Depends(session_security.get_user_id_from_access_token), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Calculate the start of the current week today = datetime.utcnow().date() @@ -102,12 +118,12 @@ async def read_activities_useractivities_thisweek_distances( if user_id == token_user_id: # Get all user activities for the requested week if the user is the owner of the token - activities = crud_activities.get_user_activities_per_timeframe( + activities = activities_crud.get_user_activities_per_timeframe( user_id, start_of_week, end_of_week, db ) else: # Get user following activities for the requested week if the user is not the owner of the token - activities = crud_activities.get_user_following_activities_per_timeframe( + activities = activities_crud.get_user_following_activities_per_timeframe( user_id, start_of_week, end_of_week, db ) @@ -117,22 +133,27 @@ async def read_activities_useractivities_thisweek_distances( return None # Return the activities distances for this week - return schema_activities.calculate_activity_distances(activities) + return activies_utils.calculate_activity_distances(activities) @router.get( - "/activities/user/{user_id}/thismonth/distances", - response_model=schema_activities.ActivityDistances | None, - tags=["activities"], + "/user/{user_id}/thismonth/distances", + response_model=activities_schema.ActivityDistances | None, ) async def read_activities_useractivities_thismonth_distances( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], token_user_id: Annotated[ Callable, - Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id), + Depends(session_security.get_user_id_from_access_token), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Calculate the start of the current month today = datetime.utcnow().date() @@ -143,12 +164,12 @@ async def read_activities_useractivities_thismonth_distances( if user_id == token_user_id: # Get all user activities for the requested month if the user is the owner of the token - activities = crud_activities.get_user_activities_per_timeframe( + activities = activities_crud.get_user_activities_per_timeframe( user_id, start_of_month, end_of_month, db ) else: # Get user following activities for the requested month if the user is not the owner of the token - activities = crud_activities.get_user_following_activities_per_timeframe( + activities = activities_crud.get_user_following_activities_per_timeframe( user_id, start_of_month, end_of_month, db ) @@ -157,22 +178,29 @@ async def read_activities_useractivities_thismonth_distances( return None # Return the activities distances for this month - return schema_activities.calculate_activity_distances(activities) + return activies_utils.calculate_activity_distances(activities) @router.get( - "/activities/user/{user_id}/thismonth/number", + "/user/{user_id}/thismonth/number", response_model=int, - tags=["activities"], ) async def read_activities_useractivities_thismonth_number( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], token_user_id: Annotated[ Callable, - Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id), + Depends( + session_security.get_user_id_from_access_token + ), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Calculate the start of the current month today = datetime.utcnow().date() @@ -183,12 +211,12 @@ async def read_activities_useractivities_thismonth_number( if user_id == token_user_id: # Get all user activities for the requested month if the user is the owner of the token - activities = crud_activities.get_user_activities_per_timeframe( + activities = activities_crud.get_user_activities_per_timeframe( user_id, start_of_month, end_of_month, db ) else: # Get user following activities for the requested month if the user is not the owner of the token - activities = crud_activities.get_user_following_activities_per_timeframe( + activities = activities_crud.get_user_following_activities_per_timeframe( user_id, start_of_month, end_of_month, db ) @@ -201,42 +229,48 @@ async def read_activities_useractivities_thismonth_number( @router.get( - "/activities/user/{user_id}/gear/{gear_id}", - response_model=list[schema_activities.Activity] | None, - tags=["activities"], + "/user/{user_id}/gear/{gear_id}", + response_model=list[activities_schema.Activity] | None, ) async def read_activities_gearactivities( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], gear_id: int, - validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)], + validate_gear_id: Annotated[Callable, Depends(gears_dependencies.validate_gear_id)], validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access: Annotated[ Callable, Depends( dependencies_session.validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access ), ], - db: Session = Depends(dependencies_database.get_db), + db: Annotated[ + Session, + Depends(database.get_db), + ], ): # Get the activities for the gear - return crud_activities.get_user_activities_by_gear_id_and_user_id( + return activities_crud.get_user_activities_by_gear_id_and_user_id( user_id, gear_id, db ) @router.get( - "/activities/user/{user_id}/number", + "/user/{user_id}/number", response_model=int, - tags=["activities"], ) async def read_activities_useractivities_number( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)], - db: Session = Depends(dependencies_database.get_db), + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], ): # Get the number of activities for the user - activities = crud_activities.get_user_activities(user_id, db) + activities = activities_crud.get_user_activities(user_id, db) # Check if activities is None and return 0 if it is if activities is None: @@ -247,23 +281,27 @@ async def read_activities_useractivities_number( @router.get( - "/activities/user/{user_id}/page_number/{page_number}/num_records/{num_records}", - response_model=list[schema_activities.Activity] | None, - tags=["activities"], + "/user/{user_id}/page_number/{page_number}/num_records/{num_records}", + response_model=list[activities_schema.Activity] | None, ) async def read_activities_useractivities_pagination( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], page_number: int, num_records: int, validate_pagination_values: Annotated[ Callable, Depends(dependencies_global.validate_pagination_values) ], - validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)], - db: Session = Depends(dependencies_database.get_db), + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], ): # Get the activities for the user with pagination - activities = crud_activities.get_user_activities_with_pagination( + activities = activities_crud.get_user_activities_with_pagination( user_id, db, page_number, num_records ) @@ -276,40 +314,48 @@ async def read_activities_useractivities_pagination( @router.get( - "/activities/user/{user_id}/followed/page_number/{page_number}/num_records/{num_records}", - response_model=list[schema_activities.Activity] | None, - tags=["activities"], + "/user/{user_id}/followed/page_number/{page_number}/num_records/{num_records}", + response_model=list[activities_schema.Activity] | None, ) async def read_activities_followed_user_activities_pagination( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], page_number: int, num_records: int, validate_pagination_values: Annotated[ Callable, Depends(dependencies_global.validate_pagination_values) ], - validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)], - db: Session = Depends(dependencies_database.get_db), + validate_token: Annotated[ + Callable, Depends(session_security.validate_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], ): # Get the activities for the following users with pagination - return crud_activities.get_user_following_activities_with_pagination( + return activities_crud.get_user_following_activities_with_pagination( user_id, page_number, num_records, db ) @router.get( - "/activities/user/{user_id}/followed/number", + "/user/{user_id}/followed/number", response_model=int, - tags=["activities"], ) async def read_activities_followed_useractivities_number( user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Annotated[Callable, Depends(dependencies_security.validate_token_expiration)], - db: Session = Depends(dependencies_database.get_db), + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[ + Callable, Depends(session_security.validate_token_expiration) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], ): # Get the number of activities for the following users - activities = crud_activities.get_user_following_activities(user_id, db) + activities = activities_crud.get_user_following_activities(user_id, db) # Check if activities is None and return 0 if it is if activities is None: @@ -320,57 +366,63 @@ async def read_activities_followed_useractivities_number( @router.get( - "/activities/{activity_id}", - response_model=schema_activities.Activity | None, - tags=["activities"], + "/{activity_id}", + response_model=activities_schema.Activity | None, ) async def read_activities_activity_from_id( activity_id: int, validate_activity_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) + Callable, Depends(activities_dependencies.validate_activity_id) ], token_user_id: Annotated[ Callable, - Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id), + Depends(session_security.get_user_id_from_access_token), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Get the activity from the database and return it - return crud_activities.get_activity_by_id_from_user_id_or_has_visibility( + return activities_crud.get_activity_by_id_from_user_id_or_has_visibility( activity_id, token_user_id, db ) @router.get( - "/activities/name/contains/{name}", - response_model=list[schema_activities.Activity] | None, - tags=["activities"], + "/name/contains/{name}", + response_model=list[activities_schema.Activity] | None, ) async def read_activities_contain_name( name: str, token_user_id: Annotated[ Callable, - Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id), + Depends(session_security.get_user_id_from_access_token), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Get the activities from the database by name - return crud_activities.get_activities_if_contains_name(name, token_user_id, db) + return activities_crud.get_activities_if_contains_name(name, token_user_id, db) @router.post( - "/activities/create/upload", + "/create/upload", status_code=201, - response_model=int, - tags=["activities"], + response_model=activities_schema.Activity, ) async def create_activity_with_uploaded_file( token_user_id: Annotated[ Callable, - Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id), + Depends(session_security.get_user_id_from_access_token), ], file: UploadFile, - db: Session = Depends(dependencies_database.get_db), + db: Annotated[ + Session, + Depends(database.get_db), + ], ): try: # Ensure the 'uploads' directory exists @@ -387,7 +439,7 @@ async def create_activity_with_uploaded_file( # Choose the appropriate parser based on file extension if file_extension.lower() == ".gpx": # Parse the GPX file - parsed_info = gpx_processor.parse_gpx_file(file.filename, token_user_id) + parsed_info = gpx_utils.parse_gpx_file(file.filename, token_user_id) elif file_extension.lower() == ".fit": # Parse the FIT file parsed_info = fit_processor.parse_fit_file(file.filename, token_user_id) @@ -399,7 +451,7 @@ async def create_activity_with_uploaded_file( ) # create the activity in the database - created_activity = crud_activities.create_activity(parsed_info["activity"], db) + created_activity = activities_crud.create_activity(parsed_info["activity"], db) # Check if created_activity is None if created_activity is None: @@ -410,18 +462,18 @@ async def create_activity_with_uploaded_file( ) # Parse the activity streams from the parsed info - activity_streams = gpx_processor.parse_activity_streams_from_gpx_file( + activity_streams = gpx_utils.parse_activity_streams_from_gpx_file( parsed_info, created_activity.id ) # Create activity streams in the database - crud_activity_streams.create_activity_streams(activity_streams, db) + activity_streams_crud.create_activity_streams(activity_streams, db) # Remove the file after processing os.remove(file.filename) # Return activity ID - return created_activity.id + return created_activity except Exception as err: # Log the exception logger.error( @@ -435,23 +487,28 @@ async def create_activity_with_uploaded_file( @router.put( - "/activities/{activity_id}/addgear/{gear_id}", - tags=["activities"], + "/{activity_id}/addgear/{gear_id}", ) async def activity_add_gear( activity_id: int, validate_activity_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) + Callable, Depends(activities_dependencies.validate_activity_id) ], gear_id: int, - validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)], + validate_gear_id: Annotated[Callable, Depends(gears_dependencies.validate_gear_id)], token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + session_security.get_user_id_from_access_token + ), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Get the gear by user id and gear id - gear = crud_gear.get_gear_user_by_id(token_user_id, gear_id, db) + gear = gears_crud.get_gear_user_by_id(token_user_id, gear_id, db) # Check if gear is None and raise an HTTPException with a 404 Not Found status code if it is if gear is None: @@ -461,7 +518,7 @@ async def activity_add_gear( ) # Get the activity by id from user id - activity = crud_activities.get_activity_by_id_from_user_id( + activity = activities_crud.get_activity_by_id_from_user_id( activity_id, token_user_id, db ) @@ -473,28 +530,33 @@ async def activity_add_gear( ) # Add the gear to the activity - crud_activities.add_gear_to_activity(activity_id, gear_id, db) + activities_crud.add_gear_to_activity(activity_id, gear_id, db) # Return success message return {"detail": f"Gear ID {gear_id} added to activity successfully"} @router.put( - "/activities/{activity_id}/deletegear", - tags=["activities"], + "/{activity_id}/deletegear", ) async def delete_activity_gear( activity_id: int, validate_activity_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) + Callable, Depends(activities_dependencies.validate_activity_id) ], token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + session_security.get_user_id_from_access_token + ), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Get the activity by id from user id - activity = crud_activities.get_activity_by_id_from_user_id( + activity = activities_crud.get_activity_by_id_from_user_id( activity_id, token_user_id, db ) @@ -506,28 +568,33 @@ async def delete_activity_gear( ) # Delete gear from the activity - crud_activities.add_gear_to_activity(activity_id, None, db) + activities_crud.add_gear_to_activity(activity_id, None, db) # Return success message return {"detail": f"Gear ID {activity.gear_id} deleted from activity successfully"} @router.delete( - "/activities/{activity_id}/delete", - tags=["activities"], + "/{activity_id}/delete", ) async def delete_activity( activity_id: int, validate_activity_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) + Callable, Depends(activities_dependencies.validate_activity_id) ], token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + session_security.get_user_id_from_access_token + ), + ], + db: Annotated[ + Session, + Depends(database.get_db), ], - db: Session = Depends(dependencies_database.get_db), ): # Get the activity by id from user id - activity = crud_activities.get_activity_by_id_from_user_id( + activity = activities_crud.get_activity_by_id_from_user_id( activity_id, token_user_id, db ) @@ -539,7 +606,7 @@ async def delete_activity( ) # Delete the activity - crud_activities.delete_activity(activity_id, db) + activities_crud.delete_activity(activity_id, db) # Return success message return {"detail": f"Activity {activity_id} deleted successfully"} diff --git a/backend/schemas/schema_activities.py b/backend/app/activities/schema.py similarity index 54% rename from backend/schemas/schema_activities.py rename to backend/app/activities/schema.py index 8e869bd46..62262a5f9 100644 --- a/backend/schemas/schema_activities.py +++ b/backend/app/activities/schema.py @@ -32,22 +32,4 @@ class Activity(BaseModel): class ActivityDistances(BaseModel): swim: float bike: float - run: float - - -def calculate_activity_distances(activities: list[Activity]): - """Calculate the distances of the activities for each type of activity (run, bike, swim)""" - # Initialize the distances - run = bike = swim = 0.0 - - # Calculate the distances - for activity in activities: - if activity.activity_type in [1, 2, 3]: - run += activity.distance - elif activity.activity_type in [4, 5, 6, 7]: - bike += activity.distance - elif activity.activity_type in [8, 9]: - swim += activity.distance - - # Return the distances - return ActivityDistances(run=run, bike=bike, swim=swim) + run: float \ No newline at end of file diff --git a/backend/processors/activity_processor.py b/backend/app/activities/utils.py similarity index 91% rename from backend/processors/activity_processor.py rename to backend/app/activities/utils.py index 5b1f1efa7..a19050faf 100644 --- a/backend/processors/activity_processor.py +++ b/backend/app/activities/utils.py @@ -7,12 +7,32 @@ from datetime import datetime from urllib.parse import urlencode from statistics import mean +import activities.schema as activities_schema + # Define a loggger created on main.py logger = logging.getLogger("myLogger") +def calculate_activity_distances(activities: list[activities_schema.Activity]): + """Calculate the distances of the activities for each type of activity (run, bike, swim)""" + # Initialize the distances + run = bike = swim = 0.0 + + # Calculate the distances + for activity in activities: + if activity.activity_type in [1, 2, 3]: + run += activity.distance + elif activity.activity_type in [4, 5, 6, 7]: + bike += activity.distance + elif activity.activity_type in [8, 9]: + swim += activity.distance + + # Return the distances + return activities_schema.ActivityDistances(run=run, bike=bike, swim=swim) + + def location_based_on_coordinates(latitude, longitude) -> dict | None: - if latitude is None or longitude is None: + if latitude is None or longitude is None: return None # Create a dictionary with the parameters for the request diff --git a/backend/processors/__init__.py b/backend/app/activity_streams/__init__.py similarity index 100% rename from backend/processors/__init__.py rename to backend/app/activity_streams/__init__.py diff --git a/backend/crud/crud_activity_streams.py b/backend/app/activity_streams/crud.py similarity index 95% rename from backend/crud/crud_activity_streams.py rename to backend/app/activity_streams/crud.py index d07f05cfe..9208f68c8 100644 --- a/backend/crud/crud_activity_streams.py +++ b/backend/app/activity_streams/crud.py @@ -3,7 +3,8 @@ import logging from fastapi import HTTPException, status from sqlalchemy.orm import Session -from schemas import schema_activity_streams +import activity_streams.schema as activity_streams_schema + import models # Define a loggger created on main.py @@ -66,7 +67,7 @@ def get_activity_stream_by_type(activity_id: int, stream_type: int, db: Session) def create_activity_streams( - activity_streams: [schema_activity_streams.ActivityStreams], db: Session + activity_streams: list[activity_streams_schema.ActivityStreams], db: Session ): try: # Create a list to store the ActivityStreams objects diff --git a/backend/dependencies/dependencies_activity_streams.py b/backend/app/activity_streams/dependencies.py similarity index 100% rename from backend/dependencies/dependencies_activity_streams.py rename to backend/app/activity_streams/dependencies.py diff --git a/backend/app/activity_streams/router.py b/backend/app/activity_streams/router.py new file mode 100644 index 000000000..7caaf06ef --- /dev/null +++ b/backend/app/activity_streams/router.py @@ -0,0 +1,74 @@ +import logging + +from typing import Annotated, Callable + +from fastapi import APIRouter, Depends, Security +from sqlalchemy.orm import Session + +import activity_streams.schema as activity_streams_schema +import activity_streams.crud as activity_streams_crud +import activity_streams.dependencies as activity_streams_dependencies + +import activities.dependencies as activities_dependencies + +import session.security as session_security + +import database + +#from dependencies import ( +# dependencies_security, +#) + +# Define the API router +router = APIRouter() + +# Define a loggger created on main.py +logger = logging.getLogger("myLogger") + + +@router.get( + "/activity_id/{activity_id}/all", + response_model=list[activity_streams_schema.ActivityStreams] | None, +) +async def read_activities_streams_for_activity_all( + activity_id: int, + validate_id: Annotated[ + Callable, Depends(activities_dependencies.validate_activity_id) + ], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Get the activity streams from the database and return them + return activity_streams_crud.get_activity_streams(activity_id, db) + + +@router.get( + "/activity_id/{activity_id}/stream_type/{stream_type}", + response_model=activity_streams_schema.ActivityStreams | None, +) +async def read_activities_streams_for_activity_stream_type( + activity_id: int, + validate_activity_id: Annotated[ + Callable, Depends(activities_dependencies.validate_activity_id) + ], + stream_type: int, + validate_activity_stream_type: Annotated[ + Callable, Depends(activity_streams_dependencies.validate_activity_stream_type) + ], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["activities:read"]) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Get the activity stream from the database and return them + return activity_streams_crud.get_activity_stream_by_type( + activity_id, stream_type, db + ) diff --git a/backend/schemas/schema_activity_streams.py b/backend/app/activity_streams/schema.py similarity index 100% rename from backend/schemas/schema_activity_streams.py rename to backend/app/activity_streams/schema.py diff --git a/backend/alembic.ini b/backend/app/alembic.ini similarity index 100% rename from backend/alembic.ini rename to backend/app/alembic.ini diff --git a/backend/alembic/README b/backend/app/alembic/README similarity index 100% rename from backend/alembic/README rename to backend/app/alembic/README diff --git a/backend/alembic/env.py b/backend/app/alembic/env.py similarity index 100% rename from backend/alembic/env.py rename to backend/app/alembic/env.py diff --git a/backend/alembic/script.py.mako b/backend/app/alembic/script.py.mako similarity index 100% rename from backend/alembic/script.py.mako rename to backend/app/alembic/script.py.mako diff --git a/backend/alembic/versions/0ab200a7f196_remove_access_tokens_table.py b/backend/app/alembic/versions/0ab200a7f196_remove_access_tokens_table.py similarity index 100% rename from backend/alembic/versions/0ab200a7f196_remove_access_tokens_table.py rename to backend/app/alembic/versions/0ab200a7f196_remove_access_tokens_table.py diff --git a/backend/alembic/versions/1bce2bd27873_initial_migration_v0_1_4.py b/backend/app/alembic/versions/1bce2bd27873_initial_migration_v0_1_4.py similarity index 100% rename from backend/alembic/versions/1bce2bd27873_initial_migration_v0_1_4.py rename to backend/app/alembic/versions/1bce2bd27873_initial_migration_v0_1_4.py diff --git a/backend/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py b/backend/app/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py similarity index 100% rename from backend/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py rename to backend/app/alembic/versions/5fd61bc55e09_add_cascade_delete_to_followers_foreign_.py diff --git a/backend/alembic/versions/ab815ee3beae_remove_photo_path_aux_column_from_users_.py b/backend/app/alembic/versions/ab815ee3beae_remove_photo_path_aux_column_from_users_.py similarity index 100% rename from backend/alembic/versions/ab815ee3beae_remove_photo_path_aux_column_from_users_.py rename to backend/app/alembic/versions/ab815ee3beae_remove_photo_path_aux_column_from_users_.py diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 000000000..fd251a5a0 --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,2 @@ +# Constant related to version +API_VERSION = "v0.3.0" \ No newline at end of file diff --git a/backend/database.py b/backend/app/database.py similarity index 75% rename from backend/database.py rename to backend/app/database.py index 8331381c7..3437dd4af 100644 --- a/backend/database.py +++ b/backend/app/database.py @@ -4,6 +4,19 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.engine.url import URL + +def get_db(): + # Create a new database session and return it + db = SessionLocal() + + try: + # Yield the database session + yield db + finally: + # Close the database session + db.close() + + # Define the database connection URL using environment variables db_url = URL.create( drivername="mysql", @@ -23,4 +36,4 @@ engine = create_engine( SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # Create a base class for declarative models -Base = declarative_base() \ No newline at end of file +Base = declarative_base() diff --git a/backend/routers/__init__.py b/backend/app/dependencies/__init__.py similarity index 100% rename from backend/routers/__init__.py rename to backend/app/dependencies/__init__.py diff --git a/backend/dependencies/dependencies_global.py b/backend/app/dependencies/dependencies_global.py similarity index 100% rename from backend/dependencies/dependencies_global.py rename to backend/app/dependencies/dependencies_global.py diff --git a/backend/dependencies/dependencies_session.py b/backend/app/dependencies/dependencies_session.py similarity index 92% rename from backend/dependencies/dependencies_session.py rename to backend/app/dependencies/dependencies_session.py index f1d2ec203..8b42f6539 100644 --- a/backend/dependencies/dependencies_session.py +++ b/backend/app/dependencies/dependencies_session.py @@ -4,8 +4,11 @@ from sqlalchemy.orm import Session from typing import Annotated -from dependencies import dependencies_database, dependencies_security -from schemas import schema_users +from session import dependencies_security + +import users.schema as users_schema + +import database # Define the OAuth2 scheme for handling bearer tokens oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -13,6 +16,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") ### Dependencies for access token validation + def validate_access_token(request: Request): # Extract the access token from the cookies access_token = request.cookies.get("endurain_access_token") @@ -87,7 +91,7 @@ def validate_refresh_token_and_get_authenticated_user_id( def validate_token_and_validate_admin_access( token: str = Depends(oauth2_scheme), - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): # Validate the token expiration dependencies_security.validate_token_expiration(token) @@ -99,7 +103,7 @@ def validate_token_and_validate_admin_access( def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_access( user_id: int | None, token: str = Depends(oauth2_scheme), - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): # Validate the token expiration dependencies_security.validate_token_expiration(token) @@ -118,17 +122,17 @@ def validate_token_and_if_user_id_equals_token_user_id_if_not_validate_admin_acc def validate_token_and_if_user_id_equals_token_user_attributtes_id_if_not_validate_admin_access( - user_attributtes: schema_users.User, + user_attributtes: users_schema.User, token: str = Depends(oauth2_scheme), - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): validate_token_user_id_admin_access(db, token, user_attributtes.id) def validate_token_and_if_user_id_equals_token_user_attributtes_password_id_if_not_validate_admin_access( - user_attributtes: schema_users.UserEditPassword, + user_attributtes: users_schema.UserEditPassword, token: str = Depends(oauth2_scheme), - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): validate_token_user_id_admin_access(db, token, user_attributtes.id) diff --git a/backend/schemas/__init__.py b/backend/app/followers/__init__.py similarity index 100% rename from backend/schemas/__init__.py rename to backend/app/followers/__init__.py diff --git a/backend/crud/crud_followers.py b/backend/app/followers/crud.py similarity index 97% rename from backend/crud/crud_followers.py rename to backend/app/followers/crud.py index 63c26a254..651db47ae 100644 --- a/backend/crud/crud_followers.py +++ b/backend/app/followers/crud.py @@ -1,13 +1,9 @@ import logging from fastapi import HTTPException, status -from sqlalchemy import func from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from urllib.parse import unquote import models -from schemas import schema_gear # Define a loggger created on main.py logger = logging.getLogger("myLogger") @@ -45,7 +41,7 @@ def get_accepted_followers_by_user_id(user_id: int, db: Session): db.query(models.Follower) .filter( (models.Follower.follower_id == user_id) - & (models.Follower.is_accepted == True) + & (models.Follower.is_accepted) ) .all() ) @@ -100,7 +96,7 @@ def get_accepted_following_by_user_id(user_id: int, db: Session): db.query(models.Follower) .filter( (models.Follower.following_id == user_id) - & (models.Follower.is_accepted == True) + & (models.Follower.is_accepted) ) .all() ) diff --git a/backend/app/followers/router.py b/backend/app/followers/router.py new file mode 100644 index 000000000..8bb383e5f --- /dev/null +++ b/backend/app/followers/router.py @@ -0,0 +1,257 @@ +import logging + +from typing import Annotated, Callable + +from fastapi import APIRouter, Depends +from fastapi.security import OAuth2PasswordBearer +from sqlalchemy.orm import Session + +import followers.schema as followers_schema +import followers.crud as followers_crud + +import users.dependencies as users_dependencies + +import session.security as session_security + +import database + +# Define the OAuth2 scheme for handling bearer tokens +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# Define the API router +router = APIRouter() + +# Define a loggger created on main.py +logger = logging.getLogger("myLogger") + + +@router.get( + "/followers/user/{user_id}/followers/all", + response_model=list[followers_schema.Follower] | None, + tags=["followers"], +) +async def get_user_follower_all( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followers + return followers_crud.get_all_following_by_user_id(user_id, db) + + +@router.get( + "/followers/user/{user_id}/followers/count/all", + response_model=int, + tags=["followers"], +) +async def get_user_follower_count_all( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followers + followers = followers_crud.get_all_followers_by_user_id(user_id, db) + + # Check if followers is None and return 0 if it is + if followers is None: + return 0 + + # Return the number of followers + return len(followers) + + +@router.get( + "/followers/user/{user_id}/followers/count/accepted", + response_model=int, + tags=["followers"], +) +async def get_user_follower_count( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followers + followers = followers_crud.get_accepted_followers_by_user_id(user_id, db) + + # Check if followers is None and return 0 if it is + if followers is None: + return 0 + + # Return the number of followers + return len(followers) + + +@router.get( + "/followers/user/{user_id}/following/all", + response_model=list[followers_schema.Follower] | None, + tags=["followers"], +) +async def get_user_following_all( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followings + return followers_crud.get_all_followers_by_user_id(user_id, db) + + +@router.get( + "/followers/user/{user_id}/following/count/all", + response_model=int, + tags=["followers"], +) +async def get_user_following_count_all( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followings + followings = followers_crud.get_all_following_by_user_id(user_id, db) + + # Check if followings is None and return 0 if it is + if followings is None: + return 0 + + # Return the number of followings + return len(followings) + + +@router.get( + "/followers/user/{user_id}/following/count/accepted", + response_model=int, + tags=["followers"], +) +async def get_user_following_count( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return followings + followings = followers_crud.get_accepted_following_by_user_id(user_id, db) + + # Check if followings is None and return 0 if it is + if followings is None: + return 0 + + # Return the number of followings + return len(followings) + + +@router.get( + "/followers/user/{user_id}/targetUser/{target_user_id}", + response_model=followers_schema.Follower | None, + tags=["followers"], +) +async def read_followers_user_specific_user( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + target_user_id: int, + validate_target_user_id: Annotated[ + Callable, Depends(users_dependencies.validate_target_user_id) + ], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return the follower + return followers_crud.get_follower_for_user_id_and_target_user_id( + user_id, target_user_id, db + ) + + +@router.post( + "/followers/create/user/{user_id}/targetUser/{target_user_id}", + status_code=201, + tags=["followers"], +) +async def create_follow( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + target_user_id: int, + validate_target_user_id: Annotated[ + Callable, Depends(users_dependencies.validate_target_user_id) + ], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Create the follower + #new_follow = followers_crud.create_follower(user_id, target_user_id, db) + + # Return the ID of the gear created + return {"detail": "Follower record created successfully"} + + +@router.put("/followers/accept/user/{user_id}/targetUser/{target_user_id}", + tags=["followers"], +) +async def accept_follow( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + target_user_id: int, + validate_target_user_id: Annotated[ + Callable, Depends(users_dependencies.validate_target_user_id) + ], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Accept the follower + followers_crud.accept_follower(user_id, target_user_id, db) + + # Return success message + return {"detail": "Follower record accepted successfully"} + + +@router.delete( + "/followers/delete/user/{user_id}/targetUser/{target_user_id}", + tags=["followers"], +) +async def delete_follow( + user_id: int, + validate_user_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], + target_user_id: int, + validate_target_user_id: Annotated[ + Callable, Depends(users_dependencies.validate_target_user_id) + ], + validate_token: Annotated[Callable, Depends(session_security.validate_token_expiration)], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Delete the follower + followers_crud.delete_follower(user_id, target_user_id, db) + + # Return success message + return {"detail": "Follower record deleted successfully"} diff --git a/backend/schemas/schema_followers.py b/backend/app/followers/schema.py similarity index 82% rename from backend/schemas/schema_followers.py rename to backend/app/followers/schema.py index 664bc5dcc..f7127de1e 100644 --- a/backend/schemas/schema_followers.py +++ b/backend/app/followers/schema.py @@ -7,4 +7,4 @@ class Follower(BaseModel): is_accepted: bool class Config: - orm_mode = True \ No newline at end of file + from_attributes = True \ No newline at end of file diff --git a/backend/uploads/__init__.py b/backend/app/gears/__init__.py similarity index 100% rename from backend/uploads/__init__.py rename to backend/app/gears/__init__.py diff --git a/backend/crud/crud_gear.py b/backend/app/gears/crud.py similarity index 88% rename from backend/crud/crud_gear.py rename to backend/app/gears/crud.py index bbe012614..4da7f3274 100644 --- a/backend/crud/crud_gear.py +++ b/backend/app/gears/crud.py @@ -1,19 +1,21 @@ import logging from fastapi import HTTPException, status -from sqlalchemy import func from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError from urllib.parse import unquote import models -from schemas import schema_gear + +import gears.schema as gears_schema +import gears.utils as gears_utils + # Define a loggger created on main.py logger = logging.getLogger("myLogger") -def get_gear_user_by_id(user_id: int, gear_id: int, db: Session) -> schema_gear.Gear | None: +def get_gear_user_by_id(user_id: int, gear_id: int, db: Session) -> gears_schema.Gear | None: try: gear = ( db.query(models.Gear) @@ -41,7 +43,7 @@ def get_gear_user_by_id(user_id: int, gear_id: int, db: Session) -> schema_gear. def get_gear_users_with_pagination( user_id: int, db: Session, page_number: int = 1, num_records: int = 5 -) -> list[schema_gear.Gear] | None: +) -> list[gears_schema.Gear] | None: try: # Get the gear by user ID from the database gear = ( @@ -73,7 +75,7 @@ def get_gear_users_with_pagination( ) from err -def get_gear_user(user_id: int, db: Session) -> list[schema_gear.Gear] | None: +def get_gear_user(user_id: int, db: Session) -> list[gears_schema.Gear] | None: try: # Get the gear by user ID from the database gears = db.query(models.Gear).filter(models.Gear.user_id == user_id).all() @@ -98,7 +100,7 @@ def get_gear_user(user_id: int, db: Session) -> list[schema_gear.Gear] | None: ) from err -def get_gear_user_by_nickname(user_id: int, nickname: str, db: Session) -> list[schema_gear.Gear] | None: +def get_gear_user_by_nickname(user_id: int, nickname: str, db: Session) -> list[gears_schema.Gear] | None: try: # Unquote the nickname and change "+" to whitespace parsed_nickname = unquote(nickname).replace("+", " ") @@ -166,7 +168,7 @@ def get_gear_by_type_and_user(gear_type: int, user_id: int, db: Session): def get_gear_by_strava_id_from_user_id( gear_strava_id: str, user_id: int, db: Session -) -> schema_gear.Gear | None: +) -> gears_schema.Gear | None: try: # Get the gear from the database gear = ( @@ -197,43 +199,14 @@ def get_gear_by_strava_id_from_user_id( ) from err -def transform_schema_gear_to_model_gear( - gear: schema_gear.Gear, user_id: int -) -> models.Gear: - # Set the created date to now - created_date = func.now() - - # If the created_at date is not None, set it to the created_date - if gear.created_at is not None: - created_date = gear.created_at - - # Create a new gear object - new_gear = models.Gear( - brand=( - unquote(gear.brand).replace("+", " ") if gear.brand is not None else None - ), - model=( - unquote(gear.model).replace("+", " ") if gear.model is not None else None - ), - nickname=unquote(gear.nickname).replace("+", " "), - gear_type=gear.gear_type, - user_id=user_id, - created_at=created_date, - is_active=True, - strava_gear_id=gear.strava_gear_id, - ) - - return new_gear - - -def create_multiple_gears(gears: [schema_gear.Gear], user_id: int, db: Session): +def create_multiple_gears(gears: list[gears_schema.Gear], user_id: int, db: Session): try: # Filter out None values from the gears list valid_gears = [gear for gear in gears if gear is not None] # Create a list of gear objects new_gears = [ - transform_schema_gear_to_model_gear(gear, user_id) for gear in valid_gears + gears_utils.transform_schema_gear_to_model_gear(gear, user_id) for gear in valid_gears ] # Add the gears to the database @@ -264,9 +237,9 @@ def create_multiple_gears(gears: [schema_gear.Gear], user_id: int, db: Session): ) from err -def create_gear(gear: schema_gear.Gear, user_id: int, db: Session): +def create_gear(gear: gears_schema.Gear, user_id: int, db: Session): try: - new_gear = transform_schema_gear_to_model_gear(gear, user_id) + new_gear = gears_utils.transform_schema_gear_to_model_gear(gear, user_id) # Add the gear to the database db.add(new_gear) @@ -299,7 +272,7 @@ def create_gear(gear: schema_gear.Gear, user_id: int, db: Session): ) from err -def edit_gear(gear_id: int, gear: schema_gear.Gear, db: Session): +def edit_gear(gear_id: int, gear: gears_schema.Gear, db: Session): try: # Get the gear from the database db_gear = db.query(models.Gear).filter(models.Gear.id == gear_id).first() @@ -370,7 +343,7 @@ def delete_all_strava_gear_for_user(user_id: int, db: Session): # Delete the gear records with strava_gear_id not null for the user num_deleted = ( db.query(models.Gear) - .filter(models.Gear.user_id == user_id, models.Gear.strava_gear_id != None) + .filter(models.Gear.user_id == user_id, models.Gear.strava_gear_id is not None) .delete() ) diff --git a/backend/dependencies/dependencies_gear.py b/backend/app/gears/dependencies.py similarity index 100% rename from backend/dependencies/dependencies_gear.py rename to backend/app/gears/dependencies.py diff --git a/backend/app/gears/router.py b/backend/app/gears/router.py new file mode 100644 index 000000000..3387f9a59 --- /dev/null +++ b/backend/app/gears/router.py @@ -0,0 +1,227 @@ +import logging + +from typing import Annotated, Callable + +from fastapi import APIRouter, Depends, HTTPException, status, Security +from sqlalchemy.orm import Session + +import session.security as session_security + +import gears.schema as gears_schema +import gears.crud as gears_crud +import gears.dependencies as gears_dependencies + +import database + +from dependencies import ( + dependencies_session, + dependencies_global, +) + +# Define the API router +router = APIRouter() + +# Define a loggger created on main.py +logger = logging.getLogger("myLogger") + + +@router.get( + "/id/{gear_id}", + response_model=gears_schema.Gear | None, +) +async def read_gear_id( + gear_id: int, + validate_gear_id: Annotated[Callable, Depends(gears_dependencies.validate_gear_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:read"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[Session, Depends(database.get_db)], +): + # Return the gear + return gears_crud.get_gear_user_by_id(token_user_id, gear_id, db) + + +@router.get( + "/page_number/{page_number}/num_records/{num_records}", + response_model=list[gears_schema.Gear] | None, +) +async def read_gear_user_pagination( + page_number: int, + num_records: int, + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:read"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return the gear + return gears_crud.get_gear_users_with_pagination( + token_user_id, db, page_number, num_records + ) + + +@router.get( + "/number", + response_model=int, +) +async def read_gear_user_number( + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:read"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Get the gear + gear = gears_crud.get_gear_user(token_user_id, db) + + # Check if gear is None and return 0 if it is + if gear is None: + return 0 + + # Return the number of gears + return len(gear) + + +@router.get( + "/nickname/{nickname}", + response_model=list[gears_schema.Gear] | None, +) +async def read_gear_user_by_nickname( + nickname: str, + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:read"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return the gear + return gears_crud.get_gear_user_by_nickname(token_user_id, nickname, db) + + +@router.get( + "/type/{gear_type}", + response_model=list[gears_schema.Gear] | None, +) +async def read_gear_user_by_type( + gear_type: int, + validate_type: Annotated[Callable, Depends(gears_dependencies.validate_gear_type)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:read"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Return the gear + return gears_crud.get_gear_by_type_and_user(gear_type, token_user_id, db) + + +@router.post( + "/create", + status_code=201, +) +async def create_gear( + gear: gears_schema.Gear, + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:write"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Create the gear + gear_created = gears_crud.create_gear(gear, token_user_id, db) + + # Return the ID of the gear created + return gear_created.id + + +@router.put("/{gear_id}/edit") +async def edit_gear( + gear_id: int, + validate_id: Annotated[Callable, Depends(gears_dependencies.validate_gear_id)], + gear: gears_schema.Gear, + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:write"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Get the gear by id + gear_db = gears_crud.get_gear_user_by_id(token_user_id, gear_id, db) + + # Check if gear is None and raise an HTTPException if it is + if gear_db is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Gear ID {gear_id} for user {token_user_id} not found", + ) + + # Edit the gear + gears_crud.edit_gear(gear_id, gear, db) + + # Return success message + return {"detail": f"Gear ID {gear_id} edited successfully"} + + +@router.delete("/{gear_id}/delete", tags=["gear"]) +async def delete_user( + gear_id: int, + validate_id: Annotated[Callable, Depends(gears_dependencies.validate_gear_id)], + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["gears:write"]) + ], + token_user_id: Annotated[ + int, Depends(session_security.get_user_id_from_access_token) + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # Get the gear by id + gear = gears_crud.get_gear_user_by_id(token_user_id, gear_id, db) + + # Check if gear is None and raise an HTTPException if it is + if gear is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Gear ID {gear_id} for user {token_user_id} not found", + ) + + # Delete the gear + gears_crud.delete_gear(gear_id, db) + + # Return success message + return {"detail": f"Gear ID {gear_id} deleted successfully"} \ No newline at end of file diff --git a/backend/schemas/schema_gear.py b/backend/app/gears/schema.py similarity index 100% rename from backend/schemas/schema_gear.py rename to backend/app/gears/schema.py diff --git a/backend/app/gears/utils.py b/backend/app/gears/utils.py new file mode 100644 index 000000000..4db4e3239 --- /dev/null +++ b/backend/app/gears/utils.py @@ -0,0 +1,35 @@ +from sqlalchemy import func +from urllib.parse import unquote + +import models + +import gears.schema as gear_schema + + +def transform_schema_gear_to_model_gear( + gear: gear_schema.Gear, user_id: int +) -> models.Gear: + # Set the created date to now + created_date = func.now() + + # If the created_at date is not None, set it to the created_date + if gear.created_at is not None: + created_date = gear.created_at + + # Create a new gear object + new_gear = models.Gear( + brand=( + unquote(gear.brand).replace("+", " ") if gear.brand is not None else None + ), + model=( + unquote(gear.model).replace("+", " ") if gear.model is not None else None + ), + nickname=unquote(gear.nickname).replace("+", " "), + gear_type=gear.gear_type, + user_id=user_id, + created_at=created_date, + is_active=True, + strava_gear_id=gear.strava_gear_id, + ) + + return new_gear diff --git a/backend/user_images/__init__.py b/backend/app/gpx/__init__.py similarity index 100% rename from backend/user_images/__init__.py rename to backend/app/gpx/__init__.py diff --git a/backend/processors/gpx_processor.py b/backend/app/gpx/utils.py similarity index 92% rename from backend/processors/gpx_processor.py rename to backend/app/gpx/utils.py index 3b6a88409..2bbe2a153 100644 --- a/backend/processors/gpx_processor.py +++ b/backend/app/gpx/utils.py @@ -4,8 +4,11 @@ import logging from fastapi import HTTPException, status -from processors import activity_processor -from schemas import schema_activities, schema_activity_streams +import activities.utils as activities_utils +import activities.schema as activities_schema + +import activity_streams.schema as activity_streams_schema + # Define a loggger created on main.py logger = logging.getLogger("myLogger") @@ -37,7 +40,7 @@ def parse_activity_streams_from_gpx_file(parsed_info: dict, activity_id: int): # Return activity streams as a list of ActivityStreams objects return [ - schema_activity_streams.ActivityStreams( + activity_streams_schema.ActivityStreams( activity_id=activity_id, stream_type=stream_type, stream_waypoints=waypoints, @@ -108,7 +111,7 @@ def parse_gpx_file(file: str, user_id: int) -> dict: # Calculate distance between waypoints if prev_latitude is not None and prev_longitude is not None: - distance += activity_processor.calculate_distance( + distance += activities_utils.calculate_distance( prev_latitude, prev_longitude, latitude, longitude ) @@ -123,7 +126,7 @@ def parse_gpx_file(file: str, user_id: int) -> dict: if process_one_time_fields == 0: # Use geocoding API to get city, town, and country based on coordinates - location_data = activity_processor.location_based_on_coordinates( + location_data = activities_utils.location_based_on_coordinates( latitude, longitude ) city = location_data["city"] @@ -164,7 +167,7 @@ def parse_gpx_file(file: str, user_id: int) -> dict: is_power_set = True # Calculate instant speed, pace, and update waypoint arrays - instant_speed = activity_processor.calculate_instant_speed( + instant_speed = activities_utils.calculate_instant_speed( last_waypoint_time, time, latitude, @@ -233,25 +236,25 @@ def parse_gpx_file(file: str, user_id: int) -> dict: ) # Calculate elevation gain/loss, pace, average speed, and average power - elevation_data = activity_processor.calculate_elevation_gain_loss(ele_waypoints) + elevation_data = activities_utils.calculate_elevation_gain_loss(ele_waypoints) elevation_gain = elevation_data["elevation_gain"] elevation_loss = elevation_data["elevation_loss"] - pace = activity_processor.calculate_pace( + pace = activities_utils.calculate_pace( distance, first_waypoint_time, last_waypoint_time ) - average_speed = activity_processor.calculate_average_speed( + average_speed = activities_utils.calculate_average_speed( distance, first_waypoint_time, last_waypoint_time ) - average_power = activity_processor.calculate_average_power(power_waypoints) + average_power = activities_utils.calculate_average_power(power_waypoints) # Create an Activity object with parsed data - activity = schema_activities.Activity( + activity = activities_schema.Activity( user_id=user_id, name=activity_name, distance=distance, - activity_type=activity_processor.define_activity_type(activity_type), + activity_type=activities_utils.define_activity_type(activity_type), start_time=first_waypoint_time.strftime("%Y-%m-%dT%H:%M:%S"), end_time=last_waypoint_time.strftime("%Y-%m-%dT%H:%M:%S"), city=city, diff --git a/backend/main.py b/backend/app/main.py similarity index 77% rename from backend/main.py rename to backend/app/main.py index 6b2ccf657..dbe099048 100644 --- a/backend/main.py +++ b/backend/app/main.py @@ -1,7 +1,7 @@ import logging import os -from fastapi import FastAPI +from fastapi import FastAPI, Request, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles @@ -17,16 +17,26 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +# from session import router as session_router +# from users import router as users_router +# from activities import router as activities_router +# from activity_streams import router as activity_streams_router +# from gears import router as gears_router +# from followers import router as followers_router + +import session.router as session_router +import session.security as session_security +import users.router as users_router +import activities.router as activities_router +import activity_streams.router as activity_streams_router +import gears.router as gears_router +import followers.router as followers_router + from routers import ( - router_session, - router_users, - router_activities, - router_activity_streams, - router_gear, - router_followers, router_strava, ) -from constants import API_VERSION + +from config import API_VERSION from database import SessionLocal from processors import strava_processor, strava_activity_processor @@ -162,13 +172,36 @@ app.add_middleware( ) # Router files -app.include_router(router_session.router) -app.include_router(router_users.router) -app.include_router(router_activities.router) -app.include_router(router_activity_streams.router) -app.include_router(router_gear.router) -app.include_router(router_followers.router) -app.include_router(router_strava.router) +app.include_router( + session_router.router, + tags=["session"], +) +app.include_router( + users_router.router, + prefix="/users", + tags=["users"], + dependencies=[Depends(session_security.validate_access_token)], +) +app.include_router( + activities_router.router, + prefix="/activities", + tags=["activities"], + dependencies=[Depends(session_security.validate_access_token)], +) +app.include_router( + activity_streams_router.router, + prefix="/activities/streams", + tags=["activity_streams"], + dependencies=[Depends(session_security.validate_access_token)], +) +app.include_router( + gears_router.router, + prefix="/gears", + tags=["gears"], + dependencies=[Depends(session_security.validate_access_token)], +) +app.include_router(followers_router.router, tags=["followers"]) +app.include_router(router_strava.router, tags=["strava"]) # Check if Jaeger tracing is enabled using the 'JAEGER_ENABLED' environment variable if os.environ.get("JAEGER_ENABLED") == "true": diff --git a/backend/models.py b/backend/app/models.py similarity index 99% rename from backend/models.py rename to backend/app/models.py index d783c246a..409ac1c26 100644 --- a/backend/models.py +++ b/backend/app/models.py @@ -120,6 +120,7 @@ class User(Base): cascade="all, delete-orphan", foreign_keys=[Follower.following_id], ) + # Establish a one-to-many relationship between User and Followers following = relationship( "Follower", diff --git a/backend/app/processors/__init__.py b/backend/app/processors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/processors/fit_processor.py b/backend/app/processors/fit_processor.py similarity index 100% rename from backend/processors/fit_processor.py rename to backend/app/processors/fit_processor.py diff --git a/backend/processors/strava_activity_processor.py b/backend/app/processors/strava_activity_processor.py similarity index 87% rename from backend/processors/strava_activity_processor.py rename to backend/app/processors/strava_activity_processor.py index 2768532bc..af0c7daef 100644 --- a/backend/processors/strava_activity_processor.py +++ b/backend/app/processors/strava_activity_processor.py @@ -5,15 +5,21 @@ from sqlalchemy.orm import Session from stravalib.client import Client from pint import Quantity -from schemas import schema_activities, schema_activity_streams, schema_user_integrations -from crud import ( - crud_activities, - crud_activity_streams, - crud_users, - crud_gear, -) +import activities.schema as activities_schema +import activities.crud as activities_crud +import activities.utils as activities_utils + +import activity_streams.schema as activity_streams_schema +import activity_streams.crud as activity_streams_crud + +import user_integrations.schema as user_integrations_schema + +import users.crud as users_crud + +import gears.crud as gears_crud + from database import SessionLocal -from processors import activity_processor, strava_processor +from processors import strava_processor # Define a loggger created on main.py logger = logging.getLogger("myLogger") @@ -23,7 +29,7 @@ def fetch_and_process_activities( strava_client: Client, start_date: datetime, user_id: int, - user_integrations: schema_user_integrations.UserIntegrations, + user_integrations: user_integrations_schema.UserIntegrations, db: Session, ) -> int: # Fetch Strava activities after the specified start date @@ -46,7 +52,13 @@ def fetch_and_process_activities( return len(strava_activities) -def parse_activity(activity, user_id: int, strava_client: Client, user_integrations: schema_user_integrations.UserIntegrations, db: Session) -> dict: +def parse_activity( + activity, + user_id: int, + strava_client: Client, + user_integrations: user_integrations_schema.UserIntegrations, + db: Session, +) -> dict: # Parse start and end dates start_date_parsed = activity.start_date @@ -69,7 +81,7 @@ def parse_activity(activity, user_id: int, strava_client: Client, user_integrati # Initialize location variables city, town, country = None, None, None - parsed_location = activity_processor.location_based_on_coordinates( + parsed_location = activities_utils.location_based_on_coordinates( latitude, longitude ) @@ -206,14 +218,16 @@ def parse_activity(activity, user_id: int, strava_client: Client, user_integrati if user_integrations.strava_sync_gear: # set the gear id for the activity - gear = crud_gear.get_gear_by_strava_id_from_user_id(activity.gear_id, user_id, db) + gear = gears_crud.get_gear_by_strava_id_from_user_id( + activity.gear_id, user_id, db + ) # set the gear id for the activity if gear is not None: gear_id = gear.id # Create the activity object - activity_to_store = schema_activities.Activity( + activity_to_store = activities_schema.Activity( user_id=user_id, name=activity.name, distance=( @@ -222,7 +236,7 @@ def parse_activity(activity, user_id: int, strava_client: Client, user_integrati else round(activity.distance) ), description=activity.description, - activity_type=activity_processor.define_activity_type(activity.sport_type), + activity_type=activities_utils.define_activity_type(activity.sport_type), start_time=start_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"), end_time=end_date_parsed.strftime("%Y-%m-%dT%H:%M:%S"), city=city, @@ -245,10 +259,10 @@ def parse_activity(activity, user_id: int, strava_client: Client, user_integrati def save_activity_and_streams( - activity: schema_activities.Activity, stream_data: list, db: Session + activity: activities_schema.Activity, stream_data: list, db: Session ): # Create the activity and get the ID - created_activity = crud_activities.create_activity(activity, db) + created_activity = activities_crud.create_activity(activity, db) # Create the empty array of activity streams activity_streams = [] @@ -257,7 +271,7 @@ def save_activity_and_streams( for is_set, stream_type, waypoints in stream_data: if is_set: activity_streams.append( - schema_activity_streams.ActivityStreams( + activity_streams_schema.ActivityStreams( activity_id=created_activity.id, stream_type=stream_type, stream_waypoints=waypoints, @@ -266,14 +280,14 @@ def save_activity_and_streams( ) # Create the activity streams in the database - crud_activity_streams.create_activity_streams(activity_streams, db) + activity_streams_crud.create_activity_streams(activity_streams, db) def process_activity( activity, user_id: int, strava_client: Client, - user_integrations: schema_user_integrations.UserIntegrations, + user_integrations: user_integrations_schema.UserIntegrations, db: Session, ): # Get the activity by Strava ID from the user @@ -287,7 +301,9 @@ def process_activity( logger.info(f"User {user_id}: Strava activity {activity.id} will be processed") # Parse the activity and streams - parsed_activity = parse_activity(activity, user_id, strava_client, user_integrations, db) + parsed_activity = parse_activity( + activity, user_id, strava_client, user_integrations, db + ) # Save the activity and streams to the database save_activity_and_streams( @@ -301,7 +317,7 @@ def retrieve_strava_users_activities_for_days(days: int): try: # Get all users - users = crud_users.get_all_users(db) + users = users_crud.get_all_users(db) finally: # Ensure the session is closed after use db.close() @@ -325,9 +341,7 @@ def get_user_strava_activities_by_days(start_date: datetime, user_id: int): ) if user_integrations is None: - logger.info( - f"User {user_id}: Strava not linked" - ) + logger.info(f"User {user_id}: Strava not linked") return None # Log the start of the activities processing diff --git a/backend/processors/strava_athlete_processor.py b/backend/app/processors/strava_athlete_processor.py similarity index 100% rename from backend/processors/strava_athlete_processor.py rename to backend/app/processors/strava_athlete_processor.py diff --git a/backend/processors/strava_gear_processor.py b/backend/app/processors/strava_gear_processor.py similarity index 82% rename from backend/processors/strava_gear_processor.py rename to backend/app/processors/strava_gear_processor.py index 76cc1ca6f..658ddcfbd 100644 --- a/backend/processors/strava_gear_processor.py +++ b/backend/app/processors/strava_gear_processor.py @@ -4,12 +4,14 @@ from fastapi import HTTPException, status from sqlalchemy.orm import Session from stravalib.client import Client -from schemas import schema_gear, schema_activities -from crud import ( - crud_user_integrations, - crud_gear, - crud_activities, -) +import gears.schema as gears_schema +import gears.crud as gears_crud + +import activities.schema as activities_schema +import activities.crud as activities_crud + +import user_integrations.crud as user_integrations_crud + from database import SessionLocal from processors import strava_processor, strava_athlete_processor @@ -62,9 +64,9 @@ def fetch_and_process_gear(strava_client: Client, user_id: int, db: Session) -> def process_gear( gear, type: str, user_id: int, strava_client: Client, db: Session -) -> schema_gear.Gear | None: +) -> gears_schema.Gear | None: # Get the gear by strava id from user id - gear_db = crud_gear.get_gear_by_strava_id_from_user_id(gear.id, user_id, db) + gear_db = gears_crud.get_gear_by_strava_id_from_user_id(gear.id, user_id, db) # Skip existing gear if gear_db: @@ -73,7 +75,7 @@ def process_gear( # Get the gear from Strava strava_gear = get_strava_gear(gear.id, strava_client) - new_gear = schema_gear.Gear( + new_gear = gears_schema.Gear( brand=strava_gear.brand_name, model=strava_gear.model_name, nickname=strava_gear.name, @@ -86,13 +88,17 @@ def process_gear( return new_gear -def save_gears(gears: [schema_gear.Gear], user_id: int, db: Session): +def save_gears(gears: [gears_schema.Gear], user_id: int, db: Session): # Save the gear to the database - crud_gear.create_multiple_gears(gears, user_id, db) + gears_crud.create_multiple_gears(gears, user_id, db) def iterate_over_activities_and_set_gear( - activity: schema_activities.Activity, gears: [schema_gear.Gear], counter: int, user_id: int, db: Session + activity: activities_schema.Activity, + gears: [gears_schema.Gear], + counter: int, + user_id: int, + db: Session, ) -> dict: # Iterate over gears and set gear if applicable @@ -109,14 +115,14 @@ def iterate_over_activities_and_set_gear( def set_activities_gear(user_id: int, db: Session) -> int: # Get user activities - activities = crud_activities.get_user_activities(user_id, db) + activities = activities_crud.get_user_activities(user_id, db) # Skip if no activities if activities is None: return 0 - + # Get user gears - gears = crud_gear.get_gear_user(user_id, db) + gears = gears_crud.get_gear_user(user_id, db) # Skip if no gears if gears is None: @@ -130,11 +136,13 @@ def set_activities_gear(user_id: int, db: Session) -> int: # iterate over activities and set gear if applicable for activity in activities: - parsed_activity = iterate_over_activities_and_set_gear(activity, gears, counter, user_id, db) + parsed_activity = iterate_over_activities_and_set_gear( + activity, gears, counter, user_id, db + ) counter = parsed_activity["counter"] activities_parsed.append(parsed_activity["activity"]) - crud_activities.edit_multiple_activities_gear_id(activities_parsed, user_id, db) + activities_crud.edit_multiple_activities_gear_id(activities_parsed, user_id, db) return counter @@ -156,7 +164,7 @@ def get_user_gear(user_id: int): strava_client = strava_processor.create_strava_client(user_integrations) # Set the user's gear to sync to True - crud_user_integrations.set_user_strava_sync_gear(user_id, True, db) + user_integrations_crud.set_user_strava_sync_gear(user_id, True, db) # Fetch Strava activities after the specified start date num_strava_gear_processed = fetch_and_process_gear(strava_client, user_id, db) diff --git a/backend/processors/strava_processor.py b/backend/app/processors/strava_processor.py similarity index 85% rename from backend/processors/strava_processor.py rename to backend/app/processors/strava_processor.py index 2f35de04e..c9bd15da2 100644 --- a/backend/processors/strava_processor.py +++ b/backend/app/processors/strava_processor.py @@ -7,8 +7,13 @@ from fastapi import HTTPException, status from sqlalchemy.orm import Session from stravalib.client import Client -from schemas import schema_activities, schema_user_integrations -from crud import crud_user_integrations, crud_activities, crud_users +import activities.schema as activities_schema +import activities.crud as activities_crud + +import user_integrations.schema as user_integrations_schema +import user_integrations.crud as user_integrations_crud + +import users.crud as users_crud # Define a loggger created on main.py logger = logging.getLogger("myLogger") @@ -16,12 +21,12 @@ logger = logging.getLogger("myLogger") def refresh_strava_tokens(db: Session): # Get all users - users = crud_users.get_all_users(db) + users = users_crud.get_all_users(db) # Iterate through all users for user in users: # Get the user integrations by user ID - user_integrations = crud_user_integrations.get_user_integrations_by_user_id( + user_integrations = user_integrations_crud.get_user_integrations_by_user_id( user.id, db ) @@ -65,7 +70,7 @@ def refresh_strava_tokens(db: Session): ) from err finally: # Update the user integrations with the tokens - crud_user_integrations.link_strava_account( + user_integrations_crud.link_strava_account( user_integrations, tokens, db ) @@ -77,9 +82,9 @@ def refresh_strava_tokens(db: Session): def fetch_and_validate_activity( activity_id: int, user_id: int, db: Session -) -> schema_activities.Activity | None: +) -> activities_schema.Activity | None: # Get the activity by Strava ID from the user - activity_db = crud_activities.get_activity_by_strava_id_from_user_id( + activity_db = activities_crud.get_activity_by_strava_id_from_user_id( activity_id, user_id, db ) @@ -98,9 +103,9 @@ def fetch_and_validate_activity( def fetch_user_integrations_and_validate_token( user_id: int, db: Session -) -> schema_user_integrations.UserIntegrations | None: +) -> user_integrations_schema.UserIntegrations | None: # Get the user integrations by user ID - user_integrations = crud_user_integrations.get_user_integrations_by_user_id( + user_integrations = user_integrations_crud.get_user_integrations_by_user_id( user_id, db ) @@ -120,7 +125,7 @@ def fetch_user_integrations_and_validate_token( def create_strava_client( - user_integrations: schema_user_integrations.UserIntegrations, + user_integrations: user_integrations_schema.UserIntegrations, ) -> Client: # Create a Strava client with the user's access token and return it return Client(access_token=user_integrations.strava_token) diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/routers/router_strava.py b/backend/app/routers/router_strava.py similarity index 81% rename from backend/routers/router_strava.py rename to backend/app/routers/router_strava.py index 77b1770fa..bf727942f 100644 --- a/backend/routers/router_strava.py +++ b/backend/app/routers/router_strava.py @@ -9,13 +9,19 @@ from fastapi.security import OAuth2PasswordBearer from fastapi.responses import RedirectResponse from sqlalchemy.orm import Session -from crud import crud_user_integrations, crud_gear, crud_activities +import user_integrations.crud as user_integrations_crud + +import gears.crud as gears_crud + +import activities.crud as activities_crud + from processors import strava_activity_processor, strava_gear_processor from dependencies import ( - dependencies_database, dependencies_session, ) +import database + # Define the OAuth2 scheme for handling bearer tokens oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") @@ -33,7 +39,7 @@ logger = logging.getLogger("myLogger") async def strava_link( state: str, code: str, - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): # Define the token URL token_url = "https://www.strava.com/oauth/token" @@ -63,7 +69,7 @@ async def strava_link( # Get the user integrations by the state user_integrations = ( - crud_user_integrations.get_user_integrations_by_strava_state(state, db) + user_integrations_crud.get_user_integrations_by_strava_state(state, db) ) # Check if user integrations is None @@ -74,7 +80,7 @@ async def strava_link( ) # Update the user integrations with the tokens - crud_user_integrations.link_strava_account(user_integrations, tokens, db) + user_integrations_crud.link_strava_account(user_integrations, tokens, db) # Redirect to the main page or any other desired page after processing redirect_url = ( @@ -107,7 +113,7 @@ async def strava_retrieve_activities_days( dependencies_session.validate_access_token_and_get_authenticated_user_id ), ], - # db: Annotated[Session, Depends(dependencies_database.get_db)], + # db: Annotated[Session, Depends(database.get_db)], background_tasks: BackgroundTasks, ): # Process strava activities in the background @@ -138,10 +144,10 @@ async def strava_set_user_unique_state( dependencies_session.validate_access_token_and_get_authenticated_user_id ), ], - db: Annotated[Session, Depends(dependencies_database.get_db)], + db: Annotated[Session, Depends(database.get_db)], ): # Set the user Strava state - crud_user_integrations.set_user_strava_state(user_id, state, db) + user_integrations_crud.set_user_strava_state(user_id, state, db) # Return success message return {"detail": f"Strava state for user {user_id} edited successfully"} @@ -153,12 +159,15 @@ async def strava_set_user_unique_state( ) async def strava_unset_user_unique_state( user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + dependencies_session.validate_access_token_and_get_authenticated_user_id + ), ], - db: Annotated[Session, Depends(dependencies_database.get_db)], + db: Annotated[Session, Depends(database.get_db)], ): # Set the user Strava state - crud_user_integrations.set_user_strava_state(user_id, None, db) + user_integrations_crud.set_user_strava_state(user_id, None, db) # Return success message return {"detail": f"Strava state for user {user_id} removed successfully"} @@ -167,18 +176,21 @@ async def strava_unset_user_unique_state( @router.delete("/strava/unlink", tags=["strava"]) async def strava_unlink( token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + dependencies_session.validate_access_token_and_get_authenticated_user_id + ), ], - db: Session = Depends(dependencies_database.get_db), + db: Session = Depends(database.get_db), ): # delete all strava gear for user - crud_gear.delete_all_strava_gear_for_user(token_user_id, db) + gears_crud.delete_all_strava_gear_for_user(token_user_id, db) # delete all strava activities for user - crud_activities.delete_all_strava_activities_for_user(token_user_id, db) + activities_crud.delete_all_strava_activities_for_user(token_user_id, db) # unlink strava account - crud_user_integrations.unlink_strava_account(token_user_id, db) + user_integrations_crud.unlink_strava_account(token_user_id, db) # Return success message return {"detail": f"Strava unlinked for user {token_user_id} successfully"} @@ -187,7 +199,10 @@ async def strava_unlink( @router.get("/strava/gear", status_code=202, tags=["strava"]) async def strava_retrieve_gear( token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) + int, + Depends( + dependencies_session.validate_access_token_and_get_authenticated_user_id + ), ], background_tasks: BackgroundTasks, ): diff --git a/backend/app/session/__init__.py b/backend/app/session/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/constants.py b/backend/app/session/constants.py similarity index 60% rename from backend/constants.py rename to backend/app/session/constants.py index 3617be5fb..b83545e66 100644 --- a/backend/constants.py +++ b/backend/app/session/constants.py @@ -1,8 +1,5 @@ import os -# Constant related to version -API_VERSION = "v0.2.1" - # JWT Token constants JWT_ALGORITHM = os.environ.get("ALGORITHM") JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES")) @@ -10,28 +7,26 @@ JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS")) JWT_SECRET_KEY = os.environ.get("SECRET_KEY") # Scopes definition -USERS_ADMIN_SCOPES = ["users:read", "users:edit", "users:write"] -USERS_REGULAR_SCOPES = ["users:read", "users:edit"] -GEARS_SCOPES = ["gears:read", "gears:edit", "gears:write"] -ACTIVITIES_SCOPES = ["activities:read", "activities:edit", "activities:write"] +USERS_REGULAR_SCOPES = ["profile", "users:read"] +USERS_ADMIN_SCOPES = ["users:write"] +GEARS_SCOPES = ["gears:read", "gears:write"] +ACTIVITIES_SCOPES = ["activities:read", "activities:write"] SCOPES_DICT = { + "profile": "Privileges over user's own profile", "users:read": "Read privileges over users", "users:write": "Create privileges over users", - "users:edit": "Edit privileges over users", "gears:read": "Read privileges over gears", "gears:write": "Create privileges over gears", - "gears:edit": "Edit privileges over gears", "activities:read": "Read privileges over activities", "activities:write": "Create privileges over activities", - "activities:edit": "Edit privileges over activities", } # Constants related to user access types REGULAR_ACCESS = 1 REGULAR_ACCESS_SCOPES = USERS_REGULAR_SCOPES + GEARS_SCOPES + ACTIVITIES_SCOPES ADMIN_ACCESS = 2 -ADMIN_ACCESS_SCOPES = USERS_ADMIN_SCOPES + GEARS_SCOPES + ACTIVITIES_SCOPES +ADMIN_ACCESS_SCOPES = USERS_REGULAR_SCOPES + USERS_ADMIN_SCOPES + GEARS_SCOPES + ACTIVITIES_SCOPES # Constants related to user active status USER_ACTIVE = 1 -USER_NOT_ACTIVE = 2 +USER_NOT_ACTIVE = 2 \ No newline at end of file diff --git a/backend/dependencies/dependencies_security.py b/backend/app/session/dependencies_security.py similarity index 61% rename from backend/dependencies/dependencies_security.py rename to backend/app/session/dependencies_security.py index 85751e5ab..070d460c3 100644 --- a/backend/dependencies/dependencies_security.py +++ b/backend/app/session/dependencies_security.py @@ -9,11 +9,14 @@ from fastapi.security import OAuth2PasswordBearer from joserfc import jwt from joserfc.jwk import OctKey -from constants import ( - JWT_ALGORITHM, - JWT_SECRET_KEY, - ADMIN_ACCESS, -) +import session.security as session_security +import session.constants as session_constants + +# from constants import ( +# JWT_ALGORITHM, +# JWT_SECRET_KEY, +# ADMIN_ACCESS, +# ) # Define the OAuth2 scheme for handling bearer tokens @@ -23,49 +26,9 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") logger = logging.getLogger("myLogger") -def decode_token(token: Annotated[str, Depends(oauth2_scheme)]): - try: - # Decode the token and return the payload - return jwt.decode(token, OctKey.import_key(JWT_SECRET_KEY)) - except Exception: - # Log the error and raise the exception - logger.info("Unable to decode token | Returning 401 response") - - # Raise an HTTPException with a 401 Unauthorized status code - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Unable to decode token", - headers={"WWW-Authenticate": "Bearer"}, - ) - - -def validate_token_expiration(token: Annotated[str, Depends(oauth2_scheme)]): - # Try to decode the token and check if it is expired - try: - # Decode the token - # Mark exp claim as required - claims_requests = jwt.JWTClaimsRegistry(exp={"essential": True}) - - # decodes the token - payload = decode_token(token) - - # Validate token exp - claims_requests.validate(payload.claims) - except Exception: - # Log the error and raise the exception - logger.info("Token expired during validation | Returning 401 response") - - # Raise an HTTPException with a 401 Unauthorized status code - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token no longer valid", - headers={"WWW-Authenticate": "Bearer"}, - ) - - def get_token_scopes(token: Annotated[str, Depends(oauth2_scheme)]): # Decode the token - payload = decode_token(token) + payload = session_security.decode_token(token) try: # Get the scopes from the payload and return it @@ -84,7 +47,7 @@ def get_token_scopes(token: Annotated[str, Depends(oauth2_scheme)]): def get_token_user_id(token: Annotated[str, Depends(oauth2_scheme)]): # Decode the token - payload = decode_token(token) + payload = session_security.decode_token(token) try: # Get the user id from the payload and return it @@ -103,7 +66,7 @@ def get_token_user_id(token: Annotated[str, Depends(oauth2_scheme)]): def get_token_access_type(token: Annotated[str, Depends(oauth2_scheme)]): # Decode the token - payload = decode_token(token) + payload = session_security.decode_token(token) try: # Get the user access_type from the payload and return it @@ -123,7 +86,7 @@ def get_token_access_type(token: Annotated[str, Depends(oauth2_scheme)]): def validate_token_admin_access(token: Annotated[str, Depends(oauth2_scheme)]): - if get_token_access_type(token) != ADMIN_ACCESS: + if get_token_access_type(token) != session_constants.ADMIN_ACCESS: # Raise an HTTPException with a 403 Forbidden status code raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -133,4 +96,8 @@ def validate_token_admin_access(token: Annotated[str, Depends(oauth2_scheme)]): def create_token(data: dict): # Encode the data and return the token - return jwt.encode({"alg": JWT_ALGORITHM}, data.copy(), JWT_SECRET_KEY) \ No newline at end of file + return jwt.encode( + {"alg": session_constants.JWT_ALGORITHM}, + data.copy(), + session_constants.JWT_SECRET_KEY, + ) diff --git a/backend/app/session/router.py b/backend/app/session/router.py new file mode 100644 index 000000000..8271e026c --- /dev/null +++ b/backend/app/session/router.py @@ -0,0 +1,93 @@ +import logging + +from typing import Annotated + +from fastapi import ( + APIRouter, + Depends, + HTTPException, + status, + Response, +) +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session + +import session.utils as session_utils +import session.security as session_security +import session.constants as session_constants + +import users.crud as users_crud + +import database + +# from constants import ( +# USER_NOT_ACTIVE, +# ) + +# Define the API router +router = APIRouter() + +# Define a loggger created on main.py +logger = logging.getLogger("myLogger") + + +@router.post("/token") +async def login_for_access_token( + response: Response, + form_data: Annotated[OAuth2PasswordRequestForm, Depends()], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + user = session_utils.authenticate_user(form_data.username, form_data.password, db) + + if user.is_active == session_constants.USER_NOT_ACTIVE: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Inactive user", + headers={"WWW-Authenticate": "Bearer"}, + ) + + response = session_utils.create_response_with_tokens(response, user) + + return {"message": "Login successful"} + + +@router.post("/refresh") +async def refresh_token( + response: Response, + user_id: Annotated[ + int, + Depends(session_security.validate_refresh_token_and_get_authenticated_user_id), + ], + db: Annotated[ + Session, + Depends(database.get_db), + ], +): + # get user + user = users_crud.get_user_by_id(user_id, db) + + if user.is_active == session_constants.USER_NOT_ACTIVE: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Inactive user", + headers={"WWW-Authenticate": "Bearer"}, + ) + + response = session_utils.create_response_with_tokens(response, user) + + return {"message": "Token refreshed successfully"} + + +@router.post("/logout") +async def logout( + response: Response, +): + # Clear the cookies by setting their expiration to the past + response.delete_cookie(key="endurain_access_token", path="/") + response.delete_cookie(key="endurain_refresh_token", path="/") + # response.delete_cookie(key="ctr_csrf_token", path="/") + + return {"message": "Logout successful"} diff --git a/backend/app/session/security.py b/backend/app/session/security.py new file mode 100644 index 000000000..6fd3714e3 --- /dev/null +++ b/backend/app/session/security.py @@ -0,0 +1,210 @@ +import bcrypt +import logging + +from typing import Annotated, Callable +from fastapi import Depends, HTTPException, status, Request +from fastapi.security import OAuth2PasswordBearer, SecurityScopes + +# import the jwt module from the joserfc package +from joserfc import jwt +from joserfc.jwk import OctKey + +import session.constants as session_constants + +# from constants import ( +# JWT_ALGORITHM, +# JWT_SECRET_KEY, +# ADMIN_ACCESS, +# ) + +# Define the OAuth2 scheme for handling bearer tokens +oauth2_scheme = OAuth2PasswordBearer( + tokenUrl="token", + scopes=session_constants.SCOPES_DICT, +) + +# Define a loggger created on main.py +logger = logging.getLogger("myLogger") + + +def hash_password(password: str): + # Hash the password and return it + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) + + +def verify_password(plain_password: str, hashed_password: str): + # Check if the password is equal to the hashed password + return bcrypt.checkpw( + plain_password.encode("utf-8"), hashed_password.encode("utf-8") + ) + + +def decode_token(token: Annotated[str, Depends(oauth2_scheme)]): + try: + # Decode the token and return the payload + return jwt.decode(token, OctKey.import_key(session_constants.JWT_SECRET_KEY)) + except Exception: + # Log the error and raise the exception + logger.info("Unable to decode token | Returning 401 response") + + # Raise an HTTPException with a 401 Unauthorized status code + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Unable to decode token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + +def validate_token_expiration(token: Annotated[str, Depends(oauth2_scheme)]): + # Try to decode the token and check if it is expired + try: + # Decode the token + # Mark exp claim as required + claims_requests = jwt.JWTClaimsRegistry(exp={"essential": True}) + + # decodes the token + payload = decode_token(token) + + # Validate token exp + claims_requests.validate(payload.claims) + except Exception: + # Log the error and raise the exception + logger.info("Token expired during validation | Returning 401 response") + + # Raise an HTTPException with a 401 Unauthorized status code + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token no longer valid", + headers={"WWW-Authenticate": "Bearer"}, + ) + + +def get_token_user_id(token: Annotated[str, Depends(oauth2_scheme)]): + # Decode the token + payload = decode_token(token) + + try: + # Get the user id from the payload and return it + return payload.claims["sub"] + except Exception: + # Log the error and raise the exception + logger.info("Claim with user ID not present in token | Returning 401 response") + + # Raise an HTTPException with a 401 Unauthorized status code + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Claim with user ID not present in token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + +def get_token_scopes(token: Annotated[str, Depends(oauth2_scheme)]): + # Decode the token + payload = decode_token(token) + + try: + # Get the scopes from the payload and return it + return payload.claims["scopes"] + except Exception: + # Log the error and raise the exception + logger.info("Scopes not present in token | Returning 401 response") + + # Raise an HTTPException with a 401 Unauthorized status code + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Scopes not present in token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + +def create_token(data: dict): + # Encode the data and return the token + return jwt.encode( + {"alg": session_constants.JWT_ALGORITHM}, + data.copy(), + session_constants.JWT_SECRET_KEY, + ) + + +## ACCESS TOKEN VALIDATION +def get_access_token_from_cookies(request: Request): + # Extract the access token from the cookies + access_token = request.cookies.get("endurain_access_token") + + # Check if the token is missing + if not access_token: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Access token missing", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Return token + return access_token + + +def validate_access_token( + access_token: Annotated[str, Depends(get_access_token_from_cookies)] +): + # Validate the token expiration + validate_token_expiration(access_token) + + +def get_user_id_from_access_token( + access_token: Annotated[str, Depends(get_access_token_from_cookies)] +): + # Return the user ID associated with the token + return get_token_user_id(access_token) + + +def get_and_return_access_token( + access_token: Annotated[str, Depends(get_access_token_from_cookies)], +): + # Return token + return access_token + + +## REFRESH TOKEN VALIDATION +def validate_token_and_return_refresh_token(request: Request): + # Extract the refresh token from the cookies + refresh_token = request.cookies.get("endurain_refresh_token") + if not refresh_token: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Refresh token missing", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Validate the token expiration + validate_token_expiration(refresh_token) + + # Return token + return refresh_token + + +def validate_refresh_token_and_get_authenticated_user_id( + refresh_token: Annotated[str, Depends(validate_token_and_return_refresh_token)] +): + # Return the user ID associated with the token + return get_token_user_id(refresh_token) + + +def check_scopes( + access_token: Annotated[ + str, Depends(get_access_token_from_cookies) + ], + security_scopes: SecurityScopes, +): + # Get the scopes from the token + scopes = get_token_scopes(access_token) + + # Check if the token has the required scopes + for scope in security_scopes.scopes: + if scope not in scopes: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=f"Unauthorized Access - Not enough permissions - scope={security_scopes.scopes}", + headers={ + "WWW-Authenticate": f'Bearer scope="{security_scopes.scopes}"' + }, + ) diff --git a/backend/app/session/utils.py b/backend/app/session/utils.py new file mode 100644 index 000000000..782972371 --- /dev/null +++ b/backend/app/session/utils.py @@ -0,0 +1,104 @@ +from datetime import datetime, timedelta, timezone +from fastapi import ( + HTTPException, + status, + Response, +) + +from sqlalchemy.orm import Session + +import session.security as session_security +import session.constants as session_constants + +import users.crud as users_crud +import users.schema as users_schema + +# from constants import ( +# REGULAR_ACCESS, +# REGULAR_ACCESS_SCOPES, +# ADMIN_ACCESS_SCOPES, +# JWT_ACCESS_TOKEN_EXPIRE_MINUTES, +# JWT_REFRESH_TOKEN_EXPIRE_DAYS, +# ) + + +def authenticate_user(username: str, password: str, db: Session): + # Get the user from the database + user = users_crud.authenticate_user(username, db) + + # Check if the user exists and if the hashed_password is correct and if not return False + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username", + headers={"WWW-Authenticate": "Bearer"}, + ) + + if not session_security.verify_password(password, user.password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Return the user if the hashed_password is correct + return user + + +def create_response_with_tokens(response: Response, user: users_schema.User): + # Check user access level and set scopes accordingly + if user.access_type == session_constants.REGULAR_ACCESS: + scopes = session_constants.REGULAR_ACCESS_SCOPES + else: + scopes = session_constants.ADMIN_ACCESS_SCOPES + + # Create the access and refresh tokens + access_token = session_security.create_token( + data={ + "sub": user.id, + "scopes": scopes, + "exp": datetime.now(timezone.utc) + + timedelta(minutes=session_constants.JWT_ACCESS_TOKEN_EXPIRE_MINUTES), + }, + ) + + refresh_token = session_security.create_token( + data={ + "sub": user.id, + "scopes": "scopes", + "exp": datetime.now(timezone.utc) + + timedelta(days=session_constants.JWT_REFRESH_TOKEN_EXPIRE_DAYS), + }, + ) + + # Set the cookies with the tokens + response.set_cookie( + key="endurain_access_token", + value=access_token, + expires=datetime.now(timezone.utc) + + timedelta(minutes=session_constants.JWT_ACCESS_TOKEN_EXPIRE_MINUTES), + httponly=True, + path="/", + secure=False, + samesite="None", + ) + response.set_cookie( + key="endurain_refresh_token", + value=refresh_token, + expires=datetime.now(timezone.utc) + + timedelta(days=session_constants.JWT_REFRESH_TOKEN_EXPIRE_DAYS), + httponly=True, + path="/", + secure=False, + samesite="None", + ) + + # Set the user id in a cookie + # response.set_cookie( + # key="endurain_logged_user_id", + # value=user.id, + # httponly=False, + # ) + + # Return the response + return response diff --git a/backend/app/uploads/__init__.py b/backend/app/uploads/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/app/user_images/__init__.py b/backend/app/user_images/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/app/user_integrations/__init__.py b/backend/app/user_integrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/crud/crud_user_integrations.py b/backend/app/user_integrations/crud.py similarity index 95% rename from backend/crud/crud_user_integrations.py rename to backend/app/user_integrations/crud.py index 64f75f95c..b73926fce 100644 --- a/backend/crud/crud_user_integrations.py +++ b/backend/app/user_integrations/crud.py @@ -4,7 +4,7 @@ from fastapi import HTTPException, status from sqlalchemy.orm import Session from datetime import datetime -from schemas import schema_user_integrations +import user_integrations.schema as user_integrations_schema import models # Define a loggger created on main.py @@ -13,17 +13,22 @@ logger = logging.getLogger("myLogger") def get_user_integrations_by_user_id(user_id: int, db: Session): try: + # Get the user integrations by the user id user_integrations = ( db.query(models.UserIntegrations) .filter(models.UserIntegrations.user_id == user_id) .first() ) + + # Check if user_integrations is None and return None if it is if user_integrations is None: # If the user was not found, return a 404 Not Found error raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User integrations not found", ) + + # Return the user integrations return user_integrations except Exception as err: # Log the exception @@ -89,7 +94,7 @@ def create_user_integrations(user_id: int, db: Session): def link_strava_account( - user_integrations: schema_user_integrations.UserIntegrations, + user_integrations: user_integrations_schema.UserIntegrations, tokens: dict, db: Session, ): diff --git a/backend/schemas/schema_user_integrations.py b/backend/app/user_integrations/schema.py similarity index 90% rename from backend/schemas/schema_user_integrations.py rename to backend/app/user_integrations/schema.py index b52d8cd14..1004c1bb8 100644 --- a/backend/schemas/schema_user_integrations.py +++ b/backend/app/user_integrations/schema.py @@ -11,4 +11,4 @@ class UserIntegrations(BaseModel): strava_sync_gear: bool class Config: - orm_mode = True + from_attributes = True diff --git a/backend/app/users/__init__.py b/backend/app/users/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/crud/crud_users.py b/backend/app/users/crud.py similarity index 99% rename from backend/crud/crud_users.py rename to backend/app/users/crud.py index 8c9d31cef..ff766d9fe 100644 --- a/backend/crud/crud_users.py +++ b/backend/app/users/crud.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError from urllib.parse import unquote -from schemas import schema_users +import users.schema as users_schema import models # Define a loggger created on main.py @@ -272,7 +272,7 @@ def get_user_photo_path_aux_by_id(user_id: int, db: Session): ) from err -def create_user(user: schema_users.UserCreate, db: Session): +def create_user(user: users_schema.UserCreate, db: Session): try: # Create a new user db_user = models.User( @@ -320,7 +320,7 @@ def create_user(user: schema_users.UserCreate, db: Session): ) from err -def edit_user(user: schema_users.User, db: Session): +def edit_user(user: users_schema.User, db: Session): try: # Get the user from the database db_user = db.query(models.User).filter(models.User.id == user.id).first() diff --git a/backend/dependencies/dependencies_users.py b/backend/app/users/dependencies.py similarity index 100% rename from backend/dependencies/dependencies_users.py rename to backend/app/users/dependencies.py diff --git a/backend/routers/router_users.py b/backend/app/users/router.py similarity index 55% rename from backend/routers/router_users.py rename to backend/app/users/router.py index ea8c16282..0b5ebcf79 100644 --- a/backend/routers/router_users.py +++ b/backend/app/users/router.py @@ -4,24 +4,26 @@ import logging from typing import Annotated, Callable from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, Security -from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session import shutil -from schemas import schema_users -from crud import crud_user_integrations, crud_users +import users.schema as users_schema +import users.crud as users_crud +import users.dependencies as users_dependencies + +import user_integrations.crud as user_integrations_crud + +import session.security as session_security + +import database + from dependencies import ( - dependencies_database, dependencies_session, dependencies_global, - dependencies_users, - dependencies_security, + # dependencies_security, ) -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - # Define the API router router = APIRouter() @@ -29,24 +31,22 @@ router = APIRouter() logger = logging.getLogger("myLogger") -@router.get("/users/me", response_model=schema_users.UserMe, tags=["users"]) +@router.get("/me", response_model=users_schema.UserMe) async def read_users_me( + check_scopes: Annotated[ + Callable, Security(session_security.check_scopes, scopes=["profile"]) + ], token_user_id: Annotated[ int, - Depends( - dependencies_session.validate_access_token_and_get_authenticated_user_id - ), - ], - check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Depends(session_security.get_user_id_from_access_token), ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the user from the database - user = crud_users.get_user_by_id(token_user_id, db) + user = users_crud.get_user_by_id(token_user_id, db) # If the user does not exist raise the exception if user is None: @@ -56,7 +56,7 @@ async def read_users_me( headers={"WWW-Authenticate": "Bearer"}, ) - user_integrations = crud_user_integrations.get_user_integrations_by_user_id( + user_integrations = user_integrations_crud.get_user_integrations_by_user_id( user.id, db ) @@ -67,34 +67,32 @@ async def read_users_me( headers={"WWW-Authenticate": "Bearer"}, ) - if user_integrations.strava_token is None: - user.is_strava_linked = 0 - else: - user.is_strava_linked = 1 + user.is_strava_linked = 1 if user_integrations.strava_token else 0 # Return the user return user -@router.get("/users/number", response_model=int, tags=["users"]) +@router.get("/number", response_model=int) async def read_users_number( validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): - return crud_users.get_users_number(db) + return users_crud.get_users_number(db) @router.get( - "/users/all/page_number/{page_number}/num_records/{num_records}", - response_model=list[schema_users.User] | None, + "/all/page_number/{page_number}/num_records/{num_records}", + response_model=list[users_schema.User] | None, tags=["users"], ) async def read_users_all_pagination( @@ -104,148 +102,151 @@ async def read_users_all_pagination( Callable, Depends(dependencies_global.validate_pagination_values) ], validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the users from the database with pagination - return crud_users.get_users_with_pagination( + return users_crud.get_users_with_pagination( db=db, page_number=page_number, num_records=num_records ) @router.get( - "/users/username/contains/{username}", - response_model=list[schema_users.User] | None, + "/username/contains/{username}", + response_model=list[users_schema.User] | None, tags=["users"], ) async def read_users_contain_username( username: str, validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the users from the database by username - return crud_users.get_user_if_contains_username(username=username, db=db) + return users_crud.get_user_if_contains_username(username=username, db=db) @router.get( - "/users/username/{username}", - response_model=schema_users.User | None, + "/username/{username}", + response_model=users_schema.User | None, tags=["users"], ) async def read_users_username( username: str, validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the user from the database by username - return crud_users.get_user_by_username(username=username, db=db) + return users_crud.get_user_by_username(username=username, db=db) -@router.get("/users/id/{user_id}", response_model=schema_users.User, tags=["users"]) +@router.get("/id/{user_id}", response_model=users_schema.User) async def read_users_id( user_id: int, - validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_access_token: Annotated[ - Callable, Depends(dependencies_session.validate_access_token) - ], + validate_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the users from the database by id - return crud_users.get_user_by_id(user_id=user_id, db=db) + return users_crud.get_user_by_id(user_id=user_id, db=db) -@router.get("/users/{username}/id", response_model=int, tags=["users"]) +@router.get("/{username}/id", response_model=int) async def read_users_username_id( username: str, validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the users from the database by username - return crud_users.get_user_id_by_username(username, db) + return users_crud.get_user_id_by_username(username, db) -@router.get("/users/{user_id}/photo_path", response_model=str | None, tags=["users"]) +@router.get("/{user_id}/photo_path", response_model=str | None) async def read_users_id_photo_path( user_id: int, - validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:read"]) + Callable, Security(session_security.check_scopes, scopes=["users:read"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Get the photo_path from the database by id - return crud_users.get_user_photo_path_by_id(user_id, db) + return users_crud.get_user_photo_path_by_id(user_id, db) -@router.post("/users/create", response_model=int, status_code=201, tags=["users"]) +@router.post("/create", response_model=int, status_code=201) async def create_user( - user: schema_users.UserCreate, + user: users_schema.UserCreate, validate_access_token_and_validate_admin_access: Annotated[ - Callable, Depends(dependencies_session.validate_access_token_and_validate_admin_access) + Callable, + Depends(dependencies_session.validate_access_token_and_validate_admin_access), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:write"]) + Callable, Security(session_security.check_scopes, scopes=["users:write"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Create the user in the database - created_user = crud_users.create_user(user, db) + created_user = users_crud.create_user(user, db) # Create the user integrations in the database - crud_user_integrations.create_user_integrations(created_user.id, db) + user_integrations_crud.create_user_integrations(created_user.id, db) # Return the user id return created_user.id @router.post( - "/users/{user_id}/upload/image", + "/{user_id}/upload/image", status_code=201, response_model=str | None, tags=["users"], @@ -260,11 +261,11 @@ async def upload_user_image( ], file: UploadFile, check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"]) + Callable, Security(session_security.check_scopes, scopes=["users:edit"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): try: @@ -280,7 +281,7 @@ async def upload_user_image( with open(file_path_to_save, "wb") as buffer: shutil.copyfileobj(file.file, buffer) - return crud_users.edit_user_photo_path(user_id, file_path_to_save, db) + return users_crud.edit_user_photo_path(user_id, file_path_to_save, db) except Exception as err: # Log the exception logger.error(f"Error in upload_user_image: {err}", exc_info=True) @@ -296,9 +297,9 @@ async def upload_user_image( ) from err -@router.put("/users/edit", tags=["users"]) +@router.put("/edit") async def edit_user( - user_attributtes: schema_users.User, + user_attributtes: users_schema.User, validate_token_user_id: Annotated[ Callable, Depends( @@ -306,23 +307,23 @@ async def edit_user( ), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"]) + Callable, Security(session_security.check_scopes, scopes=["users:edit"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Update the user in the database - crud_users.edit_user(user_attributtes, db) + users_crud.edit_user(user_attributtes, db) # Return success message return {"detail": f"User ID {user_attributtes.id} updated successfully"} -@router.put("/users/edit/password", tags=["users"]) +@router.put("/edit/password") async def edit_user_password( - user_attributtes: schema_users.UserEditPassword, + user_attributtes: users_schema.UserEditPassword, validate_token_user_id: Annotated[ Callable, Depends( @@ -330,21 +331,21 @@ async def edit_user_password( ), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"]) + Callable, Security(session_security.check_scopes, scopes=["users:edit"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Update the user password in the database - crud_users.edit_user_password(user_attributtes.id, user_attributtes.password, db) + users_crud.edit_user_password(user_attributtes.id, user_attributtes.password, db) # Return success message return {"detail": f"User ID {user_attributtes.id} password updated successfully"} -@router.put("/users/{user_id}/delete-photo", tags=["users"]) +@router.put("/{user_id}/delete-photo") async def delete_user_photo( user_id: int, validate_token_user_id: Annotated[ @@ -354,37 +355,37 @@ async def delete_user_photo( ), ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:edit"]) + Callable, Security(session_security.check_scopes, scopes=["users:edit"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Update the user photo_path in the database - crud_users.delete_user_photo(user_id, db) + users_crud.delete_user_photo(user_id, db) # Return success message return {"detail": f"User ID {user_id} photo deleted successfully"} -@router.delete("/users/{user_id}/delete", tags=["users"]) +@router.delete("/{user_id}/delete") async def delete_user( user_id: int, - validate_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], + validate_id: Annotated[Callable, Depends(users_dependencies.validate_user_id)], validate_token_validate_admin_access: Annotated[ Callable, Depends(dependencies_session.validate_token_and_validate_admin_access) ], check_scopes: Annotated[ - Callable, Security(dependencies_session.check_scopes, scopes=["users:write"]) + Callable, Security(session_security.check_scopes, scopes=["users:write"]) ], db: Annotated[ Session, - Depends(dependencies_database.get_db), + Depends(database.get_db), ], ): # Delete the user in the database - crud_users.delete_user(user_id, db) + users_crud.delete_user(user_id, db) # Return success message return {"detail": f"User ID {user_id} deleted successfully"} diff --git a/backend/schemas/schema_users.py b/backend/app/users/schema.py similarity index 97% rename from backend/schemas/schema_users.py rename to backend/app/users/schema.py index fbcabc0c7..6a331cfdd 100644 --- a/backend/schemas/schema_users.py +++ b/backend/app/users/schema.py @@ -23,7 +23,6 @@ class UserCreate(User): class UserMe(User): - id: int is_strava_linked: int | None = None class UserEditPassword(BaseModel): diff --git a/backend/dependencies/dependencies_database.py b/backend/dependencies/dependencies_database.py deleted file mode 100644 index d32c9bd72..000000000 --- a/backend/dependencies/dependencies_database.py +++ /dev/null @@ -1,12 +0,0 @@ -from database import SessionLocal - -def get_db(): - # Create a new database session and return it - db = SessionLocal() - - try: - # Yield the database session - yield db - finally: - # Close the database session - db.close() \ No newline at end of file diff --git a/backend/poetry.lock b/backend/poetry.lock new file mode 100644 index 000000000..4f68871a1 --- /dev/null +++ b/backend/poetry.lock @@ -0,0 +1,2226 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alembic" +version = "1.13.2" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, + {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +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 = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "apscheduler" +version = "3.10.4" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=3.6" +files = [ + {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, + {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, +] + +[package.dependencies] +pytz = "*" +six = ">=1.4.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" + +[package.extras] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=1.4)"] +testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + +[[package]] +name = "asgiref" +version = "3.8.1" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, +] + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "bcrypt" +version = "4.1.3" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05"}, + {file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286"}, + {file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64"}, + {file = "bcrypt-4.1.3-cp37-abi3-win32.whl", hash = "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf"}, + {file = "bcrypt-4.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978"}, + {file = "bcrypt-4.1.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08"}, + {file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73"}, + {file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d"}, + {file = "bcrypt-4.1.3-cp39-abi3-win32.whl", hash = "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2"}, + {file = "bcrypt-4.1.3-cp39-abi3-win_amd64.whl", hash = "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991"}, + {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed"}, + {file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc"}, + {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1"}, + {file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650"}, + {file = "bcrypt-4.1.3.tar.gz", hash = "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "42.0.8" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "fastapi" +version = "0.111.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0"}, + {file = "fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7"}, +] + +[package.dependencies] +email_validator = ">=2.0.0" +fastapi-cli = ">=0.0.2" +httpx = ">=0.23.0" +jinja2 = ">=2.11.2" +orjson = ">=3.2.1" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +python-multipart = ">=0.0.7" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" +ujson = ">=4.0.1,<4.0.2 || >4.0.2,<4.1.0 || >4.1.0,<4.2.0 || >4.2.0,<4.3.0 || >4.3.0,<5.0.0 || >5.0.0,<5.1.0 || >5.1.0" +uvicorn = {version = ">=0.12.0", extras = ["standard"]} + +[package.extras] +all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastapi-cli" +version = "0.0.4" +description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, + {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, +] + +[package.dependencies] +typer = ">=0.12.3" + +[package.extras] +standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] + +[[package]] +name = "flexcache" +version = "0.3" +description = "Saves and loads to the cache a transformed versions of a source object." +optional = false +python-versions = ">=3.9" +files = [ + {file = "flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32"}, + {file = "flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "pytest-mpl", "pytest-subtests"] + +[[package]] +name = "flexparser" +version = "0.3.1" +description = "Parsing made fun ... using typing." +optional = false +python-versions = ">=3.9" +files = [ + {file = "flexparser-0.3.1-py3-none-any.whl", hash = "sha256:2e3e2936bec1f9277f777ef77297522087d96adb09624d4fe4240fd56885c013"}, + {file = "flexparser-0.3.1.tar.gz", hash = "sha256:36f795d82e50f5c9ae2fde1c33f21f88922fdd67b7629550a3cc4d0b40a66856"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +test = ["pytest", "pytest-cov", "pytest-mpl", "pytest-subtests"] + +[[package]] +name = "googleapis-common-protos" +version = "1.63.2" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "gpxpy" +version = "1.6.2" +description = "GPX file parser and GPS track manipulation library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "gpxpy-1.6.2-py3-none-any.whl", hash = "sha256:289bc2d80f116c988d0a1e763fda22838f83005573ece2bbc6521817b26fb40a"}, + {file = "gpxpy-1.6.2.tar.gz", hash = "sha256:a72c484b97ec42b80834353b029cc8ee1b79f0ffca1179b2210bb3baf26c01ae"}, +] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "grpcio" +version = "1.64.1" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.8" +files = [ + {file = "grpcio-1.64.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:55697ecec192bc3f2f3cc13a295ab670f51de29884ca9ae6cd6247df55df2502"}, + {file = "grpcio-1.64.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:3b64ae304c175671efdaa7ec9ae2cc36996b681eb63ca39c464958396697daff"}, + {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:bac71b4b28bc9af61efcdc7630b166440bbfbaa80940c9a697271b5e1dabbc61"}, + {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c024ffc22d6dc59000faf8ad781696d81e8e38f4078cb0f2630b4a3cf231a90"}, + {file = "grpcio-1.64.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7cd5c1325f6808b8ae31657d281aadb2a51ac11ab081ae335f4f7fc44c1721d"}, + {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0a2813093ddb27418a4c99f9b1c223fab0b053157176a64cc9db0f4557b69bd9"}, + {file = "grpcio-1.64.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2981c7365a9353f9b5c864595c510c983251b1ab403e05b1ccc70a3d9541a73b"}, + {file = "grpcio-1.64.1-cp310-cp310-win32.whl", hash = "sha256:1262402af5a511c245c3ae918167eca57342c72320dffae5d9b51840c4b2f86d"}, + {file = "grpcio-1.64.1-cp310-cp310-win_amd64.whl", hash = "sha256:19264fc964576ddb065368cae953f8d0514ecc6cb3da8903766d9fb9d4554c33"}, + {file = "grpcio-1.64.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:58b1041e7c870bb30ee41d3090cbd6f0851f30ae4eb68228955d973d3efa2e61"}, + {file = "grpcio-1.64.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc5b1d78a7822b0a84c6f8917faa986c1a744e65d762ef6d8be9d75677af2ca"}, + {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5841dd1f284bd1b3d8a6eca3a7f062b06f1eec09b184397e1d1d43447e89a7ae"}, + {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8caee47e970b92b3dd948371230fcceb80d3f2277b3bf7fbd7c0564e7d39068e"}, + {file = "grpcio-1.64.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73819689c169417a4f978e562d24f2def2be75739c4bed1992435d007819da1b"}, + {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6503b64c8b2dfad299749cad1b595c650c91e5b2c8a1b775380fcf8d2cbba1e9"}, + {file = "grpcio-1.64.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1de403fc1305fd96cfa75e83be3dee8538f2413a6b1685b8452301c7ba33c294"}, + {file = "grpcio-1.64.1-cp311-cp311-win32.whl", hash = "sha256:d4d29cc612e1332237877dfa7fe687157973aab1d63bd0f84cf06692f04c0367"}, + {file = "grpcio-1.64.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e56462b05a6f860b72f0fa50dca06d5b26543a4e88d0396259a07dc30f4e5aa"}, + {file = "grpcio-1.64.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:4657d24c8063e6095f850b68f2d1ba3b39f2b287a38242dcabc166453e950c59"}, + {file = "grpcio-1.64.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:62b4e6eb7bf901719fce0ca83e3ed474ae5022bb3827b0a501e056458c51c0a1"}, + {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:ee73a2f5ca4ba44fa33b4d7d2c71e2c8a9e9f78d53f6507ad68e7d2ad5f64a22"}, + {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:198908f9b22e2672a998870355e226a725aeab327ac4e6ff3a1399792ece4762"}, + {file = "grpcio-1.64.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39b9d0acaa8d835a6566c640f48b50054f422d03e77e49716d4c4e8e279665a1"}, + {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5e42634a989c3aa6049f132266faf6b949ec2a6f7d302dbb5c15395b77d757eb"}, + {file = "grpcio-1.64.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1a82e0b9b3022799c336e1fc0f6210adc019ae84efb7321d668129d28ee1efb"}, + {file = "grpcio-1.64.1-cp312-cp312-win32.whl", hash = "sha256:55260032b95c49bee69a423c2f5365baa9369d2f7d233e933564d8a47b893027"}, + {file = "grpcio-1.64.1-cp312-cp312-win_amd64.whl", hash = "sha256:c1a786ac592b47573a5bb7e35665c08064a5d77ab88a076eec11f8ae86b3e3f6"}, + {file = "grpcio-1.64.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:a011ac6c03cfe162ff2b727bcb530567826cec85eb8d4ad2bfb4bd023287a52d"}, + {file = "grpcio-1.64.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4d6dab6124225496010bd22690f2d9bd35c7cbb267b3f14e7a3eb05c911325d4"}, + {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a5e771d0252e871ce194d0fdcafd13971f1aae0ddacc5f25615030d5df55c3a2"}, + {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3c1b90ab93fed424e454e93c0ed0b9d552bdf1b0929712b094f5ecfe7a23ad"}, + {file = "grpcio-1.64.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20405cb8b13fd779135df23fabadc53b86522d0f1cba8cca0e87968587f50650"}, + {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0cc79c982ccb2feec8aad0e8fb0d168bcbca85bc77b080d0d3c5f2f15c24ea8f"}, + {file = "grpcio-1.64.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a3a035c37ce7565b8f4f35ff683a4db34d24e53dc487e47438e434eb3f701b2a"}, + {file = "grpcio-1.64.1-cp38-cp38-win32.whl", hash = "sha256:1257b76748612aca0f89beec7fa0615727fd6f2a1ad580a9638816a4b2eb18fd"}, + {file = "grpcio-1.64.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a12ddb1678ebc6a84ec6b0487feac020ee2b1659cbe69b80f06dbffdb249122"}, + {file = "grpcio-1.64.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:75dbbf415026d2862192fe1b28d71f209e2fd87079d98470db90bebe57b33179"}, + {file = "grpcio-1.64.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3d9f8d1221baa0ced7ec7322a981e28deb23749c76eeeb3d33e18b72935ab62"}, + {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:5f8b75f64d5d324c565b263c67dbe4f0af595635bbdd93bb1a88189fc62ed2e5"}, + {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84ad903d0d94311a2b7eea608da163dace97c5fe9412ea311e72c3684925602"}, + {file = "grpcio-1.64.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940e3ec884520155f68a3b712d045e077d61c520a195d1a5932c531f11883489"}, + {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f10193c69fc9d3d726e83bbf0f3d316f1847c3071c8c93d8090cf5f326b14309"}, + {file = "grpcio-1.64.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac15b6c2c80a4d1338b04d42a02d376a53395ddf0ec9ab157cbaf44191f3ffdd"}, + {file = "grpcio-1.64.1-cp39-cp39-win32.whl", hash = "sha256:03b43d0ccf99c557ec671c7dede64f023c7da9bb632ac65dbc57f166e4970040"}, + {file = "grpcio-1.64.1-cp39-cp39-win_amd64.whl", hash = "sha256:ed6091fa0adcc7e4ff944090cf203a52da35c37a130efa564ded02b7aff63bcd"}, + {file = "grpcio-1.64.1.tar.gz", hash = "sha256:8d51dd1c59d5fa0f34266b80a3805ec29a1f26425c2a54736133f6d87fc4968a"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.64.1)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httptools" +version = "0.6.1" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, + {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, + {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, + {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, + {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, + {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, + {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.1.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "joserfc" +version = "0.12.0" +description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joserfc-0.12.0-py3-none-any.whl", hash = "sha256:210f21ec1c3d08c9a0d9969d7825d2020f365ad6b3a0d6c6c0b638704f96a5b0"}, + {file = "joserfc-0.12.0.tar.gz", hash = "sha256:86625aef30bb9857f8c2f4320ea2ad4342a29319a66189cb743547c74a88b1ec"}, +] + +[package.dependencies] +cryptography = "*" + +[package.extras] +drafts = ["pycryptodome"] + +[[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 = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mysqlclient" +version = "2.2.4" +description = "Python interface to MySQL" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mysqlclient-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:ac44777eab0a66c14cb0d38965572f762e193ec2e5c0723bcd11319cc5b693c5"}, + {file = "mysqlclient-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:329e4eec086a2336fe3541f1ce095d87a6f169d1cc8ba7b04ac68bcb234c9711"}, + {file = "mysqlclient-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:e1ebe3f41d152d7cb7c265349fdb7f1eca86ccb0ca24a90036cde48e00ceb2ab"}, + {file = "mysqlclient-2.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:3c318755e06df599338dad7625f884b8a71fcf322a9939ef78c9b3db93e1de7a"}, + {file = "mysqlclient-2.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:9d4c015480c4a6b2b1602eccd9846103fc70606244788d04aa14b31c4bd1f0e2"}, + {file = "mysqlclient-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d43987bb9626096a302ca6ddcdd81feaeca65ced1d5fe892a6a66b808326aa54"}, + {file = "mysqlclient-2.2.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4e80dcad884dd6e14949ac6daf769123223a52a6805345608bf49cdaf7bc8b3a"}, + {file = "mysqlclient-2.2.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9d3310295cb682232cadc28abd172f406c718b9ada41d2371259098ae37779d3"}, + {file = "mysqlclient-2.2.4.tar.gz", hash = "sha256:33bc9fb3464e7d7c10b1eaf7336c5ff8f2a3d3b88bab432116ad2490beb3bf41"}, +] + +[[package]] +name = "opentelemetry-api" +version = "1.25.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, + {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<=7.1" + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.25.0" +description = "OpenTelemetry Collector Exporters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp-1.25.0-py3-none-any.whl", hash = "sha256:d67a831757014a3bc3174e4cd629ae1493b7ba8d189e8a007003cacb9f1a6b60"}, + {file = "opentelemetry_exporter_otlp-1.25.0.tar.gz", hash = "sha256:ce03199c1680a845f82e12c0a6a8f61036048c07ec7a0bd943142aca8fa6ced0"}, +] + +[package.dependencies] +opentelemetry-exporter-otlp-proto-grpc = "1.25.0" +opentelemetry-exporter-otlp-proto-http = "1.25.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.25.0" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, +] + +[package.dependencies] +opentelemetry-proto = "1.25.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.25.0" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0-py3-none-any.whl", hash = "sha256:3131028f0c0a155a64c430ca600fd658e8e37043cb13209f0109db5c1a3e4eb4"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.25.0.tar.gz", hash = "sha256:c0b1661415acec5af87625587efa1ccab68b873745ca0ee96b69bb1042087eac"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +grpcio = ">=1.0.0,<2.0.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.25.0" +opentelemetry-proto = "1.25.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.25.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0-py3-none-any.whl", hash = "sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0.tar.gz", hash = "sha256:9f8723859e37c75183ea7afa73a3542f01d0fd274a5b97487ea24cb683d7d684"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.25.0" +opentelemetry-proto = "1.25.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" +requests = ">=2.7,<3.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.46b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation-0.46b0-py3-none-any.whl", hash = "sha256:89cd721b9c18c014ca848ccd11181e6b3fd3f6c7669e35d59c48dc527408c18b"}, + {file = "opentelemetry_instrumentation-0.46b0.tar.gz", hash = "sha256:974e0888fb2a1e01c38fbacc9483d024bb1132aad92d6d24e2e5543887a7adda"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +setuptools = ">=16.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.46b0" +description = "ASGI instrumentation for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_asgi-0.46b0-py3-none-any.whl", hash = "sha256:f13c55c852689573057837a9500aeeffc010c4ba59933c322e8f866573374759"}, + {file = "opentelemetry_instrumentation_asgi-0.46b0.tar.gz", hash = "sha256:02559f30cf4b7e2a737ab17eb52aa0779bcf4cc06573064f3e2cb4dcc7d3040a"}, +] + +[package.dependencies] +asgiref = ">=3.0,<4.0" +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.46b0" +opentelemetry-semantic-conventions = "0.46b0" +opentelemetry-util-http = "0.46b0" + +[package.extras] +instruments = ["asgiref (>=3.0,<4.0)"] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.46b0" +description = "OpenTelemetry FastAPI Instrumentation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_instrumentation_fastapi-0.46b0-py3-none-any.whl", hash = "sha256:e0f5d150c6c36833dd011f0e6ef5ede6d7406c1aed0c7c98b2d3b38a018d1b33"}, + {file = "opentelemetry_instrumentation_fastapi-0.46b0.tar.gz", hash = "sha256:928a883a36fc89f9702f15edce43d1a7104da93d740281e32d50ffd03dbb4365"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.46b0" +opentelemetry-instrumentation-asgi = "0.46b0" +opentelemetry-semantic-conventions = "0.46b0" +opentelemetry-util-http = "0.46b0" + +[package.extras] +instruments = ["fastapi (>=0.58,<1.0)"] + +[[package]] +name = "opentelemetry-proto" +version = "1.25.0" +description = "OpenTelemetry Python Proto" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, + {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.25.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, + {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" +opentelemetry-semantic-conventions = "0.46b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.46b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, + {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" + +[[package]] +name = "opentelemetry-util-http" +version = "0.46b0" +description = "Web util for OpenTelemetry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "opentelemetry_util_http-0.46b0-py3-none-any.whl", hash = "sha256:8dc1949ce63caef08db84ae977fdc1848fe6dc38e6bbaad0ae3e6ecd0d451629"}, + {file = "opentelemetry_util_http-0.46b0.tar.gz", hash = "sha256:03b6e222642f9c7eae58d9132343e045b50aca9761fcb53709bd2b663571fdf6"}, +] + +[[package]] +name = "orjson" +version = "3.10.6" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"}, + {file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"}, + {file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"}, + {file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"}, + {file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"}, + {file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"}, + {file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"}, + {file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"}, + {file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"}, + {file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"}, + {file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"}, + {file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"}, + {file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"}, + {file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"}, + {file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"}, + {file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"}, + {file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"}, + {file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"}, + {file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"}, + {file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"}, + {file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"}, + {file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"}, + {file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"}, + {file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"}, + {file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"}, + {file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"}, +] + +[[package]] +name = "pint" +version = "0.24.1" +description = "Physical quantities module" +optional = false +python-versions = ">=3.9" +files = [ + {file = "Pint-0.24.1-py3-none-any.whl", hash = "sha256:69b05357c4cb2ac8f3346e235aff4477447e2f56805a79a4f59a2b6d5fc32020"}, + {file = "pint-0.24.1.tar.gz", hash = "sha256:8849fe9d7b8532e5a5dc41e719e9e19268e18eac179d9a5645f21929a2a15caf"}, +] + +[package.dependencies] +appdirs = ">=1.4.4" +flexcache = ">=0.3" +flexparser = ">=0.3" +typing-extensions = "*" + +[package.extras] +babel = ["babel (<=2.8)"] +bench = ["pytest", "pytest-codspeed"] +dask = ["dask"] +mip = ["mip (>=1.13)"] +numpy = ["numpy (>=1.23)"] +pandas = ["pint-pandas (>=0.3)"] +test = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-mpl", "pytest-subtests"] +testbase = ["pytest", "pytest-benchmark", "pytest-cov", "pytest-subtests"] +uncertainties = ["uncertainties (>=3.1.6)"] +xarray = ["xarray"] + +[[package]] +name = "protobuf" +version = "4.25.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "1.10.17" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, + {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, + {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, + {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, + {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, + {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, + {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, + {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, + {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, + {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, + {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, + {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, + {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, + {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, + {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, + {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, + {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, + {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, + {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, + {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, + {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, + {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, + {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, + {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, + {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, + {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, + {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[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" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "70.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"}, + {file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.31" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, + {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, + {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "stravalib" +version = "1.7" +description = "A Python package that makes it easy to access and download data from the Strava V3 REST API." +optional = false +python-versions = ">=3.10" +files = [ + {file = "stravalib-1.7-py3-none-any.whl", hash = "sha256:fc8c9b34b0f1f3a2f10c7adcddbb533ffff5d0e5f2c345e459638397bfc0cb81"}, + {file = "stravalib-1.7.tar.gz", hash = "sha256:3b4f8cc51f2f361badfba0637a590c9fb702f1ededb0ebc9cdcef471dd81129b"}, +] + +[package.dependencies] +arrow = "*" +pint = "*" +pydantic = "<2.0" +pytz = "*" +requests = "*" + +[package.extras] +build = ["build"] +docs = ["autodoc-pydantic", "matplotlib", "myst-nb", "pydata-sphinx-theme", "sphinx", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs", "sphinx-remove-toctrees", "sphinxext-opengraph"] +lint = ["mypy", "pre-commit", "types-Flask", "types-pytz", "types-requests"] +tests = ["pytest", "pytest-cov", "responses"] + +[[package]] +name = "typer" +version = "0.12.3" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, + {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240316" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, + {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {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 = "ujson" +version = "5.10.0" +description = "Ultra fast JSON encoder and decoder for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, + {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"}, + {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"}, + {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"}, + {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"}, + {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"}, + {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"}, + {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"}, + {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"}, + {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"}, + {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"}, + {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"}, + {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"}, + {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"}, + {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"}, + {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"}, + {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"}, + {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"}, + {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"}, + {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"}, + {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"}, + {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"}, + {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"}, + {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"}, + {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"}, + {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"}, + {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"}, + {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"}, + {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"}, + {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"}, + {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"}, + {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"}, + {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"}, + {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"}, + {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"}, + {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"}, + {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"}, + {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"}, + {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"}, + {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.30.1" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"}, + {file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "watchfiles" +version = "0.22.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, + {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, + {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, + {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, + {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, + {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, + {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, + {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, + {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, + {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, + {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, + {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, + {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, + {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, + {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "zipp" +version = "3.19.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "2b89f9ad99640af018e47043e103b0d05f505c5642664cccc7517cbee5381062" diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 000000000..a4b75803c --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,31 @@ +[tool.poetry] +name = "endurain" +version = "0.3.0" +description = "Endurain API for the Endurain app" +authors = ["João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com>"] +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = "^3.11" +fastapi = "^0.111.0" +uvicorn = "^0.30.1" +python-dotenv = "^1.0.1" +sqlalchemy = "^2.0.31" +apscheduler = "^3.10.4" +requests = "^2.32.3" +stravalib = "^1.7" +opentelemetry-sdk = "^1.25.0" +opentelemetry-instrumentation-fastapi = "^0.46b0" +opentelemetry-exporter-otlp = "^1.25.0" +python-multipart = "^0.0.9" +gpxpy = "^1.6.2" +alembic = "^1.13.2" +joserfc = "^0.12.0" +bcrypt = "^4.1.3" +mysqlclient = "^2.2.4" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/backend/routers/router_activity_streams.py b/backend/routers/router_activity_streams.py deleted file mode 100644 index 3152b46a1..000000000 --- a/backend/routers/router_activity_streams.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging - -from typing import Annotated, Callable - -from fastapi import APIRouter, Depends -from fastapi.security import OAuth2PasswordBearer -from sqlalchemy.orm import Session - -from schemas import schema_activity_streams -from crud import crud_activity_streams -from dependencies import ( - dependencies_database, - dependencies_security, - dependencies_activities, - dependencies_activity_streams, -) - -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -# Define the API router -router = APIRouter() - -# Define a loggger created on main.py -logger = logging.getLogger("myLogger") - - -@router.get( - "/activities/streams/activity_id/{activity_id}/all", - response_model=list[schema_activity_streams.ActivityStreams] | None, - tags=["activity_streams"], -) -async def read_activities_streams_for_activity_all( - activity_id: int, - validate_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) - ], - validate_token_validate_admin_access: Annotated[ - Callable, Depends(dependencies_security.validate_token_expiration) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Get the activity streams from the database and return them - return crud_activity_streams.get_activity_streams(activity_id, db) - - -@router.get( - "/activities/streams/activity_id/{activity_id}/stream_type/{stream_type}", - response_model=schema_activity_streams.ActivityStreams | None, - tags=["activity_streams"], -) -async def read_activities_streams_for_activity_stream_type( - activity_id: int, - validate_activity_id: Annotated[ - Callable, Depends(dependencies_activities.validate_activity_id) - ], - stream_type: int, - validate_activity_stream_type: Annotated[ - Callable, Depends(dependencies_activity_streams.validate_activity_stream_type) - ], - validate_token: Annotated[ - Callable, Depends(dependencies_security.validate_token_expiration) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Get the activity stream from the database and return them - return crud_activity_streams.get_activity_stream_by_type( - activity_id, stream_type, db - ) diff --git a/backend/routers/router_followers.py b/backend/routers/router_followers.py deleted file mode 100644 index 3df4c361d..000000000 --- a/backend/routers/router_followers.py +++ /dev/null @@ -1,226 +0,0 @@ -import logging - -from typing import Annotated, Callable - -from fastapi import APIRouter, Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer -from sqlalchemy.orm import Session - -from schemas import schema_followers -from crud import crud_followers -from dependencies import ( - dependencies_database, - dependencies_security, - dependencies_users, -) - -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -# Define the API router -router = APIRouter() - -# Define a loggger created on main.py -logger = logging.getLogger("myLogger") - - -@router.get( - "/followers/user/{user_id}/followers/all", - response_model=list[schema_followers.Follower] | None, - tags=["followers"], -) -async def get_user_follower_all( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followers - return crud_followers.get_all_following_by_user_id(user_id, db) - - -@router.get( - "/followers/user/{user_id}/followers/count/all", - response_model=int, - tags=["followers"], -) -async def get_user_follower_count_all( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followers - followers = crud_followers.get_all_followers_by_user_id(user_id, db) - - # Check if followers is None and return 0 if it is - if followers is None: - return 0 - - # Return the number of followers - return len(followers) - - -@router.get( - "/followers/user/{user_id}/followers/count/accepted", - response_model=int, - tags=["followers"], -) -async def get_user_follower_count( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followers - followers = crud_followers.get_accepted_followers_by_user_id(user_id, db) - - # Check if followers is None and return 0 if it is - if followers is None: - return 0 - - # Return the number of followers - return len(followers) - - -@router.get( - "/followers/user/{user_id}/following/all", - response_model=list[schema_followers.Follower] | None, - tags=["followers"], -) -async def get_user_following_all( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followings - return crud_followers.get_all_followers_by_user_id(user_id, db) - - -@router.get( - "/followers/user/{user_id}/following/count/all", - response_model=int, - tags=["followers"], -) -async def get_user_following_count_all( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followings - followings = crud_followers.get_all_following_by_user_id(user_id, db) - - # Check if followings is None and return 0 if it is - if followings is None: - return 0 - - # Return the number of followings - return len(followings) - - -@router.get( - "/followers/user/{user_id}/following/count/accepted", - response_model=int, - tags=["followers"], -) -async def get_user_following_count( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return followings - followings = crud_followers.get_accepted_following_by_user_id(user_id, db) - - # Check if followings is None and return 0 if it is - if followings is None: - return 0 - - # Return the number of followings - return len(followings) - - -@router.get( - "/followers/user/{user_id}/targetUser/{target_user_id}", - response_model=schema_followers.Follower | None, - tags=["followers"], -) -async def read_followers_user_specific_user( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - target_user_id: int, - validate_target_user_id: Annotated[ - Callable, Depends(dependencies_users.validate_target_user_id) - ], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Return the follower - return crud_followers.get_follower_for_user_id_and_target_user_id( - user_id, target_user_id, db - ) - - -@router.post( - "/followers/create/user/{user_id}/targetUser/{target_user_id}", - status_code=201, - tags=["followers"], -) -async def create_follow( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - target_user_id: int, - validate_target_user_id: Annotated[ - Callable, Depends(dependencies_users.validate_target_user_id) - ], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Create the follower - new_follow = crud_followers.create_follower(user_id, target_user_id, db) - - # Return the ID of the gear created - return {"detail": "Follower record created successfully"} - - -@router.put("/followers/accept/user/{user_id}/targetUser/{target_user_id}", - tags=["followers"], -) -async def accept_follow( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - target_user_id: int, - validate_target_user_id: Annotated[ - Callable, Depends(dependencies_users.validate_target_user_id) - ], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Accept the follower - crud_followers.accept_follower(user_id, target_user_id, db) - - # Return success message - return {"detail": "Follower record accepted successfully"} - - -@router.delete( - "/followers/delete/user/{user_id}/targetUser/{target_user_id}", - tags=["followers"], -) -async def delete_follow( - user_id: int, - validate_user_id: Annotated[Callable, Depends(dependencies_users.validate_user_id)], - target_user_id: int, - validate_target_user_id: Annotated[ - Callable, Depends(dependencies_users.validate_target_user_id) - ], - validate_token: Callable = Depends(dependencies_security.validate_token_expiration), - db: Session = Depends(dependencies_database.get_db), -): - # Delete the follower - crud_followers.delete_follower(user_id, target_user_id, db) - - # Return success message - return {"detail": "Follower record deleted successfully"} diff --git a/backend/routers/router_gear.py b/backend/routers/router_gear.py deleted file mode 100644 index 57b893d32..000000000 --- a/backend/routers/router_gear.py +++ /dev/null @@ -1,191 +0,0 @@ -import logging - -from typing import Annotated, Callable - -from fastapi import APIRouter, Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer -from sqlalchemy.orm import Session - -from schemas import schema_gear -from crud import crud_gear -from dependencies import ( - dependencies_database, - dependencies_session, - dependencies_global, - dependencies_gear, -) - -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -# Define the API router -router = APIRouter() - -# Define a loggger created on main.py -logger = logging.getLogger("myLogger") - - -@router.get( - "/gear/id/{gear_id}", - response_model=schema_gear.Gear | None, - tags=["gear"], -) -async def read_gear_id( - gear_id: int, - validate_gear_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)], - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Annotated[Session, Depends(dependencies_database.get_db)], -): - # Return the gear - return crud_gear.get_gear_user_by_id(user_id, gear_id, db) - - -@router.get( - "/gear/page_number/{page_number}/num_records/{num_records}", - response_model=list[schema_gear.Gear] | None, - tags=["gear"], -) -async def read_gear_user_pagination( - page_number: int, - num_records: int, - validate_pagination_values: Annotated[ - Callable, Depends(dependencies_global.validate_pagination_values) - ], - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Return the gear - return crud_gear.get_gear_users_with_pagination( - user_id, db, page_number, num_records - ) - - -@router.get( - "/gear/number", - response_model=int, - tags=["gear"], -) -async def read_gear_user_number( - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Get the gear - gear = crud_gear.get_gear_user(user_id, db) - - # Check if gear is None and return 0 if it is - if gear is None: - return 0 - - # Return the number of gears - return len(gear) - - -@router.get( - "/gear/nickname/{nickname}", - response_model=list[schema_gear.Gear] | None, - tags=["gear"], -) -async def read_gear_user_by_nickname( - nickname: str, - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Return the gear - return crud_gear.get_gear_user_by_nickname(user_id, nickname, db) - - -@router.get( - "/gear/type/{gear_type}", - response_model=list[schema_gear.Gear] | None, - tags=["gear"], -) -async def read_gear_user_by_type( - gear_type: int, - validate_type: Annotated[Callable, Depends(dependencies_gear.validate_gear_type)], - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Return the gear - return crud_gear.get_gear_by_type_and_user(gear_type, user_id, db) - - -@router.post( - "/gear/create", - status_code=201, - tags=["gear"], -) -async def create_gear( - gear: schema_gear.Gear, - user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Create the gear - gear_created = crud_gear.create_gear(gear, user_id, db) - - # Return the ID of the gear created - return gear_created.id - - -@router.put("/gear/{gear_id}/edit", tags=["gear"]) -async def edit_gear( - gear_id: int, - validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)], - gear: schema_gear.Gear, - token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Get the gear by id - gear_db = crud_gear.get_gear_user_by_id(token_user_id, gear_id, db) - - # Check if gear is None and raise an HTTPException if it is - if gear_db is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Gear ID {gear_id} for user {token_user_id} not found", - ) - - # Edit the gear - crud_gear.edit_gear(gear_id, gear, db) - - # Return success message - return {"detail": f"Gear ID {gear_id} edited successfully"} - - -@router.delete("/gear/{gear_id}/delete", tags=["gear"]) -async def delete_user( - gear_id: int, - validate_id: Annotated[Callable, Depends(dependencies_gear.validate_gear_id)], - token_user_id: Annotated[ - int, Depends(dependencies_session.validate_access_token_and_get_authenticated_user_id) - ], - db: Session = Depends(dependencies_database.get_db), -): - # Get the gear by id - gear = crud_gear.get_gear_user_by_id(token_user_id, gear_id, db) - - # Check if gear is None and raise an HTTPException if it is - if gear is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Gear ID {gear_id} for user {token_user_id} not found", - ) - - # Delete the gear - crud_gear.delete_gear(gear_id, db) - - # Return success message - return {"detail": f"Gear ID {gear_id} deleted successfully"} diff --git a/backend/routers/router_session.py b/backend/routers/router_session.py deleted file mode 100644 index ed884eeb9..000000000 --- a/backend/routers/router_session.py +++ /dev/null @@ -1,195 +0,0 @@ -import logging -import bcrypt - -from datetime import datetime, timedelta, timezone -from typing import Annotated - -from fastapi import ( - APIRouter, - Depends, - HTTPException, - status, - Response, - Request, -) -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from sqlalchemy.orm import Session - -from crud import crud_users -from schemas import schema_users -from constants import ( - USER_NOT_ACTIVE, - REGULAR_ACCESS, - REGULAR_ACCESS_SCOPES, - ADMIN_ACCESS_SCOPES, - SCOPES_DICT, - JWT_ACCESS_TOKEN_EXPIRE_MINUTES, - JWT_REFRESH_TOKEN_EXPIRE_DAYS, -) -from dependencies import ( - dependencies_database, - dependencies_session, - dependencies_security, -) - -# Define the OAuth2 scheme for handling bearer tokens -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="token", - scopes=SCOPES_DICT, -) - -# Define the API router -router = APIRouter() - -# Define a loggger created on main.py -logger = logging.getLogger("myLogger") - - -def hash_password(password: str): - # Hash the password and return it - return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) - - -def verify_password(plain_password: str, hashed_password: str): - # Check if the password is equal to the hashed password - return bcrypt.checkpw( - plain_password.encode("utf-8"), hashed_password.encode("utf-8") - ) - - -def authenticate_user(username: str, password: str, db: Session): - # Get the user from the database - user = crud_users.authenticate_user(username, db) - - # Check if the user exists and if the hashed_password is correct and if not return False - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username", - headers={"WWW-Authenticate": "Bearer"}, - ) - - if not verify_password(password, user.password): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect password", - headers={"WWW-Authenticate": "Bearer"}, - ) - - # Return the user if the hashed_password is correct - return user - - -def create_response_with_tokens(response: Response, user: schema_users.User): - # Check user access level and set scopes accordingly - if user.access_type == REGULAR_ACCESS: - scopes = REGULAR_ACCESS_SCOPES - else: - scopes = ADMIN_ACCESS_SCOPES - - # Create the access and refresh tokens - access_token = dependencies_security.create_token( - data={ - "sub": user.username, - "scopes": scopes, - "id": user.id, - "access_type": user.access_type, - "exp": datetime.now(timezone.utc) - + timedelta(minutes=JWT_ACCESS_TOKEN_EXPIRE_MINUTES), - }, - ) - - refresh_token = dependencies_security.create_token( - data={ - "sub": user.username, - "scopes": "scopes", - "id": user.id, - "access_type": user.access_type, - "exp": datetime.now(timezone.utc) - + timedelta(days=JWT_REFRESH_TOKEN_EXPIRE_DAYS), - }, - ) - - # Set the cookies with the tokens - response.set_cookie( - key="endurain_access_token", - value=access_token, - expires=datetime.now(timezone.utc) - + timedelta(minutes=JWT_ACCESS_TOKEN_EXPIRE_MINUTES), - httponly=True, - path="/", - secure=False, - samesite="None", - ) - response.set_cookie( - key="endurain_refresh_token", - value=refresh_token, - expires=datetime.now(timezone.utc) - + timedelta(days=JWT_REFRESH_TOKEN_EXPIRE_DAYS), - httponly=True, - path="/", - secure=False, - samesite="None", - ) - - # Set the user id in a cookie - #response.set_cookie( - # key="endurain_logged_user_id", - # value=user.id, - # httponly=False, - #) - - # Return the response - return response - - -@router.post("/token", tags=["session"]) -async def login_for_access_token( - response: Response, - form_data: Annotated[OAuth2PasswordRequestForm, Depends()], - db: Annotated[ - Session, - Depends(dependencies_database.get_db), - ], -): - user = authenticate_user(form_data.username, form_data.password, db) - - if user.is_active == USER_NOT_ACTIVE: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Inactive user", - headers={"WWW-Authenticate": "Bearer"}, - ) - - response = create_response_with_tokens(response, user) - - return {"message": "Login successful"} - - -@router.post("/refresh", tags=["session"]) -async def refresh_token( - response: Response, - user_id: Annotated[ - int, - Depends( - dependencies_session.validate_refresh_token_and_get_authenticated_user_id - ), - ], - db: Annotated[ - Session, - Depends(dependencies_database.get_db), - ], -): - # get user - user = crud_users.get_user_by_id(user_id, db) - - if user.is_active == USER_NOT_ACTIVE: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Inactive user", - headers={"WWW-Authenticate": "Bearer"}, - ) - - response = create_response_with_tokens(response, user) - - return {"message": "Token refreshed successfully"} diff --git a/docker-compose.yml b/docker-compose.yml.example similarity index 100% rename from docker-compose.yml rename to docker-compose.yml.example diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index f9fd581a5..000000000 --- a/frontend/.env +++ /dev/null @@ -1,2 +0,0 @@ -VITE_BACKEND_PROTOCOL=MY_APP_BACKEND_PROTOCOL -VITE_BACKEND_HOST=MY_APP_BACKEND_HOST \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 4a1356a1a..000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -.DS_Store -dist -dist-ssr -coverage -*.local - -/cypress/videos/ -/cypress/screenshots/ - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -*.tsbuildinfo - -# Sentry Config File -.env.sentry-build-plugin diff --git a/Dockerfile_frontend b/frontend/Dockerfile similarity index 94% rename from Dockerfile_frontend rename to frontend/Dockerfile index a23a967fe..fd8500345 100644 --- a/Dockerfile_frontend +++ b/frontend/Dockerfile @@ -8,11 +8,11 @@ LABEL org.opencontainers.image.source https://github.com/joaovitoriasilva/endura WORKDIR /app # Copy package.json and package-lock.json -COPY frontend/package*.json ./ +COPY app/package*.json ./ RUN npm install # Copy the current directory contents into the container at /app -COPY frontend ./ +COPY app ./ # Build the app RUN npm run build diff --git a/frontend/app/.env b/frontend/app/.env new file mode 100644 index 000000000..47f07f2c2 --- /dev/null +++ b/frontend/app/.env @@ -0,0 +1,3 @@ +VITE_BACKEND_PROTOCOL=http +VITE_BACKEND_HOST=localhost +VITE_BACKEND_PORT=98 \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/app/.eslintrc.cjs similarity index 100% rename from frontend/.eslintrc.cjs rename to frontend/app/.eslintrc.cjs diff --git a/frontend/.prettierrc.json b/frontend/app/.prettierrc.json similarity index 100% rename from frontend/.prettierrc.json rename to frontend/app/.prettierrc.json diff --git a/frontend/.vscode/extensions.json b/frontend/app/.vscode/extensions.json similarity index 100% rename from frontend/.vscode/extensions.json rename to frontend/app/.vscode/extensions.json diff --git a/frontend/index.html b/frontend/app/index.html similarity index 100% rename from frontend/index.html rename to frontend/app/index.html diff --git a/frontend/jsconfig.json b/frontend/app/jsconfig.json similarity index 100% rename from frontend/jsconfig.json rename to frontend/app/jsconfig.json diff --git a/frontend/package-lock.json b/frontend/app/package-lock.json similarity index 99% rename from frontend/package-lock.json rename to frontend/app/package-lock.json index 731a2eb09..fae9847a1 100644 --- a/frontend/package-lock.json +++ b/frontend/app/package-lock.json @@ -4312,9 +4312,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/frontend/package.json b/frontend/app/package.json similarity index 100% rename from frontend/package.json rename to frontend/app/package.json diff --git a/frontend/public/logo/logo.png b/frontend/app/public/logo/logo.png similarity index 100% rename from frontend/public/logo/logo.png rename to frontend/app/public/logo/logo.png diff --git a/frontend/src/App.vue b/frontend/app/src/App.vue similarity index 58% rename from frontend/src/App.vue rename to frontend/app/src/App.vue index 7bda450a9..01977e767 100644 --- a/frontend/src/App.vue +++ b/frontend/app/src/App.vue @@ -1,11 +1,13 @@ \ No newline at end of file diff --git a/frontend/app/src/components/Toasts/ToastContainerComponent.vue b/frontend/app/src/components/Toasts/ToastContainerComponent.vue new file mode 100644 index 000000000..083da87b4 --- /dev/null +++ b/frontend/app/src/components/Toasts/ToastContainerComponent.vue @@ -0,0 +1,29 @@ + + + diff --git a/frontend/src/components/Users/UserAvatarComponent.vue b/frontend/app/src/components/Users/UserAvatarComponent.vue similarity index 100% rename from frontend/src/components/Users/UserAvatarComponent.vue rename to frontend/app/src/components/Users/UserAvatarComponent.vue diff --git a/frontend/src/i18n/en/activityView.json b/frontend/app/src/i18n/en/activityView.json similarity index 100% rename from frontend/src/i18n/en/activityView.json rename to frontend/app/src/i18n/en/activityView.json diff --git a/frontend/src/i18n/en/components/activities/activitySummaryComponent.json b/frontend/app/src/i18n/en/components/activities/activitySummaryComponent.json similarity index 100% rename from frontend/src/i18n/en/components/activities/activitySummaryComponent.json rename to frontend/app/src/i18n/en/components/activities/activitySummaryComponent.json diff --git a/frontend/src/i18n/en/components/activities/userDistanceStatsComponent.json b/frontend/app/src/i18n/en/components/activities/userDistanceStatsComponent.json similarity index 100% rename from frontend/src/i18n/en/components/activities/userDistanceStatsComponent.json rename to frontend/app/src/i18n/en/components/activities/userDistanceStatsComponent.json diff --git a/frontend/src/i18n/en/components/followers/followersListComponent.json b/frontend/app/src/i18n/en/components/followers/followersListComponent.json similarity index 100% rename from frontend/src/i18n/en/components/followers/followersListComponent.json rename to frontend/app/src/i18n/en/components/followers/followersListComponent.json diff --git a/frontend/src/i18n/en/components/footerComponent.json b/frontend/app/src/i18n/en/components/footerComponent.json similarity index 100% rename from frontend/src/i18n/en/components/footerComponent.json rename to frontend/app/src/i18n/en/components/footerComponent.json diff --git a/frontend/src/i18n/en/components/navbarComponent.json b/frontend/app/src/i18n/en/components/navbarComponent.json similarity index 100% rename from frontend/src/i18n/en/components/navbarComponent.json rename to frontend/app/src/i18n/en/components/navbarComponent.json diff --git a/frontend/src/i18n/en/components/noItemsFoundComponent.json b/frontend/app/src/i18n/en/components/noItemsFoundComponent.json similarity index 100% rename from frontend/src/i18n/en/components/noItemsFoundComponent.json rename to frontend/app/src/i18n/en/components/noItemsFoundComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json b/frontend/app/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsIntegrationsZoneComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsSecurityZoneComponent.json b/frontend/app/src/i18n/en/components/settings/settingsSecurityZoneComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsSecurityZoneComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsSecurityZoneComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsSideBarComponent.json b/frontend/app/src/i18n/en/components/settings/settingsSideBarComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsSideBarComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsSideBarComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json b/frontend/app/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsUserProfileZoneComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json b/frontend/app/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsUsersZone/usersListComponent.json diff --git a/frontend/src/i18n/en/components/settings/settingsUsersZoneComponent.json b/frontend/app/src/i18n/en/components/settings/settingsUsersZoneComponent.json similarity index 100% rename from frontend/src/i18n/en/components/settings/settingsUsersZoneComponent.json rename to frontend/app/src/i18n/en/components/settings/settingsUsersZoneComponent.json diff --git a/frontend/src/i18n/en/gears/gearView.json b/frontend/app/src/i18n/en/gears/gearView.json similarity index 100% rename from frontend/src/i18n/en/gears/gearView.json rename to frontend/app/src/i18n/en/gears/gearView.json diff --git a/frontend/src/i18n/en/gears/gearsView.json b/frontend/app/src/i18n/en/gears/gearsView.json similarity index 100% rename from frontend/src/i18n/en/gears/gearsView.json rename to frontend/app/src/i18n/en/gears/gearsView.json diff --git a/frontend/src/i18n/en/generalItens.json b/frontend/app/src/i18n/en/generalItens.json similarity index 100% rename from frontend/src/i18n/en/generalItens.json rename to frontend/app/src/i18n/en/generalItens.json diff --git a/frontend/src/i18n/en/homeView.json b/frontend/app/src/i18n/en/homeView.json similarity index 100% rename from frontend/src/i18n/en/homeView.json rename to frontend/app/src/i18n/en/homeView.json diff --git a/frontend/src/i18n/en/loginView.json b/frontend/app/src/i18n/en/loginView.json similarity index 100% rename from frontend/src/i18n/en/loginView.json rename to frontend/app/src/i18n/en/loginView.json diff --git a/frontend/src/i18n/en/notFoundView.json b/frontend/app/src/i18n/en/notFoundView.json similarity index 100% rename from frontend/src/i18n/en/notFoundView.json rename to frontend/app/src/i18n/en/notFoundView.json diff --git a/frontend/src/i18n/en/userView.json b/frontend/app/src/i18n/en/userView.json similarity index 100% rename from frontend/src/i18n/en/userView.json rename to frontend/app/src/i18n/en/userView.json diff --git a/frontend/src/i18n/index.js b/frontend/app/src/i18n/index.js similarity index 98% rename from frontend/src/i18n/index.js rename to frontend/app/src/i18n/index.js index 4bb2d0304..79cda575e 100644 --- a/frontend/src/i18n/index.js +++ b/frontend/app/src/i18n/index.js @@ -45,8 +45,8 @@ const messages = { usersListComponent: enUsersListComponent, noItemsFoundComponent: enNoItemsFoundComponent, generalItens: enGeneralItens, - home: enHomeView, - login: enLoginView, + homeView: enHomeView, + loginView: enLoginView, gears: enGearsView, gear: enGearView, activity: enActivityView, diff --git a/frontend/src/main.js b/frontend/app/src/main.js similarity index 83% rename from frontend/src/main.js rename to frontend/app/src/main.js index 86b72c7a4..4ebbdc0d5 100644 --- a/frontend/src/main.js +++ b/frontend/app/src/main.js @@ -1,6 +1,8 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' +import { useAuthStore } from './stores/authStore'; + import "bootstrap/dist/css/bootstrap.min.css" import 'bootstrap/dist/js/bootstrap.bundle.min.js'; @@ -30,4 +32,8 @@ app.component('font-awesome-icon', FontAwesomeIcon) app.use(router) app.use(i18n); +// Import the store and load the user from the storage +const authStore = useAuthStore(); +authStore.loadUserFromStorage(i18n); + app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/app/src/router/index.js similarity index 76% rename from frontend/src/router/index.js rename to frontend/app/src/router/index.js index af29f2713..e1d488eeb 100644 --- a/frontend/src/router/index.js +++ b/frontend/app/src/router/index.js @@ -11,7 +11,7 @@ import NotFoundView from '../views/NotFoundView.vue'; //import { auth } from '@/services/auth'; -//import { useAuthStore } from '@/stores/auth'; +import { useAuthStore } from '@/stores/authStore'; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -59,26 +59,25 @@ const router = createRouter({ ] }) -/* router.beforeEach((to, from, next) => { - const accessToken = localStorage.getItem('accessToken'); - const tokenType = localStorage.getItem('tokenType'); +router.beforeEach((to, from, next) => { + const authStore = useAuthStore(); - if (!accessToken && to.path !== '/login') { + if (!authStore.isAuthenticated && to.path !== '/login') { next('/login'); - } else if (accessToken && tokenType) { - if (auth.isTokenValid(accessToken)) { + } else if (authStore.isAuthenticated ) { + //if (auth.isTokenValid(accessToken)) { if (to.path === '/login') { next('/'); } else { next(); } - } else { - auth.removeLoggedUser(); - next({ path: '/login', query: { sessionExpired: 'true' } }); - } + //} else { + // auth.removeLoggedUser(); + // next({ path: '/login', query: { sessionExpired: 'true' } }); + //} } else { next(); } -}); */ +}); export default router; \ No newline at end of file diff --git a/frontend/src/services/activities.js b/frontend/app/src/services/activitiesService.js similarity index 100% rename from frontend/src/services/activities.js rename to frontend/app/src/services/activitiesService.js diff --git a/frontend/src/services/activityStreams.js b/frontend/app/src/services/activityStreams.js similarity index 100% rename from frontend/src/services/activityStreams.js rename to frontend/app/src/services/activityStreams.js diff --git a/frontend/src/services/auth.js b/frontend/app/src/services/auth.js similarity index 94% rename from frontend/src/services/auth.js rename to frontend/app/src/services/auth.js index f2a2ea08d..26655ea8e 100644 --- a/frontend/src/services/auth.js +++ b/frontend/app/src/services/auth.js @@ -1,5 +1,5 @@ import { fetchGetRequest, fetchPostFormUrlEncoded } from '@/utils/serviceUtils'; -import { useUserStore } from '@/stores/user'; +import { useUserStore } from '@/stores/userStore'; export const auth = { isTokenValid(token) { diff --git a/frontend/src/services/followers.js b/frontend/app/src/services/followersService.js similarity index 100% rename from frontend/src/services/followers.js rename to frontend/app/src/services/followersService.js diff --git a/frontend/app/src/services/gearsService.js b/frontend/app/src/services/gearsService.js new file mode 100644 index 000000000..8df3b8f31 --- /dev/null +++ b/frontend/app/src/services/gearsService.js @@ -0,0 +1,28 @@ +import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest } from '@/utils/serviceUtils'; + +export const gears = { + getGearById(gearId) { + return fetchGetRequest(`gears/id/${gearId}`); + }, + getGearFromType(gearType) { + return fetchGetRequest(`gears/type/${gearType}`); + }, + getGearByNickname(nickname) { + return fetchGetRequest(`gears/nickname/${nickname}`); + }, + getUserGearsWithPagination(pageNumber, numRecords) { + return fetchGetRequest(`gears/page_number/${pageNumber}/num_records/${numRecords}`); + }, + getUserGearsNumber() { + return fetchGetRequest('gears/number'); + }, + createGear(data) { + return fetchPostRequest('gears/create', data) + }, + editGear(gearId, data) { + return fetchPutRequest(`gears/${gearId}/edit`, data); + }, + deleteGear(gearId) { + return fetchDeleteRequest(`gears/${gearId}/delete`); + } +}; \ No newline at end of file diff --git a/frontend/app/src/services/sessionService.js b/frontend/app/src/services/sessionService.js new file mode 100644 index 000000000..d900ba7d1 --- /dev/null +++ b/frontend/app/src/services/sessionService.js @@ -0,0 +1,10 @@ +import { fetchPostRequest, fetchPostFormUrlEncoded } from '@/utils/serviceUtils'; + +export const session = { + authenticateUser(formData) { + return fetchPostFormUrlEncoded('token', formData); + }, + logoutUser() { + return fetchPostRequest('logout'); + }, +}; \ No newline at end of file diff --git a/frontend/src/services/strava.js b/frontend/app/src/services/strava.js similarity index 100% rename from frontend/src/services/strava.js rename to frontend/app/src/services/strava.js diff --git a/frontend/src/services/user.js b/frontend/app/src/services/usersService.js similarity index 100% rename from frontend/src/services/user.js rename to frontend/app/src/services/usersService.js diff --git a/frontend/src/stores/Alerts/errorAlert.js b/frontend/app/src/stores/Alerts/errorAlert.js similarity index 100% rename from frontend/src/stores/Alerts/errorAlert.js rename to frontend/app/src/stores/Alerts/errorAlert.js diff --git a/frontend/src/stores/Alerts/infoAlert.js b/frontend/app/src/stores/Alerts/infoAlert.js similarity index 100% rename from frontend/src/stores/Alerts/infoAlert.js rename to frontend/app/src/stores/Alerts/infoAlert.js diff --git a/frontend/src/stores/Alerts/loadingAlert.js b/frontend/app/src/stores/Alerts/loadingAlert.js similarity index 100% rename from frontend/src/stores/Alerts/loadingAlert.js rename to frontend/app/src/stores/Alerts/loadingAlert.js diff --git a/frontend/src/stores/Alerts/successAlert.js b/frontend/app/src/stores/Alerts/successAlert.js similarity index 100% rename from frontend/src/stores/Alerts/successAlert.js rename to frontend/app/src/stores/Alerts/successAlert.js diff --git a/frontend/src/stores/auth.js b/frontend/app/src/stores/auth.js similarity index 100% rename from frontend/src/stores/auth.js rename to frontend/app/src/stores/auth.js diff --git a/frontend/app/src/stores/authStore.js b/frontend/app/src/stores/authStore.js new file mode 100644 index 000000000..781f78b68 --- /dev/null +++ b/frontend/app/src/stores/authStore.js @@ -0,0 +1,68 @@ +import { defineStore } from 'pinia'; + +export const useAuthStore = defineStore('auth', { + state: () => ({ + user: { + id: null, + name: '', + username: '', + email: '', + city: null, + birthdate: null, + preferred_language: '', + gender: null, + access_type: null, + photo_path: '', + is_active: null, + is_strava_linked: null, + }, + isAuthenticated: false, + }), + actions: { + setUser(userData, locale) { + this.user = userData; + localStorage.setItem('user', JSON.stringify(this.user)); + this.isAuthenticated = true; + + this.setLocale(this.user.preferred_language, locale); + }, + clearUser(locale) { + this.user = { + id: null, + name: '', + username: '', + email: '', + city: null, + birthdate: null, + preferred_language: '', + gender: null, + access_type: null, + photo_path: '', + is_active: null, + is_strava_linked: null, + }; + this.isAuthenticated = false; + localStorage.removeItem('user'); + + this.setLocale('en', locale); + }, + loadUserFromStorage(locale) { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + this.user = JSON.parse(storedUser); + this.isAuthenticated = true; + this.setLocale(this.user.locale, locale); + } + }, + setPreferredLanguage(language, locale) { + this.user.locale = language; + localStorage.setItem('user', JSON.stringify(this.user)); + + this.setLocale(language, locale); + }, + setLocale(language, locale) { + locale.value = language; + localStorage.setItem('lang', language); + } + } +}); \ No newline at end of file diff --git a/frontend/src/stores/session.js b/frontend/app/src/stores/session.js similarity index 100% rename from frontend/src/stores/session.js rename to frontend/app/src/stores/session.js diff --git a/frontend/app/src/stores/toastStore.js b/frontend/app/src/stores/toastStore.js new file mode 100644 index 000000000..0942e74f3 --- /dev/null +++ b/frontend/app/src/stores/toastStore.js @@ -0,0 +1,15 @@ +import { defineStore } from 'pinia'; + +export const useToastStore = defineStore('toast', { + state: () => ({ + toasts: [], + }), + actions: { + addToast(toast) { + this.toasts.push(toast); + }, + removeToast(index) { + this.toasts.splice(index, 1); + }, + }, +}); diff --git a/frontend/app/src/stores/userStore.js b/frontend/app/src/stores/userStore.js new file mode 100644 index 000000000..8e92ecee4 --- /dev/null +++ b/frontend/app/src/stores/userStore.js @@ -0,0 +1,119 @@ +import { defineStore } from 'pinia'; +import { useAuthStore } from '@/stores/authStore'; +// Import the services +import { activities } from '@/services/activitiesService'; +import { followers } from '@/services/followersService'; + +export const useUserStore = defineStore('user', { + state: () => ({ + authStore: useAuthStore(), + thisWeekDistances: null, + thisMonthDistances: null, + thisMonthNumberOfActivities: 0, + userNumberOfActivities: 0, + userActivities: [], + followedUserActivities: [], + userFollowersCountAll: 0, + userFollowersAll: [], + userFollowersCountAccepted: 0, + //userFollowersAccepted: [], + userFollowingCountAll: 0, + userFollowingAll: [], + userFollowingCountAccepted: 0, + //userFollowingAccepted: [], + }), + actions: { + async fetchUserStats() { + try { + this.thisWeekDistances = await activities.getUserThisWeekStats(this.authStore.user.id); + this.thisMonthDistances = await activities.getUserThisMonthStats(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserThisMonthActivitiesNumber(){ + try { + this.thisMonthNumberOfActivities = await activities.getUserThisMonthActivitiesNumber(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserActivitiesNumber(){ + try { + this.userNumberOfActivities = await activities.getUserNumberOfActivities(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserActivitiesWithPagination(pageNumber, numRecords){ + try { + const newActivities = await activities.getUserActivitiesWithPagination(this.authStore.user.id, pageNumber, numRecords); + + Array.prototype.push.apply(this.userActivities, newActivities); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowedActivitiesWithPagination(pageNumber, numRecords){ + try { + this.followedUserActivities = await activities.getUserFollowersActivitiesWithPagination(this.authStore.user.id, pageNumber, numRecords); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchNewUserActivity(activityId){ + try { + const newActivity = await activities.getActivityById(activityId); + this.userActivities.unshift(newActivity); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowersAll(){ + try { + this.userFollowersAll = await followers.getUserFollowersAll(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowersCountAll(){ + try { + this.userFollowersCountAll = await followers.getUserFollowersCountAll(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowersCountAccepted(){ + try { + this.userFollowersCountAccepted = await followers.getUserFollowersCountAccepted(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowingAll(){ + try { + this.userFollowingAll = await followers.getUserFollowingAll(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowingCountAll(){ + try { + this.userFollowingCountAll = await followers.getUserFollowingCountAll(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + async fetchUserFollowingCountAccepted(){ + try { + this.userFollowingCountAccepted = await followers.getUserFollowingCountAccepted(this.authStore.user.id); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, + // Action to reset the store state + resetStore() { + Object.assign(this.$state, getDefaultState()); + } + } +}); \ No newline at end of file diff --git a/frontend/src/utils/activityUtils.js b/frontend/app/src/utils/activityUtils.js similarity index 100% rename from frontend/src/utils/activityUtils.js rename to frontend/app/src/utils/activityUtils.js diff --git a/frontend/src/utils/dateTimeUtils.js b/frontend/app/src/utils/dateTimeUtils.js similarity index 100% rename from frontend/src/utils/dateTimeUtils.js rename to frontend/app/src/utils/dateTimeUtils.js diff --git a/frontend/src/utils/serviceUtils.js b/frontend/app/src/utils/serviceUtils.js similarity index 97% rename from frontend/src/utils/serviceUtils.js rename to frontend/app/src/utils/serviceUtils.js index a7127ecb0..a7862416f 100644 --- a/frontend/src/utils/serviceUtils.js +++ b/frontend/app/src/utils/serviceUtils.js @@ -1,4 +1,4 @@ -const API_URL = `${import.meta.env.VITE_BACKEND_PROTOCOL}://${import.meta.env.VITE_BACKEND_HOST}/`; +const API_URL = `${import.meta.env.VITE_BACKEND_PROTOCOL}://${import.meta.env.VITE_BACKEND_HOST}:${import.meta.env.VITE_BACKEND_PORT}/`; async function fetchWithRetry(url, options) { try { diff --git a/frontend/app/src/utils/toastUtils.js b/frontend/app/src/utils/toastUtils.js new file mode 100644 index 000000000..01dc57e85 --- /dev/null +++ b/frontend/app/src/utils/toastUtils.js @@ -0,0 +1,10 @@ +import { useToastStore } from '../stores/toastStore'; + +export function addToast(message, type, closable) { + const toastStore = useToastStore(); + toastStore.addToast({ + message: message, + type: type, + closable: closable, + }); +} \ No newline at end of file diff --git a/frontend/src/views/ActivityView.vue b/frontend/app/src/views/ActivityView.vue similarity index 98% rename from frontend/src/views/ActivityView.vue rename to frontend/app/src/views/ActivityView.vue index 350894e2a..b7da0f4d7 100644 --- a/frontend/src/views/ActivityView.vue +++ b/frontend/app/src/views/ActivityView.vue @@ -165,13 +165,13 @@ import { useErrorAlertStore } from '@/stores/Alerts/errorAlert'; import ActivitySummaryComponent from '@/components/Activities/ActivitySummaryComponent.vue'; import ActivityMapComponent from '@/components/Activities/ActivityMapComponent.vue'; import ActivityStreamsLineChartComponent from '@/components/Activities/ActivityStreamsLineChartComponent.vue'; -import LoadingComponent from '@/components/LoadingComponent.vue'; +import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue'; import SuccessToastComponent from '@/components/Toasts/SuccessToastComponent.vue'; import ErrorToastComponent from '@/components/Toasts/ErrorToastComponent.vue'; -import BackButtonComponent from '@/components/BackButtonComponent.vue'; +import BackButtonComponent from '@/components/GeneralComponents/BackButtonComponent.vue'; // Importing the services -import { gears } from '@/services/gears'; -import { activities } from '@/services/activities'; +import { gears } from '@/services/gearsService'; +import { activities } from '@/services/activitiesService'; import { activityStreams } from '@/services/activityStreams'; export default { diff --git a/frontend/src/views/Gears/GearView.vue b/frontend/app/src/views/Gears/GearView.vue similarity index 97% rename from frontend/src/views/Gears/GearView.vue rename to frontend/app/src/views/Gears/GearView.vue index 121e5e63a..578e33c30 100644 --- a/frontend/src/views/Gears/GearView.vue +++ b/frontend/app/src/views/Gears/GearView.vue @@ -186,14 +186,14 @@ import { useRoute, useRouter } from 'vue-router'; import { useSuccessAlertStore } from '@/stores/Alerts/successAlert'; import { useErrorAlertStore } from '@/stores/Alerts/errorAlert'; // Importing the components -import NoItemsFoundComponent from '@/components/NoItemsFoundComponents.vue'; +import NoItemsFoundComponent from '@/components/GeneralComponents/NoItemsFoundComponents.vue'; import ErrorToastComponent from '@/components/Toasts/ErrorToastComponent.vue'; import SuccessToastComponent from '@/components/Toasts/SuccessToastComponent.vue'; -import LoadingComponent from '@/components/LoadingComponent.vue'; -import BackButtonComponent from '@/components/BackButtonComponent.vue'; +import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue'; +import BackButtonComponent from '@/components/GeneralComponents/BackButtonComponent.vue'; // Importing the services -import { gears } from '@/services/gears'; -import { activities } from '@/services/activities'; +import { gears } from '@/services/gearsService'; +import { activities } from '@/services/activitiesService'; import { formatDate, formatTime } from '@/utils/dateTimeUtils'; export default { diff --git a/frontend/src/views/Gears/GearsView.vue b/frontend/app/src/views/Gears/GearsView.vue similarity index 60% rename from frontend/src/views/Gears/GearsView.vue rename to frontend/app/src/views/Gears/GearsView.vue index 6ea1c1491..6ca5d41ed 100644 --- a/frontend/src/views/Gears/GearsView.vue +++ b/frontend/app/src/views/Gears/GearsView.vue @@ -58,12 +58,6 @@
- - - - - -
@@ -72,7 +66,18 @@

{{ $t("gears.displayUserNumberOfGears1") }}{{ userGearsNumber }}{{ $t("gears.displayUserNumberOfGears2") }}{{ userGears.length }}{{ $t("gears.displayUserNumberOfGears3") }}

-
    + + + + +
      +
    • + +
    • +
    + + +
    • Bycicle avatar @@ -98,6 +103,9 @@
    + + +
@@ -111,20 +119,20 @@ diff --git a/frontend/src/views/NotFoundView.vue b/frontend/app/src/views/NotFoundView.vue similarity index 100% rename from frontend/src/views/NotFoundView.vue rename to frontend/app/src/views/NotFoundView.vue diff --git a/frontend/src/views/SettingsView.vue b/frontend/app/src/views/SettingsView.vue similarity index 97% rename from frontend/src/views/SettingsView.vue rename to frontend/app/src/views/SettingsView.vue index c60cde974..199fe03c4 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/app/src/views/SettingsView.vue @@ -41,7 +41,7 @@ import SettingsUsersZone from '../components/Settings/SettingsUsersZone.vue'; import SettingsUserProfileZone from '../components/Settings/SettingsUserProfileZone.vue'; import SettingsSecurityZone from '../components/Settings/SettingsSecurityZone.vue'; import SettingsIntegrationsZone from '../components/Settings/SettingsIntegrationsZone.vue'; -import BackButtonComponent from '@/components/BackButtonComponent.vue'; +import BackButtonComponent from '@/components/GeneralComponents/BackButtonComponent.vue'; export default { components: { diff --git a/frontend/src/views/UserView.vue b/frontend/app/src/views/UserView.vue similarity index 98% rename from frontend/src/views/UserView.vue rename to frontend/app/src/views/UserView.vue index 2c9193d18..1dec19333 100644 --- a/frontend/src/views/UserView.vue +++ b/frontend/app/src/views/UserView.vue @@ -274,22 +274,22 @@ import { ref, onMounted, computed, watch } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { useUserStore } from '@/stores/user'; -import { activities } from '@/services/activities'; -import { followers } from '@/services/followers'; +import { useUserStore } from '@/stores/userStore'; +import { activities } from '@/services/activitiesService'; +import { followers } from '@/services/followersService'; // Importing the stores import { useSuccessAlertStore } from '@/stores/Alerts/successAlert'; import { useErrorAlertStore } from '@/stores/Alerts/errorAlert'; // Importing the components import UserDistanceStatsComponent from '@/components/Activities/UserDistanceStatsComponent.vue'; -import LoadingComponent from '@/components/LoadingComponent.vue'; -import NoItemsFoundComponent from '@/components/NoItemsFoundComponents.vue'; +import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue'; +import NoItemsFoundComponent from '@/components/GeneralComponents/NoItemsFoundComponents.vue'; import ActivitySummaryComponent from '@/components/Activities/ActivitySummaryComponent.vue'; import ActivityMapComponent from '@/components/Activities/ActivityMapComponent.vue'; import ErrorToastComponent from '@/components/Toasts/ErrorToastComponent.vue'; import SuccessToastComponent from '@/components/Toasts/SuccessToastComponent.vue'; import FollowersListComponent from '@/components/Followers/FollowersListComponent.vue'; -import BackButtonComponent from '@/components/BackButtonComponent.vue'; +import BackButtonComponent from '@/components/GeneralComponents/BackButtonComponent.vue'; import UserAvatarComponent from '@/components/Users/UserAvatarComponent.vue'; export default { diff --git a/frontend/vite.config.js b/frontend/app/vite.config.js similarity index 100% rename from frontend/vite.config.js rename to frontend/app/vite.config.js diff --git a/frontend/vitest.config.js b/frontend/app/vitest.config.js similarity index 100% rename from frontend/vitest.config.js rename to frontend/app/vitest.config.js diff --git a/custom_php.ini b/frontend/custom_php.ini similarity index 100% rename from custom_php.ini rename to frontend/custom_php.ini diff --git a/frontend_env.sh b/frontend/frontend_env.sh similarity index 100% rename from frontend_env.sh rename to frontend/frontend_env.sh diff --git a/nginx-custom.conf b/frontend/nginx-custom.conf similarity index 100% rename from nginx-custom.conf rename to frontend/nginx-custom.conf diff --git a/frontend/src/services/gears.js b/frontend/src/services/gears.js deleted file mode 100644 index 4aaf9f7f8..000000000 --- a/frontend/src/services/gears.js +++ /dev/null @@ -1,28 +0,0 @@ -import { fetchGetRequest, fetchPostRequest, fetchPutRequest, fetchDeleteRequest } from '@/utils/serviceUtils'; - -export const gears = { - getGearById(gearId) { - return fetchGetRequest(`gear/id/${gearId}`); - }, - getGearFromType(gearType) { - return fetchGetRequest(`gear/type/${gearType}`); - }, - getGearByNickname(nickname) { - return fetchGetRequest(`gear/nickname/${nickname}`); - }, - getUserGearsWithPagination(pageNumber, numRecords) { - return fetchGetRequest(`gear/page_number/${pageNumber}/num_records/${numRecords}`); - }, - getUserGearsNumber() { - return fetchGetRequest('gear/number'); - }, - createGear(data) { - return fetchPostRequest('gear/create', data) - }, - editGear(gearId, data) { - return fetchPutRequest(`gear/${gearId}/edit`, data); - }, - deleteGear(gearId) { - return fetchDeleteRequest(`gear/${gearId}/delete`); - } -}; \ No newline at end of file diff --git a/frontend/src/services/session.js b/frontend/src/services/session.js deleted file mode 100644 index 5116882d1..000000000 --- a/frontend/src/services/session.js +++ /dev/null @@ -1,7 +0,0 @@ -import { fetchGetRequest, fetchPostFormUrlEncoded } from '@/utils/serviceUtils'; - -export const session = { - getToken(formData) { - return fetchPostFormUrlEncoded('token', formData); - }, -}; \ No newline at end of file diff --git a/frontend/src/stores/user.js b/frontend/src/stores/user.js deleted file mode 100644 index 5e4d008ba..000000000 --- a/frontend/src/stores/user.js +++ /dev/null @@ -1,228 +0,0 @@ -import { defineStore } from 'pinia'; -import { activities } from '@/services/activities'; -import { followers } from '@/services/followers'; -import { users } from '@/services/user'; - -// Function to get the default state -const getDefaultState = () => ({ - userMe: null, - thisWeekDistances: null, - thisMonthDistances: null, - thisMonthNumberOfActivities: 0, - userNumberOfActivities: 0, - userActivities: [], - followedUserActivities: [], - userFollowersCountAll: 0, - userFollowersAll: [], - userFollowersCountAccepted: 0, - //userFollowersAccepted: [], - userFollowingCountAll: 0, - userFollowingAll: [], - userFollowingCountAccepted: 0, - //userFollowingAccepted: [], -}); - -export const useUserStore = defineStore('user', { - state: getDefaultState, - actions: { - async fetchUserMe(user_id) { - try { - this.userMe = await users.getUserById(user_id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the user's statistics for this week and this month. - * @async - * @function fetchUserStats - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @returns {Promise} A promise that resolves when the data is fetched successfully. - */ - async fetchUserStats() { - try { - this.thisWeekDistances = await activities.getUserThisWeekStats(this.userMe.id); - this.thisMonthDistances = await activities.getUserThisMonthStats(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the number of activities for the current user in the current month. - * @async - * @function fetchUserThisMonthActivitiesNumber - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @returns {Promise} A promise that resolves when the data is fetched successfully. - */ - async fetchUserThisMonthActivitiesNumber(){ - try { - this.thisMonthNumberOfActivities = await activities.getUserThisMonthActivitiesNumber(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the number of activities for the user. - * @async - * @function fetchUserActivitiesNumber - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @returns {Promise} A promise that resolves when the data is fetched successfully. - */ - async fetchUserActivitiesNumber(){ - try { - this.userNumberOfActivities = await activities.getUserNumberOfActivities(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches user activities with pagination. - * @async - * @function fetchUserActivitiesWithPagination - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @param {number} pageNumber - The page number to fetch. - * @param {number} numRecords - The number of records to fetch per page. - * @returns {Promise} - A promise that resolves when the user activities are fetched successfully. - */ - async fetchUserActivitiesWithPagination(pageNumber, numRecords){ - try { - const newActivities = await activities.getUserActivitiesWithPagination(this.userMe.id, pageNumber, numRecords); - - Array.prototype.push.apply(this.userActivities, newActivities); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the user's followed activities with pagination. - * @async - * @function fetchUserFollowedActivitiesWithPagination - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @param {number} pageNumber - The page number to fetch. - * @param {number} numRecords - The number of records to fetch per page. - * @returns {Promise} - A promise that resolves when the data is fetched successfully. - */ - async fetchUserFollowedActivitiesWithPagination(pageNumber, numRecords){ - try { - this.followedUserActivities = await activities.getUserFollowersActivitiesWithPagination(this.userMe.id, pageNumber, numRecords); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches new user activity by activity ID and adds it to the beginning of the user activities array. - * @async - * @function fetchNewUserActivity - * @memberof module:stores/user - * @throws {Error} If there is an error fetching the data. - * @param {string} activityId - The ID of the activity to fetch. - * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. - */ - async fetchNewUserActivity(activityId){ - try { - const newActivity = await activities.getActivityById(activityId); - this.userActivities.unshift(newActivity); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches all followers of the user. - * @async - * @function fetchUserFollowersAll - * @memberof module:stores/user - * @instance - * @throws {Error} If there is an error while fetching the data. - */ - async fetchUserFollowersAll(){ - try { - this.userFollowersAll = await followers.getUserFollowersAll(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the count of followers for the current user. - * @async - * @function fetchUserFollowersCountAll - * @memberof module:stores/user - * @throws {Error} If there is an error while fetching the data. - * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. - */ - async fetchUserFollowersCountAll(){ - try { - this.userFollowersCountAll = await followers.getUserFollowersCountAll(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the count of accepted followers for the current user. - * @async - * @function fetchUserFollowersCountAccepted - * @memberof module:stores/user - * @throws {Error} If there is an error while fetching the data. - * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. - */ - async fetchUserFollowersCountAccepted(){ - try { - this.userFollowersCountAccepted = await followers.getUserFollowersCountAccepted(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches all the users that the current user is following. - * @async - * @function fetchUserFollowingAll - * @memberof module:stores/user - * @throws {Error} If there is an error while fetching the data. - */ - async fetchUserFollowingAll(){ - try { - this.userFollowingAll = await followers.getUserFollowingAll(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the count of users being followed by the current user. - * @async - * @function fetchUserFollowingCountAll - * @memberof module:stores/user - * @throws {Error} If there is an error while fetching the data. - * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. - */ - async fetchUserFollowingCountAll(){ - try { - this.userFollowingCountAll = await followers.getUserFollowingCountAll(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - /** - * Fetches the count of accepted user followings. - * @async - * @function fetchUserFollowingCountAccepted - * @memberof module:stores/user - * @throws {Error} If there is an error while fetching the data. - * @returns {Promise} - A promise that resolves when the new activity is fetched and added successfully. - */ - async fetchUserFollowingCountAccepted(){ - try { - this.userFollowingCountAccepted = await followers.getUserFollowingCountAccepted(this.userMe.id); - } catch (error) { - console.error("Failed to fetch data:", error); - } - }, - // Action to reset the store state - resetStore() { - Object.assign(this.$state, getDefaultState()); - } - } -}); \ No newline at end of file diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue deleted file mode 100644 index f6d06c8ef..000000000 --- a/frontend/src/views/LoginView.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0cab1fc1e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -fastapi==0.111.0 -pydantic==1.10.15 -uvicorn==0.29.0 -python-dotenv==1.0.1 -sqlalchemy==2.0.30 -mysqlclient==2.2.4 -apscheduler==3.10.4 -requests==2.32.2 -stravalib==1.7 -opentelemetry-sdk==1.22.0 -opentelemetry-instrumentation-fastapi==0.43b0 -opentelemetry.exporter.otlp==1.22.0 -python-multipart==0.0.9 -gpxpy==1.6.2 -alembic==1.13.1 -joserfc==0.11.1 -bcrypt==4.1.3 \ No newline at end of file