From a2c6d6f2588a1c0d348833b969b7b90f54c6fd51 Mon Sep 17 00:00:00 2001 From: Leon Date: Sun, 24 Aug 2025 00:16:48 +0200 Subject: [PATCH 1/2] use reflex --- .gitignore | 4 + .pre-commit-config.yaml | 20 +- CHANGELOG.md | 6 + README.md | 14 +- docs/images/main_window.png | Bin 113870 -> 0 bytes docs/images/working_time_verification.png | Bin 0 -> 113683 bytes .../fwtv => factorialhr_analysis}/__init__.py | 0 factorialhr_analysis/components/__init__.py | 4 + .../components/date_range_selector.py | 53 + factorialhr_analysis/components/navbar.py | 71 ++ factorialhr_analysis/factorialhr_analysis.py | 13 + factorialhr_analysis/pages/__init__.py | 4 + factorialhr_analysis/pages/index.py | 478 +++++++ factorialhr_analysis/pages/login.py | 133 ++ factorialhr_analysis/routes.py | 4 + factorialhr_analysis/state.py | 181 +++ factorialhr_analysis/templates.py | 18 + .../working_time_verification/__init__.py | 4 + .../working_time_verification/helper.py | 82 ++ .../working_time_verification/verification.py | 106 ++ pyproject.toml | 40 +- rxconfig.py | 11 + src/fwtv/main.py | 73 -- src/fwtv/objects/__init__.py | 0 src/fwtv/objects/async_converter.py | 64 - src/fwtv/verifier.py | 138 -- src/fwtv/widgets/__init__.py | 0 src/fwtv/widgets/login_widget.py | 58 - src/fwtv/widgets/settings_widget.py | 97 -- src/fwtv/widgets/table_widget.py | 160 --- src/fwtv/widgets/working_time_widget.py | 140 --- tests/test_verifier.py | 315 ----- tests/test_working_time_verification.py | 332 +++++ uv.lock | 1110 ++++++++--------- 34 files changed, 2064 insertions(+), 1669 deletions(-) delete mode 100644 docs/images/main_window.png create mode 100644 docs/images/working_time_verification.png rename {src/fwtv => factorialhr_analysis}/__init__.py (100%) create mode 100644 factorialhr_analysis/components/__init__.py create mode 100644 factorialhr_analysis/components/date_range_selector.py create mode 100644 factorialhr_analysis/components/navbar.py create mode 100644 factorialhr_analysis/factorialhr_analysis.py create mode 100644 factorialhr_analysis/pages/__init__.py create mode 100644 factorialhr_analysis/pages/index.py create mode 100644 factorialhr_analysis/pages/login.py create mode 100644 factorialhr_analysis/routes.py create mode 100644 factorialhr_analysis/state.py create mode 100644 factorialhr_analysis/templates.py create mode 100644 factorialhr_analysis/working_time_verification/__init__.py create mode 100644 factorialhr_analysis/working_time_verification/helper.py create mode 100644 factorialhr_analysis/working_time_verification/verification.py create mode 100644 rxconfig.py delete mode 100644 src/fwtv/main.py delete mode 100644 src/fwtv/objects/__init__.py delete mode 100644 src/fwtv/objects/async_converter.py delete mode 100644 src/fwtv/verifier.py delete mode 100644 src/fwtv/widgets/__init__.py delete mode 100644 src/fwtv/widgets/login_widget.py delete mode 100644 src/fwtv/widgets/settings_widget.py delete mode 100644 src/fwtv/widgets/table_widget.py delete mode 100644 src/fwtv/widgets/working_time_widget.py delete mode 100644 tests/test_verifier.py create mode 100644 tests/test_working_time_verification.py diff --git a/.gitignore b/.gitignore index c97d552..b008d09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +*.db +.web +assets/external/ +.states # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f350a0e..fa145b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,13 +5,13 @@ # * Run "pre-commit install". repos: - repo: https://github.com/adrienverge/yamllint - rev: v1.35.1 + rev: v1.37.1 hooks: - id: yamllint args: ['-d {extends: relaxed, rules: { line-length: disable }}', '-s'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-toml - id: check-yaml @@ -24,30 +24,18 @@ repos: - id: check-illegal-windows-names - repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.6.3 + rev: 0.8.13 hooks: # Update the uv lockfile - id: uv-lock - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 + rev: v0.12.10 hooks: - id: ruff args: [--fix, --show-fixes] - id: ruff-format - - repo: https://github.com/RobertCraigie/pyright-python - rev: v1.1.394 - hooks: - - id: pyright - additional_dependencies: - - pytest >= 8 - - factorialhr==4.0.0b3 - - tabulate>=0.9.0 - - pyside6-essentials>=6.8.2.1 - - outcome>=1.3.0.post0 - - trio>=0.29.0 - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index d20a1a1..105ce54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to fwtv module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0) +## [Unreleased] + +## [v3.0.0] - + +### Changed app to run in the browser using reflex + ## [2.4.1] - 2025-07-07 ### Fixed diff --git a/README.md b/README.md index 8642c8f..834067c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,9 @@ This script verifies attendances whether they comply with german law. In particu - Whether the work time is longer than 6 hours without a break of 30 min - Whether the work time is longer than 9 hours without a break of 45 min - Whether the work time is longer than 10 hours without a break of 11 hours +- Whether the work time is within the time of 6am and 10pm -It also provides a way to fetch the attendances of all employees from [FactorialHR](https://apidoc.factorialhr.com/docs) using a [company api-key](https://help.factorialhr.com/how-to-create-api-keys-in-factorial). - -![main_window](./docs/images/main_window.png "Main Window") +![main_window](./docs/images/working_time_verification.png "Main Window") ## Disclaimer @@ -19,12 +18,9 @@ Errors where the time attended is 1 min above the limit are ignored, because fac ## Usage -- Install the tool with `pip install fwtv` -- Run tool with `factorial-working-time` - -### Preconditions - -Preconditions errors are syntactical errors like an attendance that starts and end and the same time, or if a `clock_in` or `clock_out` parameter is missing. +- clone this repository +- install dependencies using `uv sync --frozen` +- run app `uv run reflex run --env prod` ## Contributing diff --git a/docs/images/main_window.png b/docs/images/main_window.png deleted file mode 100644 index aa026ed3a328729e86b4a7a2e5559290d0d462ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113870 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 z 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�yK~8Fa}K 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 rx.Component: + """Date range picker component.""" + return rx.dialog.root( + rx.dialog.trigger(rx.button('Select Date Range')), + rx.dialog.content( + rx.dialog.title('Select Date Range'), + rx.dialog.description('Choose start and end dates'), + rx.vstack( + rx.text('Start Date:'), + rx.input( + type='date', value=DateRangeState.temp_start_date, on_change=DateRangeState.set_temp_start_date + ), + rx.text('End Date:'), + rx.input(type='date', value=DateRangeState.temp_end_date, on_change=DateRangeState.set_temp_end_date), + rx.flex( + rx.dialog.close(rx.button('Cancel')), + rx.dialog.close(rx.button('Apply', on_click=DateRangeState.apply_date_range)), + spacing='3', + justify='end', + ), + spacing='4', + ), + ), + ) + + +def date_inputs() -> rx.Component: + """Display the selected date range in read-only input fields.""" + return rx.hstack( + rx.input(placeholder='Start Date', value=DateRangeState.start_date, read_only=True), + rx.input(placeholder='End Date', value=DateRangeState.end_date, read_only=True), + spacing='2', + ) diff --git a/factorialhr_analysis/components/navbar.py b/factorialhr_analysis/components/navbar.py new file mode 100644 index 0000000..d361f43 --- /dev/null +++ b/factorialhr_analysis/components/navbar.py @@ -0,0 +1,71 @@ +"""The navigation bar component.""" + +import reflex as rx +from reflex.style import color_mode, set_color_mode + +from factorialhr_analysis import routes, state + + +def dark_mode_toggle() -> rx.Component: + """Toggle for dark/light mode.""" + return rx.segmented_control.root( + rx.segmented_control.item( + rx.icon(tag='monitor', size=20), + value='system', + ), + rx.segmented_control.item( + rx.icon(tag='sun', size=20), + value='light', + ), + rx.segmented_control.item( + rx.icon(tag='moon', size=20), + value='dark', + ), + on_change=set_color_mode, + variant='classic', + radius='large', + value=color_mode, + ) + + +def navbar_link(text: str, url: str) -> rx.Component: + """Link in the navigation bar.""" + return rx.link(rx.text(text, size='4', weight='medium'), href=url) + + +def navbar() -> rx.Component: + """Navigation bar component.""" + return rx.box( + rx.desktop_only( + rx.hstack( + rx.heading('Factorialhr analysis', size='7', weight='bold'), + rx.hstack( + navbar_link('Working time verification', '/#'), + spacing='5', + ), + rx.hstack( + rx.menu.root( + rx.menu.trigger( + rx.icon_button( + rx.icon('user'), + size='2', + radius='full', + ) + ), + rx.menu.content( + rx.menu.item( + rx.link(rx.text('Log out'), href=routes.INDEX, on_click=state.LoginState.logout) + ), + ), + justify='end', + ), + dark_mode_toggle(), + ), + justify='between', + align_items='center', + ), + ), + bg=rx.color('accent', 3), + padding='1em', + width='100%', + ) diff --git a/factorialhr_analysis/factorialhr_analysis.py b/factorialhr_analysis/factorialhr_analysis.py new file mode 100644 index 0000000..ee402d3 --- /dev/null +++ b/factorialhr_analysis/factorialhr_analysis.py @@ -0,0 +1,13 @@ +"""Main app file for the FactorialHR Analysis application.""" + +import dotenv +import reflex as rx + +from factorialhr_analysis import pages, routes + +dotenv.load_dotenv() + + +app = rx.App() +app.add_page(pages.index_page, route=routes.INDEX) +app.add_page(pages.login_page, route=routes.LOGIN_ROUTE) diff --git a/factorialhr_analysis/pages/__init__.py b/factorialhr_analysis/pages/__init__.py new file mode 100644 index 0000000..70e8f6b --- /dev/null +++ b/factorialhr_analysis/pages/__init__.py @@ -0,0 +1,4 @@ +from factorialhr_analysis.pages.index import index_page +from factorialhr_analysis.pages.login import login_page + +__all__ = ['index_page', 'login_page'] diff --git a/factorialhr_analysis/pages/index.py b/factorialhr_analysis/pages/index.py new file mode 100644 index 0000000..f32f55f --- /dev/null +++ b/factorialhr_analysis/pages/index.py @@ -0,0 +1,478 @@ +"""The main page of the app.""" + +import asyncio +import collections +import csv +import datetime +import io +import os +import typing +from collections.abc import Container, Sequence + +import anyio.from_thread +import factorialhr +import reflex as rx +from reflex.utils.prerequisites import get_app + +from factorialhr_analysis import state, templates, working_time_verification + + +def time_to_moment(time_: datetime.time | None) -> rx.MomentDelta: + """Convert a datetime.time to a rx.MomentDelta.""" + if time_ is None: + return rx.MomentDelta() + return rx.MomentDelta(hours=time_.hour, minutes=time_.minute, seconds=time_.second) + + +class Attendance(typing.TypedDict): + """TypedDict for attendance.""" + + date: datetime.date + clock_in: rx.MomentDelta + clock_out: rx.MomentDelta + minutes: rx.MomentDelta + + +class ErrorToShow(typing.TypedDict): + """TypedDict for errors to show.""" + + name: str + affected_days: str + error: str + cumulated_break: datetime.timedelta + cumulated_attendance: datetime.timedelta + + attendances: Sequence[Attendance] + + +class DataState(rx.State): + """State holding all the data.""" + + _shifts: collections.defaultdict[int, list[factorialhr.AttendanceShift]] = collections.defaultdict( # noqa: RUF012 + list + ) # employee id as key + _employees: list[factorialhr.Employee] = [] # noqa: RUF012 + _employee_team_name_mapping: dict[int, list[str]] = {} # noqa: RUF012 + calculated_errors: list[working_time_verification.Error] = [] # noqa: RUF012 + errors_to_show: list[ErrorToShow] = [] # noqa: RUF012 + start_date: str = '' + end_date: str = '' + is_loading: bool = False # Guard flag + tolerance: str = '' + processed_employees: int = 0 # Number of employees processed so far + + filter_value: str = '' # Placeholder for search functionality + + selected_error_ids: list[int] = [] # noqa: RUF012 + + @rx.var + def date_error(self) -> bool: + """Check if the end date is before the start date.""" + if not self.start_date or not self.end_date: + return False + return datetime.date.fromisoformat(self.end_date) < datetime.date.fromisoformat(self.start_date) + + @rx.var + def disable_submit(self) -> bool: + """Disable the submit button if there is a date error.""" + return self.date_error or not self.start_date or not self.end_date + + def _should_cancel(self) -> bool: + """Check if the current session is still valid.""" + return self.router.session.client_token not in get_app().app.event_namespace.token_to_sid + + def _cleanup(self): + """Cleanup method to reset state.""" + self._shifts = collections.defaultdict(list) + self.processed_employees = 0 + self.errors_to_show = [] + self._employees = [] + self._employee_team_name_mapping = {} + self.selected_error_ids = [] + self.is_loading = False + + @rx.var + def length_of_employees(self) -> int: + """Get the length of employees.""" + return len(self._employees) + + async def _handle_employee( + self, client: factorialhr.ApiClient, employee: factorialhr.Employee, teams: Sequence[factorialhr.Team] + ): + """Handle fetching shifts for an employee.""" + async with self: + self._employees.append(employee) + self._employee_team_name_mapping[employee.id] = [ + t.name for t in teams if t.employee_ids and employee.id in t.employee_ids + ] + shifts = await factorialhr.ShiftsEndpoint(client).all( + params={'start_on': self.start_date, 'end_on': self.end_date, 'employee_ids[]': [employee.id]}, + timeout=60, + ) + async with self: + for shift in shifts.data(): + if self._should_cancel(): + self._cleanup() + return + self._shifts[employee.id].append(shift) + + @rx.event(background=True) + async def handle_submit(self, form_data: dict): + """Fetch employees and teams data.""" + async with self: + if self.is_loading: + return + + self.start_date = form_data.get('start_date') + self.end_date = form_data.get('end_date') + self.is_loading = True + self._shifts = collections.defaultdict(list) + self.processed_employees = 0 + self.errors_to_show = [] + self._employees = [] + self._employee_team_name_mapping = {} + self.selected_error_ids = [] + yield # Send initial state update to frontend + + try: + async with self: + api_session = (await self.get_state(state.LoginState)).get_auth() + # API calls outside async with block + async with factorialhr.ApiClient(os.environ['FACTORIALHR_ENVIRONMENT_URL'], auth=api_session) as client: + employees = await factorialhr.EmployeesEndpoint(client).all() + teams = list((await factorialhr.TeamsEndpoint(client).all()).data()) + async with anyio.from_thread.create_task_group() as tg: + for employee in employees.data(): + tg.start_soon(self._handle_employee, client, employee, teams) + + async with self: + yield DataState.fill_errors_to_show # set is_loading to false + + except asyncio.CancelledError: + # Handle cancellation when page is reloaded/closed + async with self: + self._cleanup() + raise + except Exception: + # Handle other errors + async with self: + self._cleanup() + raise + + @rx.event + async def fill_errors_to_show(self): + """Fill the errors_to_show list based on the fetched data.""" + self.selected_error_ids = [] + self.errors_to_show = [] + self.processed_employees = 0 + self.is_loading = True + yield + tolerance = datetime.timedelta(minutes=int(self.tolerance) if self.tolerance.isdigit() else 0) + value = self.filter_value.lower() + for employee in self._employees: + teams = self._employee_team_name_mapping.get(employee.id, []) + if value in employee.full_name.lower() or any(value in team.lower() for team in teams): + for error in working_time_verification.get_error(self._shifts[employee.id], tolerance=tolerance): + if self._should_cancel(): + self._cleanup() + return + self.errors_to_show.append( + ErrorToShow( + name=employee.full_name, + affected_days=', '.join(str(d) for d in error.days_affected), + error=error.reason, + cumulated_break=error.break_time, + cumulated_attendance=error.time_attended, + attendances=[ + Attendance( + date=a.date, + clock_in=time_to_moment(a.clock_in) if a.clock_in is not None else None, + clock_out=time_to_moment(a.clock_out) if a.clock_out is not None else None, + minutes=rx.MomentDelta(minutes=a.minutes), + ) + for a in error.attendances + ], + ) + ) + yield + self.processed_employees += 1 + self.is_loading = False + + @rx.event + def filter_employees(self, value: str): + """Filter employees based on the search value.""" + self.filter_value = value + yield DataState.fill_errors_to_show + + @rx.event + def set_tolerance(self, value: str): + """Set the tolerance value.""" + if value == '' or value.isdigit(): + self.tolerance = value + yield DataState.fill_errors_to_show + + @rx.event + def select_row(self, index: int): + """Handle row selection.""" + if index in self.selected_error_ids: + self.selected_error_ids.remove(index) + else: + self.selected_error_ids.append(index) + + def _convert_to_csv(self, indices: Container[int]) -> str: + # Create a string buffer to hold the CSV data + with io.StringIO() as output: + writer = csv.DictWriter( + output, fieldnames=['Name', 'Affected Days', 'Cumulated Break', 'Cumulated Attendance', 'Error'] + ) + writer.writeheader() + for index, error in enumerate(self.errors_to_show): + if index in indices: + writer.writerow( + { + 'Name': error['name'], + 'Affected Days': error['affected_days'], + 'Cumulated Break': error['cumulated_break'], + 'Cumulated Attendance': error['cumulated_attendance'], + 'Error': error['error'], + } + ) + + # Get the CSV data as a string + return output.getvalue() + + @rx.event + def download(self, data: str): + """Download the given data as a CSV file.""" + file_name = ( + f'{self.start_date}-{self.end_date}_errors.csv' if self.start_date and self.end_date else 'errors.csv' + ) + yield rx.download( + data=data, + filename=file_name, + ) + + @rx.event + def download_all_errors(self): + """Download all errors as a CSV file.""" + csv_data = self._convert_to_csv(range(len(self.errors_to_show))) + yield self.download(csv_data) + + @rx.event + def download_selected_errors(self): + """Download selected errors as a CSV file.""" + csv_data = self._convert_to_csv(self.selected_error_ids) + yield self.download(csv_data) + + +def render_input() -> rx.Component: + """Render the date input form.""" + return rx.form( + rx.hstack( + rx.text('Start date'), + rx.input( + type='date', + name='start_date', + value=DataState.start_date, + on_change=DataState.set_start_date, + ), + rx.text('End date'), + rx.input( + type='date', + name='end_date', + value=DataState.end_date, + on_change=DataState.set_end_date, + ), + rx.button( + 'Submit', + type='submit', + loading=DataState.is_loading, + disabled=DataState.disable_submit, + ), + rx.cond( + DataState.date_error, + rx.text('End date must be after start date', color='red'), + ), + spacing='3', + align='center', + width='100%', + ), + on_submit=DataState.handle_submit, + width='100%', + ) + + +def render_export_buttons() -> rx.Component: + """Render the export buttons.""" + return rx.hstack( + rx.button( + 'Export Selected', + disabled=DataState.selected_error_ids.length() == 0, + on_click=DataState.download_selected_errors, + ), + rx.button( + 'Export All', disabled=DataState.errors_to_show.length() == 0, on_click=DataState.download_all_errors + ), + justify='center', + align='center', + width='100%', + ) + + +def render_search() -> rx.Component: + """Render the search input.""" + return rx.hstack( + rx.text('Search'), + rx.input( + value=DataState.filter_value, + on_change=DataState.filter_employees, + width='100%', + placeholder='Filter by name or team', + ), + width='50%', + align='center', + ) + + +def render_tolerance_input() -> rx.Component: + """Render the tolerance input.""" + return rx.hstack( + rx.text('Tolerance'), + rx.input( + placeholder='Minutes', + type='number', + value=DataState.tolerance, + on_change=DataState.set_tolerance, + width='100%', + regex=r'^\d*$', + min=0, + ), + width='25%', + align='center', + ) + + +def render_filters() -> rx.Component: + """Render the filters section.""" + return rx.hstack( + render_tolerance_input(), + render_search(), + width='100%', + align='center', + justify='end', + ) + + +def show_employee(error: rx.Var[ErrorToShow], index: int) -> rx.Component: + """Show a customer in a table row.""" + return rx.table.row( + rx.table.cell(error['name']), + rx.table.cell(error['affected_days']), + rx.table.cell(error['cumulated_break']), + rx.table.cell(error['cumulated_attendance']), + rx.table.cell(error['error'], align='left'), + rx.table.cell( + rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.icon_button('info'), + ), + rx.alert_dialog.content( + rx.alert_dialog.title('Relevant attendance records'), + rx.inset( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell('Date'), + rx.table.column_header_cell('Clock in'), + rx.table.column_header_cell('Clock out'), + rx.table.column_header_cell('Hours attended'), + ), + ), + rx.table.body( + rx.foreach( + error['attendances'], + lambda x: rx.table.row( + rx.table.cell(rx.moment(x['date'], format='YYYY-MM-DD')), + rx.table.cell( + rx.cond( + x['clock_in'].is_none(), + None, + rx.moment(x['date'], add=x['clock_in'], format='HH:mm'), + ) + ), + rx.table.cell( + rx.cond( + x['clock_out'].is_none(), + None, + rx.moment(x['date'], add=x['clock_out'], format='HH:mm'), + ) + ), + rx.table.cell(rx.moment(x['date'], add=x['minutes'], format='HH:mm')), + ), + ) + ), + ), + side='x', + margin_top='24px', + margin_bottom='24px', + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button( + 'Close', + variant='soft', + ), + ), + justify='end', + ), + ), + ), + align='right', + ), + on_click=lambda: DataState.select_row(index), + background_color=rx.cond(DataState.selected_error_ids.contains(index), rx.color('blue', 3), 'transparent'), + ) + + +def render_table() -> rx.Component: + """Render the main table showing errors.""" + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell('Name', min_width='17.5%', max_width='17.5%'), + rx.table.column_header_cell('Affected Days', min_width='15%', max_width='15%'), + rx.table.column_header_cell('Cumulated Break', min_width='12.5%', max_width='12.5%'), + rx.table.column_header_cell('Cumulated Attendance', min_width='12.5%', max_width='12.5%'), + rx.table.column_header_cell('Error', min_width='40%', max_width='40%'), + rx.table.column_header_cell('Records', align='right', min_width='2.5%', max_width='2.5%'), + ), + ), + rx.table.body( + rx.foreach( + DataState.errors_to_show, + show_employee, + ) + ), + width='100%', + ) + + +def live_progress() -> rx.Component: + """Show a live progress bar when loading data.""" + return rx.cond( + ~DataState.is_loading, + rx.fragment(), + rx.progress(value=DataState.processed_employees, max=DataState.length_of_employees), + ) + + +@templates.template +@state.redirect_for_login +def index_page() -> rx.Component: + """Index page of the app.""" + return rx.vstack( + rx.hstack(render_input(), render_export_buttons(), render_filters(), justify='between', width='100%'), + live_progress(), + render_table(), + width='100%', + ) diff --git a/factorialhr_analysis/pages/login.py b/factorialhr_analysis/pages/login.py new file mode 100644 index 0000000..8b29739 --- /dev/null +++ b/factorialhr_analysis/pages/login.py @@ -0,0 +1,133 @@ +"""Login page.""" + +import functools +import typing + +import httpx +import reflex as rx + +from factorialhr_analysis import routes, state, templates + + +class LoginFormState(rx.State): + """State for the login form.""" + + user_entered_authorization_code: str + auth_code_invalid: bool = False + auth_code_invalid_message: str + + @rx.var + def authorization_code_empty(self) -> bool: + """Check if the authorization code input is empty.""" + return not self.user_entered_authorization_code.strip() + + @rx.var + def authorization_invalid(self) -> bool: + """Check if the authorization code is invalid.""" + return self.auth_code_invalid + + @rx.var + def authorization_code_invalid_message(self) -> str: + """Get the authorization code invalid message.""" + return self.auth_code_invalid_message + + @rx.event + async def handle_submit(self, form_data: dict[str, typing.Any]): + """Handle form submission.""" + self.auth_code_invalid = False + login_state = await self.get_state(state.LoginState) + try: + await login_state.login(form_data.get('authorization_code'), grant_type='authorization_code') + yield rx.redirect(routes.INDEX) + except httpx.HTTPStatusError as e: + self.auth_code_invalid = True + self.auth_code_invalid_message = f'Login failed with status code: {e.response.status_code}' + + +def authorization_code_form() -> rx.Component: + """Form for the authorization code input.""" + return rx.form.root( + rx.form.field( + rx.vstack( + rx.form.label( + 'Authorization Code', + size='3', + weight='medium', + text_align='left', + width='100%', + ), + rx.form.control( + rx.input( + name='authorization_code', + size='3', + width='100%', + on_change=LoginFormState.set_user_entered_authorization_code, + ), + as_child=True, + ), + rx.cond( + LoginFormState.authorization_invalid, + rx.form.message( + LoginFormState.authorization_code_invalid_message, + color='var(--red-11)', + ), + ), + rx.button( + 'Sign in', size='2', width='100%', type='submit', disabled=LoginFormState.authorization_code_empty + ), + spacing='2', + width='100%', + ), + name='authorization_code', + server_invalid=LoginFormState.auth_code_invalid, + ), + on_submit=LoginFormState.handle_submit, + reset_on_submit=True, + ) + + +def redirect_if_authenticated(page: rx.app.ComponentCallable) -> rx.app.ComponentCallable: + """Redirect authenticated users away from login page.""" + + @functools.wraps(page) + def login_page_wrapper() -> rx.Component: + return rx.cond( + state.LoginState.is_hydrated, + rx.cond( + state.LoginState.is_authenticated, + rx.spinner(on_mount=state.LoginState.redir), + page(), # Show login form if not authenticated + ), + rx.spinner(), + ) + + return login_page_wrapper + + +@templates.template +@redirect_if_authenticated +def login_page() -> rx.Component: + """Login page.""" + return rx.center( + rx.card( + rx.vstack( + rx.center( + rx.heading( + 'Login to FactorialHR', + size='6', + as_='h2', + text_align='center', + width='100%', + ), + direction='column', + spacing='5', + width='100%', + ), + authorization_code_form(), + ), + size='4', + max_width='28em', + width='100%', + ), + height='100vh', + ) diff --git a/factorialhr_analysis/routes.py b/factorialhr_analysis/routes.py new file mode 100644 index 0000000..13abc2f --- /dev/null +++ b/factorialhr_analysis/routes.py @@ -0,0 +1,4 @@ +"""Routes for the application.""" + +INDEX = '/' +LOGIN_ROUTE = '/login' diff --git a/factorialhr_analysis/state.py b/factorialhr_analysis/state.py new file mode 100644 index 0000000..4a32067 --- /dev/null +++ b/factorialhr_analysis/state.py @@ -0,0 +1,181 @@ +"""Login state.""" + +import functools +import logging +import os +import time +import typing + +import factorialhr +import httpx +import pydantic +import reflex as rx + +from factorialhr_analysis import routes + +# Set up logging +logging.basicConfig(level=logging.WARNING) +logger = logging.getLogger(__name__) + + +class ApiSession(pydantic.BaseModel): + """Wrapper class for the API session cookie.""" + + access_token: str + refresh_token: str + created_at: int + token_type: str + + def access_token_expiration(self) -> int: + """Get the expiration date of the access token.""" + return self.created_at + 60 * 60 # access token is valid for 1 hour + + def is_access_token_expired(self) -> bool: + """Determine whether the access token is expired or not.""" + return self.access_token_expiration() <= time.time() + + def refresh_token_expiration(self) -> int: + """Get the expiration date of the refresh token.""" + return self.created_at + 7 * 24 * 60 * 60 # refresh token is valid for 1 week + + def is_refresh_token_expired(self) -> bool: + """Determine whether the refresh token is expired or not.""" + return self.refresh_token_expiration() <= time.time() + + +class LoginState(rx.State): + """State for managing login and authentication.""" + + api_session_cookie: str = rx.Cookie( + name='api_session', + same_site='strict', + ) + redirect_to: str = '' + + def get_auth(self) -> factorialhr.AccessTokenAuth: + """Get the authentication object for the API session.""" + api_session = self.api_session() + if api_session is None: + msg = 'api_session_cookie must be valid' + raise RuntimeError(msg) + return factorialhr.AccessTokenAuth( + access_token=api_session.access_token, + token_type=api_session.token_type, + ) + + def api_session(self) -> ApiSession | None: + """Get the API session from the cookie.""" + if not self.api_session_cookie: + return None + try: + return ApiSession.model_validate_json(self.api_session_cookie) + except pydantic.ValidationError as e: + logger.exception('parsing cookie failed', exc_info=e) + return None + + @rx.event + async def login(self, token: str, *, grant_type: typing.Literal['refresh_token', 'authorization_code']) -> None: + """Log in to the API and store the session cookie.""" + if grant_type == 'refresh_token': + data = { + 'client_id': os.environ['FACTORIALHR_CLIENT_ID'], + 'client_secret': os.environ['FACTORIALHR_CLIENT_SECRET'], + 'grant_type': 'refresh_token', + 'refresh_token': token, + } + else: + data = { + 'client_id': os.environ['FACTORIALHR_CLIENT_ID'], + 'client_secret': os.environ['FACTORIALHR_CLIENT_SECRET'], + 'code': token, + 'grant_type': 'authorization_code', + 'redirect_uri': os.environ['FACTORIALHR_REDIRECT_URI'], + } + try: + async with httpx.AsyncClient() as client: + response = await client.post( + os.environ['FACTORIALHR_ENVIRONMENT_URL'] + '/oauth/token', + data=data, + ) + response.raise_for_status() + except httpx.HTTPStatusError as e: + logger.exception('Login failed', exc_info=e) + raise + else: + self.api_session_cookie = ApiSession(**response.json()).model_dump_json() + logger.info('Refreshed access token') + + @rx.var(cache=False) + async def is_authenticated(self) -> bool: + """Check if the user is authenticated.""" + if not self.is_hydrated: + return False + api_session = self.api_session() + if api_session is None: + return False + return not api_session.is_access_token_expired() + + @rx.event + async def refresh(self) -> bool: + """Check if the user is authenticated.""" + if not self.is_hydrated: + return False + api_session = self.api_session() + if api_session is None: + return False + if api_session.is_refresh_token_expired(): + return False + if api_session.is_access_token_expired(): + try: + await self.login(token=api_session.refresh_token, grant_type='refresh_token') + except httpx.HTTPStatusError: + return False + return True + + @rx.event + async def redir(self): + """Redirect to the redirect_to route if logged in, or to the login page if not.""" + logger.info('Redirecting...') + if not self.is_hydrated: + yield self.redir() + page = self.router.url.path + is_authenticated = await self.is_authenticated + if not is_authenticated: + is_authenticated = await self.refresh() + if not is_authenticated: + self.redirect_to = page + return_value = [] + if not self.api_session_cookie: + return_value.append(rx.remove_cookie('api_session')) + if page != routes.LOGIN_ROUTE: + yield [*return_value, rx.redirect(routes.LOGIN_ROUTE)] + if is_authenticated and page == routes.LOGIN_ROUTE: + yield rx.redirect(self.redirect_to or '/') + + @rx.event + def logout(self): + """Log out the user.""" + yield [rx.remove_cookie('api_session'), rx.redirect(routes.LOGIN_ROUTE)] + + +def redirect_for_login(page: rx.app.ComponentCallable) -> rx.app.ComponentCallable: + """Require authentication before rendering a page. + + If the user is not authenticated, then redirect to the login page. + """ + + @functools.wraps(page) + def protected_page() -> rx.Component: + return rx.fragment( + rx.cond( + LoginState.is_hydrated, + rx.cond( + LoginState.is_authenticated, + page(), + rx.spinner(on_mount=LoginState.redir), + ), + rx.spinner(), # Show spinner while hydrating + ) + ) + + return protected_page diff --git a/factorialhr_analysis/templates.py b/factorialhr_analysis/templates.py new file mode 100644 index 0000000..8377d9c --- /dev/null +++ b/factorialhr_analysis/templates.py @@ -0,0 +1,18 @@ +"""Templates for the web application.""" + +import reflex as rx + +from factorialhr_analysis import components + + +def template(page: rx.app.ComponentCallable) -> rx.app.ComponentCallable: + """Wrap a page in the main template.""" + return rx.fragment( + components.navbar(), + rx.box( + page(), + padding_top='1em', # Space between navbar and content + padding_left='1em', + padding_right='1em', + ), + ) diff --git a/factorialhr_analysis/working_time_verification/__init__.py b/factorialhr_analysis/working_time_verification/__init__.py new file mode 100644 index 0000000..b715794 --- /dev/null +++ b/factorialhr_analysis/working_time_verification/__init__.py @@ -0,0 +1,4 @@ +from factorialhr_analysis.working_time_verification.helper import Error +from factorialhr_analysis.working_time_verification.verification import get_error + +__all__ = ['Error', 'get_error'] diff --git a/factorialhr_analysis/working_time_verification/helper.py b/factorialhr_analysis/working_time_verification/helper.py new file mode 100644 index 0000000..3a9a59a --- /dev/null +++ b/factorialhr_analysis/working_time_verification/helper.py @@ -0,0 +1,82 @@ +"""Helper functions for working time verification.""" + +import dataclasses +import datetime +from collections.abc import Iterable, Sequence +from collections.abc import Set as AbstractSet + +import factorialhr + + +def get_clock_in_and_clock_out( + attendance: factorialhr.AttendanceShift, +) -> tuple[datetime.datetime | None, datetime.datetime | None]: + """Get the clock in and clock out times from an attendance.""" + clock_in = ( + datetime.datetime.combine(attendance.date, attendance.clock_in.replace(second=0)) + if attendance.clock_in is not None + else None + ) + clock_out = ( + datetime.datetime.combine(attendance.date, attendance.clock_out.replace(second=0)) + if attendance.clock_out is not None + else None + ) + return clock_in, clock_out + + +def calculate_time_attended(attendances: Iterable[factorialhr.AttendanceShift]) -> datetime.timedelta: + """Calculate the time attended. + + :param attendances: list of attendances + :return: time attended + """ + return datetime.timedelta(minutes=sum(shift.minutes for shift in attendances)) + + +def calculate_break_time(attendances: Iterable[factorialhr.AttendanceShift]) -> datetime.timedelta: + """Calculate the time between the specified attendances. + + :param attendances: list of attendances + :return: time between attendances + """ + attendances = sorted(attendances, key=lambda x: datetime.datetime.combine(x.date, x.clock_in)) + if not attendances: + return datetime.timedelta(seconds=0) + + _, previous_clock_out = get_clock_in_and_clock_out(attendances[0]) + break_time = datetime.timedelta(seconds=0) + for attendance in attendances[1:]: # start at second index + clock_in, clock_out = get_clock_in_and_clock_out(attendance) + if attendance.clock_in is None or previous_clock_out is None: + return datetime.timedelta(seconds=0) + if clock_in <= previous_clock_out: + # overlapping or contiguous attendances + previous_clock_out = clock_out + continue + break_time += clock_in - previous_clock_out + previous_clock_out = clock_out + return break_time + + +@dataclasses.dataclass(frozen=True) +class Error: + """Error found during verification.""" + + reason: str + attendances: Sequence[factorialhr.AttendanceShift] + + @property + def days_affected(self) -> AbstractSet[datetime.date]: + """Get the days affected.""" + return {attendance.date for attendance in self.attendances} + + @property + def break_time(self) -> datetime.timedelta: + """Get the break time.""" + return calculate_break_time(self.attendances) + + @property + def time_attended(self) -> datetime.timedelta: + """Get the time attended.""" + return calculate_time_attended(self.attendances) diff --git a/factorialhr_analysis/working_time_verification/verification.py b/factorialhr_analysis/working_time_verification/verification.py new file mode 100644 index 0000000..0987973 --- /dev/null +++ b/factorialhr_analysis/working_time_verification/verification.py @@ -0,0 +1,106 @@ +"""Module to verify working time regulations based on attendances.""" + +import datetime +from collections.abc import Iterable, Iterator + +import factorialhr + +from factorialhr_analysis.working_time_verification import helper + +HOURS_6 = datetime.timedelta(hours=6) +HOURS_9 = datetime.timedelta(hours=9) +HOURS_10 = datetime.timedelta(hours=10) +HOURS_11 = datetime.timedelta(hours=11) +MINUTES_30 = datetime.timedelta(minutes=30) +MINUTES_45 = datetime.timedelta(minutes=45) + +SIX_AM = datetime.time(hour=6, minute=0, second=0) +TEN_PM = datetime.time(hour=22, minute=0, second=0) + + +def validate_clock_times(attendance: factorialhr.AttendanceShift) -> helper.Error | None: + """Validate that clock-in and clock-out times are present and logical.""" + clock_in, clock_out = helper.get_clock_in_and_clock_out(attendance) + if clock_in is None: + return helper.Error(f'no clock in time provided for attendance with id {attendance.id}', [attendance]) + if clock_out is None: + return helper.Error(f'no clock out time provided for clock in time {attendance.clock_in}', [attendance]) + if clock_out <= clock_in: + return helper.Error('Clocked out earlier or at the same time as clocked in', [attendance]) + return None + + +def check_attendance_time(attendance: factorialhr.AttendanceShift, tolerance: datetime.timedelta): + """Check for clock-in before 6 AM and clock-out after 10 PM, respecting tolerance.""" + clock_in, clock_out = helper.get_clock_in_and_clock_out(attendance) + six_am = datetime.datetime.combine(clock_in.date(), SIX_AM) + if clock_in < six_am - tolerance: + yield helper.Error(f'Clocked in after {SIX_AM} at {clock_in.time()}', [attendance]) + ten_pm = datetime.datetime.combine(clock_out.date(), TEN_PM) + if clock_out > ten_pm + tolerance: + yield helper.Error(f'Clocked out after {TEN_PM} at {clock_out.time()}', [attendance]) + + +def calculate_break( + current_attendances: list[factorialhr.AttendanceShift], clock_in: datetime.datetime +) -> datetime.timedelta: + """Calculate the break time between the last attendance and the current clock-in.""" + if current_attendances: + _, last_attendance_clock_out = helper.get_clock_in_and_clock_out(current_attendances[-1]) + if last_attendance_clock_out is not None and clock_in > last_attendance_clock_out: + return clock_in - last_attendance_clock_out + return datetime.timedelta(seconds=0) + + +def check_breaks_and_reset( + current_attendances: list[factorialhr.AttendanceShift], tolerance: datetime.timedelta +) -> tuple[helper.Error | None, bool]: + """Check for legal break durations and determines if attendances should be reset.""" + cumulated_time_attended = helper.calculate_time_attended(current_attendances) + cumulated_break_time = helper.calculate_break_time(current_attendances) + reason = None + reset = False + if cumulated_time_attended > HOURS_6 + tolerance and cumulated_break_time < MINUTES_30: + reason = 'Attended more than 6 hours without a cumulated break of 30 min' + if cumulated_time_attended > HOURS_9 + tolerance and cumulated_break_time < MINUTES_45: + reason = 'Attended more than 9 hours without a cumulated break of 45 min' + if cumulated_time_attended > HOURS_10 + tolerance and cumulated_break_time < HOURS_11: + reason = 'Attended more than 10 hours without a single break of 11 hours' + reset = True + return helper.Error(reason=reason, attendances=current_attendances[:]) if reason else None, reset + + +def get_error( + attendances: Iterable[factorialhr.AttendanceShift], tolerance: datetime.timedelta +) -> Iterator[helper.Error]: + """Verification function. + + Iterates over attendances and yields any errors found. Splits logic into smaller helper functions for clarity and + maintainability. + """ + current_attendances: list[factorialhr.AttendanceShift] = [] + for attendance in attendances: + # Validate clock-in/clock-out times + error = validate_clock_times(attendance) + if error: + yield error + continue + if not attendance.workable: + continue # Declared as a break, skip + # Ensure correct order + current_attendances.sort(key=lambda x: (x.clock_out, x.clock_out)) + # Check for early/late attendance + yield from check_attendance_time(attendance, tolerance) + clock_in, clock_out = helper.get_clock_in_and_clock_out(attendance) + # Calculate break time and reset attendances if needed + break_time = calculate_break(current_attendances, clock_in) + if break_time >= HOURS_11: + current_attendances = [attendance] + else: + current_attendances.append(attendance) + # Check for legal break durations and reset if necessary + error, reset = check_breaks_and_reset(current_attendances, tolerance) + if error: + yield error + if reset: + current_attendances = [attendance] diff --git a/pyproject.toml b/pyproject.toml index 998d51d..c5af558 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ authors = [ ] description = "Verification that attendances comply with german rules. Also provide an integration to the api of FactorialHR" readme = "README.md" -requires-python = ">=3.11, <3.14" +requires-python = "==3.13.*" +packages = ["factorialhr_analysis"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU Affero General Public License v3", @@ -20,8 +21,6 @@ classifiers = [ "Intended Audience :: End Users/Desktop", "Topic :: Office/Business", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] keywords = [ @@ -30,28 +29,24 @@ keywords = [ "Working time" ] dependencies = [ - "factorialhr==4.0.0b3", - "tabulate>=0.9.0", - "pyside6-essentials>=6.8.2.1", - "outcome>=1.3.0.post0", - "trio>=0.29.0", - "aiohttp>=3.11.13", + "anyio>=4.9.0", + "dotenv>=0.9.9", + "factorialhr>=4.1.0", + "httpx>=0.28.1", + "reflex==0.8.7", ] [dependency-groups] test = [ - "pytest>=8.3.5", + "pytest>=8.4.1", "pytest-html>=4", + "pytest-cov>=5.0.0", ] dev = [ - "pyright>=1.1.396", - "ruff>=0.9.10", + "ruff>=0.12.9", { include-group = "test" }, ] -[project.scripts] -factorial-working-time = "fwtv.main:main" - [project.urls] "Homepage" = "https://github.com/leon1995/fwtv" "Bug Tracker" = "https://github.com/leon1995/fwtv/issues" @@ -59,7 +54,10 @@ factorial-working-time = "fwtv.main:main" "Repository" = "https://github.com/leon1995/fwtv.git" [tool.hatch.version] -path = "src/fwtv/__init__.py" +path = "factorialhr_analysis/__init__.py" + +[tool.hatch.build.targets.wheel] +packages = ["factorialhr_analysis"] [tool.ruff] lint.select = ["ALL"] # https://docs.astral.sh/ruff/settings/#select @@ -74,7 +72,7 @@ lint.extend-ignore = [# https://docs.astral.sh/ruff/settings/#extend-ignore line-length = 120 # https://docs.astral.sh/ruff/settings/#line-length # Allow imports relative to the "src" and "test" directories. -src = ["src", "tests"] # https://docs.astral.sh/ruff/settings/#src +src = ["factorialhr_analysis", "tests"] # https://docs.astral.sh/ruff/settings/#src [tool.ruff.format] quote-style = "single" @@ -97,9 +95,10 @@ max-line-length = 120 # https://docs.astral.sh/ruff/settings/#lint_pycodestyle_ "__init__.py" = ["D104"] [tool.tox] # https://github.com/tox-dev/tox-uv -env_list = ["pre-commit", "py311", "py312", "py313"] +env_list = ["pre-commit", "py313"] [tool.tox.env_run_base] +runner = "uv-venv-lock-runner" depends = ["pre-commit"] package = "editable" commands = [["pytest"]] @@ -113,3 +112,8 @@ package = "skip" [tool.pytest.ini_options] testpaths = "tests/" +addopts = [ + "--cov=factorialhr_analysis", + "--cov-report=term-missing:skip-covered", + "--cov-report=xml", +] diff --git a/rxconfig.py b/rxconfig.py new file mode 100644 index 0000000..8b3e4ef --- /dev/null +++ b/rxconfig.py @@ -0,0 +1,11 @@ +"""Reflex configuration file.""" + +import reflex as rx + +config = rx.Config( + app_name='factorialhr_analysis', + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV4Plugin(), + ], +) diff --git a/src/fwtv/main.py b/src/fwtv/main.py deleted file mode 100644 index 5a50115..0000000 --- a/src/fwtv/main.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Entrypoint and main window of the application.""" - -import datetime -import sys -import typing - -import factorialhr -from PySide6.QtWidgets import QApplication, QMessageBox, QVBoxLayout, QWidget - -import fwtv -from fwtv.objects import async_converter -from fwtv.widgets import login_widget, working_time_widget - - -class MainWindow(QWidget): - """Main window widget.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.setWindowTitle(f'Factorial working time verification - version {fwtv.__version__}') - self.qv = QVBoxLayout() - self.login = login_widget.LoginWidget(self) - self.qv.addWidget(self.login) - - self.verification_widget = working_time_widget.WorkingTimeWidget(self) - self.qv.addWidget(self.verification_widget) - - self.setLayout(self.qv) - - self.login.button.clicked.connect(async_converter.ToAsync(self.fetch_data)) - - async def fetch_data(self): - """Fetch data from the api.""" - self.login.button.hide() - async with factorialhr.ApiClient(auth=factorialhr.ApiKeyAuth(self.login.key.text())) as api: - try: - _attendances = await factorialhr.ShiftEndpoint(api).all( - start_on=typing.cast( - datetime.date | None, - self.login.start_picker.date.date().toPython(), - ), - end_on=typing.cast( - datetime.date | None, - self.login.end_picker.date.date().toPython(), - ), - timeout=self.login.timeout.value(), - ) - _employees = await factorialhr.EmployeeEndpoint(api).all() - _teams = await factorialhr.TeamEndpoint(api).all() - except Exception as e: # noqa: BLE001 - message_box = QMessageBox(self) - message_box.setIcon(QMessageBox.Icon.Critical) - message_box.setText(f'{type(e).__name__}\n{e}') - message_box.setStandardButtons(QMessageBox.StandardButton.Ok) - message_box.setDefaultButton(QMessageBox.StandardButton.Ok) - message_box.exec() - return - finally: - self.login.button.show() - self.verification_widget.set_data(_teams, _attendances, _employees) - - -def main() -> int: - """Entrypoint for application.""" - app = QApplication(sys.argv) - window = MainWindow() - window.resize(1200, 675) - window.show() - return app.exec() - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/src/fwtv/objects/__init__.py b/src/fwtv/objects/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/fwtv/objects/async_converter.py b/src/fwtv/objects/async_converter.py deleted file mode 100644 index c56ae6b..0000000 --- a/src/fwtv/objects/async_converter.py +++ /dev/null @@ -1,64 +0,0 @@ -"""A wrapper to call async functions. - -Taken from https://doc.qt.io/qtforpython-6.5/examples/example_async_minimal.html -""" - -import traceback -import typing - -import outcome -import trio -from PySide6.QtCore import QEvent, QObject -from PySide6.QtWidgets import QApplication - - -class ToAsync(QObject): # noqa: D101 - class ReenterQtObject(QObject): - """This is a QObject to which an event will be posted, allowing Trio to resume when the event is handled. - - event.fn() is the next entry point of the Trio event loop. - """ # noqa: D404 - - def event(self, event) -> bool: # noqa: ANN001, D102 - if event.type() == QEvent.User + 1: # pyright: ignore [reportAttributeAccessIssue] - event.fn() - return True - return False - - class ReenterQtEvent(QEvent): - """This is the QEvent that will be handled by the ReenterQtObject. - - self.fn is the next entry point of the Trio event loop. - """ # noqa: D404 - - def __init__(self, fn): # noqa: ANN001 - super().__init__(QEvent.Type(QEvent.User + 1)) # pyright: ignore [reportAttributeAccessIssue] - self.fn = fn - - def __init__(self, signal: typing.Callable): - super().__init__() - self.reenter_qt = self.ReenterQtObject() - self.entry = signal - - def __call__(self, *args, **kwargs): # noqa: ANN003, ANN002, D102, ARG002 - trio.lowlevel.start_guest_run( - self.entry, - *args, - run_sync_soon_threadsafe=self.next_guest_run_schedule, - done_callback=self.trio_done_callback, - ) - - def next_guest_run_schedule(self, fn): # noqa: ANN001 - """This function serves to re-schedule the guest (Trio) event loop inside the host (Qt) event loop. - - It is called by Trio at the end of an event loop run in order to relinquish back to Qt's event loop. - By posting an event on the Qt event loop that contains Trio's next entry point, it ensures that Trio's - event loop will be scheduled again by Qt. - """ # noqa: D401, D404 - QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(fn)) - - def trio_done_callback(self, outcome_): # noqa: ANN001 - """This function is called by Trio when its event loop has finished.""" # noqa: D401, D404 - if isinstance(outcome_, outcome.Error): - error = outcome_.error - traceback.print_exception(type(error), error, error.__traceback__) diff --git a/src/fwtv/verifier.py b/src/fwtv/verifier.py deleted file mode 100644 index f453717..0000000 --- a/src/fwtv/verifier.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Contains logic for attendance verification.""" - -import dataclasses -import datetime - -HOURS_6 = datetime.timedelta(hours=6) -HOURS_9 = datetime.timedelta(hours=9) -HOURS_10 = datetime.timedelta(hours=10) -MINUTES_30 = datetime.timedelta(minutes=30) -MINUTES_45 = datetime.timedelta(minutes=45) - - -@dataclasses.dataclass(frozen=True) -class Attendance: - """A timespan relevant for verification.""" - - clock_in: datetime.datetime - clock_out: datetime.datetime - - def __post_init__(self): # noqa: D105 - if self.clock_out <= self.clock_in: - msg = f'Clocked out earlier or at the same time as clocked in for attendance {self}' - raise ValueError(msg) - - def __str__(self) -> str: - """Format this class as string.""" - return f'{type(self).__name__}(clock_in={self.clock_in!s}, clock_out={self.clock_out})' - - def __repr__(self) -> str: - """Get the representation.""" - return str(self) - - -def calculate_time_attended(attendances: list[Attendance]) -> datetime.timedelta: - """Calculate the time attended. - - :param attendances: list of attendances - :return: time attended - """ - attendances.sort(key=lambda x: x.clock_in) - attended = datetime.timedelta() - clock_out = None - for attendance in attendances: - # check overlaying attendances - clock_in = attendance.clock_in if not clock_out or clock_out < attendance.clock_in else clock_out - if not clock_out or clock_out < attendance.clock_out: - clock_out = attendance.clock_out - attended += clock_out - clock_in - return attended - - -def calculate_break_time(attendances: list[Attendance]) -> datetime.timedelta: - """Calculate the time between the specified attendances. - - :param attendances: list of attendances - :return: time between attendances - """ - if not attendances: - return datetime.timedelta(seconds=0) - attendances.sort(key=lambda x: x.clock_in) - clock_in = attendances[0].clock_in - attendances.sort(key=lambda x: x.clock_out) - clock_out = attendances[-1].clock_out - return clock_out - clock_in - calculate_time_attended(attendances) - - -@dataclasses.dataclass(frozen=True) -class Error: - """Error found during verification.""" - - reason: str - attendances: list[Attendance] - - @property - def days_affected(self) -> set[datetime.date]: - """Get the days affected.""" - days: set[datetime.date] = set() - for attendance in self.attendances: - days = days.union({attendance.clock_in.date(), attendance.clock_out.date()}) - return days - - @property - def break_time(self) -> datetime.timedelta: - """Get the break time.""" - return calculate_break_time(self.attendances) - - @property - def time_attended(self) -> datetime.timedelta: - """Get the time attended.""" - return calculate_time_attended(self.attendances) - - -def verify_attendances(attendances: list[Attendance], tolerance: datetime.timedelta) -> list[Error]: - """Verify that the specified attendances meet the requirements (in order). - - Requirements: - 1. It shall not be allowed to attend for more than 10 hours without not attended for at least 11 hours - 2. It shall not be allowed to attend for more than 9 hours without not attended for at least 45 minutes - 3. It shall not be allowed to attend for more than 6 hours without not attended for at least 30 minutes - - :param attendances: attendances to be verified - :param tolerance: adjustable tolerance which is added to the limits - :return: a list of errors found during verification - """ - errors: list[Error] = [] - attendances.sort(key=lambda x: (x.clock_out, x.clock_out)) # ensure correct order - # contains all attendances currently in verification - # will be reset once a break of 11 hours has been reached - current_attendances: list[Attendance] = [] - for attendance in attendances: - current_attendances.sort(key=lambda x: x.clock_out) - if current_attendances and attendance.clock_in > current_attendances[-1].clock_out: - break_time = attendance.clock_in - current_attendances[-1].clock_out - else: - break_time = datetime.timedelta(seconds=0) # first attendance, there is no break - if break_time >= datetime.timedelta(hours=11): - # reset relevant as 11-hour break has reached between previous attendances and current attendance - current_attendances = [attendance] - else: - current_attendances.append(attendance) - - cumulated_time_attended = calculate_time_attended(current_attendances) - cumulated_break_time = calculate_break_time(current_attendances) - reason = None - reset = False - if cumulated_time_attended > HOURS_6 + tolerance and cumulated_break_time < MINUTES_30: - reason = 'Attended more than 6 hours without a cumulated break of 30 min' - if cumulated_time_attended > HOURS_9 + tolerance and cumulated_break_time < MINUTES_45: - reason = 'Attended more than 9 hours without a cumulated break of 45 min' - if cumulated_time_attended > HOURS_10 + tolerance: - reason = 'Attended more than 10 hours without a single break of 11 hours' - reset = True - if reason: - errors.append(Error(reason=reason, attendances=current_attendances[:])) - if reset: - # in order to avoid duplicate errors, reset the counter - current_attendances = [attendance] - return errors diff --git a/src/fwtv/widgets/__init__.py b/src/fwtv/widgets/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/fwtv/widgets/login_widget.py b/src/fwtv/widgets/login_widget.py deleted file mode 100644 index 004546a..0000000 --- a/src/fwtv/widgets/login_widget.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Module for login widgets.""" - -import typing - -from PySide6.QtCore import QDate, Qt -from PySide6.QtGui import QKeyEvent -from PySide6.QtWidgets import ( - QHBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QSizePolicy, - QWidget, -) - -from fwtv.widgets import settings_widget - - -class LoginWidget(QWidget): - """Provide a way to login for the user.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qh = QHBoxLayout(self) - - self.fetch_data_label = QLabel('Fetch data', self) - self.fetch_data_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - self.qh.addWidget(self.fetch_data_label) - - # defaults to fetching data from the last 2 months in order to not fetch entire data history - self.start_picker = settings_widget.DateSettingWidget('From', QDate.currentDate().addMonths(-2), self) - self.qh.addWidget(self.start_picker) - - self.end_picker = settings_widget.DateSettingWidget('Until', QDate.currentDate(), self) - self.qh.addWidget(self.end_picker) - - self.label = QLabel('Enter api key', self) - self.label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - self.qh.addWidget(self.label) - - self.key = QLineEdit(self) - self.key.setEchoMode(QLineEdit.EchoMode.Password) - self.qh.addWidget(self.key) - - self.button = QPushButton('Fetch data', self) - self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - self.qh.addWidget(self.button) - - self.timeout = settings_widget.IntegerPickerWidget('Timeout', 5) - self.qh.addWidget(self.timeout) - - self.setLayout(self.qh) - - def keyPressEvent(self, event: QKeyEvent): # noqa: N802 - """Handle key press events.""" - super().keyPressEvent(event) - if event.key() == Qt.Key.Key_Return: - self.button.clicked.emit() diff --git a/src/fwtv/widgets/settings_widget.py b/src/fwtv/widgets/settings_widget.py deleted file mode 100644 index 60b7a48..0000000 --- a/src/fwtv/widgets/settings_widget.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Module for setting widgets.""" - -import typing - -from PySide6.QtCore import QDate -from PySide6.QtWidgets import ( - QComboBox, - QDateEdit, - QDoubleSpinBox, - QHBoxLayout, - QLabel, - QSizePolicy, - QWidget, -) - - -class TeamOrEmployeeSettingWidget(QWidget): - """A widget to select a team or employee.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qh = QHBoxLayout(self) - self.label = QLabel('Select a team or an employee', self) - self.qh.addWidget(self.label) - - self.selector = QComboBox(self) - self.selector.setEditable(True) - self.selector.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - self.qh.addWidget(self.selector) - - self.setLayout(self.qh) - - -class DateSettingWidget(QWidget): - """A picker for dates.""" - - def __init__(self, label: str, date: QDate, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qh = QHBoxLayout(self) - self.label = QLabel(label, self) - self.qh.addWidget(self.label) - - self.date = QDateEdit(date, self) - self.date.setCalendarPopup(True) - self.date.setDisplayFormat('yyyy-MM-dd') - self.qh.addWidget(self.date) - - self.setLayout(self.qh) - - -class IntegerPickerWidget(QWidget): - """A picker for integers.""" - - def __init__(self, label: str, default: int, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qh = QHBoxLayout(self) - self.label = QLabel(label, self) - self.qh.addWidget(self.label) - - self.picker = QDoubleSpinBox(self, maximum=999) # allow maximum of 9-digit number - self.picker.setSingleStep(1) - self.picker.setDecimals(0) - self.picker.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) - self.picker.setValue(default) - self.qh.addWidget(self.picker) - - self.setLayout(self.qh) - - def value(self) -> int: - """Get the value of the integer picker.""" - return int(self.picker.value()) - - -class SettingsWidget(QWidget): - """Provide some settings for the user.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qh = QHBoxLayout(self) - self.team_selector = TeamOrEmployeeSettingWidget(self) - self.qh.addWidget(self.team_selector) - - last_month = QDate.currentDate().addMonths(-1) - self.start_picker = DateSettingWidget('Start on', QDate(last_month.year(), last_month.month(), 1), self) - self.qh.addWidget(self.start_picker) - - self.end_picker = DateSettingWidget( - 'End on', - QDate(last_month.year(), last_month.month(), last_month.daysInMonth()).addDays(1), - self, - ) - self.qh.addWidget(self.end_picker) - - self.tolerance_selector = IntegerPickerWidget('Tolerance', 1) - self.qh.addWidget(self.tolerance_selector) - - self.setLayout(self.qh) diff --git a/src/fwtv/widgets/table_widget.py b/src/fwtv/widgets/table_widget.py deleted file mode 100644 index 35f8c4f..0000000 --- a/src/fwtv/widgets/table_widget.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Module for table widgets.""" - -import collections -import typing - -import tabulate -from PySide6.QtCore import Qt -from PySide6.QtGui import QClipboard, QKeyEvent -from PySide6.QtWidgets import ( - QHeaderView, - QLabel, - QSizePolicy, - QTableView, - QTableWidget, - QTableWidgetItem, - QVBoxLayout, - QWidget, -) - -from fwtv import verifier - - -class ErrorTable(QTableWidget): - """Display errors to user.""" - - def __init__(self, labels: list[str], *args: typing.Any, **kwargs: typing.Any): - self.labels = labels - super().__init__(0, len(self.labels), *args, **kwargs) - self.setHorizontalHeaderLabels(self.labels) - self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) - self._set_data({}) - - def _set_data(self, data: dict[str, list[list[str]]]): - self.setSortingEnabled(False) - self.setRowCount(sum(len(failures) for failures in data.values())) - row_index = 0 - for name, errors in data.items(): - for error in errors: - self._set_row(row_index, name, *error) - row_index += 1 - self.horizontalHeader().resizeSections(QHeaderView.ResizeMode.ResizeToContents) - self.setSortingEnabled(True) - - def _set_row(self, row_index: int, *columns: str): - for column_index, text in enumerate(columns): - column_item = QTableWidgetItem(text) - column_item.setFlags( - Qt.ItemFlag.ItemNeverHasChildren - | Qt.ItemFlag.ItemIsAutoTristate - | Qt.ItemFlag.ItemIsEnabled - | Qt.ItemFlag.ItemIsSelectable - ) - self.setItem(row_index, column_index, column_item) - - def keyPressEvent(self, event: QKeyEvent): # noqa: N802 - """Handle key press events.""" - super().keyPressEvent(event) - if event.key() == Qt.Key.Key_C and (event.modifiers() & Qt.KeyboardModifier.ControlModifier): - row_by_index = collections.defaultdict(list) - for index in self.selectedIndexes(): - row_by_index[index.row()].append(index.data()) - table = tabulate.tabulate(row_by_index.values(), headers=self.labels, tablefmt='orgtbl') - QClipboard().setText(table) - - -class PreconditionErrorsTableWidget(ErrorTable): - """Display precondition errors to user.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(['Name', 'Error'], *args, **kwargs) - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) - - def set_data(self, data: dict[str, list[list[str]]]): - """Set precondition data.""" - super()._set_data(data) - self.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) - if not any(data) and not self.isHidden(): - self.hide() - elif any(data) and self.isHidden(): - self.show() - - -class AdditionalInformationWidget(QWidget): - """Display additional information to user.""" - - def __init__(self, name: str, error: verifier.Error, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.qv = QVBoxLayout(self) - - self.label = QLabel(name) - self.qv.addWidget(self.label) - - self.attendances = QTableWidget(len(error.attendances), 2, self) - for i, a in enumerate(error.attendances): - column_item = QTableWidgetItem(a.clock_in.strftime('%d.%m %H:%M')) - column_item.setFlags( - Qt.ItemFlag.ItemNeverHasChildren | Qt.ItemFlag.ItemIsAutoTristate | Qt.ItemFlag.ItemIsEnabled - ) - self.attendances.setItem(i, 0, column_item) - column_item = QTableWidgetItem(a.clock_out.strftime('%d.%m %H:%M')) - column_item.setFlags( - Qt.ItemFlag.ItemNeverHasChildren | Qt.ItemFlag.ItemIsAutoTristate | Qt.ItemFlag.ItemIsEnabled - ) - self.attendances.setItem(i, 1, column_item) - self.attendances.setHorizontalHeaderLabels(['Clock in', 'Clock out']) - self.attendances.horizontalHeader().resizeSections(QHeaderView.ResizeMode.ResizeToContents) - self.qv.addWidget(self.attendances) - - self.error = QLabel(error.reason) - self.qv.addWidget(self.error) - - self.setLayout(self.qv) - - -class FailuresTableWidget(ErrorTable): - """Display failures to user.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__( - [ - 'Name', - 'Affected Day(s)', - 'Reason', - 'Cumulated Break', - 'Cumulated Attendance', - ], - *args, - **kwargs, - ) - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - self.data: dict[str, list[verifier.Error]] = {} - self.cellDoubleClicked.connect(self.display_additional_information) - - def set_data(self, data: dict[str, list[verifier.Error]]): - """Set verification errors in failures table.""" - self.data = data - entries = collections.defaultdict(list) - for name, error in data.items(): - for e in error: - affected_days = [str(day) for day in e.days_affected] - affected_days.sort() - entries[name].append( - [ - ', '.join(affected_days), - e.reason, - str(e.break_time), - str(e.time_attended), - ] - ) - super()._set_data(entries) - self.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) - - def display_additional_information(self, row: int, _: int): - """Display additional information to the user.""" - rows: list[tuple[str, verifier.Error]] = [] - for name, error in self.data.items(): - for e in error: - rows.insert(0, (name, e)) # instead of reversing - self.new_window = AdditionalInformationWidget(rows[row][0], rows[row][1]) - self.new_window.show() diff --git a/src/fwtv/widgets/working_time_widget.py b/src/fwtv/widgets/working_time_widget.py deleted file mode 100644 index df4fb27..0000000 --- a/src/fwtv/widgets/working_time_widget.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Module for working time widgets.""" - -import collections -import datetime -import typing - -import factorialhr -from PySide6.QtWidgets import QVBoxLayout, QWidget - -from fwtv import verifier -from fwtv.widgets import settings_widget, table_widget - - -def get_errors( - start: datetime.date, - end: datetime.date, - attendances: list[factorialhr.Shift], - employees: list[factorialhr.Employee], - tolerance: int, -) -> tuple[dict[str, list[str]], dict[str, list[verifier.Error]]]: - """Get all errors found.""" - preconditions: dict[str, list[str]] = collections.defaultdict(list) - employee_errors: dict[str, list[verifier.Error]] = collections.defaultdict(list) - for employee in employees: - name = employee.full_name - employee_attendances: list[verifier.Attendance] = [] - - for attendance in filter(lambda x: x.employee_id == employee.id, attendances): - attendance: factorialhr.Shift - if not attendance.clock_in: - preconditions[name].append(f'no clock in time provided for attendance with id "{attendance.id}"') - continue - if attendance.date < start or attendance.date > end: - continue - if not attendance.clock_out: - preconditions[name].append(f'no clock out time provided for clock in time "{attendance.clock_in}"') - continue - if not attendance.workable: - continue # it has been declared as a break - try: - # automated time tracking is not precise enough and also is not able to handle seconds precise enough - a = verifier.Attendance( - clock_in=datetime.datetime.combine(attendance.date, attendance.clock_in.replace(second=0)), - clock_out=datetime.datetime.combine(attendance.date, attendance.clock_out.replace(second=0)), - ) - employee_attendances.append(a) - except ValueError as e: - preconditions[name].append(str(e)) - continue - - errors = verifier.verify_attendances(employee_attendances, datetime.timedelta(minutes=tolerance)) - if errors: - employee_errors[name] = errors - return preconditions, employee_errors - - -class WorkingTimeWidget(QWidget): - """Widget to display working times.""" - - def __init__(self, *args: typing.Any, **kwargs: typing.Any): - super().__init__(*args, **kwargs) - self.teams = [] - self.attendances = [] - self.employees_by_team = {} - self.employees = [] - self.qv = QVBoxLayout(self) - - self.settings_widget = settings_widget.SettingsWidget(self) - self.qv.addWidget(self.settings_widget) - - self.preconditions_table = table_widget.PreconditionErrorsTableWidget(self) - self.qv.addWidget(self.preconditions_table) - - self.failures_table = table_widget.FailuresTableWidget(self) - self.qv.addWidget(self.failures_table) - - self.setLayout(self.qv) - - self.settings_widget.team_selector.selector.currentIndexChanged.connect(self.update_data) - self.settings_widget.start_picker.date.dateChanged.connect(self.update_data) - self.settings_widget.end_picker.date.dateChanged.connect(self.update_data) - self.settings_widget.tolerance_selector.picker.valueChanged.connect(self.update_data) - - self.update_data() - - def set_data( - self, - teams: list[factorialhr.Team], - attendances: list[factorialhr.Shift], - employees: list[factorialhr.Employee], - ): - """Fetch new data and update tables.""" - self.attendances = attendances - self.teams = teams - self.employees = employees - self.employees_by_team = { - t.id: [e for e in employees if t.employee_ids and e.id in t.employee_ids] for t in teams - } - for i in range(self.settings_widget.team_selector.selector.count()): - self.settings_widget.team_selector.selector.removeItem(i) - self.settings_widget.team_selector.selector.addItems([team.name for team in teams]) - self.settings_widget.team_selector.selector.addItems([employee.full_name for employee in employees]) - self.update_data() - - def get_current_selection(self) -> factorialhr.Team | factorialhr.Employee | None: - """Get current selection of teams.""" - index = self.settings_widget.team_selector.selector.currentIndex() - if 0 <= index < len(self.teams): - return self.teams[index] - if 0 <= (index := index - len(self.teams)) < len(self.employees): - return self.employees[index] - return None - - def update_data(self): - """Update the data and populate new data into tables.""" - selection = self.get_current_selection() - employees = [] - if isinstance(selection, factorialhr.Team): - employees = self.employees_by_team[selection.id] - elif isinstance(selection, factorialhr.Employee): - employees = [selection] - - preconditions, errors = get_errors( - typing.cast( - datetime.date, - self.settings_widget.start_picker.date.date().toPython(), - ), - typing.cast( - datetime.date, - self.settings_widget.end_picker.date.date().toPython(), - ), - self.attendances, - employees, - self.settings_widget.tolerance_selector.value(), - ) - entries = collections.defaultdict(list) - for k in preconditions: - entries[k] = [preconditions[k]] - self.preconditions_table.set_data(entries) - self.failures_table.set_data(errors) diff --git a/tests/test_verifier.py b/tests/test_verifier.py deleted file mode 100644 index c89d8bf..0000000 --- a/tests/test_verifier.py +++ /dev/null @@ -1,315 +0,0 @@ -"""Test for the attendance verification module.""" - -import datetime -import uuid - -import pytest - -from fwtv import verifier - - -@pytest.mark.parametrize( - ('clock_in', 'clock_out'), - [ - ( - datetime.datetime(2023, 1, 1, 0, 0, 0, 0, tzinfo=datetime.UTC), - datetime.datetime(2023, 1, 1, 0, 0, 0, 0, tzinfo=datetime.UTC), - ), - ( - datetime.datetime(2023, 1, 1, 0, 0, 0, 1, tzinfo=datetime.UTC), - datetime.datetime(2023, 1, 1, 0, 0, 0, 0, tzinfo=datetime.UTC), - ), - ], -) -def test_clock_in_earlier_than_clock_out(clock_in: datetime.datetime, clock_out: datetime.datetime): - """Test whether an error is raised if clock_out is before clock_in.""" - with pytest.raises(ValueError): # noqa: PT011 - verifier.Attendance(clock_in, clock_out) - - -@pytest.mark.parametrize( - ('clock_in', 'clock_out'), - [ - ( - datetime.datetime(2023, 1, 1, 0, 0, 0, 0, tzinfo=datetime.UTC), - datetime.datetime(2023, 1, 1, 0, 0, 0, 1, tzinfo=datetime.UTC), - ) - ], -) -def test_str_and_repr(clock_in: datetime.datetime, clock_out: datetime.datetime): - """Test whether the str and repr at as expected.""" - expected = f'Attendance(clock_in={clock_in!s}, clock_out={clock_out!s})' - str_ = str(verifier.Attendance(clock_in, clock_out)) - assert str_ == expected - repr_ = repr(verifier.Attendance(clock_in, clock_out)) - assert repr_ == expected - - -single_attendance = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ) -] -normal_attendances = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 11, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 14, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - ), -] -attendances_with_intersection = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 9, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 14, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - ), -] -begin_interception = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 9, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 17, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 14, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 16, tzinfo=datetime.UTC), - ), -] -attendances_with_inception = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 9, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 17, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 14, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 16, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 21, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 22, tzinfo=datetime.UTC), - ), -] -multiple_days = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 2, 2, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 2, 18, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 3, 19, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 3, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 4, 5, tzinfo=datetime.UTC), - ), -] -random_order = [ - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 15, 8, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 15, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 14, 9, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 14, 10, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 20, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 20, 17, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 13, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2022, 12, 31, 14, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2022, 12, 31, 15, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 16, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 2, 4, 15, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 2, 4, 21, tzinfo=datetime.UTC), - ), - verifier.Attendance( - clock_in=datetime.datetime(2023, 1, 1, 20, tzinfo=datetime.UTC), - clock_out=datetime.datetime(2023, 1, 1, 22, tzinfo=datetime.UTC), - ), -] - - -@pytest.mark.parametrize( - ('attendances', 'expected'), - [ - ([], datetime.timedelta()), - (single_attendance, datetime.timedelta(hours=2)), - (normal_attendances, datetime.timedelta(hours=10)), - (attendances_with_intersection, datetime.timedelta(hours=12)), - (attendances_with_inception, datetime.timedelta(hours=11)), - (multiple_days, datetime.timedelta(hours=53)), - (random_order, datetime.timedelta(hours=23)), - ], -) -def test_calculate_attended_time(attendances: list[verifier.Attendance], expected: datetime.timedelta): - """Test the calculated attended time.""" - actual = verifier.calculate_time_attended(attendances) - assert actual == expected - - -@pytest.mark.parametrize( - ('attendances', 'expected'), - [ - ([], datetime.timedelta()), - (single_attendance, datetime.timedelta(hours=0)), - (normal_attendances, datetime.timedelta(hours=2)), - (begin_interception, datetime.timedelta(hours=3)), - (attendances_with_intersection, datetime.timedelta(hours=0)), - (attendances_with_inception, datetime.timedelta(hours=3)), - (multiple_days, datetime.timedelta(hours=16)), - (random_order, datetime.timedelta(hours=824)), - ], -) -def test_calculate_break_time(attendances: list[verifier.Attendance], expected: datetime.timedelta): - """Test the calculated break time.""" - actual = verifier.calculate_break_time(attendances) - assert actual == expected - - -@pytest.mark.parametrize( - ('attendances', 'expected'), - [ - (single_attendance, {datetime.date(2023, 1, 1)}), - (normal_attendances, {datetime.date(2023, 1, 1)}), - (attendances_with_intersection, {datetime.date(2023, 1, 1)}), - (attendances_with_inception, {datetime.date(2023, 1, 1)}), - ( - multiple_days, - { - datetime.date(2023, 1, 1), - datetime.date(2023, 1, 2), - datetime.date(2023, 1, 3), - datetime.date(2023, 1, 4), - }, - ), - ( - random_order, - { - datetime.date(2023, 1, 1), - datetime.date(2022, 12, 31), - datetime.date(2023, 1, 15), - datetime.date(2023, 1, 14), - datetime.date(2023, 1, 20), - datetime.date(2023, 2, 4), - }, - ), - ], -) -def test_error_affected_days(attendances: list[verifier.Attendance], expected: datetime.date): - """Test whether the found error is as expected for affected days.""" - error = verifier.Error(reason=uuid.uuid4().hex, attendances=attendances) - assert error.days_affected == expected - - -@pytest.mark.parametrize( - ('attendances', 'expected'), - [ - ([], datetime.timedelta()), - (single_attendance, datetime.timedelta(hours=2)), - (normal_attendances, datetime.timedelta(hours=10)), - (attendances_with_intersection, datetime.timedelta(hours=12)), - (attendances_with_inception, datetime.timedelta(hours=11)), - (multiple_days, datetime.timedelta(hours=53)), - (random_order, datetime.timedelta(hours=23)), - ], -) -def test_error_attended_time(attendances: list[verifier.Attendance], expected: datetime.timedelta): - """Test whether the found error is as expected for attended time.""" - error = verifier.Error(reason=uuid.uuid4().hex, attendances=attendances) - assert error.time_attended == expected - - -@pytest.mark.parametrize( - ('attendances', 'expected'), - [ - ([], datetime.timedelta()), - (single_attendance, datetime.timedelta(hours=0)), - (normal_attendances, datetime.timedelta(hours=2)), - (begin_interception, datetime.timedelta(hours=3)), - (attendances_with_intersection, datetime.timedelta(hours=0)), - (attendances_with_inception, datetime.timedelta(hours=3)), - (multiple_days, datetime.timedelta(hours=16)), - (random_order, datetime.timedelta(hours=824)), - ], -) -def test_error_break_time(attendances: list[verifier.Attendance], expected: datetime.timedelta): - """Test whether the found error is as expected for break time.""" - error = verifier.Error(reason=uuid.uuid4().hex, attendances=attendances) - assert error.break_time == expected - - -@pytest.mark.parametrize( - ('attendances', 'error_count'), - [ - (single_attendance, 0), - (normal_attendances, 0), - (attendances_with_intersection, 1), - (attendances_with_inception, 1), - (multiple_days, 3), - (random_order, 2), - ], -) -def test_verify_work_time(attendances: list[verifier.Attendance], error_count: int): - """Verify found error count.""" - errors = verifier.verify_attendances(attendances, tolerance=datetime.timedelta()) - assert len(errors) == error_count diff --git a/tests/test_working_time_verification.py b/tests/test_working_time_verification.py new file mode 100644 index 0000000..64a922e --- /dev/null +++ b/tests/test_working_time_verification.py @@ -0,0 +1,332 @@ +"""Unit tests for working_time_verification module.""" + +import datetime as dt +from collections.abc import Sequence +from dataclasses import dataclass + +import pytest + +from factorialhr_analysis.working_time_verification import helper, verification + + +@dataclass(frozen=True) +class FakeShift: + """Minimal stand-in for factorialhr.AttendanceShift for testing.""" + + date: dt.date + clock_in: dt.time | None + clock_out: dt.time | None + workable: bool = True + minutes: int = 0 + id: int = 0 + + def __post_init__(self) -> None: + """Populate minutes from clock_in/clock_out when not explicitly provided.""" + # dataclass with frozen=True can't mutate; use object.__setattr__ + if self.minutes == 0 and self.clock_in and self.clock_out: + cin = dt.datetime.combine(self.date, self.clock_in) + cout = dt.datetime.combine(self.date, self.clock_out) + m = max(0, int((cout - cin).total_seconds() // 60)) + object.__setattr__(self, 'minutes', m) + + +@pytest.mark.parametrize( + ('clock_in', 'clock_out', 'exp_in', 'exp_out'), + [ + ( + dt.time(9, 0, 30), + dt.time(17, 0, 45), + dt.datetime.combine(dt.date(2024, 1, 1), dt.time(9, 0)), + dt.datetime.combine(dt.date(2024, 1, 1), dt.time(17, 0)), + ), + (None, dt.time(12, 0), None, dt.datetime.combine(dt.date(2024, 1, 1), dt.time(12, 0))), + (dt.time(8, 0), None, dt.datetime.combine(dt.date(2024, 1, 1), dt.time(8, 0)), None), + (None, None, None, None), + ], +) +def test_get_clock_in_and_clock_out( + clock_in: dt.time | None, clock_out: dt.time | None, exp_in: dt.datetime | None, exp_out: dt.datetime | None +) -> None: + """Ensure helper returns expected naive datetimes (seconds truncated).""" + shift = FakeShift(date=dt.date(2024, 1, 1), clock_in=clock_in, clock_out=clock_out) + got_in, got_out = helper.get_clock_in_and_clock_out(shift) # type: ignore[arg-type] + assert got_in == exp_in + assert got_out == exp_out + + +@pytest.mark.parametrize( + ('minutes_list', 'expected'), + [([60], dt.timedelta(hours=1)), ([30, 45], dt.timedelta(minutes=75)), ([], dt.timedelta(0))], +) +def test_calculate_time_attended(minutes_list: list[int], expected: dt.timedelta) -> None: + """Sum minutes across shifts to a timedelta.""" + base = dt.date(2024, 1, 1) + shifts = [FakeShift(base, dt.time(9, 0), dt.time(10, 0), minutes=m) for m in minutes_list] + assert helper.calculate_time_attended(shifts) == expected # type: ignore[arg-type] + + +@pytest.mark.parametrize( + ('shifts', 'expected'), + [ + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(12, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(13, 0), dt.time(17, 0)), + ], + dt.timedelta(hours=1), + ), + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(12, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(11, 0), dt.time(14, 0)), + ], + dt.timedelta(0), + ), + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(12, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(12, 0), dt.time(17, 0)), + ], + dt.timedelta(0), + ), + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(11, 0), dt.time(12, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(13, 0), dt.time(14, 0)), + ], + dt.timedelta(hours=2), + ), + ], +) +def test_calculate_break_time(shifts: Sequence[FakeShift], expected: dt.timedelta) -> None: + """Compute total gap between non-overlapping sequential shifts.""" + assert helper.calculate_break_time(shifts) == expected # type: ignore[arg-type] + + +def test_calculate_break_time_empty() -> None: + """Empty attendances should yield zero break time.""" + assert helper.calculate_break_time([]) == dt.timedelta(0) # type: ignore[arg-type] + + +def test_calculate_break_time_missing_first_clock_out_returns_zero() -> None: + """If the first shift has no clock_out, break computation returns zero early.""" + shifts = [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), None), + FakeShift(dt.date(2024, 1, 1), dt.time(10, 0), dt.time(11, 0)), + ] + assert helper.calculate_break_time(shifts) == dt.timedelta(0) # type: ignore[arg-type] + + +@pytest.mark.parametrize( + ('attendances', 'exp_days', 'exp_break', 'exp_time'), + [ + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(11, 0), dt.time(12, 0)), + ], + {dt.date(2024, 1, 1)}, + dt.timedelta(hours=1), + dt.timedelta(hours=2), + ), + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0)), + FakeShift(dt.date(2024, 1, 2), dt.time(11, 0), dt.time(12, 0)), + ], + {dt.date(2024, 1, 1), dt.date(2024, 1, 2)}, + dt.timedelta(days=1, hours=1), + dt.timedelta(hours=2), + ), + ], +) +def test_error_properties( + attendances: Sequence[FakeShift], exp_days: set[dt.date], exp_break: dt.timedelta, exp_time: dt.timedelta +) -> None: + """Validate Error aggregates derived properties correctly.""" + err = helper.Error('test', attendances) + assert err.days_affected == exp_days + assert err.break_time == exp_break + assert err.time_attended == exp_time + + +@pytest.mark.parametrize( + ('shift', 'expected_reason'), + [ + (FakeShift(dt.date(2024, 1, 1), None, dt.time(12, 0)), 'no clock in time provided'), + (FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), None), 'no clock out time provided'), + (FakeShift(dt.date(2024, 1, 1), dt.time(10, 0), dt.time(9, 0)), 'Clocked out earlier or at the same time'), + (FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(9, 1)), None), + ], +) +def test_validate_clock_times(shift: FakeShift, expected_reason: str | None) -> None: + """Validate basic structure and ordering of clock times.""" + res = verification.validate_clock_times(shift) # type: ignore[arg-type] + if expected_reason is None: + assert res is None + else: + assert isinstance(res, helper.Error) + assert expected_reason in res.reason + + +@pytest.mark.parametrize( + ('shift', 'tolerance', 'expected_flags'), + [ + ( + FakeShift(dt.date(2024, 1, 1), dt.time(5, 30), dt.time(14, 0)), + dt.timedelta(minutes=0), + (True, False), + ), + ( + FakeShift(dt.date(2024, 1, 1), dt.time(5, 50), dt.time(14, 0)), + dt.timedelta(minutes=15), + (False, False), + ), + ( + FakeShift(dt.date(2024, 1, 1), dt.time(14, 0), dt.time(22, 30)), + dt.timedelta(minutes=10), + (False, True), + ), + ( + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(17, 0)), + dt.timedelta(minutes=0), + (False, False), + ), + ], +) +def test_check_attendance_time(shift: FakeShift, tolerance: dt.timedelta, expected_flags: tuple[bool, bool]) -> None: + """Detect early clock-in and late clock-out events respecting tolerance.""" + errs = list(verification.check_attendance_time(shift, tolerance)) # type: ignore[arg-type] + reasons = [e.reason for e in errs] + exp_early, exp_late = expected_flags + assert any('Clocked in' in r for r in reasons) == exp_early + assert any('Clocked out' in r for r in reasons) == exp_late + + +@pytest.mark.parametrize( + ('existing', 'new_clock_in', 'expected'), + [ + ([], dt.datetime.combine(dt.date(2024, 1, 1), dt.time(10, 0)), dt.timedelta(0)), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0))], + dt.datetime.combine(dt.date(2024, 1, 1), dt.time(11, 0)), + dt.timedelta(hours=1), + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0))], + dt.datetime.combine(dt.date(2024, 1, 1), dt.time(9, 30)), + dt.timedelta(0), + ), + ], +) +def test_calculate_break(existing: list[FakeShift], new_clock_in: dt.datetime, expected: dt.timedelta) -> None: + """Calculate break between last shift and next clock-in.""" + assert verification.calculate_break(existing, new_clock_in) == expected # type: ignore[arg-type] + + +@pytest.mark.parametrize( + ('attendances', 'tolerance', 'expected'), + [ + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(15, 1))], + dt.timedelta(0), + ('more than 6 hours', False), + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(8, 0), dt.time(17, 1))], + dt.timedelta(0), + ('more than 9 hours', False), + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(7, 0), dt.time(17, 1))], + dt.timedelta(0), + ('more than 10 hours', True), + ), + ( + [ + FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(12, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(12, 45), dt.time(17, 0)), + ], + dt.timedelta(0), + (None, False), + ), + ], +) +def test_check_breaks_and_reset( + attendances: Sequence[FakeShift], tolerance: dt.timedelta, expected: tuple[str | None, bool] +) -> None: + """Check cumulative break requirements and whether to reset accumulation.""" + exp_reason_substr, exp_reset = expected + error, reset = verification.check_breaks_and_reset(attendances, tolerance) # type: ignore[arg-type] + if exp_reason_substr is None: + assert error is None + assert reset is False + else: + assert isinstance(error, helper.Error) + assert exp_reason_substr in error.reason + assert reset is exp_reset + + +@pytest.mark.parametrize( + ('attendances', 'tolerance', 'exp_reasons_contains'), + [ + ( + [FakeShift(dt.date(2024, 1, 1), None, dt.time(12, 0))], + dt.timedelta(0), + ['no clock in'], + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(5, 30), dt.time(12, 0))], + dt.timedelta(0), + ['Clocked in'], + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(14, 0), dt.time(22, 30))], + dt.timedelta(minutes=0), + ['Clocked out'], + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(15, 1))], + dt.timedelta(0), + ['more than 6 hours'], + ), + ( + [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), None, workable=False)], + dt.timedelta(0), + ['no clock out'], + ), + ], +) +def test_get_error_end_to_end( + attendances: Sequence[FakeShift], tolerance: dt.timedelta, exp_reasons_contains: Sequence[str] +) -> None: + """End-to-end verification across common scenarios.""" + reasons = [e.reason for e in verification.get_error(attendances, tolerance)] # type: ignore[arg-type] + for needle in exp_reasons_contains: + assert any(needle in r for r in reasons) + + +def test_get_error_skips_non_workable_with_valid_times() -> None: + """Non-workable attendances with valid times should be skipped with no errors.""" + shifts = [FakeShift(dt.date(2024, 1, 1), dt.time(9, 0), dt.time(10, 0), workable=False)] + errors = list(verification.get_error(shifts, dt.timedelta(0))) # type: ignore[arg-type] + assert errors == [] + + +def test_get_error_resets_on_eleven_hour_break() -> None: + """A break >= 11 hours should reset accumulation between shifts.""" + shifts = [ + FakeShift(dt.date(2024, 1, 1), dt.time(8, 0), dt.time(9, 0)), + FakeShift(dt.date(2024, 1, 1), dt.time(21, 30), dt.time(21, 45)), # 12.5h break + ] + reasons = [e.reason for e in verification.get_error(shifts, dt.timedelta(0))] # type: ignore[arg-type] + # No break-violation errors expected; early/late bounds are satisfied + assert all('more than' not in r for r in reasons) + + +def test_get_error_triggers_reset_on_ten_hour_violation() -> None: + """Working more than 10 hours with <11h break should yield an error and reset accumulation.""" + shifts = [FakeShift(dt.date(2024, 1, 1), dt.time(7, 0), dt.time(17, 1))] # >10h + errors = list(verification.get_error(shifts, dt.timedelta(0))) # type: ignore[arg-type] + assert any('more than 10 hours' in e.reason for e in errors) diff --git a/uv.lock b/uv.lock index 50769d7..9738477 100644 --- a/uv.lock +++ b/uv.lock @@ -1,293 +1,255 @@ version = 1 -requires-python = ">=3.11, <3.14" +revision = 3 +requires-python = "==3.13.*" [[package]] -name = "aiohappyeyeballs" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/0c/458958007041f4b4de2d307e6b75d9e7554dad0baf26fe7a48b741aac126/aiohappyeyeballs-2.5.0.tar.gz", hash = "sha256:18fde6204a76deeabc97c48bdd01d5801cfda5d6b9c8bbeb1aaaee9d648ca191", size = 22494 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/9a/e4886864ce06e1579bd428208127fbdc0d62049c751e4e9e3b509c0059dc/aiohappyeyeballs-2.5.0-py3-none-any.whl", hash = "sha256:0850b580748c7071db98bffff6d4c94028d0d3035acc20fd721a0ce7e8cac35d", size = 15128 }, -] - -[[package]] -name = "aiohttp" -version = "3.11.13" +name = "alembic" +version = "1.16.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/3f/c4a667d184c69667b8f16e0704127efc5f1e60577df429382b4d95fd381e/aiohttp-3.11.13.tar.gz", hash = "sha256:8ce789231404ca8fff7f693cdce398abf6d90fd5dae2b1847477196c243b1fbb", size = 7674284 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/93/8e012ae31ff1bda5d43565d6f9e0bad325ba6f3f2d78f298bd39645be8a3/aiohttp-3.11.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b35aab22419ba45f8fc290d0010898de7a6ad131e468ffa3922b1b0b24e9d2e", size = 709013 }, - { url = "https://files.pythonhosted.org/packages/d8/be/fc7c436678ffe547d038319add8e44fd5e33090158752e5c480aed51a8d0/aiohttp-3.11.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81cba651db8795f688c589dd11a4fbb834f2e59bbf9bb50908be36e416dc760", size = 468896 }, - { url = "https://files.pythonhosted.org/packages/d9/1c/56906111ac9d4dab4baab43c89d35d5de1dbb38085150257895005b08bef/aiohttp-3.11.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f55d0f242c2d1fcdf802c8fabcff25a9d85550a4cf3a9cf5f2a6b5742c992839", size = 455968 }, - { url = "https://files.pythonhosted.org/packages/ba/16/229d36ed27c2bb350320364efb56f906af194616cc15fc5d87f3ef21dbef/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4bea08a6aad9195ac9b1be6b0c7e8a702a9cec57ce6b713698b4a5afa9c2e33", size = 1686082 }, - { url = "https://files.pythonhosted.org/packages/3a/44/78fd174509c56028672e5dfef886569cfa1fced0c5fd5c4480426db19ac9/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6070bcf2173a7146bb9e4735b3c62b2accba459a6eae44deea0eb23e0035a23", size = 1744056 }, - { url = "https://files.pythonhosted.org/packages/a3/11/325145c6dce8124b5caadbf763e908f2779c14bb0bc5868744d1e5cb9cb7/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:718d5deb678bc4b9d575bfe83a59270861417da071ab44542d0fcb6faa686636", size = 1785810 }, - { url = "https://files.pythonhosted.org/packages/95/de/faba18a0af09969e10eb89fdbd4cb968bea95e75449a7fa944d4de7d1d2f/aiohttp-3.11.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f6b2c5b4a4d22b8fb2c92ac98e0747f5f195e8e9448bfb7404cd77e7bfa243f", size = 1675540 }, - { url = "https://files.pythonhosted.org/packages/ea/53/0437c46e960b79ae3b1ff74c1ec12f04bf4f425bd349c8807acb38aae3d7/aiohttp-3.11.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747ec46290107a490d21fe1ff4183bef8022b848cf9516970cb31de6d9460088", size = 1620210 }, - { url = "https://files.pythonhosted.org/packages/04/2f/31769ed8e29cc22baaa4005bd2749a7fd0f61ad0f86024d38dff8e394cf6/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01816f07c9cc9d80f858615b1365f8319d6a5fd079cd668cc58e15aafbc76a54", size = 1654399 }, - { url = "https://files.pythonhosted.org/packages/b0/24/acb24571815b9a86a8261577c920fd84f819178c02a75b05b1a0d7ab83fb/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a08ad95fcbd595803e0c4280671d808eb170a64ca3f2980dd38e7a72ed8d1fea", size = 1660424 }, - { url = "https://files.pythonhosted.org/packages/91/45/30ca0c3ba5bbf7592eee7489eae30437736f7ff912eaa04cfdcf74edca8c/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c97be90d70f7db3aa041d720bfb95f4869d6063fcdf2bb8333764d97e319b7d0", size = 1650415 }, - { url = "https://files.pythonhosted.org/packages/86/8d/4d887df5e732cc70349243c2c9784911979e7bd71c06f9e7717b8a896f75/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ab915a57c65f7a29353c8014ac4be685c8e4a19e792a79fe133a8e101111438e", size = 1733292 }, - { url = "https://files.pythonhosted.org/packages/40/c9/bd950dac0a4c84d44d8da8d6e0f9c9511d45e02cf908a4e1fca591f46a25/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:35cda4e07f5e058a723436c4d2b7ba2124ab4e0aa49e6325aed5896507a8a42e", size = 1755536 }, - { url = "https://files.pythonhosted.org/packages/32/04/aafeda6b4ed3693a44bb89eae002ebaa74f88b2265a7e68f8a31c33330f5/aiohttp-3.11.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:af55314407714fe77a68a9ccaab90fdb5deb57342585fd4a3a8102b6d4370080", size = 1693126 }, - { url = "https://files.pythonhosted.org/packages/a1/4f/67729187e884b0f002a0317d2cc7962a5a0416cadc95ea88ba92477290d9/aiohttp-3.11.13-cp311-cp311-win32.whl", hash = "sha256:42d689a5c0a0c357018993e471893e939f555e302313d5c61dfc566c2cad6185", size = 416800 }, - { url = "https://files.pythonhosted.org/packages/29/23/d98d491ca073ee92cc6a741be97b6b097fb06dacc5f95c0c9350787db549/aiohttp-3.11.13-cp311-cp311-win_amd64.whl", hash = "sha256:b73a2b139782a07658fbf170fe4bcdf70fc597fae5ffe75e5b67674c27434a9f", size = 442891 }, - { url = "https://files.pythonhosted.org/packages/9a/a9/6657664a55f78db8767e396cc9723782ed3311eb57704b0a5dacfa731916/aiohttp-3.11.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eabb269dc3852537d57589b36d7f7362e57d1ece308842ef44d9830d2dc3c90", size = 705054 }, - { url = "https://files.pythonhosted.org/packages/3b/06/f7df1fe062d16422f70af5065b76264f40b382605cf7477fa70553a9c9c1/aiohttp-3.11.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b77ee42addbb1c36d35aca55e8cc6d0958f8419e458bb70888d8c69a4ca833d", size = 464440 }, - { url = "https://files.pythonhosted.org/packages/22/3a/8773ea866735754004d9f79e501fe988bdd56cfac7fdecbc8de17fc093eb/aiohttp-3.11.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55789e93c5ed71832e7fac868167276beadf9877b85697020c46e9a75471f55f", size = 456394 }, - { url = "https://files.pythonhosted.org/packages/7f/61/8e2f2af2327e8e475a2b0890f15ef0bbfd117e321cce1e1ed210df81bbac/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c929f9a7249a11e4aa5c157091cfad7f49cc6b13f4eecf9b747104befd9f56f2", size = 1682752 }, - { url = "https://files.pythonhosted.org/packages/24/ed/84fce816bc8da39aa3f6c1196fe26e47065fea882b1a67a808282029c079/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d33851d85537bbf0f6291ddc97926a754c8f041af759e0aa0230fe939168852b", size = 1737375 }, - { url = "https://files.pythonhosted.org/packages/d9/de/35a5ba9e3d21ebfda1ebbe66f6cc5cbb4d3ff9bd6a03e5e8a788954f8f27/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9229d8613bd8401182868fe95688f7581673e1c18ff78855671a4b8284f47bcb", size = 1793660 }, - { url = "https://files.pythonhosted.org/packages/ff/fe/0f650a8c7c72c8a07edf8ab164786f936668acd71786dd5885fc4b1ca563/aiohttp-3.11.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669dd33f028e54fe4c96576f406ebb242ba534dd3a981ce009961bf49960f117", size = 1692233 }, - { url = "https://files.pythonhosted.org/packages/a8/20/185378b3483f968c6303aafe1e33b0da0d902db40731b2b2b2680a631131/aiohttp-3.11.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1b20a1ace54af7db1f95af85da530fe97407d9063b7aaf9ce6a32f44730778", size = 1619708 }, - { url = "https://files.pythonhosted.org/packages/a4/f9/d9c181750980b17e1e13e522d7e82a8d08d3d28a2249f99207ef5d8d738f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5724cc77f4e648362ebbb49bdecb9e2b86d9b172c68a295263fa072e679ee69d", size = 1641802 }, - { url = "https://files.pythonhosted.org/packages/50/c7/1cb46b72b1788710343b6e59eaab9642bd2422f2d87ede18b1996e0aed8f/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa36c35e94ecdb478246dd60db12aba57cfcd0abcad43c927a8876f25734d496", size = 1684678 }, - { url = "https://files.pythonhosted.org/packages/71/87/89b979391de840c5d7c34e78e1148cc731b8aafa84b6a51d02f44b4c66e2/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9b5b37c863ad5b0892cc7a4ceb1e435e5e6acd3f2f8d3e11fa56f08d3c67b820", size = 1646921 }, - { url = "https://files.pythonhosted.org/packages/a7/db/a463700ac85b72f8cf68093e988538faaf4e865e3150aa165cf80ee29d6e/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e06cf4852ce8c4442a59bae5a3ea01162b8fcb49ab438d8548b8dc79375dad8a", size = 1702493 }, - { url = "https://files.pythonhosted.org/packages/b8/32/1084e65da3adfb08c7e1b3e94f3e4ded8bd707dee265a412bc377b7cd000/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5194143927e494616e335d074e77a5dac7cd353a04755330c9adc984ac5a628e", size = 1735004 }, - { url = "https://files.pythonhosted.org/packages/a0/bb/a634cbdd97ce5d05c2054a9a35bfc32792d7e4f69d600ad7e820571d095b/aiohttp-3.11.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:afcb6b275c2d2ba5d8418bf30a9654fa978b4f819c2e8db6311b3525c86fe637", size = 1694964 }, - { url = "https://files.pythonhosted.org/packages/fd/cf/7d29db4e5c28ec316e5d2ac9ac9df0e2e278e9ea910e5c4205b9b64c2c42/aiohttp-3.11.13-cp312-cp312-win32.whl", hash = "sha256:7104d5b3943c6351d1ad7027d90bdd0ea002903e9f610735ac99df3b81f102ee", size = 411746 }, - { url = "https://files.pythonhosted.org/packages/65/a9/13e69ad4fd62104ebd94617f9f2be58231b50bb1e6bac114f024303ac23b/aiohttp-3.11.13-cp312-cp312-win_amd64.whl", hash = "sha256:47dc018b1b220c48089b5b9382fbab94db35bef2fa192995be22cbad3c5730c8", size = 438078 }, - { url = "https://files.pythonhosted.org/packages/87/dc/7d58d33cec693f1ddf407d4ab975445f5cb507af95600f137b81683a18d8/aiohttp-3.11.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9862d077b9ffa015dbe3ce6c081bdf35135948cb89116e26667dd183550833d1", size = 698372 }, - { url = "https://files.pythonhosted.org/packages/84/e7/5d88514c9e24fbc8dd6117350a8ec4a9314f4adae6e89fe32e3e639b0c37/aiohttp-3.11.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbfef0666ae9e07abfa2c54c212ac18a1f63e13e0760a769f70b5717742f3ece", size = 461057 }, - { url = "https://files.pythonhosted.org/packages/96/1a/8143c48a929fa00c6324f85660cb0f47a55ed9385f0c1b72d4b8043acf8e/aiohttp-3.11.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a1f7d857c4fcf7cabb1178058182c789b30d85de379e04f64c15b7e88d66fb", size = 453340 }, - { url = "https://files.pythonhosted.org/packages/2f/1c/b8010e4d65c5860d62681088e5376f3c0a940c5e3ca8989cae36ce8c3ea8/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba40b7ae0f81c7029583a338853f6607b6d83a341a3dcde8bed1ea58a3af1df9", size = 1665561 }, - { url = "https://files.pythonhosted.org/packages/19/ed/a68c3ab2f92fdc17dfc2096117d1cfaa7f7bdded2a57bacbf767b104165b/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5b95787335c483cd5f29577f42bbe027a412c5431f2f80a749c80d040f7ca9f", size = 1718335 }, - { url = "https://files.pythonhosted.org/packages/27/4f/3a0b6160ce663b8ebdb65d1eedff60900cd7108838c914d25952fe2b909f/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7d474c5c1f0b9405c1565fafdc4429fa7d986ccbec7ce55bc6a330f36409cad", size = 1775522 }, - { url = "https://files.pythonhosted.org/packages/0b/58/9da09291e19696c452e7224c1ce8c6d23a291fe8cd5c6b247b51bcda07db/aiohttp-3.11.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e83fb1991e9d8982b3b36aea1e7ad27ea0ce18c14d054c7a404d68b0319eebb", size = 1677566 }, - { url = "https://files.pythonhosted.org/packages/3d/18/6184f2bf8bbe397acbbbaa449937d61c20a6b85765f48e5eddc6d84957fe/aiohttp-3.11.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4586a68730bd2f2b04a83e83f79d271d8ed13763f64b75920f18a3a677b9a7f0", size = 1603590 }, - { url = "https://files.pythonhosted.org/packages/04/94/91e0d1ca0793012ccd927e835540aa38cca98bdce2389256ab813ebd64a3/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fe4eb0e7f50cdb99b26250d9328faef30b1175a5dbcfd6d0578d18456bac567", size = 1618688 }, - { url = "https://files.pythonhosted.org/packages/71/85/d13c3ea2e48a10b43668305d4903838834c3d4112e5229177fbcc23a56cd/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2a8a6bc19818ac3e5596310ace5aa50d918e1ebdcc204dc96e2f4d505d51740c", size = 1658053 }, - { url = "https://files.pythonhosted.org/packages/12/6a/3242a35100de23c1e8d9e05e8605e10f34268dee91b00d9d1e278c58eb80/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f27eec42f6c3c1df09cfc1f6786308f8b525b8efaaf6d6bd76c1f52c6511f6a", size = 1616917 }, - { url = "https://files.pythonhosted.org/packages/f5/b3/3f99b6f0a9a79590a7ba5655dbde8408c685aa462247378c977603464d0a/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2a4a13dfbb23977a51853b419141cd0a9b9573ab8d3a1455c6e63561387b52ff", size = 1685872 }, - { url = "https://files.pythonhosted.org/packages/8a/2e/99672181751f280a85e24fcb9a2c2469e8b1a0de1746b7b5c45d1eb9a999/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:02876bf2f69b062584965507b07bc06903c2dc93c57a554b64e012d636952654", size = 1715719 }, - { url = "https://files.pythonhosted.org/packages/7a/cd/68030356eb9a7d57b3e2823c8a852709d437abb0fbff41a61ebc351b7625/aiohttp-3.11.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b992778d95b60a21c4d8d4a5f15aaab2bd3c3e16466a72d7f9bfd86e8cea0d4b", size = 1673166 }, - { url = "https://files.pythonhosted.org/packages/03/61/425397a9a2839c609d09fdb53d940472f316a2dbeaa77a35b2628dae6284/aiohttp-3.11.13-cp313-cp313-win32.whl", hash = "sha256:507ab05d90586dacb4f26a001c3abf912eb719d05635cbfad930bdbeb469b36c", size = 410615 }, - { url = "https://files.pythonhosted.org/packages/9c/54/ebb815bc0fe057d8e7a11c086c479e972e827082f39aeebc6019dd4f0862/aiohttp-3.11.13-cp313-cp313-win_amd64.whl", hash = "sha256:5ceb81a4db2decdfa087381b5fc5847aa448244f973e5da232610304e199e7b2", size = 436452 }, -] - -[[package]] -name = "aiosignal" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +sdist = { url = "https://files.pythonhosted.org/packages/83/52/72e791b75c6b1efa803e491f7cbab78e963695e76d4ada05385252927e76/alembic-1.16.4.tar.gz", hash = "sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2", size = 1968161, upload-time = "2025-07-10T16:17:20.192Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, + { url = "https://files.pythonhosted.org/packages/c2/62/96b5217b742805236614f05904541000f55422a6060a90d7fd4ce26c172d/alembic-1.16.4-py3-none-any.whl", hash = "sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d", size = 247026, upload-time = "2025-07-10T16:17:21.845Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "anyio" -version = "4.8.0" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] [[package]] -name = "attrs" -version = "25.1.0" +name = "bidict" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/6e/026678aa5a830e07cd9498a05d3e7e650a4f56a42f267a53d22bcda1bdc9/bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71", size = 29093, upload-time = "2024-02-18T19:09:05.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, + { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, ] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] -name = "cffi" -version = "1.17.1" +name = "click" +version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/83/153f54356c7c200013a752ce1ed5448573dca546ce125801afca9e1ac1a4/coverage-7.10.5.tar.gz", hash = "sha256:f2e57716a78bc3ae80b2207be0709a3b2b63b9f2dcf9740ee6ac03588a2015b6", size = 821662, upload-time = "2025-08-23T14:42:44.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/08/4166ecfb60ba011444f38a5a6107814b80c34c717bc7a23be0d22e92ca09/coverage-7.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef3b83594d933020f54cf65ea1f4405d1f4e41a009c46df629dd964fcb6e907c", size = 217106, upload-time = "2025-08-23T14:41:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/d7/b71022408adbf040a680b8c64bf6ead3be37b553e5844f7465643979f7ca/coverage-7.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b96bfdf7c0ea9faebce088a3ecb2382819da4fbc05c7b80040dbc428df6af44", size = 217353, upload-time = "2025-08-23T14:41:16.656Z" }, + { url = "https://files.pythonhosted.org/packages/74/68/21e0d254dbf8972bb8dd95e3fe7038f4be037ff04ba47d6d1b12b37510ba/coverage-7.10.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63df1fdaffa42d914d5c4d293e838937638bf75c794cf20bee12978fc8c4e3bc", size = 248350, upload-time = "2025-08-23T14:41:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/90/65/28752c3a896566ec93e0219fc4f47ff71bd2b745f51554c93e8dcb659796/coverage-7.10.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8002dc6a049aac0e81ecec97abfb08c01ef0c1fbf962d0c98da3950ace89b869", size = 250955, upload-time = "2025-08-23T14:41:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/ca6b7967f57f6fef31da8749ea20417790bb6723593c8cd98a987be20423/coverage-7.10.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63d4bb2966d6f5f705a6b0c6784c8969c468dbc4bcf9d9ded8bff1c7e092451f", size = 252230, upload-time = "2025-08-23T14:41:20.959Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/17a411b2a2a18f8b8c952aa01c00f9284a1fbc677c68a0003b772ea89104/coverage-7.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1f672efc0731a6846b157389b6e6d5d5e9e59d1d1a23a5c66a99fd58339914d5", size = 250387, upload-time = "2025-08-23T14:41:22.644Z" }, + { url = "https://files.pythonhosted.org/packages/c7/89/97a9e271188c2fbb3db82235c33980bcbc733da7da6065afbaa1d685a169/coverage-7.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3f39cef43d08049e8afc1fde4a5da8510fc6be843f8dea350ee46e2a26b2f54c", size = 248280, upload-time = "2025-08-23T14:41:24.061Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0ad7d0137257553eb4706b4ad6180bec0a1b6a648b092c5bbda48d0e5b2c/coverage-7.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2968647e3ed5a6c019a419264386b013979ff1fb67dd11f5c9886c43d6a31fc2", size = 249894, upload-time = "2025-08-23T14:41:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/56/fb3aba936addb4c9e5ea14f5979393f1c2466b4c89d10591fd05f2d6b2aa/coverage-7.10.5-cp313-cp313-win32.whl", hash = "sha256:0d511dda38595b2b6934c2b730a1fd57a3635c6aa2a04cb74714cdfdd53846f4", size = 219536, upload-time = "2025-08-23T14:41:27.694Z" }, + { url = "https://files.pythonhosted.org/packages/fc/54/baacb8f2f74431e3b175a9a2881feaa8feb6e2f187a0e7e3046f3c7742b2/coverage-7.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a86281794a393513cf117177fd39c796b3f8e3759bb2764259a2abba5cce54b", size = 220330, upload-time = "2025-08-23T14:41:29.081Z" }, + { url = "https://files.pythonhosted.org/packages/64/8a/82a3788f8e31dee51d350835b23d480548ea8621f3effd7c3ba3f7e5c006/coverage-7.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:cebd8e906eb98bb09c10d1feed16096700b1198d482267f8bf0474e63a7b8d84", size = 218961, upload-time = "2025-08-23T14:41:30.511Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a1/590154e6eae07beee3b111cc1f907c30da6fc8ce0a83ef756c72f3c7c748/coverage-7.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0520dff502da5e09d0d20781df74d8189ab334a1e40d5bafe2efaa4158e2d9e7", size = 217819, upload-time = "2025-08-23T14:41:31.962Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ff/436ffa3cfc7741f0973c5c89405307fe39b78dcf201565b934e6616fc4ad/coverage-7.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d9cd64aca68f503ed3f1f18c7c9174cbb797baba02ca8ab5112f9d1c0328cd4b", size = 218040, upload-time = "2025-08-23T14:41:33.472Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ca/5787fb3d7820e66273913affe8209c534ca11241eb34ee8c4fd2aaa9dd87/coverage-7.10.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0913dd1613a33b13c4f84aa6e3f4198c1a21ee28ccb4f674985c1f22109f0aae", size = 259374, upload-time = "2025-08-23T14:41:34.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/89/21af956843896adc2e64fc075eae3c1cadb97ee0a6960733e65e696f32dd/coverage-7.10.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b7181c0feeb06ed8a02da02792f42f829a7b29990fef52eff257fef0885d760", size = 261551, upload-time = "2025-08-23T14:41:36.333Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/390a69244ab837e0ac137989277879a084c786cf036c3c4a3b9637d43a89/coverage-7.10.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d42b7396b605f774d4372dd9c49bed71cbabce4ae1ccd074d155709dd8f235", size = 263776, upload-time = "2025-08-23T14:41:38.25Z" }, + { url = "https://files.pythonhosted.org/packages/00/32/cfd6ae1da0a521723349f3129b2455832fc27d3f8882c07e5b6fefdd0da2/coverage-7.10.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b4fdc777e05c4940b297bf47bf7eedd56a39a61dc23ba798e4b830d585486ca5", size = 261326, upload-time = "2025-08-23T14:41:40.343Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c4/bf8d459fb4ce2201e9243ce6c015936ad283a668774430a3755f467b39d1/coverage-7.10.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:42144e8e346de44a6f1dbd0a56575dd8ab8dfa7e9007da02ea5b1c30ab33a7db", size = 259090, upload-time = "2025-08-23T14:41:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5d/a234f7409896468e5539d42234016045e4015e857488b0b5b5f3f3fa5f2b/coverage-7.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:66c644cbd7aed8fe266d5917e2c9f65458a51cfe5eeff9c05f15b335f697066e", size = 260217, upload-time = "2025-08-23T14:41:43.591Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/87560f036099f46c2ddd235be6476dd5c1d6be6bb57569a9348d43eeecea/coverage-7.10.5-cp313-cp313t-win32.whl", hash = "sha256:2d1b73023854068c44b0c554578a4e1ef1b050ed07cf8b431549e624a29a66ee", size = 220194, upload-time = "2025-08-23T14:41:45.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/a8/04a482594fdd83dc677d4a6c7e2d62135fff5a1573059806b8383fad9071/coverage-7.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:54a1532c8a642d8cc0bd5a9a51f5a9dcc440294fd06e9dda55e743c5ec1a8f14", size = 221258, upload-time = "2025-08-23T14:41:46.44Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ad/7da28594ab66fe2bc720f1bc9b131e62e9b4c6e39f044d9a48d18429cc21/coverage-7.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:74d5b63fe3f5f5d372253a4ef92492c11a4305f3550631beaa432fc9df16fcff", size = 219521, upload-time = "2025-08-23T14:41:47.882Z" }, + { url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" }, +] + +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, ] [[package]] name = "factorialhr" -version = "4.0.0b3" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/87/890a4f0f174c99d04e8ae702690257fc64fece8f05051a7e4d0ff9530e38/factorialhr-4.0.0b3.tar.gz", hash = "sha256:333bb89e2f505de6447bdcccb258f948226e4d9bf95ded293064bebedbd59393", size = 33193 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/4b/3b1b34a9b4aa18b63b1f590c7aa70f4f707c7e826cc9850084ad0dcf5004/factorialhr-4.0.0b3-py3-none-any.whl", hash = "sha256:e6b823fdd76013b764d6780b8d71ff96600212d7205b8c651fafdc9f306488f9", size = 43004 }, -] - -[[package]] -name = "frozenlist" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/43/0bed28bf5eb1c9e4301003b74453b8e7aa85fb293b31dde352aac528dafc/frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", size = 94987 }, - { url = "https://files.pythonhosted.org/packages/bb/bf/b74e38f09a246e8abbe1e90eb65787ed745ccab6eaa58b9c9308e052323d/frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", size = 54584 }, - { url = "https://files.pythonhosted.org/packages/2c/31/ab01375682f14f7613a1ade30149f684c84f9b8823a4391ed950c8285656/frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", size = 52499 }, - { url = "https://files.pythonhosted.org/packages/98/a8/d0ac0b9276e1404f58fec3ab6e90a4f76b778a49373ccaf6a563f100dfbc/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", size = 276357 }, - { url = "https://files.pythonhosted.org/packages/ad/c9/c7761084fa822f07dac38ac29f841d4587570dd211e2262544aa0b791d21/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", size = 287516 }, - { url = "https://files.pythonhosted.org/packages/a1/ff/cd7479e703c39df7bdab431798cef89dc75010d8aa0ca2514c5b9321db27/frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", size = 283131 }, - { url = "https://files.pythonhosted.org/packages/59/a0/370941beb47d237eca4fbf27e4e91389fd68699e6f4b0ebcc95da463835b/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", size = 261320 }, - { url = "https://files.pythonhosted.org/packages/b8/5f/c10123e8d64867bc9b4f2f510a32042a306ff5fcd7e2e09e5ae5100ee333/frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", size = 274877 }, - { url = "https://files.pythonhosted.org/packages/fa/79/38c505601ae29d4348f21706c5d89755ceded02a745016ba2f58bd5f1ea6/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", size = 269592 }, - { url = "https://files.pythonhosted.org/packages/19/e2/39f3a53191b8204ba9f0bb574b926b73dd2efba2a2b9d2d730517e8f7622/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", size = 265934 }, - { url = "https://files.pythonhosted.org/packages/d5/c9/3075eb7f7f3a91f1a6b00284af4de0a65a9ae47084930916f5528144c9dd/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", size = 283859 }, - { url = "https://files.pythonhosted.org/packages/05/f5/549f44d314c29408b962fa2b0e69a1a67c59379fb143b92a0a065ffd1f0f/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", size = 287560 }, - { url = "https://files.pythonhosted.org/packages/9d/f8/cb09b3c24a3eac02c4c07a9558e11e9e244fb02bf62c85ac2106d1eb0c0b/frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", size = 277150 }, - { url = "https://files.pythonhosted.org/packages/37/48/38c2db3f54d1501e692d6fe058f45b6ad1b358d82cd19436efab80cfc965/frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", size = 45244 }, - { url = "https://files.pythonhosted.org/packages/ca/8c/2ddffeb8b60a4bce3b196c32fcc30d8830d4615e7b492ec2071da801b8ad/frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", size = 51634 }, - { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, - { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, - { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, - { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, - { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, - { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, - { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, - { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, - { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, - { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, - { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, - { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, - { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, - { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, - { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, - { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, - { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, - { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, - { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, - { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, - { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, - { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, - { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, - { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, - { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, - { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, - { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, - { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, - { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, - { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, - { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, +sdist = { url = "https://files.pythonhosted.org/packages/c4/e1/c7fc3e92a296d8b8ae00075ddd9f63edbf874b3aec84ca8228b0a64e4b36/factorialhr-4.1.0.tar.gz", hash = "sha256:594f52d0e134bec2e02fce9282b236a2a8329d163576333f85462177e7aee6cc", size = 75042, upload-time = "2025-08-22T08:36:48.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/3c/8c4ed48d56e149e64977b5d898c4ef9266a9441ec21272302c333f3aa756/factorialhr-4.1.0-py3-none-any.whl", hash = "sha256:b0eee684c32982fd297de2d7fcc8c70a34e856121ec5aa19cd3da8837907563c", size = 100648, upload-time = "2025-08-22T08:36:47.109Z" }, ] [[package]] name = "fwtv" source = { editable = "." } dependencies = [ - { name = "aiohttp" }, + { name = "anyio" }, + { name = "dotenv" }, { name = "factorialhr" }, - { name = "outcome" }, - { name = "pyside6-essentials" }, - { name = "tabulate" }, - { name = "trio" }, + { name = "httpx" }, + { name = "reflex" }, ] [package.dev-dependencies] dev = [ - { name = "pyright" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "pytest-html" }, { name = "ruff" }, ] test = [ { name = "pytest" }, + { name = "pytest-cov" }, { name = "pytest-html" }, ] [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = ">=3.11.13" }, - { name = "factorialhr", specifier = "==4.0.0b3" }, - { name = "outcome", specifier = ">=1.3.0.post0" }, - { name = "pyside6-essentials", specifier = ">=6.8.2.1" }, - { name = "tabulate", specifier = ">=0.9.0" }, - { name = "trio", specifier = ">=0.29.0" }, + { name = "anyio", specifier = ">=4.9.0" }, + { name = "dotenv", specifier = ">=0.9.9" }, + { name = "factorialhr", specifier = ">=4.1.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "reflex", specifier = "==0.8.7" }, ] [package.metadata.requires-dev] dev = [ - { name = "pyright", specifier = ">=1.1.396" }, - { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, { name = "pytest-html", specifier = ">=4" }, - { name = "ruff", specifier = ">=0.9.10" }, + { name = "ruff", specifier = ">=0.12.9" }, ] test = [ - { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, { name = "pytest-html", specifier = ">=4" }, ] +[[package]] +name = "granian" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/91/6b51c5749a58e5d86063b193c15914700464f0d64eda84178bf432dbbcf9/granian-2.5.0.tar.gz", hash = "sha256:bed0d047c9c0c6c6a5a85ee5b3c7e2683fc63e03ac032eaf3d7654fa96bde102", size = 110336, upload-time = "2025-07-30T18:55:15.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/d8/c3c8a452f1b590400bb2cdef1ca61da8e9913762884cf3e6ba801fd3fdad/granian-2.5.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:50d4dc74ab763c1bf396cf85d93a8202bf1bfb74150b03f9fd62b600cd0c777c", size = 3026123, upload-time = "2025-07-30T18:53:20.94Z" }, + { url = "https://files.pythonhosted.org/packages/89/f0/e7038189d4e3b5f1e10bc23547b687bcdecdefbddef87013db64efea6800/granian-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:31705782cd616b9b70536c1b61b7f15815ebc4dcccdb72f58aa806ba7ac5dfa1", size = 2584469, upload-time = "2025-07-30T18:53:22.579Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2d/718620f393b6030d4a9ac5d1bc66cc5d159fb7f7c60c5d4483fd43902f7d/granian-2.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bbc4ebc727202ad4b3073ca8148c2af49904710d6fce84872191b2dd5cd36916", size = 3326593, upload-time = "2025-07-30T18:53:24.288Z" }, + { url = "https://files.pythonhosted.org/packages/07/57/c8b5f014673717e850db6d551057e74e330aadad5250d3f312cee432b1ec/granian-2.5.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af272218076663280fdc293b7da3adb716f23d54211cefad92fcf7e01b3eed19", size = 2937464, upload-time = "2025-07-30T18:53:25.72Z" }, + { url = "https://files.pythonhosted.org/packages/fe/91/8ae8aa9f0c3bbdf42fada15d623a5ab9fffd38acc243ec5619b6cbd60b9a/granian-2.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36493c4f2b672d027eb11b05ca6660f9fd4944452841d213cb0cb64da869539b", size = 3229316, upload-time = "2025-07-30T18:53:27.262Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8c/6c294cf3d77dc9524530d30057f9b6e334cc12c5414feb604fb277d030a3/granian-2.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:afafac4908d5931e4b2c2a09612e063d7ccd05e531f16b7f11e3bccc4ca8972c", size = 3109818, upload-time = "2025-07-30T18:53:29.004Z" }, + { url = "https://files.pythonhosted.org/packages/4a/49/acc3f1e02e35009d9486e4e00d2c951798a8098935d2374f52c7d2728438/granian-2.5.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:fb157c3d66301ffad4113da4c51aed4d56006b9ebe9d0892c682a634b5fff773", size = 3099384, upload-time = "2025-07-30T18:53:30.448Z" }, + { url = "https://files.pythonhosted.org/packages/c1/87/7cdd96fbeabbceea3820736e65bd6d8c0021983605cee26ef1bf2e11e24b/granian-2.5.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:879fdeb71fe279175a25d709d95dd2db01eb67cd12d300e51e3dc704ca5e52fd", size = 3468575, upload-time = "2025-07-30T18:53:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/fd/64/bd41efc6bfbca0ff871ce28a13b9e687055dd70913dfce92c4a21a264bf7/granian-2.5.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:74601bda3aedb249a3d5059d48108acfa61d6f71686162bda0bedc013a443efb", size = 3274703, upload-time = "2025-07-30T18:53:33.378Z" }, + { url = "https://files.pythonhosted.org/packages/97/42/c1a8f51d7ce3408a6aeebf68338e0282f0123b65c2fef1d7c202a407062d/granian-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:76dc084d1c528683a88c2d1a00786c9bc013b695b1776ad8a3c71419c45e1df0", size = 2321042, upload-time = "2025-07-30T18:53:34.82Z" }, + { url = "https://files.pythonhosted.org/packages/e9/43/af71556ea889c28b8c1c74e9f50a64c040a92bae5e4412b8617638a8aa0e/granian-2.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f371dd9eedae26158901fee3eb934e8fa61491cc78d234470ce364b989c78a1f", size = 2955162, upload-time = "2025-07-30T18:53:36.263Z" }, + { url = "https://files.pythonhosted.org/packages/c2/35/14c2c050f3df95eb054f2a44b41a02c30c8a04dc8cca888330f55c43a436/granian-2.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f7bf7ed30bcda9bbc9962f187081c5dfa6aa07e06c3a59486bc573b5def35914", size = 2548356, upload-time = "2025-07-30T18:53:38.116Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/8944acd78ff37a2effcdaf1d6163179e38c46c67c98bb1a68e93a75eb2c2/granian-2.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3152037d799ea97e5736de054a48bf75368fb79b7cfee7e6aa46de1076a43882", size = 3091535, upload-time = "2025-07-30T18:53:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/ce/49/cf0c89eaa41ac81271e3ae33834f71db945cc09dba609f6dc0e75247fd35/granian-2.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:9a53151c2d31dbcf1acbe6af89ce0282387614b6401650d511ca4260ba0e03c1", size = 2977716, upload-time = "2025-07-30T18:53:41.03Z" }, + { url = "https://files.pythonhosted.org/packages/47/58/e828bd5a02c412484b4056cef9aa505b014cc0bb1882f5dbdaf26782f147/granian-2.5.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:8f9918bee3c21eb1410f4323440d76eaa0c2d2e6ca4fa3e3a20d07cc54b788f6", size = 3094250, upload-time = "2025-07-30T18:53:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ba/e70f0de5cd6e5bca15ea5e5bc6c5598d34f9d4e9e6707e79e6edb63f1fac/granian-2.5.0-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:c28a34951c1ed8eea97948882bdbc374ce111be5a59293693613d25043ba1313", size = 3458806, upload-time = "2025-07-30T18:53:44.308Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c5/4d45042c86a924703f0c9617859742e929cdfe31b644bb16f9845c75342c/granian-2.5.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:944ea3bd400a7ccc8129835eda65bd6a37f8fb77828f4e6ded2f06827d6ec25f", size = 3254382, upload-time = "2025-07-30T18:53:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/ab/8e/bbe7564e28385ebb35e94dd7127238b9ba5c29ee09791f3f69d77b3985d4/granian-2.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9f6d080e45735dd93e4c60a79e42ee9ed37124a9580a08292d83b0961c705e39", size = 2312124, upload-time = "2025-07-30T18:53:47.204Z" }, +] + +[package.optional-dependencies] +reload = [ + { name = "watchfiles" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, +] + [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -300,27 +262,27 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -330,341 +292,189 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] -name = "markupsafe" -version = "3.0.2" +name = "mako" +version = "1.3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, -] - -[[package]] -name = "multidict" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/13/df3505a46d0cd08428e4c8169a196131d1b0c4b515c3649829258843dde6/multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", size = 48570 }, - { url = "https://files.pythonhosted.org/packages/f0/e1/a215908bfae1343cdb72f805366592bdd60487b4232d039c437fe8f5013d/multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", size = 29316 }, - { url = "https://files.pythonhosted.org/packages/70/0f/6dc70ddf5d442702ed74f298d69977f904960b82368532c88e854b79f72b/multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", size = 29640 }, - { url = "https://files.pythonhosted.org/packages/d8/6d/9c87b73a13d1cdea30b321ef4b3824449866bd7f7127eceed066ccb9b9ff/multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", size = 131067 }, - { url = "https://files.pythonhosted.org/packages/cc/1e/1b34154fef373371fd6c65125b3d42ff5f56c7ccc6bfff91b9b3c60ae9e0/multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", size = 138507 }, - { url = "https://files.pythonhosted.org/packages/fb/e0/0bc6b2bac6e461822b5f575eae85da6aae76d0e2a79b6665d6206b8e2e48/multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", size = 133905 }, - { url = "https://files.pythonhosted.org/packages/ba/af/73d13b918071ff9b2205fcf773d316e0f8fefb4ec65354bbcf0b10908cc6/multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", size = 129004 }, - { url = "https://files.pythonhosted.org/packages/74/21/23960627b00ed39643302d81bcda44c9444ebcdc04ee5bedd0757513f259/multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", size = 121308 }, - { url = "https://files.pythonhosted.org/packages/8b/5c/cf282263ffce4a596ed0bb2aa1a1dddfe1996d6a62d08842a8d4b33dca13/multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", size = 132608 }, - { url = "https://files.pythonhosted.org/packages/d7/3e/97e778c041c72063f42b290888daff008d3ab1427f5b09b714f5a8eff294/multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", size = 127029 }, - { url = "https://files.pythonhosted.org/packages/47/ac/3efb7bfe2f3aefcf8d103e9a7162572f01936155ab2f7ebcc7c255a23212/multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", size = 137594 }, - { url = "https://files.pythonhosted.org/packages/42/9b/6c6e9e8dc4f915fc90a9b7798c44a30773dea2995fdcb619870e705afe2b/multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", size = 134556 }, - { url = "https://files.pythonhosted.org/packages/1d/10/8e881743b26aaf718379a14ac58572a240e8293a1c9d68e1418fb11c0f90/multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", size = 130993 }, - { url = "https://files.pythonhosted.org/packages/45/84/3eb91b4b557442802d058a7579e864b329968c8d0ea57d907e7023c677f2/multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", size = 26405 }, - { url = "https://files.pythonhosted.org/packages/9f/0b/ad879847ecbf6d27e90a6eabb7eff6b62c129eefe617ea45eae7c1f0aead/multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", size = 28795 }, - { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, - { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, - { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, - { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, - { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, - { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, - { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, - { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, - { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, - { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, - { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, - { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, - { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, - { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, - { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, - { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, - { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, - { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, - { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, - { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, - { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, - { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, - { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, - { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, - { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, - { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, - { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, - { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, - { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, - { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, - { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, -] - -[[package]] -name = "outcome" -version = "1.3.0.post0" +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060 } + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "propcache" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/76/f941e63d55c0293ff7829dd21e7cf1147e90a526756869a9070f287a68c9/propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5", size = 42722 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/c9/cf09ff7e6d09f14149094f7cd50d2dec032b24e61af21fc4540da2b17bfb/propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51", size = 79568 }, - { url = "https://files.pythonhosted.org/packages/c8/32/2424d89da88cd81b7d148e0d2b3131461b570a02aa9d84a2e567509adb0d/propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e", size = 45895 }, - { url = "https://files.pythonhosted.org/packages/f6/91/ee5b6aa7aa31754fefcf0c5180e09223cac380ef195c4ddc8c266eb641ea/propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa", size = 45427 }, - { url = "https://files.pythonhosted.org/packages/bf/73/38f0128462b8b616181d8c53bd5d04eac41c50c449b07615c65d56ba0a9b/propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf", size = 232427 }, - { url = "https://files.pythonhosted.org/packages/59/82/f3d4e84f4539dcfc9c3d338282b9e915f5b63c921986ecfdf7af2d12f87c/propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b", size = 239985 }, - { url = "https://files.pythonhosted.org/packages/42/e8/029f58cccbae83c9969a7ee7a06558d5b83a93dfc54e0f4f70234bbaea1b/propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9", size = 238827 }, - { url = "https://files.pythonhosted.org/packages/8b/a2/c373561777c0cb9b9e7b9b9a10b9b3a7b6bde75a2535b962231cecc8fdb8/propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6", size = 231348 }, - { url = "https://files.pythonhosted.org/packages/d7/d2/4673f715beedf6038b485bcd976813149231d9df5bb6196cb69a09c185c9/propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c", size = 220426 }, - { url = "https://files.pythonhosted.org/packages/e0/f6/1da65f900927bafd4675a16e890618ec7643f2f922bf0e4d84bb38645618/propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075", size = 220294 }, - { url = "https://files.pythonhosted.org/packages/ff/86/620451bdc02e91b1712cd71890c17077ee97e2a28493836a87e47b8e70ff/propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c", size = 212492 }, - { url = "https://files.pythonhosted.org/packages/6e/1b/e8f86921ed4016da80faf3b8f515f7829decabdbff106736bfff353bceba/propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810", size = 215113 }, - { url = "https://files.pythonhosted.org/packages/1a/95/a61d86cc49aa0945f6c06f3a4614fc543e311a50558c92861f5e9691a37c/propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3", size = 228330 }, - { url = "https://files.pythonhosted.org/packages/8f/7d/10dbae48ff2bb189e92c2b3487a48f3229146a25941ad0d485934d1104d4/propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7", size = 231942 }, - { url = "https://files.pythonhosted.org/packages/39/ce/82d16aec96c5513ae7db13ab901a65a1e54c915292fb5b2390e33275b61d/propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c", size = 223077 }, - { url = "https://files.pythonhosted.org/packages/c8/e0/cb077e8e7a583c733df7f53327fcbdb92e42be59b976ce60bf1d904a0efe/propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d", size = 40455 }, - { url = "https://files.pythonhosted.org/packages/d8/35/57abeb6146fe3c19081eeaf3d9d4cfea256f87f1e5101acf80d3332c1820/propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32", size = 44705 }, - { url = "https://files.pythonhosted.org/packages/8d/2c/921f15dc365796ec23975b322b0078eae72995c7b4d49eba554c6a308d70/propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e", size = 79867 }, - { url = "https://files.pythonhosted.org/packages/11/a5/4a6cc1a559d1f2fb57ea22edc4245158cdffae92f7f92afcee2913f84417/propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af", size = 46109 }, - { url = "https://files.pythonhosted.org/packages/e1/6d/28bfd3af3a567ad7d667348e7f46a520bda958229c4d545ba138a044232f/propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5", size = 45635 }, - { url = "https://files.pythonhosted.org/packages/73/20/d75b42eaffe5075eac2f4e168f6393d21c664c91225288811d85451b2578/propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b", size = 242159 }, - { url = "https://files.pythonhosted.org/packages/a5/fb/4b537dd92f9fd4be68042ec51c9d23885ca5fafe51ec24c58d9401034e5f/propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667", size = 248163 }, - { url = "https://files.pythonhosted.org/packages/e7/af/8a9db04ac596d531ca0ef7dde518feaadfcdabef7b17d6a5ec59ee3effc2/propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7", size = 248794 }, - { url = "https://files.pythonhosted.org/packages/9d/c4/ecfc988879c0fd9db03228725b662d76cf484b6b46f7e92fee94e4b52490/propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7", size = 243912 }, - { url = "https://files.pythonhosted.org/packages/04/a2/298dd27184faa8b7d91cc43488b578db218b3cc85b54d912ed27b8c5597a/propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf", size = 229402 }, - { url = "https://files.pythonhosted.org/packages/be/0d/efe7fec316ca92dbf4bc4a9ba49ca889c43ca6d48ab1d6fa99fc94e5bb98/propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138", size = 226896 }, - { url = "https://files.pythonhosted.org/packages/60/63/72404380ae1d9c96d96e165aa02c66c2aae6072d067fc4713da5cde96762/propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86", size = 221447 }, - { url = "https://files.pythonhosted.org/packages/9d/18/b8392cab6e0964b67a30a8f4dadeaff64dc7022b5a34bb1d004ea99646f4/propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d", size = 222440 }, - { url = "https://files.pythonhosted.org/packages/6f/be/105d9ceda0f97eff8c06bac1673448b2db2a497444de3646464d3f5dc881/propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e", size = 234104 }, - { url = "https://files.pythonhosted.org/packages/cb/c9/f09a4ec394cfcce4053d8b2a04d622b5f22d21ba9bb70edd0cad061fa77b/propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64", size = 239086 }, - { url = "https://files.pythonhosted.org/packages/ea/aa/96f7f9ed6def82db67c972bdb7bd9f28b95d7d98f7e2abaf144c284bf609/propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c", size = 230991 }, - { url = "https://files.pythonhosted.org/packages/5a/11/bee5439de1307d06fad176f7143fec906e499c33d7aff863ea8428b8e98b/propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d", size = 40337 }, - { url = "https://files.pythonhosted.org/packages/e4/17/e5789a54a0455a61cb9efc4ca6071829d992220c2998a27c59aeba749f6f/propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57", size = 44404 }, - { url = "https://files.pythonhosted.org/packages/3a/0f/a79dd23a0efd6ee01ab0dc9750d8479b343bfd0c73560d59d271eb6a99d4/propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568", size = 77287 }, - { url = "https://files.pythonhosted.org/packages/b8/51/76675703c90de38ac75adb8deceb3f3ad99b67ff02a0fa5d067757971ab8/propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9", size = 44923 }, - { url = "https://files.pythonhosted.org/packages/01/9b/fd5ddbee66cf7686e73c516227c2fd9bf471dbfed0f48329d095ea1228d3/propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767", size = 44325 }, - { url = "https://files.pythonhosted.org/packages/13/1c/6961f11eb215a683b34b903b82bde486c606516c1466bf1fa67f26906d51/propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8", size = 225116 }, - { url = "https://files.pythonhosted.org/packages/ef/ea/f8410c40abcb2e40dffe9adeed017898c930974650a63e5c79b886aa9f73/propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0", size = 229905 }, - { url = "https://files.pythonhosted.org/packages/ef/5a/a9bf90894001468bf8e6ea293bb00626cc9ef10f8eb7996e9ec29345c7ed/propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d", size = 233221 }, - { url = "https://files.pythonhosted.org/packages/dd/ce/fffdddd9725b690b01d345c1156b4c2cc6dca09ab5c23a6d07b8f37d6e2f/propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05", size = 227627 }, - { url = "https://files.pythonhosted.org/packages/58/ae/45c89a5994a334735a3032b48e8e4a98c05d9536ddee0719913dc27da548/propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe", size = 214217 }, - { url = "https://files.pythonhosted.org/packages/01/84/bc60188c3290ff8f5f4a92b9ca2d93a62e449c8daf6fd11ad517ad136926/propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1", size = 212921 }, - { url = "https://files.pythonhosted.org/packages/14/b3/39d60224048feef7a96edabb8217dc3f75415457e5ebbef6814f8b2a27b5/propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92", size = 208200 }, - { url = "https://files.pythonhosted.org/packages/9d/b3/0a6720b86791251273fff8a01bc8e628bc70903513bd456f86cde1e1ef84/propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787", size = 208400 }, - { url = "https://files.pythonhosted.org/packages/e9/4f/bb470f3e687790547e2e78105fb411f54e0cdde0d74106ccadd2521c6572/propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545", size = 218116 }, - { url = "https://files.pythonhosted.org/packages/34/71/277f7f9add469698ac9724c199bfe06f85b199542121a71f65a80423d62a/propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e", size = 222911 }, - { url = "https://files.pythonhosted.org/packages/92/e3/a7b9782aef5a2fc765b1d97da9ec7aed2f25a4e985703608e73232205e3f/propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626", size = 216563 }, - { url = "https://files.pythonhosted.org/packages/ab/76/0583ca2c551aa08ffcff87b2c6849c8f01c1f6fb815a5226f0c5c202173e/propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374", size = 39763 }, - { url = "https://files.pythonhosted.org/packages/80/ec/c6a84f9a36f608379b95f0e786c111d5465926f8c62f12be8cdadb02b15c/propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a", size = 43650 }, - { url = "https://files.pythonhosted.org/packages/ee/95/7d32e3560f5bf83fc2f2a4c1b0c181d327d53d5f85ebd045ab89d4d97763/propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf", size = 82140 }, - { url = "https://files.pythonhosted.org/packages/86/89/752388f12e6027a5e63f5d075f15291ded48e2d8311314fff039da5a9b11/propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0", size = 47296 }, - { url = "https://files.pythonhosted.org/packages/1b/4c/b55c98d586c69180d3048984a57a5ea238bdeeccf82dbfcd598e935e10bb/propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829", size = 46724 }, - { url = "https://files.pythonhosted.org/packages/0f/b6/67451a437aed90c4e951e320b5b3d7eb584ade1d5592f6e5e8f678030989/propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa", size = 291499 }, - { url = "https://files.pythonhosted.org/packages/ee/ff/e4179facd21515b24737e1e26e02615dfb5ed29416eed4cf5bc6ac5ce5fb/propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6", size = 293911 }, - { url = "https://files.pythonhosted.org/packages/76/8d/94a8585992a064a23bd54f56c5e58c3b8bf0c0a06ae10e56f2353ae16c3d/propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db", size = 293301 }, - { url = "https://files.pythonhosted.org/packages/b0/b8/2c860c92b4134f68c7716c6f30a0d723973f881c32a6d7a24c4ddca05fdf/propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54", size = 281947 }, - { url = "https://files.pythonhosted.org/packages/cd/72/b564be7411b525d11757b713c757c21cd4dc13b6569c3b2b8f6d3c96fd5e/propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121", size = 268072 }, - { url = "https://files.pythonhosted.org/packages/37/68/d94649e399e8d7fc051e5a4f2334efc567993525af083db145a70690a121/propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e", size = 275190 }, - { url = "https://files.pythonhosted.org/packages/d8/3c/446e125f5bbbc1922964dd67cb541c01cdb678d811297b79a4ff6accc843/propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e", size = 254145 }, - { url = "https://files.pythonhosted.org/packages/f4/80/fd3f741483dc8e59f7ba7e05eaa0f4e11677d7db2077522b92ff80117a2a/propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a", size = 257163 }, - { url = "https://files.pythonhosted.org/packages/dc/cf/6292b5ce6ed0017e6a89024a827292122cc41b6259b30ada0c6732288513/propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac", size = 280249 }, - { url = "https://files.pythonhosted.org/packages/e8/f0/fd9b8247b449fe02a4f96538b979997e229af516d7462b006392badc59a1/propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e", size = 288741 }, - { url = "https://files.pythonhosted.org/packages/64/71/cf831fdc2617f86cfd7f414cfc487d018e722dac8acc098366ce9bba0941/propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf", size = 277061 }, - { url = "https://files.pythonhosted.org/packages/42/78/9432542a35d944abeca9e02927a0de38cd7a298466d8ffa171536e2381c3/propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863", size = 42252 }, - { url = "https://files.pythonhosted.org/packages/6f/45/960365f4f8978f48ebb56b1127adf33a49f2e69ecd46ac1f46d6cf78a79d/propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46", size = 46425 }, - { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101 }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, -] - -[[package]] -name = "pyright" -version = "1.1.396" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/73/f20cb1dea1bdc1774e7f860fb69dc0718c7d8dea854a345faec845eb086a/pyright-1.1.396.tar.gz", hash = "sha256:142901f5908f5a0895be3d3befcc18bedcdb8cc1798deecaec86ef7233a29b03", size = 3814400 } +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/be/ecb7cfb42d242b7ee764b52e6ff4782beeec00e3b943a3ec832b281f9da6/pyright-1.1.396-py3-none-any.whl", hash = "sha256:c635e473095b9138c471abccca22b9fedbe63858e0b40d4fc4b67da041891844", size = 5689355 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, ] [[package]] -name = "pyside6-essentials" -version = "6.8.2.1" +name = "pygments" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "shiboken6" }, -] +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/bb/0127a53530cec0f9e7268e2fe235322b7b6e592caeb36c558b64da6ec52c/PySide6_Essentials-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:ae5cc48f7e9a08e73e3ec2387ce245c8150e620b8d5a87548ebd4b8e3aeae49b", size = 134909713 }, - { url = "https://files.pythonhosted.org/packages/d2/f9/aa4ff511ff1f3dd177f7e8f5a635e03fe578fa2045c8d6be4577e7db3b28/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5ab31e5395a4724102edd6e8ff980fa3f7cde2aa79050763a1dcc30bb914195a", size = 95331575 }, - { url = "https://files.pythonhosted.org/packages/fd/69/595002d860ee58431fe7add081d6f54fff94ae9680f2eb8cd355c1649bb6/PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:7aed46f91d44399b4c713cf7387f5fb6f0114413fbcdbde493a528fb8e19f6ed", size = 93200219 }, - { url = "https://files.pythonhosted.org/packages/5b/54/28a8b03f327e2c1d27d4a1ccf1a44997afc73c00ad07125d889640367194/PySide6_Essentials-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:18de224f09108998d194e60f2fb8a1e86367dd525dd8a6192598e80e6ada649e", size = 72502927 }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -676,9 +486,9 @@ dependencies = [ { name = "pytest" }, { name = "pytest-metadata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/ab/4862dcb5a8a514bd87747e06b8d55483c0c9e987e1b66972336946e49b49/pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07", size = 150773 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ab/4862dcb5a8a514bd87747e06b8d55483c0c9e987e1b66972336946e49b49/pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07", size = 150773, upload-time = "2023-11-07T15:44:28.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/c7/c160021cbecd956cc1a6f79e5fe155f7868b2e5b848f1320dad0b3e3122f/pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71", size = 23491 }, + { url = "https://files.pythonhosted.org/packages/c8/c7/c160021cbecd956cc1a6f79e5fe155f7868b2e5b848f1320dad0b3e3122f/pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71", size = 23491, upload-time = "2023-11-07T15:44:27.149Z" }, ] [[package]] @@ -688,158 +498,296 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952 } +sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428 }, + { url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" }, ] [[package]] -name = "ruff" -version = "0.9.10" +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-engineio" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "simple-websocket" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/0b/67295279b66835f9fa7a491650efcd78b20321c127036eef62c11a31e028/python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa", size = 91677, upload-time = "2025-06-04T19:22:18.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f", size = 59536, upload-time = "2025-06-04T19:22:16.916Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/8e/fafaa6f15c332e73425d9c44ada85360501045d5ab0b81400076aff27cf6/ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7", size = 3759776 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/b2/af7c2cc9e438cbc19fafeec4f20bfcd72165460fe75b2b6e9a0958c8c62b/ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d", size = 10049494 }, - { url = "https://files.pythonhosted.org/packages/6d/12/03f6dfa1b95ddd47e6969f0225d60d9d7437c91938a310835feb27927ca0/ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d", size = 10853584 }, - { url = "https://files.pythonhosted.org/packages/02/49/1c79e0906b6ff551fb0894168763f705bf980864739572b2815ecd3c9df0/ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d", size = 10155692 }, - { url = "https://files.pythonhosted.org/packages/5b/01/85e8082e41585e0e1ceb11e41c054e9e36fed45f4b210991052d8a75089f/ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c", size = 10369760 }, - { url = "https://files.pythonhosted.org/packages/a1/90/0bc60bd4e5db051f12445046d0c85cc2c617095c0904f1aa81067dc64aea/ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e", size = 9912196 }, - { url = "https://files.pythonhosted.org/packages/66/ea/0b7e8c42b1ec608033c4d5a02939c82097ddcb0b3e393e4238584b7054ab/ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12", size = 11434985 }, - { url = "https://files.pythonhosted.org/packages/d5/86/3171d1eff893db4f91755175a6e1163c5887be1f1e2f4f6c0c59527c2bfd/ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16", size = 12155842 }, - { url = "https://files.pythonhosted.org/packages/89/9e/700ca289f172a38eb0bca752056d0a42637fa17b81649b9331786cb791d7/ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52", size = 11613804 }, - { url = "https://files.pythonhosted.org/packages/f2/92/648020b3b5db180f41a931a68b1c8575cca3e63cec86fd26807422a0dbad/ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1", size = 13823776 }, - { url = "https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c", size = 11302673 }, - { url = "https://files.pythonhosted.org/packages/6c/db/d31c361c4025b1b9102b4d032c70a69adb9ee6fde093f6c3bf29f831c85c/ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43", size = 10235358 }, - { url = "https://files.pythonhosted.org/packages/d1/86/d6374e24a14d4d93ebe120f45edd82ad7dcf3ef999ffc92b197d81cdc2a5/ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c", size = 9886177 }, - { url = "https://files.pythonhosted.org/packages/00/62/a61691f6eaaac1e945a1f3f59f1eea9a218513139d5b6c2b8f88b43b5b8f/ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5", size = 10864747 }, - { url = "https://files.pythonhosted.org/packages/ee/94/2c7065e1d92a8a8a46d46d9c3cf07b0aa7e0a1e0153d74baa5e6620b4102/ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8", size = 11360441 }, - { url = "https://files.pythonhosted.org/packages/a7/8f/1f545ea6f9fcd7bf4368551fb91d2064d8f0577b3079bb3f0ae5779fb773/ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029", size = 10247401 }, - { url = "https://files.pythonhosted.org/packages/4f/18/fb703603ab108e5c165f52f5b86ee2aa9be43bb781703ec87c66a5f5d604/ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1", size = 11366360 }, - { url = "https://files.pythonhosted.org/packages/35/85/338e603dc68e7d9994d5d84f24adbf69bae760ba5efd3e20f5ff2cec18da/ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69", size = 10436892 }, + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] [[package]] -name = "shiboken6" -version = "6.8.2.1" +name = "python-socketio" +version = "5.13.0" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bidict" }, + { name = "python-engineio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/1a/396d50ccf06ee539fa758ce5623b59a9cb27637fc4b2dc07ed08bf495e77/python_socketio-5.13.0.tar.gz", hash = "sha256:ac4e19a0302ae812e23b712ec8b6427ca0521f7c582d6abb096e36e24a263029", size = 121125, upload-time = "2025-04-12T15:46:59.933Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/8f/71ccc3642edb59efaca35d4ba974248b1d7847f5e4d87d3ea323e73b2cab/shiboken6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:d3dedeb3732ecfc920c9f97da769c0022a1c3bda99346a9eba56fbf093deaa75", size = 401266 }, - { url = "https://files.pythonhosted.org/packages/7b/ff/ab4f287b9573e50b5a47c10e2af8feb5abecc3c7431bd5deec135efc969e/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c83e90056f13d0872cc4d2b7bf60b6d6e3b1b172f1f91910c0ba5b641af01758", size = 204273 }, - { url = "https://files.pythonhosted.org/packages/a6/b0/4fb102eb5260ee06d379769f3c4f0b82ef397c15f1cbbbbb3f6dceb86d5d/shiboken6-6.8.2.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8592401423acc693f51dbbfae5e7493cc3ed6738be79daaf90afa07f4da5bb25", size = 200909 }, - { url = "https://files.pythonhosted.org/packages/ae/88/b56bdb38a11066e4eecd1da6be4205bb406398b733b392b11c5aaf9547f7/shiboken6-6.8.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:1b751d47b759762b7ca31bad278d52eca4105d3028880d93979261ebbfba810c", size = 1150270 }, + { url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" }, +] + +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "reflex" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "click" }, + { name = "granian", extra = ["reload"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "psutil", marker = "sys_platform == 'win32'" }, + { name = "pydantic" }, + { name = "python-multipart" }, + { name = "python-socketio" }, + { name = "redis" }, + { name = "reflex-hosting-cli" }, + { name = "rich" }, + { name = "sqlmodel" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/3a/d9b54b0f5021ec99f28af5753275fc279d43b43b30fa5f7d71ecc3d3f80c/reflex-0.8.7.tar.gz", hash = "sha256:42af70890ea817e520e3c7a5dd0d94a56d43e393ff777dbec4253bc0b6bcac1c", size = 587068, upload-time = "2025-08-18T19:37:21.705Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/14/b25f38370d96017ed02e5548012d898166d7e8da7bc0ee63bee04da2faea/reflex-0.8.7-py3-none-any.whl", hash = "sha256:ac0e3b56ec11a5d97d2cdec1c8aa2aaee501597a02a12381d62a2918ce6783a1", size = 883153, upload-time = "2025-08-18T19:37:19.433Z" }, +] + +[[package]] +name = "reflex-hosting-cli" +version = "0.1.55" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/04/5920b46c3363edf5b4165909c3f87a197ee42d42a33cbd8497479f2cfefd/reflex_hosting_cli-0.1.55.tar.gz", hash = "sha256:f5d5b0c26cefdc7ba3356954b7be6bfa8ec50267c13a2247f6cf18a140e95918", size = 35818, upload-time = "2025-08-14T21:04:55.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b3/e3e96eec558368cce20d7064862f50849886565187b2dd1df600874799c9/reflex_hosting_cli-0.1.55-py3-none-any.whl", hash = "sha256:84e78715a1f112996a0a1f8c5503958931a7fc6f0f61b7332645b8fde96c3b17", size = 45323, upload-time = "2025-08-14T21:04:53.9Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, + { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, +] + +[[package]] +name = "simple-websocket" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300, upload-time = "2024-10-10T22:39:31.412Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842, upload-time = "2024-10-10T22:39:29.645Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] -name = "sortedcontainers" -version = "2.4.0" +name = "sqlalchemy" +version = "2.0.43" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, + { url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, ] [[package]] -name = "tabulate" -version = "0.9.0" +name = "sqlmodel" +version = "0.0.24" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780, upload-time = "2025-03-07T05:43:32.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, + { url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622, upload-time = "2025-03-07T05:43:30.37Z" }, ] [[package]] -name = "trio" -version = "0.29.0" +name = "starlette" +version = "0.47.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, - { name = "idna" }, - { name = "outcome" }, - { name = "sniffio" }, - { name = "sortedcontainers" }, + { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/47/f62e62a1a6f37909aed0bf8f5d5411e06fa03846cfcb64540cd1180ccc9f/trio-0.29.0.tar.gz", hash = "sha256:ea0d3967159fc130acb6939a0be0e558e364fee26b5deeecc893a6b08c361bdf", size = 588952 } +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/55/c4d9bea8b3d7937901958f65124123512419ab0eb73695e5f382521abbfb/trio-0.29.0-py3-none-any.whl", hash = "sha256:d8c463f1a9cc776ff63e331aba44c125f423a5a13c684307e828d930e625ba66", size = 492920 }, + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] -name = "yarl" -version = "1.18.3" +name = "typing-inspection" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/93/282b5f4898d8e8efaf0790ba6d10e2245d2c9f30e199d1a85cae9356098c/yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069", size = 141555 }, - { url = "https://files.pythonhosted.org/packages/6d/9c/0a49af78df099c283ca3444560f10718fadb8a18dc8b3edf8c7bd9fd7d89/yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193", size = 94351 }, - { url = "https://files.pythonhosted.org/packages/5a/a1/205ab51e148fdcedad189ca8dd587794c6f119882437d04c33c01a75dece/yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889", size = 92286 }, - { url = "https://files.pythonhosted.org/packages/ed/fe/88b690b30f3f59275fb674f5f93ddd4a3ae796c2b62e5bb9ece8a4914b83/yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8", size = 340649 }, - { url = "https://files.pythonhosted.org/packages/07/eb/3b65499b568e01f36e847cebdc8d7ccb51fff716dbda1ae83c3cbb8ca1c9/yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca", size = 356623 }, - { url = "https://files.pythonhosted.org/packages/33/46/f559dc184280b745fc76ec6b1954de2c55595f0ec0a7614238b9ebf69618/yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8", size = 354007 }, - { url = "https://files.pythonhosted.org/packages/af/ba/1865d85212351ad160f19fb99808acf23aab9a0f8ff31c8c9f1b4d671fc9/yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae", size = 344145 }, - { url = "https://files.pythonhosted.org/packages/94/cb/5c3e975d77755d7b3d5193e92056b19d83752ea2da7ab394e22260a7b824/yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3", size = 336133 }, - { url = "https://files.pythonhosted.org/packages/19/89/b77d3fd249ab52a5c40859815765d35c91425b6bb82e7427ab2f78f5ff55/yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb", size = 347967 }, - { url = "https://files.pythonhosted.org/packages/35/bd/f6b7630ba2cc06c319c3235634c582a6ab014d52311e7d7c22f9518189b5/yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e", size = 346397 }, - { url = "https://files.pythonhosted.org/packages/18/1a/0b4e367d5a72d1f095318344848e93ea70da728118221f84f1bf6c1e39e7/yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59", size = 350206 }, - { url = "https://files.pythonhosted.org/packages/b5/cf/320fff4367341fb77809a2d8d7fe75b5d323a8e1b35710aafe41fdbf327b/yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d", size = 362089 }, - { url = "https://files.pythonhosted.org/packages/57/cf/aadba261d8b920253204085268bad5e8cdd86b50162fcb1b10c10834885a/yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e", size = 366267 }, - { url = "https://files.pythonhosted.org/packages/54/58/fb4cadd81acdee6dafe14abeb258f876e4dd410518099ae9a35c88d8097c/yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a", size = 359141 }, - { url = "https://files.pythonhosted.org/packages/9a/7a/4c571597589da4cd5c14ed2a0b17ac56ec9ee7ee615013f74653169e702d/yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1", size = 84402 }, - { url = "https://files.pythonhosted.org/packages/ae/7b/8600250b3d89b625f1121d897062f629883c2f45339623b69b1747ec65fa/yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5", size = 91030 }, - { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, - { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, - { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, - { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, - { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, - { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, - { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, - { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, - { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, - { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, - { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, - { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, - { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, - { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, - { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, - { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, - { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, - { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, - { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, - { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, - { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, - { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, - { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, - { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, - { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, - { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, - { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, - { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, - { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, - { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, - { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, - { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, - { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, ] From b629d66d360dfa05f71c8f717984886c87b403a9 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 25 Aug 2025 08:47:54 +0200 Subject: [PATCH 2/2] create env file --- .env.sample | 10 ++++++++++ README.md | 1 + factorialhr_analysis/state.py | 9 +++++---- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 .env.sample diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..ea12f08 --- /dev/null +++ b/.env.sample @@ -0,0 +1,10 @@ +# https://api.demo.factorial.dev for demo or "https://api.factorialhr.com" for production environment +FACTORIALHR_ENVIRONMENT_URL="https://api.factorialhr.com" + +# oauth settings, required if not using api key authentication +FACTORIALHR_CLIENT_ID="" +FACTORIALHR_CLIENT_SECRET="" +FACTORIALHR_REDIRECT_URI="" + +# if you want to use api key authentication instead of oauth, you can set it here +#FACTORIALHR_API_KEY="" diff --git a/README.md b/README.md index 834067c..658f77f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Errors where the time attended is 1 min above the limit are ignored, because fac - clone this repository - install dependencies using `uv sync --frozen` +- copy [`.env.sample`](.env.sample) to `.env` and adjust the environment variables - run app `uv run reflex run --env prod` ## Contributing diff --git a/factorialhr_analysis/state.py b/factorialhr_analysis/state.py index 4a32067..96dc35f 100644 --- a/factorialhr_analysis/state.py +++ b/factorialhr_analysis/state.py @@ -52,8 +52,10 @@ class LoginState(rx.State): ) redirect_to: str = '' - def get_auth(self) -> factorialhr.AccessTokenAuth: + def get_auth(self) -> factorialhr.AccessTokenAuth | factorialhr.ApiKeyAuth: """Get the authentication object for the API session.""" + if api_token := os.environ.get('FACTORIALHR_API_KEY'): + return factorialhr.ApiKeyAuth(api_key=api_token) api_session = self.api_session() if api_session is None: msg = 'api_session_cookie must be valid' @@ -108,8 +110,8 @@ async def login(self, token: str, *, grant_type: typing.Literal['refresh_token', @rx.var(cache=False) async def is_authenticated(self) -> bool: """Check if the user is authenticated.""" - if not self.is_hydrated: - return False + if os.environ.get('FACTORIALHR_API_KEY'): + return True # If using API key, always authenticated api_session = self.api_session() if api_session is None: return False @@ -135,7 +137,6 @@ async def refresh(self) -> bool: @rx.event async def redir(self): """Redirect to the redirect_to route if logged in, or to the login page if not.""" - logger.info('Redirecting...') if not self.is_hydrated: yield self.redir() page = self.router.url.path