From 73e17e047bfe8c4707cb74224a654b1a295d3b78 Mon Sep 17 00:00:00 2001 From: Graeme Holliday Date: Mon, 30 Sep 2024 16:21:34 -0500 Subject: [PATCH] uv formatting (#166) * uv formatting * add backtesting --- .github/pull_request_template.md | 3 +- .readthedocs.yaml | 22 +- Makefile | 4 +- docs/conf.py | 21 +- docs/requirements.txt | 5 - pyproject.toml | 2 + tastytrade/__init__.py | 156 ++-- tastytrade/account.py | 370 ++++---- tastytrade/backtest.py | 205 +++++ tastytrade/dxfeed/__init__.py | 22 +- tastytrade/dxfeed/candle.py | 1 + tastytrade/dxfeed/event.py | 28 +- tastytrade/dxfeed/greeks.py | 1 + tastytrade/dxfeed/profile.py | 1 + tastytrade/dxfeed/quote.py | 1 + tastytrade/dxfeed/summary.py | 1 + tastytrade/dxfeed/theoprice.py | 1 + tastytrade/dxfeed/timeandsale.py | 1 + tastytrade/dxfeed/trade.py | 1 + tastytrade/dxfeed/underlying.py | 1 + tastytrade/instruments.py | 350 ++++---- tastytrade/metrics.py | 57 +- tastytrade/order.py | 132 +-- tastytrade/search.py | 15 +- tastytrade/session.py | 72 +- tastytrade/streamer.py | 358 ++++---- tastytrade/utils.py | 16 +- tastytrade/watchlists.py | 89 +- tests/conftest.py | 12 +- tests/test_account.py | 44 +- tests/test_backtest.py | 28 + tests/test_instruments.py | 51 +- tests/test_metrics.py | 14 +- tests/test_search.py | 6 +- tests/test_streamer.py | 10 +- tests/test_utils.py | 28 +- tests/test_watchlists.py | 14 +- uv.lock | 1385 ++++++++++++++++++++++++++++++ 38 files changed, 2586 insertions(+), 942 deletions(-) delete mode 100644 docs/requirements.txt create mode 100644 tastytrade/backtest.py create mode 100644 tests/test_backtest.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5dc604a..3f530a9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,8 @@ Fixes ... ## Pre-merge checklist -- [ ] Passing tests LOCALLY +- [ ] Code formatted correctly with `uv run ruff format .` +- [ ] Passing tests locally - [ ] New tests added (if applicable) Please note that, in order to pass the tests, you'll need to set up your Tastytrade credentials as repository secrets on your local fork. Read more at CONTRIBUTING.md. diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5ef59aa..e39658f 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,17 +1,15 @@ version: 2 build: - os: "ubuntu-22.04" - tools: - python: "3.10" - -sphinx: - configuration: docs/conf.py - -python: - install: - - requirements: docs/requirements.txt - - method: pip - path: . + os: "ubuntu-22.04" + tools: + python: "3.10" + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv sync + - uv pip install . + - .venv/bin/python -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/htmlo formats: all diff --git a/Makefile b/Makefile index 0b83cb1..e81c09d 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ install: uv pip install -e . lint: - uv run ruff check tastytrade/ - uv run ruff check tests/ + uv run ruff check . + uv run ruff format . uv run mypy -p tastytrade uv run mypy -p tests diff --git a/docs/conf.py b/docs/conf.py index 774889e..2e6fd37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,10 +6,10 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'tastytrade' -copyright = '2024, Graeme Holliday' -author = 'Graeme Holliday' -release = '8.4' +project = "tastytrade" +copyright = "2024, Graeme Holliday" +author = "Graeme Holliday" +release = "8.4" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -22,26 +22,25 @@ "sphinx.ext.intersphinx", "sphinx_toolbox.more_autodoc.autotypeddict", "enum_tools.autoenum", - "sphinxcontrib.autodoc_pydantic" + "sphinxcontrib.autodoc_pydantic", ] intersphinx_mapping = { "rtd": ("https://docs.readthedocs.io/en/stable/", None), "python": ("https://docs.python.org/3/", None), - "sphinx": ("https://www.sphinx-doc.org/en/master/", None) + "sphinx": ("https://www.sphinx-doc.org/en/master/", None), } intersphinx_disabled_domains = ["std"] -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] # -- Options for pydantic ------------------------------------------------- autodoc_pydantic_model_show_json = True diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 5c7a26b..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -sphinx==6.2.1 -sphinx-rtd-theme==1.2.2 -sphinx-toolbox==3.4.0 -enum-tools==0.10.0 -autodoc-pydantic==2.0.1 diff --git a/pyproject.toml b/pyproject.toml index 04a234f..60ab408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ authors = [ dependencies = [ "fake-useragent>=1.5.1", + "httpx>=0.27.2", "pandas-market-calendars>=4.4.1", "pydantic>=2.9.2", "requests>=2.32.3", @@ -35,6 +36,7 @@ dev-dependencies = [ "sphinx-toolbox==3.4.0", "enum-tools>=0.12.0", "autodoc-pydantic>=2.2.0", + "jupyter>=1.1.1", ] [tool.setuptools.package-data] diff --git a/tastytrade/__init__.py b/tastytrade/__init__.py index d8623c1..2b4a231 100644 --- a/tastytrade/__init__.py +++ b/tastytrade/__init__.py @@ -1,81 +1,107 @@ import logging -API_URL = 'https://api.tastyworks.com' -CERT_URL = 'https://api.cert.tastyworks.com' -VERSION = '8.3' +API_URL = "https://api.tastyworks.com" +BACKTEST_URL = "https://backtester.vast.tastyworks.com" +CERT_URL = "https://api.cert.tastyworks.com" +VERSION = "8.4" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -# flake8: noqa +# ruff: noqa: E402 from .account import Account from .dxfeed import EventType -from .instruments import (Cryptocurrency, Equity, Future, FutureOption, - FutureOptionProduct, FutureProduct, - NestedFutureOptionChain, NestedOptionChain, Option, - OptionType, Warrant, get_future_option_chain, - get_option_chain, get_quantity_decimal_precisions) -from .metrics import (get_dividends, get_earnings, get_market_metrics, - get_risk_free_rate) -from .order import (ComplexOrderType, InstrumentType, NewComplexOrder, - NewOrder, OrderAction, OrderStatus, OrderTimeInForce, - OrderType, PriceEffect) +from .instruments import ( + Cryptocurrency, + Equity, + Future, + FutureOption, + FutureOptionProduct, + FutureProduct, + NestedFutureOptionChain, + NestedOptionChain, + Option, + OptionType, + Warrant, + get_future_option_chain, + get_option_chain, + get_quantity_decimal_precisions, +) +from .metrics import get_dividends, get_earnings, get_market_metrics, get_risk_free_rate +from .order import ( + ComplexOrderType, + InstrumentType, + NewComplexOrder, + NewOrder, + OrderAction, + OrderStatus, + OrderTimeInForce, + OrderType, + PriceEffect, +) from .search import symbol_search from .session import Session from .streamer import AlertStreamer, AlertType, DXLinkStreamer -from .utils import (get_future_fx_monthly, get_future_grain_monthly, - get_future_index_monthly, get_future_metal_monthly, - get_future_oil_monthly, get_future_treasury_monthly, - get_tasty_monthly, get_third_friday, now_in_new_york, - today_in_new_york) +from .utils import ( + get_future_fx_monthly, + get_future_grain_monthly, + get_future_index_monthly, + get_future_metal_monthly, + get_future_oil_monthly, + get_future_treasury_monthly, + get_tasty_monthly, + get_third_friday, + now_in_new_york, + today_in_new_york, +) from .watchlists import PairsWatchlist, Watchlist __all__ = [ - 'Account', - 'AlertStreamer', - 'AlertType', - 'ComplexOrderType', - 'Cryptocurrency', - 'DXLinkStreamer', - 'Equity', - 'EventType', - 'Future', - 'FutureOption', - 'FutureOptionProduct', - 'FutureProduct', - 'InstrumentType', - 'NestedFutureOptionChain', - 'NestedOptionChain', - 'NewComplexOrder', - 'NewOrder', - 'Option', - 'OptionType', - 'OrderAction', - 'OrderStatus', - 'OrderTimeInForce', - 'OrderType', - 'PairsWatchlist', - 'PriceEffect', - 'Session', - 'Warrant', - 'Watchlist', - 'get_dividends', - 'get_earnings', - 'get_future_fx_monthly', - 'get_future_grain_monthly', - 'get_future_index_monthly', - 'get_future_metal_monthly', - 'get_future_oil_monthly', - 'get_future_option_chain', - 'get_future_treasury_monthly', - 'get_market_metrics', - 'get_option_chain', - 'get_quantity_decimal_precisions', - 'get_risk_free_rate', - 'get_tasty_monthly', - 'get_third_friday', - 'now_in_new_york', - 'symbol_search', - 'today_in_new_york' + "Account", + "AlertStreamer", + "AlertType", + "ComplexOrderType", + "Cryptocurrency", + "DXLinkStreamer", + "Equity", + "EventType", + "Future", + "FutureOption", + "FutureOptionProduct", + "FutureProduct", + "InstrumentType", + "NestedFutureOptionChain", + "NestedOptionChain", + "NewComplexOrder", + "NewOrder", + "Option", + "OptionType", + "OrderAction", + "OrderStatus", + "OrderTimeInForce", + "OrderType", + "PairsWatchlist", + "PriceEffect", + "Session", + "Warrant", + "Watchlist", + "get_dividends", + "get_earnings", + "get_future_fx_monthly", + "get_future_grain_monthly", + "get_future_index_monthly", + "get_future_metal_monthly", + "get_future_oil_monthly", + "get_future_option_chain", + "get_future_treasury_monthly", + "get_market_metrics", + "get_option_chain", + "get_quantity_decimal_precisions", + "get_risk_free_rate", + "get_tasty_monthly", + "get_third_friday", + "now_in_new_york", + "symbol_search", + "today_in_new_york", ] diff --git a/tastytrade/account.py b/tastytrade/account.py index 3bf8f41..8b8adc4 100644 --- a/tastytrade/account.py +++ b/tastytrade/account.py @@ -1,26 +1,39 @@ from datetime import date, datetime from decimal import Decimal -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Literal, Optional, Union from pydantic import BaseModel -from tastytrade.order import (InstrumentType, NewComplexOrder, NewOrder, - OrderAction, OrderStatus, PlacedComplexOrder, - PlacedOrder, PlacedOrderResponse, PriceEffect) +from tastytrade.order import ( + InstrumentType, + NewComplexOrder, + NewOrder, + OrderAction, + OrderStatus, + PlacedComplexOrder, + PlacedOrder, + PlacedOrderResponse, + PriceEffect, +) from tastytrade.session import Session -from tastytrade.utils import (TastytradeError, TastytradeJsonDataclass, - today_in_new_york, validate_response) +from tastytrade.utils import ( + TastytradeError, + TastytradeJsonDataclass, + today_in_new_york, + validate_response, +) class EmptyDict(BaseModel): class Config: - extra = 'forbid' + extra = "forbid" class AccountBalance(TastytradeJsonDataclass): """ Dataclass containing account balance information. """ + account_number: str cash_balance: Decimal long_equity_value: Decimal @@ -76,6 +89,7 @@ class AccountBalanceSnapshot(TastytradeJsonDataclass): """ Dataclass containing account balance for a moment in time (snapshot). """ + account_number: str cash_balance: Decimal long_equity_value: Decimal @@ -122,6 +136,7 @@ class CurrentPosition(TastytradeJsonDataclass): Dataclass containing imformation about an individual position in a portfolio. """ + account_number: str symbol: str instrument_type: InstrumentType @@ -161,6 +176,7 @@ class Lot(TastytradeJsonDataclass): """ Dataclass containing information about the lot of a position. """ + id: str transaction_id: int quantity: Decimal @@ -175,6 +191,7 @@ class MarginReportEntry(TastytradeJsonDataclass): Dataclass containing an individual entry (relating to a specific position) as part of the overall margin report. """ + description: str code: str buying_power: Decimal @@ -200,6 +217,7 @@ class MarginReport(TastytradeJsonDataclass): """ Dataclass containing an overall portfolio margin report. """ + account_number: str description: str margin_calculation_type: str @@ -228,6 +246,7 @@ class MarginRequirement(TastytradeJsonDataclass): """ Dataclass containing general margin requirement information for a symbol. """ + underlying_symbol: str long_equity_initial: Decimal short_equity_initial: Decimal @@ -245,6 +264,7 @@ class NetLiqOhlc(TastytradeJsonDataclass): Dataclass containing historical net liquidation data in OHLC format (open, high, low, close), with a timestamp. """ + open: Decimal high: Decimal low: Decimal @@ -264,6 +284,7 @@ class PositionLimit(TastytradeJsonDataclass): """ Dataclass containing information about general account limits. """ + account_number: str equity_order_size: int equity_option_order_size: int @@ -281,6 +302,7 @@ class TradingStatus(TastytradeJsonDataclass): Dataclass containing information about an account's trading status, such as what types of trades are allowed (e.g. margin, crypto, futures) """ + account_number: str equities_margin_calculation_type: str fee_schedule_name: str @@ -324,6 +346,7 @@ class Transaction(TastytradeJsonDataclass): """ Dataclass containing information about a past transaction. """ + id: int account_number: str transaction_type: str @@ -377,6 +400,7 @@ class Account(TastytradeJsonDataclass): methods for retrieving information about the account, placing orders, and retrieving past transactions. """ + account_number: str opened_at: datetime nickname: str @@ -403,11 +427,7 @@ class Account(TastytradeJsonDataclass): submitting_user_id: Optional[str] = None @classmethod - def get_accounts( - cls, - session: Session, - include_closed=False - ) -> List['Account']: + def get_accounts(cls, session: Session, include_closed=False) -> List["Account"]: """ Gets all trading accounts associated with the Tastytrade user. @@ -415,22 +435,22 @@ def get_accounts( :param include_closed: whether to include closed accounts in the results (default False) """ - data = session.get('/customers/me/accounts') + data = session.get("/customers/me/accounts") return [ - cls(**i['account']) - for i in data['items'] - if include_closed or not i['account']['is-closed'] + cls(**i["account"]) + for i in data["items"] + if include_closed or not i["account"]["is-closed"] ] @classmethod - def get_account(cls, session: Session, account_number: str) -> 'Account': + def get_account(cls, session: Session, account_number: str) -> "Account": """ Returns the Tastytrade account associated with the given account ID. :param session: the session to use for the request. :param account_number: the account ID to get. """ - data = session.get(f'/customers/me/accounts/{account_number}') + data = session.get(f"/customers/me/accounts/{account_number}") return cls(**data) def get_trading_status(self, session: Session) -> TradingStatus: @@ -439,7 +459,7 @@ def get_trading_status(self, session: Session) -> TradingStatus: :param session: the session to use for the request. """ - data = session.get(f'/accounts/{self.account_number}/trading-status') + data = session.get(f"/accounts/{self.account_number}/trading-status") return TradingStatus(**data) def get_balances(self, session: Session) -> AccountBalance: @@ -448,37 +468,69 @@ def get_balances(self, session: Session) -> AccountBalance: :param session: the session to use for the request. """ - data = session.get(f'/accounts/{self.account_number}/balances') + data = session.get(f"/accounts/{self.account_number}/balances") return AccountBalance(**data) def get_balance_snapshots( self, session: Session, + per_page: int = 250, + page_offset: Optional[int] = None, + currency: str = "USD", + end_date: Optional[date] = None, + start_date: Optional[date] = None, snapshot_date: Optional[date] = None, - time_of_day: Optional[str] = None + time_of_day: Literal["BOD", "EOD"] = "EOD", ) -> List[AccountBalanceSnapshot]: """ - Returns a list of two balance snapshots. The first one is the - specified date, or, if not provided, the oldest snapshot available. - The second one is the most recent snapshot. - - If you provide the snapshot date, you must also provide the time of - day. + Returns a list of balance snapshots. This list will + just have a few snapshots if you don't pass a start + date; otherwise, it will be each day's balances in + the given range. :param session: the session to use for the request. + :param currency: the currency to show balances in. + :param start_date: the starting date of the range. + :param end_date: the ending date of the range. :param snapshot_date: the date of the snapshot to get. :param time_of_day: - the time of day of the snapshot to get, either 'EOD' or 'BOD'. + the time of day of the snapshots to get, either 'EOD' (End Of Day) or 'BOD' (Beginning Of Day). """ + paginate = False + if page_offset is None: + page_offset = 0 + paginate = True params = { - 'snapshot-date': snapshot_date, - 'time-of-day': time_of_day + "per-page": per_page, + "page-offset": page_offset, + "currency": currency, + "end-date": end_date, + "start-date": start_date, + "snapshot-date": snapshot_date, + "time-of-day": time_of_day, } - data = session.get( - f'/accounts/{self.account_number}/balance-snapshots', - params={k: v for k, v in params.items() if v is not None} - ) - return [AccountBalanceSnapshot(**i) for i in data['items']] + snapshots = [] + while True: + response = session.client.get( + (f"{session.base_url}/accounts/{self.account_number}/balance-snapshots"), + params={ + k: v # type: ignore + for k, v in params.items() + if v is not None + }, + ) + validate_response(response) + json = response.json() + snapshots.extend([AccountBalanceSnapshot(**i) for i in json["data"]["items"]]) + # handle pagination + pagination = json["pagination"] + if ( + pagination["page-offset"] >= pagination["total-pages"] - 1 + or not paginate + ): + break + params["page-offset"] += 1 # type: ignore + return snapshots def get_positions( self, @@ -490,7 +542,7 @@ def get_positions( underlying_product_code: Optional[str] = None, partition_keys: Optional[List[str]] = None, net_positions: Optional[bool] = None, - include_marks: Optional[bool] = None + include_marks: Optional[bool] = None, ) -> List[CurrentPosition]: """ Get the current positions of the account. @@ -510,27 +562,27 @@ def get_positions( include current quote mark (note: can decrease performance). """ params = { - 'underlying-symbol[]': underlying_symbols, - 'symbol': symbol, - 'instrument-type': instrument_type, - 'include-closed-positions': include_closed, - 'underlying-product-code': underlying_product_code, - 'partition-keys[]': partition_keys, - 'net-positions': net_positions, - 'include-marks': include_marks + "underlying-symbol[]": underlying_symbols, + "symbol": symbol, + "instrument-type": instrument_type, + "include-closed-positions": include_closed, + "underlying-product-code": underlying_product_code, + "partition-keys[]": partition_keys, + "net-positions": net_positions, + "include-marks": include_marks, } data = session.get( - f'/accounts/{self.account_number}/positions', - params={k: v for k, v in params.items() if v is not None} + f"/accounts/{self.account_number}/positions", + params={k: v for k, v in params.items() if v is not None}, ) - return [CurrentPosition(**i) for i in data['items']] + return [CurrentPosition(**i) for i in data["items"]] def get_history( self, session: Session, per_page: int = 250, page_offset: Optional[int] = None, - sort: str = 'Desc', + sort: str = "Desc", type: Optional[str] = None, types: Optional[List[str]] = None, sub_types: Optional[List[str]] = None, @@ -543,7 +595,7 @@ def get_history( partition_key: Optional[str] = None, futures_symbol: Optional[str] = None, start_at: Optional[datetime] = None, - end_at: Optional[datetime] = None + end_at: Optional[datetime] = None, ) -> List[Transaction]: """ Get transaction history of the account. @@ -578,46 +630,45 @@ def get_history( page_offset = 0 paginate = True params = { - 'per-page': per_page, - 'page-offset': page_offset, - 'sort': sort, - 'type': type, - 'types[]': types, - 'sub-type[]': sub_types, - 'start-date': start_date, - 'end-date': end_date, - 'instrument-type': instrument_type, - 'symbol': symbol, - 'underlying-symbol': underlying_symbol, - 'action': action, - 'partition-key': partition_key, - 'futures-symbol': futures_symbol, - 'start-at': start_at, - 'end-at': end_at + "per-page": per_page, + "page-offset": page_offset, + "sort": sort, + "type": type, + "types[]": types, + "sub-type[]": sub_types, + "start-date": start_date, + "end-date": end_date, + "instrument-type": instrument_type, + "symbol": symbol, + "underlying-symbol": underlying_symbol, + "action": action, + "partition-key": partition_key, + "futures-symbol": futures_symbol, + "start-at": start_at, + "end-at": end_at, } # loop through pages and get all transactions txns = [] while True: response = session.client.get( - (f'{session.base_url}/accounts/{self.account_number}' - f'/transactions'), + (f"{session.base_url}/accounts/{self.account_number}" f"/transactions"), params={ k: v # type: ignore for k, v in params.items() if v is not None - } + }, ) validate_response(response) json = response.json() - txns.extend([Transaction(**i) for i in json['data']['items']]) + txns.extend([Transaction(**i) for i in json["data"]["items"]]) # handle pagination - pagination = json['pagination'] + pagination = json["pagination"] if ( - pagination['page-offset'] >= pagination['total-pages'] - 1 or - not paginate + pagination["page-offset"] >= pagination["total-pages"] - 1 + or not paginate ): break - params['page-offset'] += 1 # type: ignore + params["page-offset"] += 1 # type: ignore return txns @@ -628,14 +679,11 @@ def get_transaction(self, session: Session, id: int) -> Transaction: :param session: the session to use for the request. :param id: the ID of the transaction to fetch. """ - data = session.get(f'/accounts/{self.account_number}/transactions/' - f'{id}') + data = session.get(f"/accounts/{self.account_number}/transactions/" f"{id}") return Transaction(**data) def get_total_fees( - self, - session: Session, - date: date = today_in_new_york() + self, session: Session, date: date = today_in_new_york() ) -> FeesInfo: """ Get the total fees for a given date. @@ -644,8 +692,8 @@ def get_total_fees( :param date: the date to get fees for. """ data = session.get( - f'/accounts/{self.account_number}/transactions/total-fees', - params={'date': date} + f"/accounts/{self.account_number}/transactions/total-fees", + params={"date": date}, ) return FeesInfo(**data) @@ -653,7 +701,7 @@ def get_net_liquidating_value_history( self, session: Session, time_back: Optional[str] = None, - start_time: Optional[datetime] = None + start_time: Optional[datetime] = None, ) -> List[NetLiqOhlc]: """ Returns a list of account net liquidating value snapshots over the @@ -672,17 +720,16 @@ def get_net_liquidating_value_history( params = {} if start_time: # format to Tastytrade DateTime format - params = {'start-time': start_time.strftime('%Y-%m-%dT%H:%M:%SZ')} + params = {"start-time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ")} elif not time_back: - msg = 'Either time_back or start_time must be specified.' + msg = "Either time_back or start_time must be specified." raise TastytradeError(msg) else: - params = {'time-back': time_back} + params = {"time-back": time_back} data = session.get( - f'/accounts/{self.account_number}/net-liq/history', - params=params + f"/accounts/{self.account_number}/net-liq/history", params=params ) - return [NetLiqOhlc(**i) for i in data['items']] + return [NetLiqOhlc(**i) for i in data["items"]] def get_position_limit(self, session: Session) -> PositionLimit: """ @@ -690,13 +737,11 @@ def get_position_limit(self, session: Session) -> PositionLimit: :param session: the session to use for the request. """ - data = session.get(f'/accounts/{self.account_number}/position-limit') + data = session.get(f"/accounts/{self.account_number}/position-limit") return PositionLimit(**data) def get_effective_margin_requirements( - self, - session: Session, - symbol: str + self, session: Session, symbol: str ) -> MarginRequirement: """ Get the effective margin requirements for a given symbol. @@ -706,9 +751,11 @@ def get_effective_margin_requirements( :param symbol: the symbol to get margin requirements for. """ if symbol: - symbol = symbol.replace('/', '%2F') - data = session.get(f'/accounts/{self.account_number}/margin-' - f'requirements/{symbol}/effective') + symbol = symbol.replace("/", "%2F") + data = session.get( + f"/accounts/{self.account_number}/margin-" + f"requirements/{symbol}/effective" + ) return MarginRequirement(**data) def get_margin_requirements(self, session: Session) -> MarginReport: @@ -718,8 +765,7 @@ def get_margin_requirements(self, session: Session) -> MarginReport: :param session: the session to use for the request. """ - data = session.get(f'/margin/accounts/{self.account_number}' - f'/requirements') + data = session.get(f"/margin/accounts/{self.account_number}" f"/requirements") return MarginReport(**data) def get_live_orders(self, session: Session) -> List[PlacedOrder]: @@ -728,35 +774,28 @@ def get_live_orders(self, session: Session) -> List[PlacedOrder]: :param session: the session to use for the request. """ - data = session.get(f'/accounts/{self.account_number}/orders/live') - return [PlacedOrder(**i) for i in data['items']] + data = session.get(f"/accounts/{self.account_number}/orders/live") + return [PlacedOrder(**i) for i in data["items"]] - def get_live_complex_orders( - self, - session: Session - ) -> List[PlacedComplexOrder]: + def get_live_complex_orders(self, session: Session) -> List[PlacedComplexOrder]: """ Get complex orders placed today for the account. :param session: the session to use for the request. """ - data = session.get(f'/accounts/{self.account_number}/complex-' - f'orders/live') - return [PlacedComplexOrder(**i) for i in data['items']] + data = session.get(f"/accounts/{self.account_number}/complex-" f"orders/live") + return [PlacedComplexOrder(**i) for i in data["items"]] - def get_complex_order( - self, - session: Session, - order_id: int - ) -> PlacedComplexOrder: + def get_complex_order(self, session: Session, order_id: int) -> PlacedComplexOrder: """ Gets a complex order with the given ID. :param session: the session to use for the request. :param order_id: the ID of the order to fetch. """ - data = session.get(f'/accounts/{self.account_number}/complex-' - f'orders/{order_id}') + data = session.get( + f"/accounts/{self.account_number}/complex-" f"orders/{order_id}" + ) return PlacedComplexOrder(**data) def get_order(self, session: Session, order_id: int) -> PlacedOrder: @@ -766,8 +805,7 @@ def get_order(self, session: Session, order_id: int) -> PlacedOrder: :param session: the session to use for the request. :param order_id: the ID of the order to fetch. """ - data = session.get(f'/accounts/{self.account_number}/orders' - f'/{order_id}') + data = session.get(f"/accounts/{self.account_number}/orders" f"/{order_id}") return PlacedOrder(**data) def delete_complex_order(self, session: Session, order_id: int) -> None: @@ -777,8 +815,7 @@ def delete_complex_order(self, session: Session, order_id: int) -> None: :param session: the session to use for the request. :param order_id: the ID of the order to delete. """ - session.delete(f'/accounts/{self.account_number}/complex-' - f'orders/{order_id}') + session.delete(f"/accounts/{self.account_number}/complex-" f"orders/{order_id}") def delete_order(self, session: Session, order_id: int) -> None: """ @@ -787,7 +824,7 @@ def delete_order(self, session: Session, order_id: int) -> None: :param session: the session to use for the request. :param order_id: the ID of the order to delete. """ - session.delete(f'/accounts/{self.account_number}/orders/{order_id}') + session.delete(f"/accounts/{self.account_number}/orders/{order_id}") def get_order_history( self, @@ -802,7 +839,7 @@ def get_order_history( underlying_instrument_type: Optional[InstrumentType] = None, sort: Optional[str] = None, start_at: Optional[datetime] = None, - end_at: Optional[datetime] = None + end_at: Optional[datetime] = None, ) -> List[PlacedOrder]: """ Get order history of the account. @@ -831,48 +868,45 @@ def get_order_history( page_offset = 0 paginate = True params = { - 'per-page': per_page, - 'page-offset': page_offset, - 'start-date': start_date, - 'end-date': end_date, - 'underlying-symbol': underlying_symbol, - 'status[]': statuses, - 'futures-symbol': futures_symbol, - 'underlying-instrument-type': underlying_instrument_type, - 'sort': sort, - 'start-at': start_at, - 'end-at': end_at + "per-page": per_page, + "page-offset": page_offset, + "start-date": start_date, + "end-date": end_date, + "underlying-symbol": underlying_symbol, + "status[]": statuses, + "futures-symbol": futures_symbol, + "underlying-instrument-type": underlying_instrument_type, + "sort": sort, + "start-at": start_at, + "end-at": end_at, } # loop through pages and get all transactions orders = [] while True: response = session.client.get( - f'{session.base_url}/accounts/{self.account_number}/orders', + f"{session.base_url}/accounts/{self.account_number}/orders", params={ k: v # type: ignore for k, v in params.items() if v is not None - } + }, ) validate_response(response) json = response.json() - orders.extend([PlacedOrder(**i) for i in json['data']['items']]) + orders.extend([PlacedOrder(**i) for i in json["data"]["items"]]) # handle pagination - pagination = json['pagination'] + pagination = json["pagination"] if ( - pagination['page-offset'] >= pagination['total-pages'] - 1 or - not paginate + pagination["page-offset"] >= pagination["total-pages"] - 1 + or not paginate ): break - params['page-offset'] += 1 # type: ignore + params["page-offset"] += 1 # type: ignore return orders def get_complex_order_history( - self, - session: Session, - per_page: int = 50, - page_offset: Optional[int] = None + self, session: Session, per_page: int = 50, page_offset: Optional[int] = None ) -> List[PlacedComplexOrder]: """ Get order history of the account. @@ -888,39 +922,33 @@ def get_complex_order_history( if page_offset is None: page_offset = 0 paginate = True - params = { - 'per-page': per_page, - 'page-offset': page_offset - } + params = {"per-page": per_page, "page-offset": page_offset} # loop through pages and get all transactions orders = [] while True: response = session.client.get( - (f'{session.base_url}/accounts/{self.account_number}' - f'/complex-orders'), - params={k: v for k, v in params.items() if v is not None} + ( + f"{session.base_url}/accounts/{self.account_number}" + f"/complex-orders" + ), + params={k: v for k, v in params.items() if v is not None}, ) validate_response(response) json = response.json() - orders.extend( - [PlacedComplexOrder(**i) for i in json['data']['items']] - ) + orders.extend([PlacedComplexOrder(**i) for i in json["data"]["items"]]) # handle pagination - pagination = json['pagination'] + pagination = json["pagination"] if ( - pagination['page-offset'] >= pagination['total-pages'] - 1 or - not paginate + pagination["page-offset"] >= pagination["total-pages"] - 1 + or not paginate ): break - params['page-offset'] += 1 # type: ignore + params["page-offset"] += 1 # type: ignore return orders def place_order( - self, - session: Session, - order: NewOrder, - dry_run: bool = True + self, session: Session, order: NewOrder, dry_run: bool = True ) -> PlacedOrderResponse: """ Place the given order. @@ -929,18 +957,15 @@ def place_order( :param order: the order to place. :param dry_run: whether this is a test order or not. """ - url = f'/accounts/{self.account_number}/orders' + url = f"/accounts/{self.account_number}/orders" if dry_run: - url += '/dry-run' + url += "/dry-run" json = order.model_dump_json(exclude_none=True, by_alias=True) data = session.post(url, data=json) return PlacedOrderResponse(**data) def place_complex_order( - self, - session: Session, - order: NewComplexOrder, - dry_run: bool = True + self, session: Session, order: NewComplexOrder, dry_run: bool = True ) -> PlacedOrderResponse: """ Place the given order. @@ -949,18 +974,15 @@ def place_complex_order( :param order: the order to place. :param dry_run: whether this is a test order or not. """ - url = f'/accounts/{self.account_number}/complex-orders' + url = f"/accounts/{self.account_number}/complex-orders" if dry_run: - url += '/dry-run' + url += "/dry-run" json = order.model_dump_json(exclude_none=True, by_alias=True) data = session.post(url, data=json) return PlacedOrderResponse(**data) def replace_order( - self, - session: Session, - old_order_id: int, - new_order: NewOrder + self, session: Session, old_order_id: int, new_order: NewOrder ) -> PlacedOrder: """ Replace an order with a new order with different characteristics (but @@ -971,11 +993,9 @@ def replace_order( :param new_order: the new order to replace the old order with. """ data = session.put( - f'/accounts/{self.account_number}/orders/{old_order_id}', + f"/accounts/{self.account_number}/orders/{old_order_id}", data=new_order.model_dump_json( - exclude={'legs'}, - exclude_none=True, - by_alias=True - ) + exclude={"legs"}, exclude_none=True, by_alias=True + ), ) return PlacedOrder(**data) diff --git a/tastytrade/backtest.py b/tastytrade/backtest.py new file mode 100644 index 0000000..1ebb9aa --- /dev/null +++ b/tastytrade/backtest.py @@ -0,0 +1,205 @@ +import asyncio +from datetime import date, datetime +from decimal import Decimal +from typing import AsyncGenerator, List, Literal, Optional + +import httpx +from fake_useragent import UserAgent # type: ignore +from pydantic import BaseModel, Field +from pydantic.alias_generators import to_camel + +from tastytrade import BACKTEST_URL +from tastytrade.session import Session +from tastytrade.utils import ( + TastytradeError, + validate_response, +) + + +class BacktestJsonDataclass(BaseModel): + """ + Dataclass for converting backtest JSON naming conventions to snake case. + """ + + class Config: + alias_generator = to_camel + populate_by_name = True + + +class BacktestEntry(BacktestJsonDataclass): + """ + Dataclass of parameters for backtest trade entry. + """ + + use_exact_DTE: bool = Field(default=True, serialization_alias="useExactDTE") + maximum_active_trials: Optional[int] = None + maximum_active_trials_behavior: Optional[Literal["close oldest", "don't enter"]] = ( + None + ) + frequency: str = "every day" + + +class BacktestExit(BacktestJsonDataclass): + """ + Dataclass of parameters for backtest trade exit. + """ + + after_days_in_trade: Optional[int] = None + stop_loss_percentage: Optional[int] = None + take_profit_percentage: Optional[int] = None + at_days_to_expiration: Optional[int] = None + + +class BacktestLeg(BacktestJsonDataclass): + """ + Dataclass of parameters for placing legs of backtest trades. + Leg delta must be a multiple of 5. + """ + + days_until_expiration: int = 45 + delta: int = 15 + direction: Literal["buy", "sell"] = "sell" + quantity: int = 1 + side: Literal["call", "put"] = "call" + + +class Backtest(BacktestJsonDataclass): + """ + Dataclass of configuration options for a backtest. + Date must be <= 2024-07-31. + """ + + symbol: str + entry_conditions: BacktestEntry + exit_conditions: BacktestExit + legs: List[BacktestLeg] + start_date: date + end_date: date = date(2024, 7, 31) + status: str = "pending" + + +class BacktestSnapshot(BacktestJsonDataclass): + """ + Dataclass containing a snapshot in time during the backtest. + """ + + date_time: datetime + profit_loss: Decimal + normalized_underlying_price: Optional[Decimal] = None + underlying_price: Optional[Decimal] = None + + +class BacktestTrial(BacktestJsonDataclass): + """ + Dataclass containing information on trades placed during the backtest. + """ + + close_date_time: datetime + open_date_time: datetime + profit_loss: Decimal + + +class BacktestStatistics(BaseModel): + """ + Dataclass containing statistics on the overall performance of a backtest. + """ + + class Config: + populate_by_name = True + + avg_bp_per_trade: Decimal = Field(validation_alias="Avg. BPR per trade") + avg_daily_pnl_change: Decimal = Field(validation_alias="Avg. daily change in PNL") + avg_daily_net_liq_change: Decimal = Field( + validation_alias="Avg. daily change in net liq" + ) + avg_days_in_trade: Decimal = Field(validation_alias="Avg. days in trade") + avg_premium: Decimal = Field(validation_alias="Avg. premium") + avg_profit_loss_per_trade: Decimal = Field( + validation_alias="Avg. profit/loss per trade" + ) + avg_return_per_trade: Decimal = Field(validation_alias="Avg. return per trade") + highest_profit: Decimal = Field(validation_alias="Highest profit") + loss_percentage: Decimal = Field(validation_alias="Loss percentage") + losses: int = Field(validation_alias="Losses") + max_drawdown: Decimal = Field(validation_alias="Max drawdown") + number_of_trades: int = Field(validation_alias="Number of trades") + premium_capture_rate: Decimal = Field(validation_alias="Premium capture rate") + return_on_used_capital: Decimal = Field(validation_alias="Return on used capital") + total_fees: Decimal = Field(validation_alias="Total fees") + total_premium: Decimal = Field(validation_alias="Total premium") + total_profit_loss: Decimal = Field(validation_alias="Total profit/loss") + used_capital: Decimal = Field(validation_alias="Used capital") + win_percentage: Decimal = Field(validation_alias="Win percentage") + wins: int = Field(validation_alias="Wins") + worst_loss: Decimal = Field(validation_alias="Worst loss") + + +class BacktestResults(BacktestJsonDataclass): + """ + Dataclass containing partial or finished results of a backtest. + """ + + snapshots: Optional[List[BacktestSnapshot]] + statistics: Optional[BacktestStatistics] + trials: Optional[List[BacktestTrial]] + + +class BacktestResponse(Backtest): + """ + Dataclass containing a backtest and associated information. + """ + + created_at: datetime + id: str + results: BacktestResults + eta: Optional[int] = None + progress: Optional[Decimal] = None + + +class BacktestSession: + """ + Class for creating a backtesting session which can be reused for multiple backtests. + + Example usage:: + + from tastytrade import BacktestSession, Backtest + from tqdm.asyncio import tqdm # progress bar + + backtest = Backtest(...) + backtest_session = BacktestSession(session) + results = [r async for r in tqdm(backtest_session.run(backtest))] + print(results[-1]) + + """ + + def __init__(self, session: Session): + if session.is_test: + raise TastytradeError("Certification sessions can't run backtests!") + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": UserAgent().random, + } + # Pull backtest token + response = httpx.post( + f"{BACKTEST_URL}/sessions", + json={"tastytradeToken": session.session_token}, + ) + validate_response(response) + # Token used for backtesting + backtest_token = response.json()["token"] + headers["Authorization"] = f"Bearer {backtest_token}" + self.client = httpx.AsyncClient(base_url=BACKTEST_URL, headers=headers) + + async def run(self, backtest: Backtest) -> AsyncGenerator[BacktestResponse, None]: + json = backtest.model_dump_json(by_alias=True, exclude_none=True) + response = await self.client.post("/backtests", data=json) # type: ignore + validate_response(response) + results = BacktestResponse(**response.json()) + while results.status != "completed": + yield results + await asyncio.sleep(0.5) + response = await self.client.get(f"/backtests/{results.id}") + validate_response(response) + results = BacktestResponse(**response.json()) + yield results diff --git a/tastytrade/dxfeed/__init__.py b/tastytrade/dxfeed/__init__.py index 0d2ac3f..7fb86a4 100644 --- a/tastytrade/dxfeed/__init__.py +++ b/tastytrade/dxfeed/__init__.py @@ -10,15 +10,15 @@ from .underlying import Underlying __all__ = [ - 'Candle', - 'Event', - 'EventType', - 'Greeks', - 'Profile', - 'Quote', - 'Summary', - 'TheoPrice', - 'TimeAndSale', - 'Trade', - 'Underlying' + "Candle", + "Event", + "EventType", + "Greeks", + "Profile", + "Quote", + "Summary", + "TheoPrice", + "TimeAndSale", + "Trade", + "Underlying", ] diff --git a/tastytrade/dxfeed/candle.py b/tastytrade/dxfeed/candle.py index 6611c94..e1c1f5e 100644 --- a/tastytrade/dxfeed/candle.py +++ b/tastytrade/dxfeed/candle.py @@ -10,6 +10,7 @@ class Candle(Event): for a specific period. Candles are build with a specified period using a specified price type with data taken from a specified exchange. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/event.py b/tastytrade/dxfeed/event.py index 2dcb86a..2cc95ce 100644 --- a/tastytrade/dxfeed/event.py +++ b/tastytrade/dxfeed/event.py @@ -16,26 +16,26 @@ class EventType(str, Enum): `_. """ - CANDLE = 'Candle' - GREEKS = 'Greeks' - PROFILE = 'Profile' - QUOTE = 'Quote' - SUMMARY = 'Summary' - THEO_PRICE = 'TheoPrice' - TIME_AND_SALE = 'TimeAndSale' - TRADE = 'Trade' - UNDERLYING = 'Underlying' + CANDLE = "Candle" + GREEKS = "Greeks" + PROFILE = "Profile" + QUOTE = "Quote" + SUMMARY = "Summary" + THEO_PRICE = "TheoPrice" + TIME_AND_SALE = "TimeAndSale" + TRADE = "Trade" + UNDERLYING = "Underlying" class Event(BaseModel): - @validator('*', pre=True) + @validator("*", pre=True) def change_nan_to_none(cls, v): - if v == 'NaN' or v == 'Infinity' or v == '-Infinity': + if v == "NaN" or v == "Infinity" or v == "-Infinity": return None return v @classmethod - def from_stream(cls, data: list) -> List['Event']: # pragma: no cover + def from_stream(cls, data: list) -> List["Event"]: # pragma: no cover """ Makes a list of event objects from a list of raw trade data fetched by a :class:`~tastyworks.streamer.DXFeedStreamer`. @@ -48,12 +48,12 @@ def from_stream(cls, data: list) -> List['Event']: # pragma: no cover size = len(cls.model_fields) multiples = len(data) / size if not multiples.is_integer(): - msg = 'Mapper data input values are not a multiple of the key size' + msg = "Mapper data input values are not a multiple of the key size" raise TastytradeError(msg) keys = cls.model_fields.keys() for i in range(int(multiples)): offset = i * size - local_values = data[offset:(i + 1) * size] + local_values = data[offset : (i + 1) * size] event_dict = dict(zip(keys, local_values)) objs.append(cls(**event_dict)) return objs diff --git a/tastytrade/dxfeed/greeks.py b/tastytrade/dxfeed/greeks.py index a21e57b..df371ba 100644 --- a/tastytrade/dxfeed/greeks.py +++ b/tastytrade/dxfeed/greeks.py @@ -12,6 +12,7 @@ class Greeks(Event): in different axes. If a derivative is very far from zero, then the portfolio has a risky sensitivity in this parameter. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/profile.py b/tastytrade/dxfeed/profile.py index 1c8f208..5fc1d4d 100644 --- a/tastytrade/dxfeed/profile.py +++ b/tastytrade/dxfeed/profile.py @@ -10,6 +10,7 @@ class Profile(Event): represents the most recent information that is available about the traded security on the market at any given moment of time. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/quote.py b/tastytrade/dxfeed/quote.py index 3c2f3e1..e0eee3c 100644 --- a/tastytrade/dxfeed/quote.py +++ b/tastytrade/dxfeed/quote.py @@ -9,6 +9,7 @@ class Quote(Event): A Quote event is a snapshot of the best bid and ask prices, and other fields that change with each quote. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/summary.py b/tastytrade/dxfeed/summary.py index 62336f0..eb6f92b 100644 --- a/tastytrade/dxfeed/summary.py +++ b/tastytrade/dxfeed/summary.py @@ -14,6 +14,7 @@ class Summary(Event): Before opening the bidding, the values are reset to N/A or NaN. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/theoprice.py b/tastytrade/dxfeed/theoprice.py index fc4c6e2..e7c3d88 100644 --- a/tastytrade/dxfeed/theoprice.py +++ b/tastytrade/dxfeed/theoprice.py @@ -11,6 +11,7 @@ class TheoPrice(Event): you with a formula so you can perform calculations based on values from this event. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/timeandsale.py b/tastytrade/dxfeed/timeandsale.py index ae61353..146902f 100644 --- a/tastytrade/dxfeed/timeandsale.py +++ b/tastytrade/dxfeed/timeandsale.py @@ -12,6 +12,7 @@ class TimeAndSale(Event): TimeAndSale events have a unique index that can be used for later correction/cancellation processing. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/trade.py b/tastytrade/dxfeed/trade.py index 62accc7..5f75290 100644 --- a/tastytrade/dxfeed/trade.py +++ b/tastytrade/dxfeed/trade.py @@ -12,6 +12,7 @@ class Trade(Event): about all transactions, but only about the last transaction for a single instrument. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/dxfeed/underlying.py b/tastytrade/dxfeed/underlying.py index ac3da6c..2daea8d 100644 --- a/tastytrade/dxfeed/underlying.py +++ b/tastytrade/dxfeed/underlying.py @@ -10,6 +10,7 @@ class Underlying(Event): represents the most recent information that is available about the corresponding values on the market at any given moment of time. """ + #: symbol of this event eventSymbol: str #: time of this event diff --git a/tastytrade/instruments.py b/tastytrade/instruments.py index fee16e1..8c5f21e 100644 --- a/tastytrade/instruments.py +++ b/tastytrade/instruments.py @@ -15,8 +15,9 @@ class OptionType(str, Enum): This is an :class:`~enum.Enum` that contains the valid types of options and their abbreviations in the API. """ - CALL = 'C' - PUT = 'P' + + CALL = "C" + PUT = "P" class FutureMonthCode(str, Enum): @@ -26,24 +27,26 @@ class FutureMonthCode(str, Enum): This is really here for reference, as the API barely uses these codes. """ - JAN = 'F' - FEB = 'G' - MAR = 'H' - APR = 'J' - MAY = 'K' - JUN = 'M' - JUL = 'N' - AUG = 'Q' - SEP = 'U' - OCT = 'V' - NOV = 'X' - DEC = 'Z' + + JAN = "F" + FEB = "G" + MAR = "H" + APR = "J" + MAY = "K" + JUN = "M" + JUL = "N" + AUG = "Q" + SEP = "U" + OCT = "V" + NOV = "X" + DEC = "Z" class Deliverable(TastytradeJsonDataclass): """ Dataclass representing the deliverable for an option. """ + id: int root_symbol: str deliverable_type: str @@ -59,6 +62,7 @@ class DestinationVenueSymbol(TastytradeJsonDataclass): Dataclass representing a specific destination venue symbol for a cryptocurrency. """ + id: int symbol: str destination_venue: str @@ -72,6 +76,7 @@ class QuantityDecimalPrecision(TastytradeJsonDataclass): Dataclass representing the decimal precision (number of places) for an instrument. """ + instrument_type: InstrumentType value: int minimum_increment_precision: int @@ -83,6 +88,7 @@ class Strike(TastytradeJsonDataclass): Dataclass representing a specific strike in an options chain, containing the symbols for the call and put options. """ + strike_price: Decimal call: str put: str @@ -94,6 +100,7 @@ class TickSize(TastytradeJsonDataclass): """ Dataclass representing the tick size for an instrument. """ + value: Decimal threshold: Optional[Decimal] = None symbol: Optional[str] = None @@ -103,6 +110,7 @@ class NestedOptionChainExpiration(TastytradeJsonDataclass): """ Dataclass representing an expiration in a nested options chain. """ + expiration_type: str expiration_date: date days_to_expiration: int @@ -114,6 +122,7 @@ class NestedFutureOptionChainExpiration(TastytradeJsonDataclass): """ Dataclass representing an expiration in a nested future options chain. """ + root_symbol: str notional_value: Decimal underlying_symbol: str @@ -137,6 +146,7 @@ class NestedFutureOptionFuture(TastytradeJsonDataclass): Dataclass representing an underlying future in a nested future options chain. """ + root_symbol: str days_to_expiration: int expiration_date: date @@ -153,6 +163,7 @@ class FutureEtfEquivalent(TastytradeJsonDataclass): Dataclass that represents the ETF equivalent for a future (aka, the number of shares of the ETF that are equivalent to one future, leverage-wise). """ + symbol: str share_quantity: int @@ -161,6 +172,7 @@ class Roll(TastytradeJsonDataclass): """ Dataclass representing a roll for a future. """ + name: str active_count: int cash_settled: bool @@ -174,6 +186,7 @@ class Cryptocurrency(TradeableTastytradeJsonDataclass): information about the cryptocurrency and methods to populate that data using cryptocurrency symbol(s). """ + id: int short_description: str description: str @@ -185,34 +198,28 @@ class Cryptocurrency(TradeableTastytradeJsonDataclass): @classmethod def get_cryptocurrencies( - cls, - session: Session, - symbols: List[str] = [] - ) -> List['Cryptocurrency']: + cls, session: Session, symbols: List[str] = [] + ) -> List["Cryptocurrency"]: """ Returns a list of cryptocurrency objects from the given symbols. :param session: the session to use for the request. :param symbols: the symbols to get the cryptocurrencies for. """ - params = {'symbol[]': symbols} if symbols else None - data = session.get('/instruments/cryptocurrencies', params=params) - return [cls(**i) for i in data['items']] + params = {"symbol[]": symbols} if symbols else None + data = session.get("/instruments/cryptocurrencies", params=params) + return [cls(**i) for i in data["items"]] @classmethod - def get_cryptocurrency( - cls, - session: Session, - symbol: str - ) -> 'Cryptocurrency': + def get_cryptocurrency(cls, session: Session, symbol: str) -> "Cryptocurrency": """ Returns a Cryptocurrency object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the cryptocurrency for. """ - symbol = symbol.replace('/', '%2F') - data = session.get(f'/instruments/cryptocurrencies/{symbol}') + symbol = symbol.replace("/", "%2F") + data = session.get(f"/instruments/cryptocurrencies/{symbol}") return cls(**data) @@ -221,6 +228,7 @@ class Equity(TradeableTastytradeJsonDataclass): Dataclass that represents a Tastytrade equity object. Contains information about the equity and methods to populate that data using equity symbol(s). """ + id: int is_index: bool listed_market: str @@ -248,8 +256,8 @@ def get_active_equities( session: Session, per_page: int = 1000, page_offset: Optional[int] = None, - lendability: Optional[str] = None - ) -> List['Equity']: + lendability: Optional[str] = None, + ) -> List["Equity"]: """ Returns a list of actively traded Equity objects. @@ -268,28 +276,28 @@ def get_active_equities( page_offset = 0 paginate = True params = { - 'per-page': per_page, - 'page-offset': page_offset, - 'lendability': lendability + "per-page": per_page, + "page-offset": page_offset, + "lendability": lendability, } # loop through pages and get all active equities equities = [] while True: response = session.client.get( - f'{session.base_url}/instruments/equities/active', - params={k: v for k, v in params.items() if v is not None} + f"{session.base_url}/instruments/equities/active", + params={k: v for k, v in params.items() if v is not None}, ) validate_response(response) json = response.json() - equities.extend([cls(**i) for i in json['data']['items']]) + equities.extend([cls(**i) for i in json["data"]["items"]]) # handle pagination - pagination = json['pagination'] + pagination = json["pagination"] if ( - pagination['page-offset'] >= pagination['total-pages'] - 1 or - not paginate + pagination["page-offset"] >= pagination["total-pages"] - 1 + or not paginate ): break - params['page-offset'] += 1 # type: ignore + params["page-offset"] += 1 # type: ignore return equities @@ -300,8 +308,8 @@ def get_equities( symbols: Optional[List[str]] = None, lendability: Optional[str] = None, is_index: Optional[bool] = None, - is_etf: Optional[bool] = None - ) -> List['Equity']: + is_etf: Optional[bool] = None, + ) -> List["Equity"]: """ Returns a list of Equity objects from the given symbols. @@ -314,27 +322,27 @@ def get_equities( :param is_etf: whether the equities are ETFs. """ params = { - 'symbol[]': symbols, - 'lendability': lendability, - 'is-index': is_index, - 'is-etf': is_etf + "symbol[]": symbols, + "lendability": lendability, + "is-index": is_index, + "is-etf": is_etf, } data = session.get( - '/instruments/equities', - params={k: v for k, v in params.items() if v is not None} + "/instruments/equities", + params={k: v for k, v in params.items() if v is not None}, ) - return [cls(**i) for i in data['items']] + return [cls(**i) for i in data["items"]] @classmethod - def get_equity(cls, session: Session, symbol: str) -> 'Equity': + def get_equity(cls, session: Session, symbol: str) -> "Equity": """ Returns a Equity object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the equity for. """ - symbol = symbol.replace('/', '%2F') - data = session.get(f'/instruments/equities/{symbol}') + symbol = symbol.replace("/", "%2F") + data = session.get(f"/instruments/equities/{symbol}") return cls(**data) @@ -343,6 +351,7 @@ class Option(TradeableTastytradeJsonDataclass): Dataclass that represents a Tastytrade option object. Contains information about the option and methods to populate that data using option symbol(s). """ + active: bool strike_price: Decimal root_symbol: str @@ -375,8 +384,8 @@ def get_options( session: Session, symbols: Optional[List[str]] = None, active: Optional[bool] = None, - with_expired: Optional[bool] = None - ) -> List['Option']: + with_expired: Optional[bool] = None, + ) -> List["Option"]: """ Returns a list of Option objects from the given symbols. @@ -385,49 +394,40 @@ def get_options( :param active: whether the options are active. :param with_expired: whether to include expired options. """ - params = { - 'symbol[]': symbols, - 'active': active, - 'with-expired': with_expired - } + params = {"symbol[]": symbols, "active": active, "with-expired": with_expired} data = session.get( - '/instruments/equity-options', - params={k: v for k, v in params.items() if v is not None} + "/instruments/equity-options", + params={k: v for k, v in params.items() if v is not None}, ) - return [cls(**i) for i in data['items']] + return [cls(**i) for i in data["items"]] @classmethod def get_option( - cls, - session: Session, - symbol: str, - active: Optional[bool] = None - ) -> 'Option': + cls, session: Session, symbol: str, active: Optional[bool] = None + ) -> "Option": """ Returns a Option object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the option for, OCC format """ - symbol = symbol.replace('/', '%2F') - params = {'active': active} if active is not None else None - data = session.get( - f'/instruments/equity-options/{symbol}', - params=params - ) + symbol = symbol.replace("/", "%2F") + params = {"active": active} if active is not None else None + data = session.get(f"/instruments/equity-options/{symbol}", params=params) return cls(**data) def _set_streamer_symbol(self) -> None: if self.strike_price % 1 == 0: - strike = '{0:.0f}'.format(self.strike_price) + strike = "{0:.0f}".format(self.strike_price) else: - strike = '{0:.2f}'.format(self.strike_price) - if strike[-1] == '0': + strike = "{0:.2f}".format(self.strike_price) + if strike[-1] == "0": strike = strike[:-1] - exp = self.expiration_date.strftime('%y%m%d') - self.streamer_symbol = \ - f'.{self.underlying_symbol}{exp}{self.option_type.value}{strike}' + exp = self.expiration_date.strftime("%y%m%d") + self.streamer_symbol = ( + f".{self.underlying_symbol}{exp}{self.option_type.value}{strike}" + ) @classmethod def streamer_symbol_to_occ(cls, streamer_symbol) -> str: @@ -436,12 +436,9 @@ def streamer_symbol_to_occ(cls, streamer_symbol) -> str: :param streamer_symbol: the streamer symbol to convert """ - match = re.match( - r'\.([A-Z]+)(\d{6})([CP])(\d+)(\.(\d+))?', - streamer_symbol - ) + match = re.match(r"\.([A-Z]+)(\d{6})([CP])(\d+)(\.(\d+))?", streamer_symbol) if match is None: - return '' + return "" symbol = match.group(1)[:6].ljust(6) exp = match.group(2) option_type = match.group(3) @@ -449,9 +446,9 @@ def streamer_symbol_to_occ(cls, streamer_symbol) -> str: if match.group(6) is not None: decimal = str(100 * int(match.group(6))).zfill(3) else: - decimal = '000' + decimal = "000" - return f'{symbol}{exp}{option_type}{strike}{decimal}' + return f"{symbol}{exp}{option_type}{strike}{decimal}" @classmethod def occ_to_streamer_symbol(cls, occ) -> str: @@ -463,18 +460,15 @@ def occ_to_streamer_symbol(cls, occ) -> str: """ symbol = occ[:6].split()[0] info = occ[6:] - match = re.match( - r'(\d{6})([CP])(\d{5})(\d{3})', - info - ) + match = re.match(r"(\d{6})([CP])(\d{5})(\d{3})", info) if match is None: - return '' + return "" exp = match.group(1) option_type = match.group(2) strike = int(match.group(3)) decimal = int(match.group(4)) - res = f'.{symbol}{exp}{option_type}{strike}' + res = f".{symbol}{exp}{option_type}{strike}" if decimal != 0: decimal_str = str(decimal / 1000.0) res += decimal_str[1:] @@ -491,6 +485,7 @@ class NestedOptionChain(TastytradeJsonDataclass): create actual :class:`Option` objects you'll need to make an extra API request or two. """ + underlying_symbol: str root_symbol: str option_chain_type: str @@ -500,16 +495,16 @@ class NestedOptionChain(TastytradeJsonDataclass): expirations: List[NestedOptionChainExpiration] @classmethod - def get_chain(cls, session: Session, symbol: str) -> 'NestedOptionChain': + def get_chain(cls, session: Session, symbol: str) -> "NestedOptionChain": """ Gets the option chain for the given symbol in nested format. :param session: the session to use for the request. :param symbol: the symbol to get the option chain for. """ - symbol = symbol.replace('/', '%2F') - data = session.get(f'/option-chains/{symbol}/nested') - return cls(**data['items'][0]) + symbol = symbol.replace("/", "%2F") + data = session.get(f"/option-chains/{symbol}/nested") + return cls(**data["items"][0]) class FutureProduct(TastytradeJsonDataclass): @@ -521,6 +516,7 @@ class FutureProduct(TastytradeJsonDataclass): Useful for fetching general information about a family of futures, without knowing the specific expirations or symbols. """ + root_symbol: str code: str description: str @@ -549,28 +545,22 @@ class FutureProduct(TastytradeJsonDataclass): clearport_code: Optional[str] = None legacy_code: Optional[str] = None legacy_exchange_code: Optional[str] = None - option_products: Optional[List['FutureOptionProduct']] = None + option_products: Optional[List["FutureOptionProduct"]] = None @classmethod - def get_future_products( - cls, - session: Session - ) -> List['FutureProduct']: + def get_future_products(cls, session: Session) -> List["FutureProduct"]: """ Returns a list of FutureProduct objects available. :param session: the session to use for the request. """ - data = session.get('/instruments/future-products') - return [cls(**i) for i in data['items']] + data = session.get("/instruments/future-products") + return [cls(**i) for i in data["items"]] @classmethod def get_future_product( - cls, - session: Session, - code: str, - exchange: str = 'CME' - ) -> 'FutureProduct': + cls, session: Session, code: str, exchange: str = "CME" + ) -> "FutureProduct": """ Returns a FutureProduct object from the given symbol. @@ -579,8 +569,8 @@ def get_future_product( :param exchange: the exchange to fetch from: 'CME', 'SMALLS', 'CFE', 'CBOED' """ - code = code.replace('/', '') - data = session.get(f'/instruments/future-products/{exchange}/{code}') + code = code.replace("/", "") + data = session.get(f"/instruments/future-products/{exchange}/{code}") return cls(**data) @@ -589,6 +579,7 @@ class Future(TradeableTastytradeJsonDataclass): Dataclass that represents a Tastytrade future object. Contains information about the future and methods to fetch futures for symbol(s). """ + product_code: str tick_size: Decimal notional_multiplier: Decimal @@ -609,7 +600,7 @@ class Future(TradeableTastytradeJsonDataclass): instrument_type: InstrumentType = InstrumentType.FUTURE streamer_symbol: Optional[str] = None is_tradeable: Optional[bool] = None - future_product: Optional['FutureProduct'] = None + future_product: Optional["FutureProduct"] = None contract_size: Optional[Decimal] = None main_fraction: Optional[Decimal] = None sub_fraction: Optional[Decimal] = None @@ -626,8 +617,8 @@ def get_futures( cls, session: Session, symbols: Optional[List[str]] = None, - product_codes: Optional[List[str]] = None - ) -> List['Future']: + product_codes: Optional[List[str]] = None, + ) -> List["Future"]: """ Returns a list of Future objects from the given symbols or product codes. @@ -639,26 +630,23 @@ def get_futures( the product codes of the futures, e.g. 'ES', '6A'. Ignored if symbols are provided. """ - params = { - 'symbol[]': symbols, - 'product-code[]': product_codes - } + params = {"symbol[]": symbols, "product-code[]": product_codes} data = session.get( - '/instruments/futures', - params={k: v for k, v in params.items() if v is not None} + "/instruments/futures", + params={k: v for k, v in params.items() if v is not None}, ) - return [cls(**i) for i in data['items']] + return [cls(**i) for i in data["items"]] @classmethod - def get_future(cls, session: Session, symbol: str) -> 'Future': + def get_future(cls, session: Session, symbol: str) -> "Future": """ Returns a Future object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the future for. """ - symbol = symbol.replace('/', '') - data = session.get(f'/instruments/futures/{symbol}') + symbol = symbol.replace("/", "") + data = session.get(f"/instruments/futures/{symbol}") return cls(**data) @@ -668,6 +656,7 @@ class FutureOptionProduct(TastytradeJsonDataclass): Contains information about the future option product (deliverable for the future option). """ + root_symbol: str cash_settled: bool code: str @@ -681,31 +670,27 @@ class FutureOptionProduct(TastytradeJsonDataclass): clearing_exchange_code: str clearing_price_multiplier: Decimal is_rollover: bool - future_product: Optional['FutureProduct'] = None + future_product: Optional["FutureProduct"] = None product_subtype: Optional[str] = None legacy_code: Optional[str] = None clearport_code: Optional[str] = None @classmethod def get_future_option_products( - cls, - session: Session - ) -> List['FutureOptionProduct']: + cls, session: Session + ) -> List["FutureOptionProduct"]: """ Returns a list of FutureOptionProduct objects available. :param session: the session to use for the request. """ - data = session.get('/instruments/future-option-products') - return [cls(**i) for i in data['items']] + data = session.get("/instruments/future-option-products") + return [cls(**i) for i in data["items"]] @classmethod def get_future_option_product( - cls, - session: Session, - root_symbol: str, - exchange: str = 'CME' - ) -> 'FutureOptionProduct': + cls, session: Session, root_symbol: str, exchange: str = "CME" + ) -> "FutureOptionProduct": """ Returns a FutureOptionProduct object from the given symbol. @@ -713,9 +698,10 @@ def get_future_option_product( :param code: the root symbol of the future option :param exchange: the exchange to get the product from """ - root_symbol = root_symbol.replace('/', '') - data = session.get(f'/instruments/future-option-products/' - f'{exchange}/{root_symbol}') + root_symbol = root_symbol.replace("/", "") + data = session.get( + f"/instruments/future-option-products/" f"{exchange}/{root_symbol}" + ) return cls(**data) @@ -724,6 +710,7 @@ class FutureOption(TradeableTastytradeJsonDataclass): Dataclass that represents a Tastytrade future option object. Contains information about the future option, and methods to get future options. """ + underlying_symbol: str product_code: str expiration_date: date @@ -756,7 +743,7 @@ class FutureOption(TradeableTastytradeJsonDataclass): security_exchange: str sx_id: str instrument_type: InstrumentType = InstrumentType.FUTURE_OPTION - future_option_product: Optional['FutureOptionProduct'] = None + future_option_product: Optional["FutureOptionProduct"] = None @classmethod def get_future_options( @@ -766,8 +753,8 @@ def get_future_options( root_symbol: Optional[str] = None, expiration_date: Optional[date] = None, option_type: Optional[OptionType] = None, - strike_price: Optional[Decimal] = None - ) -> List['FutureOption']: + strike_price: Optional[Decimal] = None, + ) -> List["FutureOption"]: """ Returns a list of FutureOption objects from the given symbols. @@ -783,32 +770,28 @@ def get_future_options( :param strike_price: the strike price to filter by. """ params = { - 'symbol[]': symbols, - 'option-root-symbol': root_symbol, - 'expiration-date': expiration_date, - 'option-type': option_type, - 'strike-price': strike_price + "symbol[]": symbols, + "option-root-symbol": root_symbol, + "expiration-date": expiration_date, + "option-type": option_type, + "strike-price": strike_price, } data = session.get( - '/instruments/future-options', - params={k: v for k, v in params.items() if v is not None} + "/instruments/future-options", + params={k: v for k, v in params.items() if v is not None}, ) - return [cls(**i) for i in data['items']] + return [cls(**i) for i in data["items"]] @classmethod - def get_future_option( - cls, - session: Session, - symbol: str - ) -> 'FutureOption': + def get_future_option(cls, session: Session, symbol: str) -> "FutureOption": """ Returns a FutureOption object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the option for, Tastytrade format """ - symbol = symbol.replace('/', '%2F').replace(' ', '%20') - data = session.get(f'/instruments/future-options/{symbol}') + symbol = symbol.replace("/", "%2F").replace(" ", "%20") + data = session.get(f"/instruments/future-options/{symbol}") return cls(**data) @@ -817,6 +800,7 @@ class NestedFutureOptionSubchain(TastytradeJsonDataclass): Dataclass that represents a Tastytrade nested future option chain for a specific futures underlying symbol. """ + underlying_symbol: str root_symbol: str exercise_style: str @@ -832,23 +816,20 @@ class NestedFutureOptionChain(TastytradeJsonDataclass): want to create actual :class:`FutureOption` objects you'll need to make an extra API request or two. """ + futures: List[NestedFutureOptionFuture] option_chains: List[NestedFutureOptionSubchain] @classmethod - def get_chain( - cls, - session: Session, - symbol: str - ) -> 'NestedFutureOptionChain': + def get_chain(cls, session: Session, symbol: str) -> "NestedFutureOptionChain": """ Gets the futures option chain for the given symbol in nested format. :param session: the session to use for the request. :param symbol: the symbol to get the option chain for. """ - symbol = symbol.replace('/', '') - data = session.get(f'/futures-option-chains/{symbol}/nested') + symbol = symbol.replace("/", "") + data = session.get(f"/futures-option-chains/{symbol}/nested") return cls(**data) @@ -857,6 +838,7 @@ class Warrant(TastytradeJsonDataclass): Dataclass that represents a Tastytrade warrant object. Contains information about the warrant, and methods to get warrants. """ + symbol: str instrument_type: InstrumentType listed_market: str @@ -867,29 +849,27 @@ class Warrant(TastytradeJsonDataclass): @classmethod def get_warrants( - cls, - session: Session, - symbols: Optional[List[str]] = None - ) -> List['Warrant']: + cls, session: Session, symbols: Optional[List[str]] = None + ) -> List["Warrant"]: """ Returns a list of Warrant objects from the given symbols. :param session: the session to use for the request. :param symbols: symbols of the warrants, e.g. 'NKLAW' """ - params = {'symbol[]': symbols} if symbols else None - data = session.get('/instruments/warrants', params=params) - return [cls(**i) for i in data['items']] + params = {"symbol[]": symbols} if symbols else None + data = session.get("/instruments/warrants", params=params) + return [cls(**i) for i in data["items"]] @classmethod - def get_warrant(cls, session: Session, symbol: str) -> 'Warrant': + def get_warrant(cls, session: Session, symbol: str) -> "Warrant": """ Returns a Warrant object from the given symbol. :param session: the session to use for the request. :param symbol: the symbol to get the warrant for. """ - data = session.get(f'/instruments/warrants/{symbol}') + data = session.get(f"/instruments/warrants/{symbol}") return cls(**data) @@ -897,23 +877,18 @@ def get_warrant(cls, session: Session, symbol: str) -> 'Warrant': FutureProduct.update_forward_refs() -def get_quantity_decimal_precisions( - session: Session -) -> List[QuantityDecimalPrecision]: +def get_quantity_decimal_precisions(session: Session) -> List[QuantityDecimalPrecision]: """ Returns a list of QuantityDecimalPrecision objects for different types of instruments. :param session: the session to use for the request. """ - data = session.get('/instruments/quantity-decimal-precisions') - return [QuantityDecimalPrecision(**i) for i in data['items']] + data = session.get("/instruments/quantity-decimal-precisions") + return [QuantityDecimalPrecision(**i) for i in data["items"]] -def get_option_chain( - session: Session, - symbol: str -) -> Dict[date, List[Option]]: +def get_option_chain(session: Session, symbol: str) -> Dict[date, List[Option]]: """ Returns a mapping of expiration date to a list of option objects representing the options chain for the given symbol. @@ -926,10 +901,10 @@ def get_option_chain( :param session: the session to use for the request. :param symbol: the symbol to get the option chain for. """ - symbol = symbol.replace('/', '%2F') - data = session.get(f'/option-chains/{symbol}') + symbol = symbol.replace("/", "%2F") + data = session.get(f"/option-chains/{symbol}") chain = defaultdict(list) - for i in data['items']: + for i in data["items"]: option = Option(**i) chain[option.expiration_date].append(option) @@ -937,8 +912,7 @@ def get_option_chain( def get_future_option_chain( - session: Session, - symbol: str + session: Session, symbol: str ) -> Dict[date, List[FutureOption]]: """ Returns a mapping of expiration date to a list of futures options @@ -952,10 +926,10 @@ def get_future_option_chain( :param session: the session to use for the request. :param symbol: the symbol to get the option chain for. """ - symbol = symbol.replace('/', '') - data = session.get(f'/futures-option-chains/{symbol}') + symbol = symbol.replace("/", "") + data = session.get(f"/futures-option-chains/{symbol}") chain = defaultdict(list) - for i in data['items']: + for i in data["items"]: option = FutureOption(**i) chain[option.expiration_date].append(option) diff --git a/tastytrade/metrics.py b/tastytrade/metrics.py index 0d0821e..9a75df3 100644 --- a/tastytrade/metrics.py +++ b/tastytrade/metrics.py @@ -10,6 +10,7 @@ class DividendInfo(TastytradeJsonDataclass): """ Dataclass representing dividend information for a given symbol. """ + occurred_date: date amount: Decimal @@ -18,6 +19,7 @@ class EarningsInfo(TastytradeJsonDataclass): """ Dataclass representing earnings information for a given symbol. """ + occurred_date: date eps: Decimal @@ -27,6 +29,7 @@ class EarningsReport(TastytradeJsonDataclass): Dataclass containing information about a recent earnings report, or the expected date of the next one. """ + estimated: bool late_flag: int visible: bool @@ -42,6 +45,7 @@ class Liquidity(TastytradeJsonDataclass): """ Dataclass representing liquidity information for a given symbol. """ + sum: Decimal count: int started_at: datetime @@ -53,6 +57,7 @@ class OptionExpirationImpliedVolatility(TastytradeJsonDataclass): Dataclass containing implied volatility information for a given symbol and expiration date. """ + expiration_date: date settlement_type: str option_chain_type: str @@ -65,6 +70,7 @@ class MarketMetricInfo(TastytradeJsonDataclass): Contains lots of useful information, like IV rank, IV percentile and beta. """ + symbol: str implied_volatility_index: Optional[Decimal] = None implied_volatility_index_5_day_change: Optional[Decimal] = None @@ -77,7 +83,9 @@ class MarketMetricInfo(TastytradeJsonDataclass): implied_volatility_updated_at: Optional[datetime] = None liquidity_rating: Optional[int] = None updated_at: datetime - option_expiration_implied_volatilities: Optional[List[OptionExpirationImpliedVolatility]] = None # noqa: E501 + option_expiration_implied_volatilities: Optional[ + List[OptionExpirationImpliedVolatility] + ] = None # noqa: E501 beta: Optional[Decimal] = None corr_spy_3month: Optional[Decimal] = None market_cap: Decimal @@ -105,44 +113,32 @@ class MarketMetricInfo(TastytradeJsonDataclass): borrow_rate: Optional[Decimal] = None -def get_market_metrics( - session: Session, - symbols: List[str] -) -> List[MarketMetricInfo]: +def get_market_metrics(session: Session, symbols: List[str]) -> List[MarketMetricInfo]: """ Retrieves market metrics for the given symbols. :param session: active user session to use :param symbols: list of symbols to retrieve metrics for """ - data = session.get( - '/market-metrics', - params={'symbols': ','.join(symbols)} - ) - return [MarketMetricInfo(**i) for i in data['items']] + data = session.get("/market-metrics", params={"symbols": ",".join(symbols)}) + return [MarketMetricInfo(**i) for i in data["items"]] -def get_dividends( - session: Session, - symbol: str -) -> List[DividendInfo]: +def get_dividends(session: Session, symbol: str) -> List[DividendInfo]: """ Retrieves dividend information for the given symbol. :param session: active user session to use :param symbol: symbol to retrieve dividend information for """ - symbol = symbol.replace('/', '%2F') - data = session.get(f'/market-metrics/historic-corporate-events/' - f'dividends/{symbol}') - return [DividendInfo(**i) for i in data['items']] + symbol = symbol.replace("/", "%2F") + data = session.get( + f"/market-metrics/historic-corporate-events/" f"dividends/{symbol}" + ) + return [DividendInfo(**i) for i in data["items"]] -def get_earnings( - session: Session, - symbol: str, - start_date: date -) -> List[EarningsInfo]: +def get_earnings(session: Session, symbol: str, start_date: date) -> List[EarningsInfo]: """ Retrieves earnings information for the given symbol. @@ -150,14 +146,13 @@ def get_earnings( :param symbol: symbol to retrieve earnings information for :param start_date: limits earnings to those on or after the given date """ - symbol = symbol.replace('/', '%2F') - params = {'start-date': start_date} + symbol = symbol.replace("/", "%2F") + params = {"start-date": start_date} data = session.get( - (f'/market-metrics/historic-corporate-events/' - f'earnings-reports/{symbol}'), - params=params + (f"/market-metrics/historic-corporate-events/" f"earnings-reports/{symbol}"), + params=params, ) - return [EarningsInfo(**i) for i in data['items']] + return [EarningsInfo(**i) for i in data["items"]] def get_risk_free_rate(session: Session) -> Decimal: @@ -166,5 +161,5 @@ def get_risk_free_rate(session: Session) -> Decimal: :param session: active user session to use """ - data = session.get('/margin-requirements-public-configuration') - return Decimal(data['risk-free-rate']) + data = session.get("/margin-requirements-public-configuration") + return Decimal(data["risk-free-rate"]) diff --git a/tastytrade/order.py b/tastytrade/order.py index b3c522b..2e89e73 100644 --- a/tastytrade/order.py +++ b/tastytrade/order.py @@ -12,33 +12,35 @@ class InstrumentType(str, Enum): This is an :class:`~enum.Enum` that contains the valid types of instruments and their representation in the API. """ - BOND = 'Bond' - CRYPTOCURRENCY = 'Cryptocurrency' - CURRENCY_PAIR = 'Currency Pair' - EQUITY = 'Equity' - EQUITY_OFFERING = 'Equity Offering' - EQUITY_OPTION = 'Equity Option' - FIXED_INCOME = 'Fixed Income Security' - FUTURE = 'Future' - FUTURE_OPTION = 'Future Option' - INDEX = 'Index' - LIQUIDITY_POOL = 'Liquidity Pool' - UNKNOWN = 'Unknown' - WARRANT = 'Warrant' + + BOND = "Bond" + CRYPTOCURRENCY = "Cryptocurrency" + CURRENCY_PAIR = "Currency Pair" + EQUITY = "Equity" + EQUITY_OFFERING = "Equity Offering" + EQUITY_OPTION = "Equity Option" + FIXED_INCOME = "Fixed Income Security" + FUTURE = "Future" + FUTURE_OPTION = "Future Option" + INDEX = "Index" + LIQUIDITY_POOL = "Liquidity Pool" + UNKNOWN = "Unknown" + WARRANT = "Warrant" class OrderAction(str, Enum): """ This is an :class:`~enum.Enum` that contains the valid order actions. """ - BUY_TO_OPEN = 'Buy to Open' - BUY_TO_CLOSE = 'Buy to Close' - SELL_TO_OPEN = 'Sell to Open' - SELL_TO_CLOSE = 'Sell to Close' + + BUY_TO_OPEN = "Buy to Open" + BUY_TO_CLOSE = "Buy to Close" + SELL_TO_OPEN = "Sell to Open" + SELL_TO_CLOSE = "Sell to Close" #: for futures only - BUY = 'Buy' + BUY = "Buy" #: for futures only - SELL = 'Sell' + SELL = "Sell" class OrderStatus(str, Enum): @@ -48,51 +50,55 @@ class OrderStatus(str, Enum): RECEIVED -> LIVE -> FILLED """ - RECEIVED = 'Received' - CANCELLED = 'Cancelled' - FILLED = 'Filled' - EXPIRED = 'Expired' - LIVE = 'Live' - REJECTED = 'Rejected' - CONTINGENT = 'Contingent' - ROUTED = 'Routed' - IN_FLIGHT = 'In Flight' - CANCEL_REQUESTED = 'Cancel Requested' - REPLACE_REQUESTED = 'Replace Requested' - REMOVED = 'Removed' - PARTIALLY_REMOVED = 'Partially Removed' + + RECEIVED = "Received" + CANCELLED = "Cancelled" + FILLED = "Filled" + EXPIRED = "Expired" + LIVE = "Live" + REJECTED = "Rejected" + CONTINGENT = "Contingent" + ROUTED = "Routed" + IN_FLIGHT = "In Flight" + CANCEL_REQUESTED = "Cancel Requested" + REPLACE_REQUESTED = "Replace Requested" + REMOVED = "Removed" + PARTIALLY_REMOVED = "Partially Removed" class OrderTimeInForce(str, Enum): """ This is an :class:`~enum.Enum` that contains the valid TIFs for orders. """ - DAY = 'Day' - GTC = 'GTC' - GTD = 'GTD' - EXT = 'Ext' - GTC_EXT = 'GTC Ext' - IOC = 'IOC' + + DAY = "Day" + GTC = "GTC" + GTD = "GTD" + EXT = "Ext" + GTC_EXT = "GTC Ext" + IOC = "IOC" class OrderType(str, Enum): """ This is an :class:`~enum.Enum` that contains the valid types of orders. """ - LIMIT = 'Limit' - MARKET = 'Market' - MARKETABLE_LIMIT = 'Marketable Limit' - STOP = 'Stop' - STOP_LIMIT = 'Stop Limit' - NOTIONAL_MARKET = 'Notional Market' + + LIMIT = "Limit" + MARKET = "Market" + MARKETABLE_LIMIT = "Marketable Limit" + STOP = "Stop" + STOP_LIMIT = "Stop Limit" + NOTIONAL_MARKET = "Notional Market" class ComplexOrderType(str, Enum): """ This is an :class:`~enum.Enum` that contains the valid complex order types. """ - OCO = 'OCO' - OTOCO = 'OTOCO' + + OCO = "OCO" + OTOCO = "OTOCO" class PriceEffect(str, Enum): @@ -100,15 +106,17 @@ class PriceEffect(str, Enum): This is an :class:`~enum.Enum` that shows the sign of a price effect, since Tastytrade is apparently against negative numbers. """ - CREDIT = 'Credit' - DEBIT = 'Debit' - NONE = 'None' + + CREDIT = "Credit" + DEBIT = "Debit" + NONE = "None" class FillInfo(TastytradeJsonDataclass): """ Dataclass that contains information about an order fill. """ + fill_id: str quantity: Decimal fill_price: Decimal @@ -125,6 +133,7 @@ class Leg(TastytradeJsonDataclass): Classes that inherit from :class:`TradeableTastytradeJsonDataclass` can call :meth:`build_leg` to build a leg from the dataclass. """ + instrument_type: InstrumentType symbol: str action: OrderAction @@ -140,6 +149,7 @@ class TradeableTastytradeJsonDataclass(TastytradeJsonDataclass): Classes that inherit from this class can call :meth:`build_leg` to build a leg from the dataclass. """ + instrument_type: InstrumentType symbol: str @@ -156,7 +166,7 @@ def build_leg(self, quantity: Decimal, action: OrderAction) -> Leg: instrument_type=self.instrument_type, symbol=self.symbol, quantity=quantity, - action=action + action=action, ) @@ -165,18 +175,20 @@ class Message(TastytradeJsonDataclass): Dataclass that represents a message from the Tastytrade API, usually a warning or an error. """ + code: str message: str preflight_id: Optional[str] = None def __str__(self): - return f'{self.code}: {self.message}' + return f"{self.code}: {self.message}" class OrderConditionPriceComponent(TastytradeJsonDataclass): """ Dataclass that represents a price component of an order condition. """ + symbol: str instrument_type: InstrumentType quantity: Decimal @@ -187,6 +199,7 @@ class OrderCondition(TastytradeJsonDataclass): """ Dataclass that represents an order condition for an order rule. """ + id: str action: str symbol: str @@ -204,6 +217,7 @@ class OrderRule(TastytradeJsonDataclass): """ Dataclass that represents an order rule for a complex order. """ + route_after: datetime routed_at: datetime cancel_at: datetime @@ -216,9 +230,10 @@ class NewOrder(TastytradeJsonDataclass): Dataclass containing information about a new order. Also used for modifying existing orders. """ + time_in_force: OrderTimeInForce order_type: OrderType - source: str = f'tastyware/tastytrade:v{VERSION}' + source: str = f"tastyware/tastytrade:v{VERSION}" legs: List[Leg] gtc_date: Optional[date] = None stop_trigger: Optional[Decimal] = None @@ -236,8 +251,9 @@ class NewComplexOrder(TastytradeJsonDataclass): Dataclass containing information about a new OTOCO order. Also used for modifying existing orders. """ + orders: List[NewOrder] - source: str = f'tastyware/tastytrade:v{VERSION}' + source: str = f"tastyware/tastytrade:v{VERSION}" trigger_order: Optional[NewOrder] = None type: ComplexOrderType = ComplexOrderType.OCO @@ -252,6 +268,7 @@ class PlacedOrder(TastytradeJsonDataclass): Dataclass containing information about an existing order, whether it's been filled or not. """ + account_number: str time_in_force: OrderTimeInForce order_type: OrderType @@ -295,6 +312,7 @@ class PlacedComplexOrder(TastytradeJsonDataclass): """ Dataclass containing information about an already placed complex order. """ + account_number: str type: str orders: List[PlacedOrder] @@ -312,6 +330,7 @@ class BuyingPowerEffect(TastytradeJsonDataclass): Dataclass containing information about the effect of a trade on buying power. """ + change_in_margin_requirement: Decimal change_in_margin_requirement_effect: PriceEffect change_in_buying_power: Decimal @@ -331,6 +350,7 @@ class FeeCalculation(TastytradeJsonDataclass): """ Dataclass containing information about the fees associated with a trade. """ + regulatory_fees: Decimal regulatory_fees_effect: PriceEffect clearing_fees: Decimal @@ -347,6 +367,7 @@ class PlacedOrderResponse(TastytradeJsonDataclass): """ Dataclass grouping together information about a placed order. """ + buying_power_effect: BuyingPowerEffect fee_calculation: Optional[FeeCalculation] = None order: Optional[PlacedOrder] = None @@ -359,6 +380,7 @@ class OrderChainEntry(TastytradeJsonDataclass): """ Dataclass containing information about a single order in an order chain. """ + symbol: str instrument_type: InstrumentType quantity: str @@ -371,6 +393,7 @@ class OrderChainLeg(TastytradeJsonDataclass): Dataclass containing information about a single leg in an order from an order chain. """ + symbol: str instrument_type: InstrumentType action: OrderAction @@ -382,6 +405,7 @@ class OrderChainNode(TastytradeJsonDataclass): """ Dataclass containing information about a single node in an order chain. """ + node_type: str id: str description: str @@ -403,6 +427,7 @@ class ComputedData(TastytradeJsonDataclass): """ Dataclass containing computed data about an order chain. """ + open: bool updated_at: datetime total_fees: Decimal @@ -440,6 +465,7 @@ class OrderChain(TastytradeJsonDataclass): for a specific underlying, such as total P/L, rolls, current P/L in a symbol, etc. """ + id: int updated_at: datetime created_at: datetime diff --git a/tastytrade/search.py b/tastytrade/search.py index 6409f2f..b101566 100644 --- a/tastytrade/search.py +++ b/tastytrade/search.py @@ -8,14 +8,12 @@ class SymbolData(TastytradeJsonDataclass): """ Dataclass holding search results for an individual item. """ + symbol: str description: str -def symbol_search( - session: Session, - symbol: str -) -> List[SymbolData]: +def symbol_search(session: Session, symbol: str) -> List[SymbolData]: """ Performs a symbol search using the Tastytrade API and returns a list of symbols that are similar to the given search phrase. @@ -23,12 +21,11 @@ def symbol_search( :param session: active user session to use :param symbol: search phrase """ - symbol = symbol.replace('/', '%2F') - response = session.client.get(f'{session.base_url}/symbols/search/' - f'{symbol}') + symbol = symbol.replace("/", "%2F") + response = session.client.get(f"{session.base_url}/symbols/search/" f"{symbol}") if response.status_code // 100 != 2: # here it doesn't really make sense to throw an exception return [] else: - data = response.json()['data'] - return [SymbolData(**i) for i in data['items']] + data = response.json()["data"] + return [SymbolData(**i) for i in data["items"]] diff --git a/tastytrade/session.py b/tastytrade/session.py index f3f7a53..83b587d 100644 --- a/tastytrade/session.py +++ b/tastytrade/session.py @@ -4,8 +4,7 @@ from fake_useragent import UserAgent # type: ignore from tastytrade import API_URL, CERT_URL -from tastytrade.utils import (TastytradeError, TastytradeJsonDataclass, - validate_response) +from tastytrade.utils import TastytradeError, TastytradeJsonDataclass, validate_response class TwoFactorInfo(TastytradeJsonDataclass): @@ -33,6 +32,7 @@ class Session: :param dxfeed_tos_compliant: whether to use the dxfeed TOS-compliant API endpoint for the streamer """ + def __init__( self, login: str, @@ -41,64 +41,61 @@ def __init__( remember_token: Optional[str] = None, is_test: bool = False, two_factor_authentication: Optional[str] = None, - dxfeed_tos_compliant: bool = False + dxfeed_tos_compliant: bool = False, ): - body = { - 'login': login, - 'remember-me': remember_me - } + body = {"login": login, "remember-me": remember_me} if password is not None: - body['password'] = password + body["password"] = password elif remember_token is not None: - body['remember-token'] = remember_token + body["remember-token"] = remember_token else: - raise TastytradeError('You must provide a password or remember ' - 'token to log in.') + raise TastytradeError( + "You must provide a password or remember " "token to log in." + ) # The base url to use for API requests self.base_url = CERT_URL if is_test else API_URL #: Whether this is a cert or real session self.is_test = is_test # The headers to use for API requests headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': UserAgent().random + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": UserAgent().random, } # Set client for requests self.client = requests.Session() self.client.headers.update(headers) if two_factor_authentication is not None: response = self.client.post( - f'{self.base_url}/sessions', + f"{self.base_url}/sessions", json=body, - headers={'X-Tastyworks-OTP': two_factor_authentication} + headers={"X-Tastyworks-OTP": two_factor_authentication}, ) else: - response = self.client.post( - f'{self.base_url}/sessions', - json=body - ) + response = self.client.post(f"{self.base_url}/sessions", json=body) validate_response(response) # throws exception if not 200 json = response.json() #: The user dict returned by the API; contains basic user information - self.user = json['data']['user'] + self.user = json["data"]["user"] #: The session token used to authenticate requests - self.session_token = json['data']['session-token'] + self.session_token = json["data"]["session-token"] #: A single-use token which can be used to login without a password - self.remember_token = json['data'].get('remember-token') - self.client.headers.update({'Authorization': self.session_token}) + self.remember_token = json["data"].get("remember-token") + self.client.headers.update({"Authorization": self.session_token}) self.validate() # Pull streamer tokens and urls - url = ('/api-quote-tokens' - if dxfeed_tos_compliant or is_test - else '/quote-streamer-tokens') + url = ( + "/api-quote-tokens" + if dxfeed_tos_compliant or is_test + else "/quote-streamer-tokens" + ) data = self.get(url) #: Auth token for dxfeed websocket - self.streamer_token = data['token'] + self.streamer_token = data["token"] #: URL for dxfeed websocket - self.dxlink_url = data['dxlink-url'] + self.dxlink_url = data["dxlink-url"] def get(self, url, **kwargs) -> Dict[str, Any]: response = self.client.get(self.base_url + url, timeout=30, **kwargs) @@ -116,12 +113,9 @@ def put(self, url, **kwargs) -> Dict[str, Any]: response = self.client.put(self.base_url + url, **kwargs) return self._validate_and_parse(response) - def _validate_and_parse( - self, - response: requests.Response - ) -> Dict[str, Any]: + def _validate_and_parse(self, response: requests.Response) -> Dict[str, Any]: validate_response(response) - return response.json()['data'] + return response.json()["data"] def validate(self) -> bool: """ @@ -129,15 +123,15 @@ def validate(self) -> bool: :return: True if the session is valid and False otherwise. """ - response = self.client.post(f'{self.base_url}/sessions/validate') - return (response.status_code // 100 == 2) + response = self.client.post(f"{self.base_url}/sessions/validate") + return response.status_code // 100 == 2 def destroy(self) -> None: """ Sends a API request to log out of the existing session. This will invalidate the current session token and login. """ - self.delete('/sessions') + self.delete("/sessions") def get_customer(self) -> Dict[str, Any]: """ @@ -145,12 +139,12 @@ def get_customer(self) -> Dict[str, Any]: :return: a Tastytrade 'Customer' object in JSON format. """ - data = self.get('/customers/me') + data = self.get("/customers/me") return data def get_2fa_info(self) -> TwoFactorInfo: """ Gets the 2FA info for the current user. """ - data = self.get('/users/me/two-factor-method') + data = self.get("/users/me/two-factor-method") return TwoFactorInfo(**data) diff --git a/tastytrade/streamer.py b/tastytrade/streamer.py index 745bfbe..1ab1487 100644 --- a/tastytrade/streamer.py +++ b/tastytrade/streamer.py @@ -12,27 +12,42 @@ from websockets import WebSocketClientProtocol from tastytrade import logger -from tastytrade.account import (Account, AccountBalance, CurrentPosition, - TradingStatus) -from tastytrade.dxfeed import (Candle, Event, EventType, Greeks, Profile, - Quote, Summary, TheoPrice, TimeAndSale, Trade, - Underlying) -from tastytrade.order import (InstrumentType, OrderChain, PlacedComplexOrder, - PlacedOrder, PriceEffect) +from tastytrade.account import Account, AccountBalance, CurrentPosition, TradingStatus +from tastytrade.dxfeed import ( + Candle, + Event, + EventType, + Greeks, + Profile, + Quote, + Summary, + TheoPrice, + TimeAndSale, + Trade, + Underlying, +) +from tastytrade.order import ( + InstrumentType, + OrderChain, + PlacedComplexOrder, + PlacedOrder, + PriceEffect, +) from tastytrade.session import Session from tastytrade.utils import TastytradeError, TastytradeJsonDataclass from tastytrade.watchlists import Watchlist -CERT_STREAMER_URL = 'wss://streamer.cert.tastyworks.com' -STREAMER_URL = 'wss://streamer.tastyworks.com' +CERT_STREAMER_URL = "wss://streamer.cert.tastyworks.com" +STREAMER_URL = "wss://streamer.tastyworks.com" -DXLINK_VERSION = '0.1-js/1.0.0-beta.4' +DXLINK_VERSION = "0.1-js/1.0.0-beta.4" class QuoteAlert(TastytradeJsonDataclass): """ Dataclass that contains information about a quote alert """ + user_external_id: str symbol: str alert_external_id: str @@ -52,6 +67,7 @@ class UnderlyingYearGainSummary(TastytradeJsonDataclass): Dataclass that contains information about the yearly gain or loss for an underlying """ + year: int account_number: str symbol: str @@ -71,11 +87,12 @@ class SubscriptionType(str, Enum): This is an :class:`~enum.Enum` that contains the subscription types for the alert streamer. """ - ACCOUNT = 'account-subscribe' # may be 'connect' in the future - HEARTBEAT = 'heartbeat' - PUBLIC_WATCHLISTS = 'public-watchlists-subscribe' - QUOTE_ALERTS = 'quote-alerts-subscribe' - USER_MESSAGE = 'user-message-subscribe' + + ACCOUNT = "account-subscribe" # may be 'connect' in the future + HEARTBEAT = "heartbeat" + PUBLIC_WATCHLISTS = "public-watchlists-subscribe" + QUOTE_ALERTS = "quote-alerts-subscribe" + USER_MESSAGE = "user-message-subscribe" class AlertType(str, Enum): @@ -83,15 +100,16 @@ class AlertType(str, Enum): This is an :class:`~enum.Enum` that contains the event types for the account streamer. """ - ACCOUNT_BALANCE = 'AccountBalance' - COMPLEX_ORDER = 'ComplexOrder' - ORDER = 'Order' - ORDER_CHAIN = 'OrderChain' - POSITION = 'CurrentPosition' - QUOTE_ALERT = 'QuoteAlert' - TRADING_STATUS = 'TradingStatus' - UNDERLYING_SUMMARY = 'UnderlyingYearGainSummary' - WATCHLIST = 'PublicWatchlists' + + ACCOUNT_BALANCE = "AccountBalance" + COMPLEX_ORDER = "ComplexOrder" + ORDER = "Order" + ORDER_CHAIN = "OrderChain" + POSITION = "CurrentPosition" + QUOTE_ALERT = "QuoteAlert" + TRADING_STATUS = "TradingStatus" + UNDERLYING_SUMMARY = "UnderlyingYearGainSummary" + WATCHLIST = "PublicWatchlists" class AlertStreamer: @@ -119,12 +137,12 @@ class AlertStreamer: print(data) """ + def __init__(self, session: Session): #: The active session used to initiate the streamer or make requests self.token: str = session.session_token #: The base url for the streamer websocket - self.base_url: str = (CERT_STREAMER_URL - if session.is_test else STREAMER_URL) + self.base_url: str = CERT_STREAMER_URL if session.is_test else STREAMER_URL self._queues: Dict[AlertType, Queue] = defaultdict(Queue) self._websocket: Optional[WebSocketClientProtocol] = None @@ -136,12 +154,12 @@ async def __aenter__(self): await asyncio.sleep(0.1) time_out -= 1 if time_out < 0: - raise TastytradeError('Connection timed out') + raise TastytradeError("Connection timed out") return self @classmethod - async def create(cls, session: Session) -> 'AlertStreamer': + async def create(cls, session: Session) -> "AlertStreamer": self = cls(session) return await self.__aenter__() @@ -160,25 +178,23 @@ async def _connect(self) -> None: Connect to the websocket server using the URL and authorization token provided during initialization. """ - headers = {'Authorization': f'Bearer {self.token}'} + headers = {"Authorization": f"Bearer {self.token}"} async with websockets.connect( - self.base_url, - extra_headers=headers + self.base_url, extra_headers=headers ) as websocket: # type: ignore self._websocket = websocket self._heartbeat_task = asyncio.create_task(self._heartbeat()) while True: raw_message = await self._websocket.recv() # type: ignore - logger.debug('raw message: %s', raw_message) + logger.debug("raw message: %s", raw_message) data = json.loads(raw_message) - type_str = data.get('type') + type_str = data.get("type") if type_str is not None: - await self._map_message(type_str, data['data']) + await self._map_message(type_str, data["data"]) async def listen( - self, - event_type: AlertType + self, event_type: AlertType ) -> AsyncIterator[ Union[ AccountBalance, @@ -189,7 +205,7 @@ async def listen( QuoteAlert, TradingStatus, UnderlyingYearGainSummary, - Watchlist + Watchlist, ] ]: """ @@ -199,54 +215,35 @@ async def listen( while True: yield await self._queues[event_type].get() - async def _map_message( - self, - type_str: str, - data: dict - ): # pragma: no cover + async def _map_message(self, type_str: str, data: dict): # pragma: no cover """ I'm not sure what the user-status messages look like, so they're absent. """ if type_str == AlertType.ACCOUNT_BALANCE: - await self._queues[AlertType.ACCOUNT_BALANCE].put( - AccountBalance(**data) - ) + await self._queues[AlertType.ACCOUNT_BALANCE].put(AccountBalance(**data)) elif type_str == AlertType.POSITION: - await self._queues[AlertType.POSITION].put( - CurrentPosition(**data) - ) + await self._queues[AlertType.POSITION].put(CurrentPosition(**data)) elif type_str == AlertType.COMPLEX_ORDER: - await self._queues[AlertType.COMPLEX_ORDER].put( - PlacedComplexOrder(**data) - ) + await self._queues[AlertType.COMPLEX_ORDER].put(PlacedComplexOrder(**data)) elif type_str == AlertType.ORDER: - await self._queues[AlertType.ORDER].put( - PlacedOrder(**data) - ) + await self._queues[AlertType.ORDER].put(PlacedOrder(**data)) elif type_str == AlertType.ORDER_CHAIN: - await self._queues[AlertType.ORDER_CHAIN].put( - OrderChain(**data) - ) + await self._queues[AlertType.ORDER_CHAIN].put(OrderChain(**data)) elif type_str == AlertType.QUOTE_ALERT: - await self._queues[AlertType.QUOTE_ALERT].put( - QuoteAlert(**data) - ) + await self._queues[AlertType.QUOTE_ALERT].put(QuoteAlert(**data)) elif type_str == AlertType.TRADING_STATUS: - await self._queues[AlertType.TRADING_STATUS].put( - TradingStatus(**data) - ) + await self._queues[AlertType.TRADING_STATUS].put(TradingStatus(**data)) elif type_str == AlertType.UNDERLYING_SUMMARY: await self._queues[AlertType.UNDERLYING_SUMMARY].put( UnderlyingYearGainSummary(**data) ) elif type_str == AlertType.WATCHLIST: - await self._queues[AlertType.WATCHLIST].put( - Watchlist(**data) - ) + await self._queues[AlertType.WATCHLIST].put(Watchlist(**data)) else: - logger.error(f'Unknown message type {type_str}! Please open an ' - f'issue.\n{data}') + logger.error( + f"Unknown message type {type_str}! Please open an " f"issue.\n{data}" + ) async def subscribe_accounts(self, accounts: List[Account]) -> None: """ @@ -255,8 +252,7 @@ async def subscribe_accounts(self, accounts: List[Account]) -> None: :param accounts: list of :class:`Account` to subscribe to updates for """ await self._subscribe( - SubscriptionType.ACCOUNT, - [a.account_number for a in accounts] + SubscriptionType.ACCOUNT, [a.account_number for a in accounts] ) async def subscribe_public_watchlists(self) -> None: @@ -275,7 +271,7 @@ async def subscribe_user_messages(self, session: Session) -> None: """ Subscribes to user-level messages, e.g. new account creation. """ - external_id = session.user['external-id'] + external_id = session.user["external-id"] await self._subscribe(SubscriptionType.USER_MESSAGE, value=external_id) async def _heartbeat(self) -> None: @@ -284,26 +280,23 @@ async def _heartbeat(self) -> None: alive. """ while True: - await self._subscribe(SubscriptionType.HEARTBEAT, '') + await self._subscribe(SubscriptionType.HEARTBEAT, "") # send the heartbeat every 10 seconds await asyncio.sleep(10) async def _subscribe( self, subscription: SubscriptionType, - value: Union[Optional[str], List[str]] = '' + value: Union[Optional[str], List[str]] = "", ) -> None: """ Subscribes to a :class:`SubscriptionType`. Depending on the kind of subscription, the value parameter may be required. """ - message: Dict[str, Any] = { - 'auth-token': self.token, - 'action': subscription - } + message: Dict[str, Any] = {"auth-token": self.token, "action": subscription} if value: - message['value'] = value - logger.debug('sending alert subscription: %s', message) + message["value"] = value + logger.debug("sending alert subscription: %s", message) await self._websocket.send(json.dumps(message)) # type: ignore @@ -327,10 +320,9 @@ class DXLinkStreamer: print(quote) """ + def __init__( - self, - session: Session, - ssl_context: SSLContext = create_default_context() + self, session: Session, ssl_context: SSLContext = create_default_context() ): self._counter = 0 self._lock: Lock = Lock() @@ -346,8 +338,9 @@ def __init__( EventType.TRADE: 15, EventType.UNDERLYING: 17, } - self._subscription_state: Dict[EventType, str] = \ - defaultdict(lambda: 'CHANNEL_CLOSED') + self._subscription_state: Dict[EventType, str] = defaultdict( + lambda: "CHANNEL_CLOSED" + ) #: The unique client identifier received from the server self._session = session @@ -364,16 +357,14 @@ async def __aenter__(self): await asyncio.sleep(0.1) time_out -= 1 if time_out < 0: - raise TastytradeError('Connection timed out') + raise TastytradeError("Connection timed out") return self @classmethod async def create( - cls, - session: Session, - ssl_context: SSLContext = create_default_context() - ) -> 'DXLinkStreamer': + cls, session: Session, ssl_context: SSLContext = create_default_context() + ) -> "DXLinkStreamer": self = cls(session, ssl_context=ssl_context) return await self.__aenter__() @@ -394,8 +385,7 @@ async def _connect(self) -> None: """ async with websockets.connect( - self._wss_url, - ssl=self._ssl_context + self._wss_url, ssl=self._ssl_context ) as websocket: self._websocket = websocket await self._setup_connection() @@ -405,44 +395,44 @@ async def _connect(self) -> None: raw_message = await self._websocket.recv() message = json.loads(raw_message) - logger.debug('received: %s', message) - if message['type'] == 'SETUP': + logger.debug("received: %s", message) + if message["type"] == "SETUP": await self._authenticate_connection() - elif message['type'] == 'AUTH_STATE': - if message['state'] == 'AUTHORIZED': + elif message["type"] == "AUTH_STATE": + if message["state"] == "AUTHORIZED": self._authenticated = True - self._heartbeat_task = \ - asyncio.create_task(self._heartbeat()) - elif message['type'] == 'CHANNEL_OPENED': - channel = next(k for k, v in self._channels.items() - if v == message['channel']) - self._subscription_state[channel] = message['type'] - elif message['type'] == 'CHANNEL_CLOSED': - logger.debug('Channel closed: %s', message) - elif message['type'] == 'FEED_CONFIG': - logger.debug('Feed configured: %s', message) - elif message['type'] == 'FEED_DATA': - await self._map_message(message['data']) - elif message['type'] == 'KEEPALIVE': + self._heartbeat_task = asyncio.create_task(self._heartbeat()) + elif message["type"] == "CHANNEL_OPENED": + channel = next( + k for k, v in self._channels.items() if v == message["channel"] + ) + self._subscription_state[channel] = message["type"] + elif message["type"] == "CHANNEL_CLOSED": + logger.debug("Channel closed: %s", message) + elif message["type"] == "FEED_CONFIG": + logger.debug("Feed configured: %s", message) + elif message["type"] == "FEED_DATA": + await self._map_message(message["data"]) + elif message["type"] == "KEEPALIVE": pass else: - raise TastytradeError('Unknown message type:', message) + raise TastytradeError("Unknown message type:", message) async def _setup_connection(self): message = { - 'type': 'SETUP', - 'channel': 0, - 'keepaliveTimeout': 60, - 'acceptKeepaliveTimeout': 60, - 'version': DXLINK_VERSION + "type": "SETUP", + "channel": 0, + "keepaliveTimeout": 60, + "acceptKeepaliveTimeout": 60, + "version": DXLINK_VERSION, } await self._websocket.send(json.dumps(message)) async def _authenticate_connection(self): message = { - 'type': 'AUTH', - 'channel': 0, - 'token': self._auth_token, + "type": "AUTH", + "channel": 0, + "token": self._auth_token, } await self._websocket.send(json.dumps(message)) @@ -483,22 +473,15 @@ async def _heartbeat(self) -> None: Sends a keepalive message every 30 seconds to keep the connection alive. """ - message = { - 'type': 'KEEPALIVE', - 'channel': 0 - } + message = {"type": "KEEPALIVE", "channel": 0} while True: - logger.debug('sending keepalive message: %s', message) + logger.debug("sending keepalive message: %s", message) await self._websocket.send(json.dumps(message)) # send the heartbeat every 30 seconds await asyncio.sleep(30) - async def subscribe( - self, - event_type: EventType, - symbols: List[str] - ) -> None: + async def subscribe(self, event_type: EventType, symbols: List[str]) -> None: """ Subscribes to quotes for given list of symbols. Used for recurring data feeds. @@ -507,15 +490,14 @@ async def subscribe( :param event_type: type of subscription to add :param symbols: list of symbols to subscribe for """ - if self._subscription_state[event_type] != 'CHANNEL_OPENED': + if self._subscription_state[event_type] != "CHANNEL_OPENED": await self._channel_request(event_type) message = { - 'type': 'FEED_SUBSCRIPTION', - 'channel': self._channels[event_type], - 'add': [{'symbol': symbol, 'type': event_type} - for symbol in symbols] + "type": "FEED_SUBSCRIPTION", + "channel": self._channels[event_type], + "add": [{"symbol": symbol, "type": event_type} for symbol in symbols], } - logger.debug('sending subscription: %s', message) + logger.debug("sending subscription: %s", message) await self._websocket.send(json.dumps(message)) async def cancel_channel(self, event_type: EventType) -> None: @@ -525,43 +507,43 @@ async def cancel_channel(self, event_type: EventType) -> None: :param event_type: cancel the channel for this event """ message = { - 'type': 'CHANNEL_CANCEL', - 'channel': self._channels[event_type], + "type": "CHANNEL_CANCEL", + "channel": self._channels[event_type], } - logger.debug('sending channel cancel: %s', message) + logger.debug("sending channel cancel: %s", message) await self._websocket.send(json.dumps(message)) async def _channel_request(self, event_type: EventType) -> None: message = { - 'type': 'CHANNEL_REQUEST', - 'channel': self._channels[event_type], - 'service': 'FEED', - 'parameters': { - 'contract': 'AUTO', + "type": "CHANNEL_REQUEST", + "channel": self._channels[event_type], + "service": "FEED", + "parameters": { + "contract": "AUTO", }, } - logger.debug('sending subscription: %s', message) + logger.debug("sending subscription: %s", message) await self._websocket.send(json.dumps(message)) time_out = 100 - while not self._subscription_state[event_type] == 'CHANNEL_OPENED': + while not self._subscription_state[event_type] == "CHANNEL_OPENED": await asyncio.sleep(0.1) time_out -= 1 if time_out <= 0: - raise TastytradeError('Subscription channel not opened') + raise TastytradeError("Subscription channel not opened") # setup the feed await self._channel_setup(event_type) async def _channel_setup(self, event_type: EventType) -> None: message = { - 'type': 'FEED_SETUP', - 'channel': self._channels[event_type], - 'acceptAggregationPeriod': 10, - 'acceptDataFormat': 'COMPACT' + "type": "FEED_SETUP", + "channel": self._channels[event_type], + "acceptAggregationPeriod": 10, + "acceptDataFormat": "COMPACT", } def dict_from_schema(event_class: Any): schema = event_class.schema() - return {schema['title']: list(schema['properties'].keys())} + return {schema["title"]: list(schema["properties"].keys())} if event_type == EventType.CANDLE: accept = dict_from_schema(Candle) @@ -581,16 +563,12 @@ def dict_from_schema(event_class: Any): accept = dict_from_schema(Trade) elif event_type == EventType.UNDERLYING: accept = dict_from_schema(Underlying) - message['acceptEventFields'] = accept + message["acceptEventFields"] = accept # send message - logger.debug('setting up feed: %s', message) + logger.debug("setting up feed: %s", message) await self._websocket.send(json.dumps(message)) - async def unsubscribe( - self, - event_type: EventType, - symbols: List[str] - ) -> None: + async def unsubscribe(self, event_type: EventType, symbols: List[str]) -> None: """ Removes existing subscription for given list of symbols. For candles, use :meth:`unsubscribe_candle` instead. @@ -599,15 +577,16 @@ async def unsubscribe( :param symbols: list of symbols to unsubscribe from """ if not self._authenticated: - raise TastytradeError('Stream not authenticated') - event_type_str = str(event_type).split('.')[1].capitalize() + raise TastytradeError("Stream not authenticated") + event_type_str = str(event_type).split(".")[1].capitalize() message = { - 'type': 'FEED_SUBSCRIPTION', - 'channel': self._channels[event_type], - 'remove': [{'symbol': symbol, "type": event_type_str} for symbol in - symbols] + "type": "FEED_SUBSCRIPTION", + "channel": self._channels[event_type], + "remove": [ + {"symbol": symbol, "type": event_type_str} for symbol in symbols + ], } - logger.debug('sending subscription: %s', message) + logger.debug("sending subscription: %s", message) await self._websocket.send(json.dumps(message)) async def subscribe_candle( @@ -616,7 +595,7 @@ async def subscribe_candle( interval: str, start_time: datetime, end_time: Optional[datetime] = None, - extended_trading_hours: bool = False + extended_trading_hours: bool = False, ) -> None: """ Subscribes to time series data for the given symbol. @@ -629,27 +608,33 @@ async def subscribe_candle( :param end_time: ending time for the data range :param extended_trading_hours: whether to include extended trading """ - if self._subscription_state[EventType.CANDLE] != 'CHANNEL_OPENED': + if self._subscription_state[EventType.CANDLE] != "CHANNEL_OPENED": await self._channel_request(EventType.CANDLE) message = { - 'type': 'FEED_SUBSCRIPTION', - 'channel': self._channels[EventType.CANDLE], - 'add': [{ - 'symbol': (f'{ticker}{{={interval}}}' if extended_trading_hours - else f'{ticker}{{={interval},tho=true}}'), - 'type': 'Candle', - 'fromTime': int(start_time.timestamp() * 1000) - } for ticker in symbols] + "type": "FEED_SUBSCRIPTION", + "channel": self._channels[EventType.CANDLE], + "add": [ + { + "symbol": ( + f"{ticker}{{={interval}}}" + if extended_trading_hours + else f"{ticker}{{={interval},tho=true}}" + ), + "type": "Candle", + "fromTime": int(start_time.timestamp() * 1000), + } + for ticker in symbols + ], } if end_time is not None: - raise TastytradeError('End time no longer supported') + raise TastytradeError("End time no longer supported") await self._websocket.send(json.dumps(message)) async def unsubscribe_candle( self, ticker: str, interval: Optional[str] = None, - extended_trading_hours: bool = False + extended_trading_hours: bool = False, ) -> None: """ Removes existing subscription for a candle. @@ -660,13 +645,18 @@ async def unsubscribe_candle( whether candle to unsubscribe from contains extended trading hours """ message = { - 'type': 'FEED_SUBSCRIPTION', - 'channel': self._channels[EventType.CANDLE], - 'remove': [{ - 'symbol': (f'{ticker}{{={interval}}}' if extended_trading_hours - else f'{ticker}{{={interval},tho=true}}'), - 'type': 'Candle' - }] + "type": "FEED_SUBSCRIPTION", + "channel": self._channels[EventType.CANDLE], + "remove": [ + { + "symbol": ( + f"{ticker}{{={interval}}}" + if extended_trading_hours + else f"{ticker}{{={interval},tho=true}}" + ), + "type": "Candle", + } + ], } await self._websocket.send(json.dumps(message)) @@ -677,7 +667,7 @@ async def _map_message(self, message) -> None: # pragma: no cover :param message: raw JSON data from the websocket """ - logger.debug('received message: %s', message) + logger.debug("received message: %s", message) if isinstance(message[0], str): msg_type = message[0] else: @@ -721,4 +711,4 @@ async def _map_message(self, message) -> None: # pragma: no cover for underlying in underlyings: await self._queues[EventType.UNDERLYING].put(underlying) else: - raise TastytradeError(f'Unknown message type received: {message}') + raise TastytradeError(f"Unknown message type received: {message}") diff --git a/tastytrade/utils.py b/tastytrade/utils.py index 2bf1a78..32c26dc 100644 --- a/tastytrade/utils.py +++ b/tastytrade/utils.py @@ -1,12 +1,14 @@ from datetime import date, datetime, timedelta +from typing import Union import pandas_market_calendars as mcal # type: ignore import pytz +from httpx._models import Response as HTTPXReponse from pydantic import BaseModel from requests import Response -NYSE = mcal.get_calendar('NYSE') -TZ = pytz.timezone('US/Eastern') +NYSE = mcal.get_calendar("NYSE") +TZ = pytz.timezone("US/Eastern") def now_in_new_york() -> datetime: @@ -182,6 +184,7 @@ class TastytradeError(Exception): """ An internal error raised by the Tastytrade API. """ + pass @@ -193,7 +196,7 @@ def _dasherize(s: str) -> str: :return: dasherized string """ - return s.replace('_', '-') + return s.replace("_", "-") class TastytradeJsonDataclass(BaseModel): @@ -201,21 +204,22 @@ class TastytradeJsonDataclass(BaseModel): A pydantic dataclass that converts keys from snake case to dasherized and performs type validation and coercion. """ + class Config: alias_generator = _dasherize populate_by_name = True -def validate_response(response: Response) -> None: +def validate_response(response: Union[Response, HTTPXReponse]) -> None: """ Checks if the given code is an error; if so, raises an exception. :param response: response to check for errors """ if response.status_code // 100 != 2: - content = response.json()['error'] + content = response.json()["error"] error_message = f"{content['code']}: {content['message']}" - errors = content.get('errors') + errors = content.get("errors") if errors is not None: for error in errors: if "code" in error: diff --git a/tastytrade/watchlists.py b/tastytrade/watchlists.py index 44a599e..255c808 100644 --- a/tastytrade/watchlists.py +++ b/tastytrade/watchlists.py @@ -9,6 +9,7 @@ class Pair(TastytradeJsonDataclass): """ Dataclass that represents a specific pair in a pairs watchlist. """ + left_action: str left_symbol: str left_quantity: int @@ -21,36 +22,30 @@ class PairsWatchlist(TastytradeJsonDataclass): """ Dataclass that represents a pairs watchlist object. """ + name: str order_index: int pairs_equations: List[Pair] @classmethod - def get_pairs_watchlists( - cls, - session: Session - ) -> List['PairsWatchlist']: + def get_pairs_watchlists(cls, session: Session) -> List["PairsWatchlist"]: """ Fetches a list of all Tastytrade public pairs watchlists. :param session: the session to use for the request. """ - data = session.get('/pairs-watchlists') - return [cls(**i) for i in data['items']] + data = session.get("/pairs-watchlists") + return [cls(**i) for i in data["items"]] @classmethod - def get_pairs_watchlist( - cls, - session: Session, - name: str - ) -> 'PairsWatchlist': + def get_pairs_watchlist(cls, session: Session, name: str) -> "PairsWatchlist": """ Fetches a Tastytrade public pairs watchlist by name. :param session: the session to use for the request. :param name: the name of the pairs watchlist to fetch. """ - data = session.get(f'/pairs-watchlists/{name}') + data = session.get(f"/pairs-watchlists/{name}") return cls(**data) @@ -59,70 +54,55 @@ class Watchlist(TastytradeJsonDataclass): Dataclass that represents a watchlist object (public or private), with functions to update, publish, modify and remove watchlists. """ + name: str watchlist_entries: Optional[List[Dict[str, Any]]] = None - group_name: str = 'default' + group_name: str = "default" order_index: int = 9999 @classmethod def get_public_watchlists( - cls, - session: Session, - counts_only: bool = False - ) -> List['Watchlist']: + cls, session: Session, counts_only: bool = False + ) -> List["Watchlist"]: """ Fetches a list of all Tastytrade public watchlists. :param session: the session to use for the request. :param counts_only: whether to only fetch the counts of the watchlists. """ - data = session.get( - '/public-watchlists', - params={'counts-only': counts_only} - ) - return [cls(**i) for i in data['items']] + data = session.get("/public-watchlists", params={"counts-only": counts_only}) + return [cls(**i) for i in data["items"]] @classmethod - def get_public_watchlist( - cls, - session: Session, - name: str - ) -> 'Watchlist': + def get_public_watchlist(cls, session: Session, name: str) -> "Watchlist": """ Fetches a Tastytrade public watchlist by name. :param session: the session to use for the request. :param name: the name of the watchlist to fetch. """ - data = session.get(f'/public-watchlists/{name}') + data = session.get(f"/public-watchlists/{name}") return cls(**data) @classmethod - def get_private_watchlists( - cls, - session: Session - ) -> List['Watchlist']: + def get_private_watchlists(cls, session: Session) -> List["Watchlist"]: """ Fetches a the user's private watchlists. :param session: the session to use for the request. """ - data = session.get('/watchlists') - return [cls(**i) for i in data['items']] + data = session.get("/watchlists") + return [cls(**i) for i in data["items"]] @classmethod - def get_private_watchlist( - cls, - session: Session, - name: str - ) -> 'Watchlist': + def get_private_watchlist(cls, session: Session, name: str) -> "Watchlist": """ Fetches a user's watchlist by name. :param session: the session to use for the request. :param name: the name of the watchlist to fetch. """ - data = session.get(f'/watchlists/{name}') + data = session.get(f"/watchlists/{name}") return cls(**data) @classmethod @@ -133,7 +113,7 @@ def remove_private_watchlist(cls, session: Session, name: str) -> None: :param session: the session to use for the request. :param name: the name of the watchlist to delete. """ - session.delete(f'/watchlists/{name}') + session.delete(f"/watchlists/{name}") def upload_private_watchlist(self, session: Session) -> None: """ @@ -141,7 +121,7 @@ def upload_private_watchlist(self, session: Session) -> None: :param session: the session to use for the request. """ - session.post('/watchlists', json=self.model_dump(by_alias=True)) + session.post("/watchlists", json=self.model_dump(by_alias=True)) def update_private_watchlist(self, session: Session) -> None: """ @@ -149,10 +129,7 @@ def update_private_watchlist(self, session: Session) -> None: :param session: the session to use for the request. """ - session.put( - f'/watchlists/{self.name}', - json=self.model_dump(by_alias=True) - ) + session.put(f"/watchlists/{self.name}", json=self.model_dump(by_alias=True)) def add_symbol(self, symbol: str, instrument_type: InstrumentType) -> None: """ @@ -160,21 +137,15 @@ def add_symbol(self, symbol: str, instrument_type: InstrumentType) -> None: """ if self.watchlist_entries is None: self.watchlist_entries = [] - self.watchlist_entries.append({ - 'symbol': symbol, - 'instrument-type': instrument_type - }) + self.watchlist_entries.append( + {"symbol": symbol, "instrument-type": instrument_type} + ) - def remove_symbol( - self, - symbol: str, - instrument_type: InstrumentType - ) -> None: + def remove_symbol(self, symbol: str, instrument_type: InstrumentType) -> None: """ Removes a symbol from the watchlist. """ if self.watchlist_entries is not None: - self.watchlist_entries.remove({ - 'symbol': symbol, - 'instrument-type': instrument_type - }) + self.watchlist_entries.remove( + {"symbol": symbol, "instrument-type": instrument_type} + ) diff --git a/tests/conftest.py b/tests/conftest.py index f2e80d4..6cc059a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,19 +4,19 @@ from tastytrade import Session -CERT_USERNAME = 'tastyware' -CERT_PASSWORD = ':4s-S9/9L&Q~C]@v' +CERT_USERNAME = "tastyware" +CERT_PASSWORD = ":4s-S9/9L&Q~C]@v" -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def get_cert_credentials(): return CERT_USERNAME, CERT_PASSWORD -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def session(): - username = os.environ.get('TT_USERNAME', None) - password = os.environ.get('TT_PASSWORD', None) + username = os.environ.get("TT_USERNAME", None) + password = os.environ.get("TT_PASSWORD", None) assert username is not None assert password is not None diff --git a/tests/test_account.py b/tests/test_account.py index 0068748..e8aa9d1 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -5,11 +5,17 @@ from tastytrade import Account, Session from tastytrade.instruments import Equity -from tastytrade.order import (NewComplexOrder, NewOrder, OrderAction, - OrderTimeInForce, OrderType, PriceEffect) +from tastytrade.order import ( + NewComplexOrder, + NewOrder, + OrderAction, + OrderTimeInForce, + OrderType, + PriceEffect, +) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def account(session): return Account.get_accounts(session)[0] @@ -64,16 +70,16 @@ def test_get_margin_requirements(session, account): def test_get_net_liquidating_value_history(session, account): - account.get_net_liquidating_value_history(session, time_back='1y') + account.get_net_liquidating_value_history(session, time_back="1y") def test_get_effective_margin_requirements(session, account): - account.get_effective_margin_requirements(session, 'SPY') + account.get_effective_margin_requirements(session, "SPY") -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def new_order(session): - symbol = Equity.get_equity(session, 'F') + symbol = Equity.get_equity(session, "F") leg = symbol.build_leg(Decimal(1), OrderAction.BUY_TO_OPEN) return NewOrder( @@ -81,11 +87,11 @@ def new_order(session): order_type=OrderType.LIMIT, legs=[leg], price=Decimal(3), - price_effect=PriceEffect.DEBIT + price_effect=PriceEffect.DEBIT, ) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def placed_order(session, account, new_order): return account.place_order(session, new_order, dry_run=False).order @@ -97,7 +103,7 @@ def test_get_order(session, account, placed_order): def test_replace_and_delete_order(session, account, new_order, placed_order): modified_order = new_order.model_copy() - modified_order.price = Decimal('3.01') + modified_order.price = Decimal("3.01") replaced = account.replace_order(session, placed_order.id, modified_order) sleep(3) account.delete_order(session, replaced.id) @@ -148,7 +154,7 @@ def test_place_oco_order(session, account): def test_place_otoco_order(session, account): - symbol = Equity.get_equity(session, 'AAPL') + symbol = Equity.get_equity(session, "AAPL") opening = symbol.build_leg(Decimal(1), OrderAction.BUY_TO_OPEN) closing = symbol.build_leg(Decimal(1), OrderAction.SELL_TO_CLOSE) otoco = NewComplexOrder( @@ -156,25 +162,25 @@ def test_place_otoco_order(session, account): time_in_force=OrderTimeInForce.DAY, order_type=OrderType.LIMIT, legs=[opening], - price=Decimal('100'), # won't fill - price_effect=PriceEffect.DEBIT + price=Decimal("100"), # won't fill + price_effect=PriceEffect.DEBIT, ), orders=[ NewOrder( time_in_force=OrderTimeInForce.GTC, order_type=OrderType.LIMIT, legs=[closing], - price=Decimal('400'), # won't fill - price_effect=PriceEffect.CREDIT + price=Decimal("400"), # won't fill + price_effect=PriceEffect.CREDIT, ), NewOrder( time_in_force=OrderTimeInForce.GTC, order_type=OrderType.STOP, legs=[closing], - stop_trigger=Decimal('25'), # won't fill - price_effect=PriceEffect.CREDIT - ) - ] + stop_trigger=Decimal("25"), # won't fill + price_effect=PriceEffect.CREDIT, + ), + ], ) resp = account.place_complex_order(session, otoco, dry_run=False) sleep(3) diff --git a/tests/test_backtest.py b/tests/test_backtest.py new file mode 100644 index 0000000..a22da95 --- /dev/null +++ b/tests/test_backtest.py @@ -0,0 +1,28 @@ +from datetime import timedelta + +import pytest + +from tastytrade import today_in_new_york +from tastytrade.backtest import ( + Backtest, + BacktestEntry, + BacktestExit, + BacktestLeg, + BacktestSession, +) + +pytest_plugins = ("pytest_asyncio",) + + +@pytest.mark.asyncio +async def test_backtest_simple(session): + backtest_session = BacktestSession(session) + backtest = Backtest( + symbol="SPY", + entry_conditions=BacktestEntry(), + exit_conditions=BacktestExit(at_days_to_expiration=21), + legs=[BacktestLeg(), BacktestLeg(side="put")], + start_date=today_in_new_york() - timedelta(days=365), + ) + results = [r async for r in backtest_session.run(backtest)] + assert results[-1].status == "completed" diff --git a/tests/test_instruments.py b/tests/test_instruments.py index bcc37fb..70669f6 100644 --- a/tests/test_instruments.py +++ b/tests/test_instruments.py @@ -1,13 +1,22 @@ -from tastytrade.instruments import (Cryptocurrency, Equity, Future, - FutureOption, FutureOptionProduct, - FutureProduct, NestedFutureOptionChain, - NestedOptionChain, Option, Warrant, - get_future_option_chain, get_option_chain, - get_quantity_decimal_precisions) +from tastytrade.instruments import ( + Cryptocurrency, + Equity, + Future, + FutureOption, + FutureOptionProduct, + FutureProduct, + NestedFutureOptionChain, + NestedOptionChain, + Option, + Warrant, + get_future_option_chain, + get_option_chain, + get_quantity_decimal_precisions, +) def test_get_cryptocurrency(session): - Cryptocurrency.get_cryptocurrency(session, 'ETH/USD') + Cryptocurrency.get_cryptocurrency(session, "ETH/USD") def test_get_cryptocurrencies(session): @@ -19,24 +28,24 @@ def test_get_active_equities(session): def test_get_equities(session): - Equity.get_equities(session, ['AAPL', 'SPY']) + Equity.get_equities(session, ["AAPL", "SPY"]) def test_get_equity(session): - Equity.get_equity(session, 'AAPL') + Equity.get_equity(session, "AAPL") def test_get_futures(session): - futures = Future.get_futures(session, product_codes=['ES']) + futures = Future.get_futures(session, product_codes=["ES"]) Future.get_future(session, futures[0].symbol) def test_get_future_product(session): - FutureProduct.get_future_product(session, 'ZN') + FutureProduct.get_future_product(session, "ZN") def test_get_future_option_product(session): - FutureOptionProduct.get_future_option_product(session, 'LO') + FutureOptionProduct.get_future_option_product(session, "LO") def test_get_future_option_products(session): @@ -48,11 +57,11 @@ def test_get_future_products(session): def test_get_nested_option_chain(session): - NestedOptionChain.get_chain(session, 'SPY') + NestedOptionChain.get_chain(session, "SPY") def test_get_nested_future_option_chain(session): - NestedFutureOptionChain.get_chain(session, 'ES') + NestedFutureOptionChain.get_chain(session, "ES") def test_get_warrants(session): @@ -60,7 +69,7 @@ def test_get_warrants(session): def test_get_warrant(session): - Warrant.get_warrant(session, 'NKLAW') + Warrant.get_warrant(session, "NKLAW") def test_get_quantity_decimal_precisions(session): @@ -68,14 +77,14 @@ def test_get_quantity_decimal_precisions(session): def test_get_option_chain(session): - chain = get_option_chain(session, 'SPY') + chain = get_option_chain(session, "SPY") for options in chain.values(): Option.get_option(session, options[0].symbol) break def test_get_future_option_chain(session): - chain = get_future_option_chain(session, 'ES') + chain = get_future_option_chain(session, "ES") for options in chain.values(): FutureOption.get_future_option(session, options[0].symbol) FutureOption.get_future_options(session, options[:4]) @@ -83,12 +92,12 @@ def test_get_future_option_chain(session): def test_streamer_symbol_to_occ(): - dxf = '.SPY240324P480.5' - occ = 'SPY 240324P00480500' + dxf = ".SPY240324P480.5" + occ = "SPY 240324P00480500" assert Option.streamer_symbol_to_occ(dxf) == occ def test_occ_to_streamer_symbol(): - dxf = '.SPY240324P480.5' - occ = 'SPY 240324P00480500' + dxf = ".SPY240324P480.5" + occ = "SPY 240324P00480500" assert Option.occ_to_streamer_symbol(occ) == dxf diff --git a/tests/test_metrics.py b/tests/test_metrics.py index fe43822..98c91e7 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -1,19 +1,23 @@ from datetime import date -from tastytrade.metrics import (get_dividends, get_earnings, - get_market_metrics, get_risk_free_rate) +from tastytrade.metrics import ( + get_dividends, + get_earnings, + get_market_metrics, + get_risk_free_rate, +) def test_get_dividends(session): - get_dividends(session, 'SPY') + get_dividends(session, "SPY") def test_get_earnings(session): - get_earnings(session, 'AAPL', date.today()) + get_earnings(session, "AAPL", date.today()) def test_get_market_metrics(session): - get_market_metrics(session, ['SPY', 'AAPL']) + get_market_metrics(session, ["SPY", "AAPL"]) def test_get_risk_free_rate(session): diff --git a/tests/test_search.py b/tests/test_search.py index dc7ce2c..4d0bcb3 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -2,11 +2,11 @@ def test_symbol_search_valid(session): - results = symbol_search(session, 'AAP') + results = symbol_search(session, "AAP") symbols = [s.symbol for s in results] - assert 'AAPL' in symbols + assert "AAPL" in symbols def test_symbol_search_invalid(session): - results = symbol_search(session, 'ASDFGJKL') + results = symbol_search(session, "ASDFGJKL") assert results == [] diff --git a/tests/test_streamer.py b/tests/test_streamer.py index d0163cd..d14fa24 100644 --- a/tests/test_streamer.py +++ b/tests/test_streamer.py @@ -5,7 +5,7 @@ from tastytrade import Account, AlertStreamer, DXLinkStreamer from tastytrade.dxfeed import EventType -pytest_plugins = ('pytest_asyncio',) +pytest_plugins = ("pytest_asyncio",) @pytest.mark.asyncio @@ -21,16 +21,16 @@ async def test_account_streamer(session): @pytest.mark.asyncio async def test_dxlink_streamer(session): async with DXLinkStreamer(session) as streamer: - subs = ['SPY', 'AAPL'] + subs = ["SPY", "AAPL"] await streamer.subscribe(EventType.QUOTE, subs) # this symbol doesn't exist - await streamer.subscribe(EventType.TRADE, ['QQQQ']) + await streamer.subscribe(EventType.TRADE, ["QQQQ"]) start_date = datetime.today() - timedelta(days=30) - await streamer.subscribe_candle(subs, '1d', start_date) + await streamer.subscribe_candle(subs, "1d", start_date) _ = await streamer.get_event(EventType.CANDLE) assert streamer.get_event_nowait(EventType.CANDLE) is not None assert streamer.get_event_nowait(EventType.TRADE) is None async for _ in streamer.listen(EventType.QUOTE): break - await streamer.unsubscribe_candle(subs[0], '1d') + await streamer.unsubscribe_candle(subs[0], "1d") await streamer.unsubscribe(EventType.QUOTE, subs[1]) diff --git a/tests/test_utils.py b/tests/test_utils.py index 2aeeab8..dc290e2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,10 +1,16 @@ from datetime import date -from tastytrade.utils import (get_future_fx_monthly, get_future_grain_monthly, - get_future_index_monthly, - get_future_metal_monthly, get_future_oil_monthly, - get_future_treasury_monthly, get_tasty_monthly, - get_third_friday, today_in_new_york) +from tastytrade.utils import ( + get_future_fx_monthly, + get_future_grain_monthly, + get_future_index_monthly, + get_future_metal_monthly, + get_future_oil_monthly, + get_future_treasury_monthly, + get_tasty_monthly, + get_third_friday, + today_in_new_york, +) def test_get_third_friday(): @@ -34,7 +40,7 @@ def test_get_future_fx_monthly(): date(2025, 3, 7), date(2025, 6, 6), date(2025, 9, 5), - date(2025, 12, 5) + date(2025, 12, 5), ] for exp in exps: assert get_future_fx_monthly(exp) == exp @@ -47,7 +53,7 @@ def test_get_future_treasury_monthly(): date(2024, 4, 26), date(2024, 5, 24), date(2024, 6, 21), - date(2024, 8, 23) + date(2024, 8, 23), ] for exp in exps: assert get_future_treasury_monthly(exp) == exp @@ -67,7 +73,7 @@ def test_get_future_grain_monthly(): date(2025, 6, 20), date(2025, 11, 21), date(2026, 6, 26), - date(2026, 11, 20) + date(2026, 11, 20), ] for exp in exps: assert get_future_grain_monthly(exp) == exp @@ -103,7 +109,7 @@ def test_get_future_metal_monthly(): date(2028, 5, 25), date(2028, 11, 27), date(2029, 5, 24), - date(2029, 11, 27) + date(2029, 11, 27), ] for exp in exps: assert get_future_metal_monthly(exp) == exp @@ -131,7 +137,7 @@ def test_get_future_oil_monthly(): date(2031, 8, 15), date(2032, 2, 17), date(2033, 4, 14), - date(2034, 1, 17) + date(2034, 1, 17), ] for exp in exps: assert get_future_oil_monthly(exp) == exp @@ -148,7 +154,7 @@ def test_get_future_index_monthly(): date(2024, 9, 30), date(2024, 12, 31), date(2025, 3, 31), - date(2025, 6, 30) + date(2025, 6, 30), ] for exp in exps: assert get_future_index_monthly(exp) == exp diff --git a/tests/test_watchlists.py b/tests/test_watchlists.py index 04e388c..0851c8b 100644 --- a/tests/test_watchlists.py +++ b/tests/test_watchlists.py @@ -5,7 +5,7 @@ from tastytrade.instruments import InstrumentType from tastytrade.watchlists import PairsWatchlist, Watchlist -WATCHLIST_NAME = 'TestWatchlist' +WATCHLIST_NAME = "TestWatchlist" def test_get_pairs_watchlists(session): @@ -13,7 +13,7 @@ def test_get_pairs_watchlists(session): def test_get_pairs_watchlist(session): - PairsWatchlist.get_pairs_watchlist(session, 'Stocks') + PairsWatchlist.get_pairs_watchlist(session, "Stocks") def test_get_public_watchlists(session): @@ -21,18 +21,18 @@ def test_get_public_watchlists(session): def test_get_public_watchlist(session): - Watchlist.get_public_watchlist(session, 'Crypto') + Watchlist.get_public_watchlist(session, "Crypto") def test_get_private_watchlists(session): Watchlist.get_private_watchlists(session) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def private_wl(): wl = Watchlist(name=WATCHLIST_NAME) - wl.add_symbol('MSFT', InstrumentType.EQUITY) - wl.add_symbol('AAPL', InstrumentType.EQUITY) + wl.add_symbol("MSFT", InstrumentType.EQUITY) + wl.add_symbol("AAPL", InstrumentType.EQUITY) return wl @@ -46,7 +46,7 @@ def test_get_private_watchlist(session): def test_update_private_watchlist(session, private_wl): - private_wl.remove_symbol('AAPL', InstrumentType.EQUITY) + private_wl.remove_symbol("AAPL", InstrumentType.EQUITY) sleep(1) private_wl.update_private_watchlist(session) diff --git a/uv.lock b/uv.lock index b2b5b76..9dd8c45 100644 --- a/uv.lock +++ b/uv.lock @@ -25,6 +25,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] +[[package]] +name = "anyio" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "sniffio", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/49/f3f17ec11c4a91fe79275c426658e509b07547f874b14c1a526d86a83fc8/anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", size = 170983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/ef/7a4f225581a0d7886ea28359179cb861d7fbcdefad29663fc1167b86f69f/anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a", size = 89631 }, +] + [[package]] name = "apeye" version = "1.4.1" @@ -53,6 +68,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/9f/fa9971d2a0c6fef64c87ba362a493a4f230eff4ea8dfb9f4c7cbdf71892e/apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf", size = 99286 }, ] +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, + { url = "https://files.pythonhosted.org/packages/34/da/d105a3235ae86c1c1a80c1e9c46953e6e53cc8c4c61fb3c5ac8a39bbca48/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", size = 23689 }, + { url = "https://files.pythonhosted.org/packages/43/f3/20bc53a6e50471dfea16a63dc9b69d2a9ec78fd2b9532cc25f8317e121d9/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", size = 28122 }, + { url = "https://files.pythonhosted.org/packages/2e/f1/48888db30b6a4a0c78ab7bc7444058a1135b223b6a2a5f2ac7d6780e7443/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", size = 27882 }, + { url = "https://files.pythonhosted.org/packages/ee/0f/a2260a207f21ce2ff4cad00a417c31597f08eafb547e00615bcbf403d8ea/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", size = 30745 }, + { url = "https://files.pythonhosted.org/packages/ed/55/f8ba268bc9005d0ca57a862e8f1b55bf1775e97a36bd30b0a8fb568c265c/argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", size = 28587 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "types-python-dateutil", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, +] + +[[package]] +name = "async-lru" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/e2/2b4651eff771f6fd900d233e175ddc5e2be502c7eb62c0c42f975c6d36cd/async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627", size = 10019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9f/3c3503693386c4b0f245eaf5ca6198e3b28879ca0a40bde6b0e319793453/async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224", size = 6111 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + [[package]] name = "autodoc-pydantic" version = "2.2.0" @@ -99,6 +207,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 }, ] +[[package]] +name = "bleach" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version >= '3.10'" }, + { name = "webencodings", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/10/77f32b088738f40d4f5be801daa5f327879eadd4562f36a2b5ab975ae571/bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe", size = 202119 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/63/da7237f805089ecc28a3f36bca6a21c31fcbc2eb380f3b8f1be3312abd14/bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6", size = 162750 }, +] + [[package]] name = "cachecontrol" version = "0.14.0" @@ -126,6 +247,83 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, ] +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { 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/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { 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/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { 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/48/08/15bf6b43ae9bd06f6b00ad8a91f5a8fe1069d4c9fab550a866755402724e/cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", size = 182457 }, + { url = "https://files.pythonhosted.org/packages/c2/5b/f1523dd545f92f7df468e5f653ffa4df30ac222f3c884e51e139878f1cb5/cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", size = 425932 }, + { url = "https://files.pythonhosted.org/packages/53/93/7e547ab4105969cc8c93b38a667b82a835dd2cc78f3a7dad6130cfd41e1d/cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", size = 448585 }, + { url = "https://files.pythonhosted.org/packages/56/c4/a308f2c332006206bb511de219efeff090e9d63529ba0a77aae72e82248b/cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", size = 456268 }, + { url = "https://files.pythonhosted.org/packages/ca/5b/b63681518265f2f4060d2b60755c1c77ec89e5e045fc3773b72735ddaad5/cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", size = 436592 }, + { url = "https://files.pythonhosted.org/packages/bb/19/b51af9f4a4faa4a8ac5a0e5d5c2522dcd9703d07fac69da34a36c4d960d3/cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", size = 446512 }, + { url = "https://files.pythonhosted.org/packages/e2/63/2bed8323890cb613bbecda807688a31ed11a7fe7afe31f8faaae0206a9a3/cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", size = 171576 }, + { url = "https://files.pythonhosted.org/packages/2f/70/80c33b044ebc79527447fd4fbc5455d514c3bb840dede4455de97da39b4d/cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", size = 181229 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220 }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605 }, + { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910 }, + { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200 }, + { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565 }, + { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635 }, + { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218 }, + { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486 }, + { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911 }, + { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632 }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -219,6 +417,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + [[package]] name = "coverage" version = "7.6.1" @@ -315,6 +525,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747 }, ] +[[package]] +name = "debugpy" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/b3/05c94639560cf0eaef33662ee5102d3e2a8b9e8c527c53190bf7187bacdb/debugpy-1.8.6.zip", hash = "sha256:c931a9371a86784cee25dec8d65bc2dc7a21f3f1552e3833d9ef8f919d22280a", size = 4956612 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/ce/5e093945df2da28dbd1bc14c631d71431d1aa08adc629e221c9658841f82/debugpy-1.8.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:30f467c5345d9dfdcc0afdb10e018e47f092e383447500f125b4e013236bf14b", size = 2089048 }, + { url = "https://files.pythonhosted.org/packages/d4/7a/a5fe4eaf648016a27a875403735a089ba7cc9a4cc906d37c8fdb2997b50d/debugpy-1.8.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d73d8c52614432f4215d0fe79a7e595d0dd162b5c15233762565be2f014803b", size = 3547450 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/53d6d46e4a1cb5fb1a979695a9a26c8a04aed6d6ce4ba808a6d42341beba/debugpy-1.8.6-cp310-cp310-win32.whl", hash = "sha256:e3e182cd98eac20ee23a00653503315085b29ab44ed66269482349d307b08df9", size = 5151732 }, + { url = "https://files.pythonhosted.org/packages/ce/68/127cfc6012fbeef126eab1e168ad788ee9832b8b0d572743e5c6fa03ea83/debugpy-1.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:e3a82da039cfe717b6fb1886cbbe5c4a3f15d7df4765af857f4307585121c2dd", size = 5183983 }, + { url = "https://files.pythonhosted.org/packages/9f/cc/3158aa2c96c677e324981230dfd33087ef4bfb5afb1d9cd40b7a1b35edb2/debugpy-1.8.6-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67479a94cf5fd2c2d88f9615e087fcb4fec169ec780464a3f2ba4a9a2bb79955", size = 2203403 }, + { url = "https://files.pythonhosted.org/packages/d5/9f/5691af62c556392ee45ed9b5c3fde4aaa7cb3b519cc8bea92fc27eab31fc/debugpy-1.8.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb8653f6cbf1dd0a305ac1aa66ec246002145074ea57933978346ea5afdf70b", size = 3120088 }, + { url = "https://files.pythonhosted.org/packages/5e/3e/e32b36f9a391af4f8ff6b9c068ee822b5e4aa2d9cf4dc0937696e9249fa6/debugpy-1.8.6-cp311-cp311-win32.whl", hash = "sha256:cdaf0b9691879da2d13fa39b61c01887c34558d1ff6e5c30e2eb698f5384cd43", size = 5077329 }, + { url = "https://files.pythonhosted.org/packages/9d/de/ddad801b7fdbe2f97c744b44bb61169c4e0ab48a90f881c8f43b463f206b/debugpy-1.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:43996632bee7435583952155c06881074b9a742a86cee74e701d87ca532fe833", size = 5101373 }, + { url = "https://files.pythonhosted.org/packages/b8/9e/882dae43f281fc4742fd9e5d2e0f5dae77f38d4f345e78bf1ed5e1f6202e/debugpy-1.8.6-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:db891b141fc6ee4b5fc6d1cc8035ec329cabc64bdd2ae672b4550c87d4ecb128", size = 2526807 }, + { url = "https://files.pythonhosted.org/packages/77/cf/6c0497f4b092cb4a408dda5ab84750032e5535f994d21eb812086d62094d/debugpy-1.8.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:567419081ff67da766c898ccf21e79f1adad0e321381b0dfc7a9c8f7a9347972", size = 4162582 }, + { url = "https://files.pythonhosted.org/packages/8e/66/e9c0aef0a5118aeaa6dfccb6d4f388602271cfb37c689da5e7b6168075d2/debugpy-1.8.6-cp312-cp312-win32.whl", hash = "sha256:c9834dfd701a1f6bf0f7f0b8b1573970ae99ebbeee68314116e0ccc5c78eea3c", size = 5193541 }, + { url = "https://files.pythonhosted.org/packages/c2/97/2196c4132c29f7cd8e574bb05a4b03ed35f94e3fcd1f56e72ea9f10732f4/debugpy-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:e4ce0570aa4aca87137890d23b86faeadf184924ad892d20c54237bcaab75d8f", size = 5233374 }, + { url = "https://files.pythonhosted.org/packages/b9/db/1fcb9b0cd12cd417fecaab545df7d3c77793092adbe614c51b8352904e85/debugpy-1.8.6-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:df5dc9eb4ca050273b8e374a4cd967c43be1327eeb42bfe2f58b3cdfe7c68dcb", size = 2090260 }, + { url = "https://files.pythonhosted.org/packages/dd/07/301ab6ce54793213eef5f85f86fd0f0c7b3579d204149405ffdb7e5c67db/debugpy-1.8.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a85707c6a84b0c5b3db92a2df685b5230dd8fb8c108298ba4f11dba157a615a", size = 3617517 }, + { url = "https://files.pythonhosted.org/packages/73/f9/934930dd49ed8f5660fe0c39fded7a5b180eabc6db03e49d9243ce316f1a/debugpy-1.8.6-cp38-cp38-win32.whl", hash = "sha256:538c6cdcdcdad310bbefd96d7850be1cd46e703079cc9e67d42a9ca776cdc8a8", size = 5156468 }, + { url = "https://files.pythonhosted.org/packages/e0/19/fa925b305b4c1766ec20ee1c6301fc16a40fb7affba0a0d3d1d12248ef3d/debugpy-1.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:22140bc02c66cda6053b6eb56dfe01bbe22a4447846581ba1dd6df2c9f97982d", size = 5189752 }, + { url = "https://files.pythonhosted.org/packages/e4/61/38fa2e907aae3a293e887b04045e4d30f931aafc462b207f4cb846e78c13/debugpy-1.8.6-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:c1cef65cffbc96e7b392d9178dbfd524ab0750da6c0023c027ddcac968fd1caa", size = 2090326 }, + { url = "https://files.pythonhosted.org/packages/39/b0/9790509ffeee155038f9707b74d031ed90a17552fe6a63e9069c9c42e0d9/debugpy-1.8.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e60bd06bb3cc5c0e957df748d1fab501e01416c43a7bdc756d2a992ea1b881", size = 3544122 }, + { url = "https://files.pythonhosted.org/packages/7a/a1/d95a015eadf79997cdd2028a5fe3d1f37fbe2548b51470517d3d425960dc/debugpy-1.8.6-cp39-cp39-win32.whl", hash = "sha256:f7158252803d0752ed5398d291dee4c553bb12d14547c0e1843ab74ee9c31123", size = 5152549 }, + { url = "https://files.pythonhosted.org/packages/81/6a/32e2c9e980924f3c4b1b644a5c3d949d05fa7b445673ecf3e3244c883669/debugpy-1.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3358aa619a073b620cd0d51d8a6176590af24abcc3fe2e479929a154bf591b51", size = 5185080 }, + { url = "https://files.pythonhosted.org/packages/05/ce/785925e87ce735cc3da7fb2bd66d8ca83173d8a0b60ce35a59a60b8d636f/debugpy-1.8.6-py2.py3-none-any.whl", hash = "sha256:b48892df4d810eff21d3ef37274f4c60d32cdcafc462ad5647239036b0f0649f", size = 5209208 }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "dict2css" version = "0.3.0.post1" @@ -389,6 +646,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/8f/f3303c61b8e925ded53f37b3bb3365e349449ef40b687e39cd7b59f87d14/exchange_calendars-4.5.6-py3-none-any.whl", hash = "sha256:5abf5ebcb8ceef0ced36fe4e20071d42517091bf081e6c44354cb343009d672b", size = 196161 }, ] +[[package]] +name = "executing" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, +] + [[package]] name = "fake-useragent" version = "1.5.1" @@ -398,6 +664,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190 }, ] +[[package]] +name = "fastjsonschema" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/3f/3ad5e7be13b4b8b55f4477141885ab2364f65d5f6ad5f7a9daffd634d066/fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", size = 373056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/ca/086311cdfc017ec964b2436fe0c98c1f4efcb7e4c328956a22456e497655/fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a", size = 23543 }, +] + [[package]] name = "filelock" version = "3.16.1" @@ -407,6 +682,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, ] +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + +[[package]] +name = "h11" +version = "0.14.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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + [[package]] name = "html5lib" version = "1.1" @@ -420,6 +713,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, ] +[[package]] +name = "httpcore" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.10'" }, + { name = "h11", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/b0/5e8b8674f8d203335a62fdfcfa0d11ebe09e23613c3391033cbba35f7926/httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", size = 83234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/d4/e5d7e4f2174f8a4d63c8897d79eb8fe2503f7ecc03282fee1fa2719c2704/httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5", size = 77926 }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.10'" }, + { name = "certifi", marker = "python_full_version >= '3.10'" }, + { name = "httpcore", marker = "python_full_version >= '3.10'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "sniffio", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, +] + [[package]] name = "idna" version = "3.10" @@ -447,6 +769,92 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "python_full_version >= '3.10' and platform_system == 'Darwin'" }, + { name = "comm", marker = "python_full_version >= '3.10'" }, + { name = "debugpy", marker = "python_full_version >= '3.10'" }, + { name = "ipython", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "psutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "jedi", marker = "python_full_version >= '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.10'" }, + { name = "pexpect", marker = "python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "stack-data", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/24/d4fabaca03c8804bf0b8d994c8ae3a20e57e9330d277fb43d83e558dec5e/ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e", size = 5494984 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a2/6c725958e6f135d8e5de081e69841bb2c1d84b3fc259d02eb092b8fc203a/ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c", size = 818986 }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm", marker = "python_full_version >= '3.10'" }, + { name = "ipython", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-widgets", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "widgetsnbextension", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[package]] +name = "jedi" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/99b493cec4bf43176b678de30f81ed003fd6a647a301b9c927280c600f0a/jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", size = 1227821 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9f/bc63f0f0737ad7a60800bfd472a4836661adae21f9c2535f3957b1e54ceb/jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0", size = 1569361 }, +] + [[package]] name = "jinja2" version = "3.1.4" @@ -459,6 +867,263 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, ] +[[package]] +name = "json5" +version = "0.9.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/59/51b032d53212a51f17ebbcc01bd4217faab6d6c09ed0d856a987a5f42bbc/json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae", size = 40332 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/3c/4f8791ee53ab9eeb0b022205aa79387119a74cc9429582ce04098e6fc540/json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f", size = 30109 }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" }, + { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn", marker = "python_full_version >= '3.10'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "isoduration", marker = "python_full_version >= '3.10'" }, + { name = "jsonpointer", marker = "python_full_version >= '3.10'" }, + { name = "rfc3339-validator", marker = "python_full_version >= '3.10'" }, + { name = "rfc3986-validator", marker = "python_full_version >= '3.10'" }, + { name = "uri-template", marker = "python_full_version >= '3.10'" }, + { name = "webcolors", marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, +] + +[[package]] +name = "jupyter" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", marker = "python_full_version >= '3.10'" }, + { name = "ipywidgets", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-console", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab", marker = "python_full_version >= '3.10'" }, + { name = "nbconvert", marker = "python_full_version >= '3.10'" }, + { name = "notebook", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel", marker = "python_full_version >= '3.10'" }, + { name = "ipython", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs", marker = "python_full_version >= '3.10'" }, + { name = "pywin32", marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyter-events" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"], marker = "python_full_version >= '3.10'" }, + { name = "python-json-logger", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "rfc3339-validator", marker = "python_full_version >= '3.10'" }, + { name = "rfc3986-validator", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/53/7537a1aa558229bb0b1b178d814c9d68a9c697d3aecb808a1cb2646acf1f/jupyter_events-0.10.0.tar.gz", hash = "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22", size = 61516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/94/059180ea70a9a326e1815176b2370da56376da347a796f8c4f0b830208ef/jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960", size = 18777 }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/e0/7bd7cff65594fd9936e2f9385701e44574fc7d721331ff676ce440b14100/jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", size = 69146 }, +] + +[[package]] +name = "jupyter-server" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.10'" }, + { name = "argon2-cffi", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-events", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-server-terminals", marker = "python_full_version >= '3.10'" }, + { name = "nbconvert", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "overrides", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "prometheus-client", marker = "python_full_version >= '3.10'" }, + { name = "pywinpty", marker = "python_full_version >= '3.10' and os_name == 'nt'" }, + { name = "pyzmq", marker = "python_full_version >= '3.10'" }, + { name = "send2trash", marker = "python_full_version >= '3.10'" }, + { name = "terminado", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, + { name = "websocket-client", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/34/88b47749c7fa9358e10eac356c4b97d94a91a67d5c935a73f69bc4a31118/jupyter_server-2.14.2.tar.gz", hash = "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b", size = 719933 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/e1/085edea6187a127ca8ea053eb01f4e1792d778b4d192c74d32eb6730fed6/jupyter_server-2.14.2-py3-none-any.whl", hash = "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd", size = 383556 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "python_full_version >= '3.10' and os_name == 'nt'" }, + { name = "terminado", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[package]] +name = "jupyterlab" +version = "4.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru", marker = "python_full_version >= '3.10'" }, + { name = "httpx", marker = "python_full_version >= '3.10'" }, + { name = "ipykernel", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-lsp", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-server", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-server", marker = "python_full_version >= '3.10'" }, + { name = "notebook-shim", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "setuptools", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/78/ba006df6edaa561fe40be26c35e9da3f9316f071167cd7cc1a1a25bd2664/jupyterlab-4.2.5.tar.gz", hash = "sha256:ae7f3a1b8cb88b4f55009ce79fa7c06f99d70cd63601ee4aa91815d054f46f75", size = 21508698 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3f/24a0f0ce60959cfd9756a3291cd3a5581e51cbd6f7b4aa121f5bba5320e3/jupyterlab-4.2.5-py3-none-any.whl", hash = "sha256:73b6e0775d41a9fee7ee756c80f58a6bed4040869ccc21411dc559818874d321", size = 11641981 }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "json5", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-server", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700 }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, +] + [[package]] name = "korean-lunar-calendar" version = "0.3.1" @@ -535,6 +1200,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/f8/4da07de16f10551ca1f640c92b5f316f9394088b183c6a57183df6de5ae4/MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", size = 17211 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "mistune" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c8/f0173fe3bf85fd891aee2e7bcd8207dfe26c2c683d727c5a6cc3aec7b628/mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8", size = 90840 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/74/c95adcdf032956d9ef6c89a9b8a5152bf73915f8c633f3e3d88d06bd699c/mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205", size = 47958 }, +] + [[package]] name = "more-itertools" version = "10.5.0" @@ -672,6 +1358,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 }, ] +[[package]] +name = "nbclient" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/d2/39bc36604f24bccd44d374ac34769bc58c53a1da5acd1e83f0165aa4940e/nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09", size = 62246 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e8/00517a23d3eeaed0513e718fbc94aab26eaa1758f5690fc8578839791c79/nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f", size = 25318 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4", marker = "python_full_version >= '3.10'" }, + { name = "bleach", marker = "python_full_version >= '3.10'" }, + { name = "defusedxml", marker = "python_full_version >= '3.10'" }, + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-pygments", marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", marker = "python_full_version >= '3.10'" }, + { name = "mistune", marker = "python_full_version >= '3.10'" }, + { name = "nbclient", marker = "python_full_version >= '3.10'" }, + { name = "nbformat", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pandocfilters", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tinycss2", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/e8/ba521a033b21132008e520c28ceb818f9f092da5f0261e94e509401b29f9/nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4", size = 854422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/bb/bb5b6a515d1584aa2fd89965b11db6632e4bdc69495a52374bcc36e56cfa/nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3", size = 257388 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema", marker = "python_full_version >= '3.10'" }, + { name = "jupyter-core", marker = "python_full_version >= '3.10'" }, + { name = "traitlets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "notebook" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab", marker = "python_full_version >= '3.10'" }, + { name = "jupyterlab-server", marker = "python_full_version >= '3.10'" }, + { name = "notebook-shim", marker = "python_full_version >= '3.10'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/33/30b83c1c84e368087059bde1269549612584924db156bff53654e165a498/notebook-7.2.2.tar.gz", hash = "sha256:2ef07d4220421623ad3fe88118d687bc0450055570cdd160814a59cf3a1c516e", size = 4948876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/77/53732fbf48196af9e51c2a61833471021c1d77d335d57b96ee3588c0c53d/notebook-7.2.2-py3-none-any.whl", hash = "sha256:c89264081f671bc02eec0ed470a627ed791b9156cad9285226b31611d3e9fe1c", size = 5037123 }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, +] + [[package]] name = "numpy" version = "2.1.1" @@ -732,6 +1511,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/31/3cbba87e998748b2e33ca5bc6fcc5662c867037f980918e302aebdf139a2/numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39", size = 12789681 }, ] +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + [[package]] name = "packaging" version = "24.1" @@ -811,6 +1599,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/e4/588a15467222602a5ca02d52700c3e3039f5f4fcce586a17f6ee0f6ba832/pandas_market_calendars-4.4.1-py3-none-any.whl", hash = "sha256:4ea4dd850ed01942f1274d64e29f5f09bc29f5dae771567b94f70bd0924cd2a2", size = 107262 }, ] +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -829,6 +1647,71 @@ 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 = "prometheus-client" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/54/a369868ed7a7f1ea5163030f4fc07d85d22d7a1d270560dab675188fb612/prometheus_client-0.21.0.tar.gz", hash = "sha256:96c83c606b71ff2b0a433c98889d275f51ffec6c5e267de37c7a2b5c9aa9233e", size = 78634 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/2d/46ed6436849c2c88228c3111865f44311cff784b4aabcdef4ea2545dbc3d/prometheus_client-0.21.0-py3-none-any.whl", hash = "sha256:4fa6b4dd0ac16d58bb587c04b1caae65b8c5043e85f778f42f5f632f6af2e166", size = 54686 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, +] + +[[package]] +name = "psutil" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 }, + { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 }, + { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 }, + { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 }, + { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046 }, + { url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560 }, + { url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399 }, + { url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[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 }, +] + [[package]] name = "pydantic" version = "2.9.2" @@ -1036,6 +1919,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, ] +[[package]] +name = "python-json-logger" +version = "2.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/da/95963cebfc578dabd323d7263958dfb68898617912bb09327dd30e9c8d13/python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c", size = 10508 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/a6/145655273568ee78a581e734cf35beb9e33a370b29c5d3c8fee3744de29f/python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd", size = 8067 }, +] + [[package]] name = "pytz" version = "2024.2" @@ -1045,6 +1937,216 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, ] +[[package]] +name = "pywin32" +version = "306" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422 }, + { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392 }, + { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, + { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, + { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, + { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, + { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, + { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, + { url = "https://files.pythonhosted.org/packages/0e/57/c3ec32b498f24a2392404d1f0fd29f47a3f7339d7d579df7a0560cff337c/pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a", size = 8632118 }, + { url = "https://files.pythonhosted.org/packages/fa/80/a6b22e031590cc5f4fcbd5bf4bcf63a9dabce9d59065f53add99a8caaec5/pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0", size = 9373699 }, + { url = "https://files.pythonhosted.org/packages/7e/7f/419c4fcadcaa374a0ae41cbdf6c3a81452892dd6c523aea629d17e49146e/pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802", size = 8573451 }, + { url = "https://files.pythonhosted.org/packages/1c/f7/24d8ed4fd9c43b90354df7764f81f0dd5e623f9a50f1538f90fe085d6dff/pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4", size = 9312883 }, +] + +[[package]] +name = "pywinpty" +version = "2.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/d9/93956af389ab7d4ef2f558b1cc6c5cb48885d254ac882f212964c30a1e4f/pywinpty-2.0.13.tar.gz", hash = "sha256:c34e32351a3313ddd0d7da23d27f835c860d32fe4ac814d372a3ea9594f41dde", size = 28240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c4/940928b15435d56f7af38c0fab36cd00413f185721fcef4265d06bd543c9/pywinpty-2.0.13-cp310-none-win_amd64.whl", hash = "sha256:697bff211fb5a6508fee2dc6ff174ce03f34a9a233df9d8b5fe9c8ce4d5eaf56", size = 1398988 }, + { url = "https://files.pythonhosted.org/packages/02/f0/2004a0c907eb74155b6fafa5801931d9e15d55905db6811f146cc2d145cd/pywinpty-2.0.13-cp311-none-win_amd64.whl", hash = "sha256:b96fb14698db1284db84ca38c79f15b4cfdc3172065b5137383910567591fa99", size = 1399007 }, + { url = "https://files.pythonhosted.org/packages/49/37/c0dcb1dca094af3605dd22c0528839a65bc4e1e78bb91eb12841d18fa3f1/pywinpty-2.0.13-cp312-none-win_amd64.whl", hash = "sha256:2fd876b82ca750bb1333236ce98488c1be96b08f4f7647cfdf4129dfad83c2d4", size = 1399803 }, + { url = "https://files.pythonhosted.org/packages/e2/84/b27efd41c14e3a62ce768b14928fb3c6134ba4b1dcdbf40264f279f92b37/pywinpty-2.0.13-cp38-none-win_amd64.whl", hash = "sha256:61d420c2116c0212808d31625611b51caf621fe67f8a6377e2e8b617ea1c1f7d", size = 1399479 }, + { url = "https://files.pythonhosted.org/packages/c9/f2/20ee2f6aab76ddd10abfab7dfb98c024c6beb471d2990f35eee41684b123/pywinpty-2.0.13-cp39-none-win_amd64.whl", hash = "sha256:71cb613a9ee24174730ac7ae439fd179ca34ccb8c5349e8d7b72ab5dea2c6f4b", size = 1399252 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218 }, + { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067 }, + { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812 }, + { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531 }, + { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820 }, + { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514 }, + { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "python_full_version >= '3.10' and implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/a8/9837c39aba390eb7d01924ace49d761c8dbe7bc2d6082346d00c8332e431/pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629", size = 1340058 }, + { url = "https://files.pythonhosted.org/packages/a2/1f/a006f2e8e4f7d41d464272012695da17fb95f33b54342612a6890da96ff6/pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b", size = 1008818 }, + { url = "https://files.pythonhosted.org/packages/b6/09/b51b6683fde5ca04593a57bbe81788b6b43114d8f8ee4e80afc991e14760/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764", size = 673199 }, + { url = "https://files.pythonhosted.org/packages/c9/78/486f3e2e824f3a645238332bf5a4c4b4477c3063033a27c1e4052358dee2/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c", size = 911762 }, + { url = "https://files.pythonhosted.org/packages/5e/3b/2eb1667c9b866f53e76ee8b0c301b0469745a23bd5a87b7ee3d5dd9eb6e5/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a", size = 868773 }, + { url = "https://files.pythonhosted.org/packages/16/29/ca99b4598a9dc7e468b5417eda91f372b595be1e3eec9b7cbe8e5d3584e8/pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88", size = 868834 }, + { url = "https://files.pythonhosted.org/packages/ad/e5/9efaeb1d2f4f8c50da04144f639b042bc52869d3a206d6bf672ab3522163/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f", size = 1202861 }, + { url = "https://files.pythonhosted.org/packages/c3/62/c721b5608a8ac0a69bb83cbb7d07a56f3ff00b3991a138e44198a16f94c7/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282", size = 1515304 }, + { url = "https://files.pythonhosted.org/packages/87/84/e8bd321aa99b72f48d4606fc5a0a920154125bd0a4608c67eab742dab087/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea", size = 1414712 }, + { url = "https://files.pythonhosted.org/packages/cd/cd/420e3fd1ac6977b008b72e7ad2dae6350cc84d4c5027fc390b024e61738f/pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2", size = 578113 }, + { url = "https://files.pythonhosted.org/packages/5c/57/73930d56ed45ae0cb4946f383f985c855c9b3d4063f26416998f07523c0e/pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971", size = 641631 }, + { url = "https://files.pythonhosted.org/packages/61/d2/ae6ac5c397f1ccad59031c64beaafce7a0d6182e0452cc48f1c9c87d2dd0/pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa", size = 543528 }, + { url = "https://files.pythonhosted.org/packages/12/20/de7442172f77f7c96299a0ac70e7d4fb78cd51eca67aa2cf552b66c14196/pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", size = 1340639 }, + { url = "https://files.pythonhosted.org/packages/98/4d/5000468bd64c7910190ed0a6c76a1ca59a68189ec1f007c451dc181a22f4/pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", size = 1008710 }, + { url = "https://files.pythonhosted.org/packages/e1/bf/c67fd638c2f9fbbab8090a3ee779370b97c82b84cc12d0c498b285d7b2c0/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", size = 673129 }, + { url = "https://files.pythonhosted.org/packages/86/94/99085a3f492aa538161cbf27246e8886ff850e113e0c294a5b8245f13b52/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317", size = 910107 }, + { url = "https://files.pythonhosted.org/packages/31/1d/346809e8a9b999646d03f21096428453465b1bca5cd5c64ecd048d9ecb01/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf", size = 867960 }, + { url = "https://files.pythonhosted.org/packages/ab/68/6fb6ae5551846ad5beca295b7bca32bf0a7ce19f135cb30e55fa2314e6b6/pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e", size = 869204 }, + { url = "https://files.pythonhosted.org/packages/0f/f9/18417771dee223ccf0f48e29adf8b4e25ba6d0e8285e33bcbce078070bc3/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37", size = 1203351 }, + { url = "https://files.pythonhosted.org/packages/e0/46/f13e67fe0d4f8a2315782cbad50493de6203ea0d744610faf4d5f5b16e90/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3", size = 1514204 }, + { url = "https://files.pythonhosted.org/packages/50/11/ddcf7343b7b7a226e0fc7b68cbf5a5bb56291fac07f5c3023bb4c319ebb4/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6", size = 1414339 }, + { url = "https://files.pythonhosted.org/packages/01/14/1c18d7d5b7be2708f513f37c61bfadfa62161c10624f8733f1c8451b3509/pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4", size = 576928 }, + { url = "https://files.pythonhosted.org/packages/3b/1b/0a540edd75a41df14ec416a9a500b9fec66e554aac920d4c58fbd5756776/pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5", size = 642317 }, + { url = "https://files.pythonhosted.org/packages/98/77/1cbfec0358078a4c5add529d8a70892db1be900980cdb5dd0898b3d6ab9d/pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003", size = 543834 }, + { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, + { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, + { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, + { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, + { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, + { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, + { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, + { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, + { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, + { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, + { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, + { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, + { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, + { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, + { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, + { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, + { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, + { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, + { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, + { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, + { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, + { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, + { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, + { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, + { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, + { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, + { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, + { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, + { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, + { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, + { url = "https://files.pythonhosted.org/packages/64/e7/d5d59205d446c299001d27bfc18702c5353512c5485b11ec7cf6df9552d7/pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f", size = 1340492 }, + { url = "https://files.pythonhosted.org/packages/59/bb/aa6616a83694ab43cfb3bdb868d194a5ee2fa24b49e6ec7ec4400691ac3b/pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2", size = 1008257 }, + { url = "https://files.pythonhosted.org/packages/a6/b6/e578e6c08970df0daa08b7c54e82b606211f9a7e61317ef2db79cc334389/pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6", size = 907602 }, + { url = "https://files.pythonhosted.org/packages/ab/3a/a26b98aebeb7924b24e9973a2f5bf8974201bb5a3f6ed06ddc3bac19372d/pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289", size = 862291 }, + { url = "https://files.pythonhosted.org/packages/c1/b5/7eedb8d63af13c2858beb9c1f58e90e7e00929176b57f45e3592fccd56dc/pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732", size = 673879 }, + { url = "https://files.pythonhosted.org/packages/af/22/38734f47543e61b4eb97eee476f0f7ae544988533215eea22fc65e1ca1d7/pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780", size = 1207011 }, + { url = "https://files.pythonhosted.org/packages/59/a4/104cc979ae88ed948ef829db5fb49bca4a771891125fa4166bba1598b2ec/pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640", size = 1516183 }, + { url = "https://files.pythonhosted.org/packages/52/8f/73a8e08897f8ed21fe44fc73b5faf3ea4cacb97bfd219a63ee5f3ea203a8/pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd", size = 1417481 }, + { url = "https://files.pythonhosted.org/packages/67/cf/f418670a83fb3a91e2d6d26f271a828a58e0265199944a76e4ef274f9ba7/pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988", size = 577930 }, + { url = "https://files.pythonhosted.org/packages/f0/51/1f2b47c8d8fb85c07f088e21df6364b8b5e8298e75bb23ea0e65340ebd82/pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f", size = 642503 }, + { url = "https://files.pythonhosted.org/packages/ac/9e/ad5fbbe1bcc7a9d1e8c5f4f7de48f2c1dc481e151ef80cc1ce9a7fe67b55/pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2", size = 1341256 }, + { url = "https://files.pythonhosted.org/packages/4c/d9/d7a8022108c214803a82b0b69d4885cee00933d21928f1f09dca371cf4bf/pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c", size = 1009385 }, + { url = "https://files.pythonhosted.org/packages/ed/69/0529b59ac667ea8bfe8796ac71796b688fbb42ff78e06525dabfed3bc7ae/pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98", size = 908009 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/3ff3e1172f12f55769793a3a334e956ec2886805ebfb2f64756b6b5c6a1a/pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9", size = 862078 }, + { url = "https://files.pythonhosted.org/packages/c3/ec/ab13585c3a1f48e2874253844c47b194d56eb25c94718691349c646f336f/pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db", size = 673756 }, + { url = "https://files.pythonhosted.org/packages/1e/be/febcd4b04dd50ee6d514dfbc33a3d5d9cb38ec9516e02bbfc929baa0f141/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073", size = 1203684 }, + { url = "https://files.pythonhosted.org/packages/16/28/304150e71afd2df3b82f52f66c0d8ab9ac6fe1f1ffdf92bad4c8cc91d557/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc", size = 1515864 }, + { url = "https://files.pythonhosted.org/packages/18/89/8d48d8cd505c12a1f5edee597cc32ffcedc65fd8d2603aebaaedc38a7041/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940", size = 1415383 }, + { url = "https://files.pythonhosted.org/packages/d4/7e/43a60c3b179f7da0cbc2b649bd2702fd6a39bff5f72aa38d6e1aeb00256d/pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44", size = 578540 }, + { url = "https://files.pythonhosted.org/packages/3a/55/8841dcd28f783ad06674c8fe8d7d72794b548d0bff8829aaafeb72e8b44d/pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec", size = 642147 }, + { url = "https://files.pythonhosted.org/packages/b4/78/b3c31ccfcfcdd6ea50b6abc8f46a2a7aadb9c3d40531d1b908d834aaa12e/pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb", size = 543903 }, + { url = "https://files.pythonhosted.org/packages/53/fb/36b2b2548286e9444e52fcd198760af99fd89102b5be50f0660fcfe902df/pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", size = 906955 }, + { url = "https://files.pythonhosted.org/packages/77/8f/6ce54f8979a01656e894946db6299e2273fcee21c8e5fa57c6295ef11f57/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", size = 565701 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/bf8cd66730a866b16db8483286078892b7f6536f8c389fb46e4beba0a970/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", size = 794312 }, + { url = "https://files.pythonhosted.org/packages/71/43/91fa4ff25bbfdc914ab6bafa0f03241d69370ef31a761d16bb859f346582/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca", size = 752775 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/3b2ab40f455a256cb6672186bea95cd97b459ce4594050132d71e76f0d6f/pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", size = 550762 }, + { url = "https://files.pythonhosted.org/packages/38/a7/1c80b0c8013befad391b92ba8a8e597de8884605ad5ad8ab943c888eb3ca/pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20", size = 906946 }, + { url = "https://files.pythonhosted.org/packages/9c/ac/34a7ee2e7edb07c7222752096650313424eb05f18401ed0a964e996088fb/pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919", size = 802021 }, + { url = "https://files.pythonhosted.org/packages/cd/70/c65ddccfb88b469b6044f9664c81f0b7f649711e0dc172cba8b2a968ad99/pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5", size = 756818 }, + { url = "https://files.pythonhosted.org/packages/07/7a/fc77f6d57f592207403eab2deca4c6f1ffa9c78b0f03b59e69069a12a1a1/pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc", size = 565698 }, + { url = "https://files.pythonhosted.org/packages/dc/13/e8494ba2d161fb471955fadbef7f48076bd29b19a4dd3c5d61d22e500505/pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277", size = 550757 }, + { url = "https://files.pythonhosted.org/packages/6c/78/3096d72581365dfb0081ac9512a3b53672fa69854aa174d78636510c4db8/pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3", size = 906945 }, + { url = "https://files.pythonhosted.org/packages/da/f2/8054574d77c269c31d055d4daf3d8407adf61ea384a50c8d14b158551d09/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a", size = 565698 }, + { url = "https://files.pythonhosted.org/packages/77/21/c3ad93236d1d60eea10b67528f55e7db115a9d32e2bf163fcf601f85e9cc/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6", size = 794307 }, + { url = "https://files.pythonhosted.org/packages/6a/49/e95b491724500fcb760178ce8db39b923429e328e57bcf9162e32c2c187c/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a", size = 752769 }, + { url = "https://files.pythonhosted.org/packages/9b/a9/50c9c06762b30792f71aaad8d1886748d39c4bffedc1171fbc6ad2b92d67/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4", size = 751338 }, + { url = "https://files.pythonhosted.org/packages/ca/63/27e6142b4f67a442ee480986ca5b88edb01462dd2319843057683a5148bd/pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f", size = 550757 }, +] + +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "rpds-py", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -1060,6 +2162,137 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/2d/a7e60483b72b91909e18f29a5c5ae847bac4e2ae95b77bb77e1f41819a58/rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", size = 318432 }, + { url = "https://files.pythonhosted.org/packages/b5/b4/f15b0c55a6d880ce74170e7e28c3ed6c5acdbbd118df50b91d1dabf86008/rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", size = 311333 }, + { url = "https://files.pythonhosted.org/packages/36/10/3f4e490fe6eb069c07c22357d0b4804cd94cb9f8d01345ef9b1d93482b9d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", size = 366697 }, + { url = "https://files.pythonhosted.org/packages/f5/c8/cd6ab31b4424c7fab3b17e153b6ea7d1bb0d7cabea5c1ef683cc8adb8bc2/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", size = 368386 }, + { url = "https://files.pythonhosted.org/packages/60/5e/642a44fda6dda90b5237af7a0ef1d088159c30a504852b94b0396eb62125/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", size = 395374 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/ff18c093c9e72630f6d6242e5ccb0728ef8265ba0a154b5972f89d23790a/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", size = 433189 }, + { url = "https://files.pythonhosted.org/packages/4a/6d/1166a157b227f2333f8e8ae320b6b7ea2a6a38fbe7a3563ad76dffc8608d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", size = 354849 }, + { url = "https://files.pythonhosted.org/packages/70/a4/70ea49863ea09ae4c2971f2eef58e80b757e3c0f2f618c5815bb751f7847/rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", size = 373233 }, + { url = "https://files.pythonhosted.org/packages/3b/d3/822a28152a1e7e2ba0dc5d06cf8736f4cd64b191bb6ec47fb51d1c3c5ccf/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", size = 541852 }, + { url = "https://files.pythonhosted.org/packages/c6/a5/6ef91e4425dc8b3445ff77d292fc4c5e37046462434a0423c4e0a596a8bd/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", size = 547630 }, + { url = "https://files.pythonhosted.org/packages/72/f8/d5625ee05c4e5c478954a16d9359069c66fe8ac8cd5ddf28f80d3b321837/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", size = 525766 }, + { url = "https://files.pythonhosted.org/packages/94/3c/1ff1ed6ae323b3e16fdfcdae0f0a67f373a6c3d991229dc32b499edeffb7/rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", size = 199174 }, + { url = "https://files.pythonhosted.org/packages/ec/ba/5762c0aee2403dfea14ed74b0f8a2415cfdbb21cf745d600d9a8ac952c5b/rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", size = 213543 }, + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, + { url = "https://files.pythonhosted.org/packages/37/cf/0081318cde7d7e89f802b4939ec8d079d7b59b0ee3fc168435bde31e861c/rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24", size = 319397 }, + { url = "https://files.pythonhosted.org/packages/09/4e/ea988bb4fe0f39613dd2b868fc698c19fd970e33dfe4f1bd90658f94fed3/rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29", size = 311674 }, + { url = "https://files.pythonhosted.org/packages/9e/38/d4a1f901068dfcb51183266a91bcef614f616d4d820a4dd288ccaff83cbb/rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965", size = 367607 }, + { url = "https://files.pythonhosted.org/packages/7a/e3/dc75f3f118f3dc29be962f68729b2d203be9ad52ad34b1ae907c60271302/rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1", size = 367870 }, + { url = "https://files.pythonhosted.org/packages/8d/23/2b6acbc76fddb7f89ef2382f136a7a4cf10e078e6e149508a59d7448e2e1/rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752", size = 395245 }, + { url = "https://files.pythonhosted.org/packages/53/21/9d405f690986f0215d655c2980de10f63c073e66c56bd5f4d039214d1624/rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c", size = 431699 }, + { url = "https://files.pythonhosted.org/packages/88/9d/07fedb6afebe0fe6f1c2981223ffa82c3ff3cc09ffeab8c9859b4852d7e3/rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751", size = 355123 }, + { url = "https://files.pythonhosted.org/packages/16/80/857ed7ca6dbb33805f2c8298868d029f9cf0a06f182d7058c8484b47941b/rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8", size = 373349 }, + { url = "https://files.pythonhosted.org/packages/97/69/ae242d3c59f04ca3f56d9dbd768e7cabfc093cfb9e030dfc8fbd7fadbc4d/rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e", size = 542737 }, + { url = "https://files.pythonhosted.org/packages/9f/c1/06d6c461c41e73c8187471595ce1c9a67c280d533fbd705889e6a0e9da2f/rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253", size = 547562 }, + { url = "https://files.pythonhosted.org/packages/1c/0b/918acbb2aa360822f18c6bc8672ee3c231d357f55d5e7f980d8207360742/rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a", size = 525769 }, + { url = "https://files.pythonhosted.org/packages/0e/7f/446eb1f1ed22ca855e3966e1b97e10f68f3a40578d9596a4b83323456cef/rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5", size = 199321 }, + { url = "https://files.pythonhosted.org/packages/3d/c7/ae73dfcf417fa1bb087341b670083afc3228d6a496d0d2221afd5b20d95f/rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232", size = 213048 }, + { url = "https://files.pythonhosted.org/packages/a1/55/228f6d9a8c6940c8d5e49db5e0434ffcbad669c33509ac39cb0af061b0fa/rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", size = 319496 }, + { url = "https://files.pythonhosted.org/packages/68/61/074236253586feb550954f8b4359d38eefb45bafcbbb7d2e74062a82f386/rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", size = 311837 }, + { url = "https://files.pythonhosted.org/packages/03/67/ed6c2fe076bf78296934d4356145fedf3c7c2f8d490e099bcf6f31794dc0/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", size = 367819 }, + { url = "https://files.pythonhosted.org/packages/30/25/4a9e7b89b6760ac032f375cb236e4f8e518ad1fad685c40b6a9752056d6f/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", size = 368322 }, + { url = "https://files.pythonhosted.org/packages/67/17/0255bb0e564517b53343ea672ebec9fb7ad40e9083ca09a4080fbc986bb9/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", size = 395552 }, + { url = "https://files.pythonhosted.org/packages/af/6e/77c65ccb0d7cdc39ec2be19b918a4d4fe9e2d2a1c5cab36745b36f2c1e59/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", size = 433735 }, + { url = "https://files.pythonhosted.org/packages/04/d8/e73d56b1908a6c0e3e5982365eb293170cd458cc25a19363f69c76e00fd2/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", size = 355542 }, + { url = "https://files.pythonhosted.org/packages/47/df/e72c79053b0c882b818bfd8f0ed1f1ace550bc9cdba27165cb73dddb9394/rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", size = 373644 }, + { url = "https://files.pythonhosted.org/packages/7f/00/3e16cb08c0cc6a233f0f61e4d009e3098cbe280ec975d14f28935bd15316/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", size = 543139 }, + { url = "https://files.pythonhosted.org/packages/41/71/799c6b6f6031ed535f22fcf6802601cc7f981842bd28007bb7bb4bd10b2f/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", size = 548007 }, + { url = "https://files.pythonhosted.org/packages/53/58/ad03eb6718e814fa045198c72d45d2ae60180eb48338f22c9fa34bd89964/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", size = 526102 }, + { url = "https://files.pythonhosted.org/packages/78/99/a52e5b460f2311fc8ee75ff769e8d67e76208947180eacb4f153af2d9967/rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", size = 199391 }, + { url = "https://files.pythonhosted.org/packages/0c/7d/fd42a27fe392a69faf4a5e635870fc425edcb998485ee73afbc734ecef16/rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", size = 213205 }, + { url = "https://files.pythonhosted.org/packages/06/39/bf1f664c347c946ef56cecaa896e3693d91acc741afa78ebb3fdb7aba08b/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", size = 319444 }, + { url = "https://files.pythonhosted.org/packages/c1/71/876135d3cb90d62468540b84e8e83ff4dc92052ab309bfdea7ea0b9221ad/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", size = 311699 }, + { url = "https://files.pythonhosted.org/packages/f7/da/8ccaeba6a3dda7467aebaf893de9eafd56275e2c90773c83bf15fb0b8374/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", size = 367825 }, + { url = "https://files.pythonhosted.org/packages/04/b6/02a54c47c178d180395b3c9a8bfb3b93906e08f9acf7b4a1067d27c3fae0/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", size = 369046 }, + { url = "https://files.pythonhosted.org/packages/a7/64/df4966743aa4def8727dc13d06527c8b13eb7412c1429def2d4701bee520/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", size = 395896 }, + { url = "https://files.pythonhosted.org/packages/6f/d9/7ff03ff3642c600f27ff94512bb158a8d815fea5ed4162c75a7e850d6003/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", size = 432427 }, + { url = "https://files.pythonhosted.org/packages/b8/c6/e1b886f7277b3454e55e85332e165091c19114eecb5377b88d892fd36ccf/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", size = 355403 }, + { url = "https://files.pythonhosted.org/packages/e2/62/e26bd5b944e547c7bfd0b6ca7e306bfa430f8bd298ab72a1217976a7ca8d/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", size = 374491 }, + { url = "https://files.pythonhosted.org/packages/c3/92/93c5a530898d3a5d1ce087455071ba714b77806ed9ffee4070d0c7a53b7e/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", size = 543622 }, + { url = "https://files.pythonhosted.org/packages/01/9e/d68fba289625b5d3c9d1925825d7da716fbf812bda2133ac409021d5db13/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", size = 548558 }, + { url = "https://files.pythonhosted.org/packages/bf/d6/4b2fad4898154365f0f2bd72ffd190349274a4c1d6a6f94f02a83bb2b8f1/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", size = 525753 }, + { url = "https://files.pythonhosted.org/packages/d2/ea/6f121d1802f3adae1981aea4209ea66f9d3c7f2f6d6b85ef4f13a61d17ef/rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", size = 213529 }, + { url = "https://files.pythonhosted.org/packages/0a/6f/7ab47005469f0d73dad89d29b733e3555d454a45146c30f5628242e56d33/rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", size = 320800 }, + { url = "https://files.pythonhosted.org/packages/cc/a1/bef9e0ef30f89c7516559ca7acc40e8ae70397535a0b1a4535a4a01d9ed0/rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", size = 312001 }, + { url = "https://files.pythonhosted.org/packages/31/44/9093c5dca95ee463c3669651e710af182eb6f9cd83626b15a2ebde2247b1/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", size = 369279 }, + { url = "https://files.pythonhosted.org/packages/6f/ac/0c36e067681fa3fe4c60a9422b011ec0ccc80c1e124f5210951f7982e887/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", size = 369716 }, + { url = "https://files.pythonhosted.org/packages/6b/78/8896e08625d46ea5bfdd526ee688b91eeafecbc3cf7223612c82ed77905b/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", size = 396708 }, + { url = "https://files.pythonhosted.org/packages/24/5f/d865ae460e47e46fd2b489f2aceed34439bd8f18a1ff414c299142e0e22a/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", size = 433356 }, + { url = "https://files.pythonhosted.org/packages/bd/8b/04031937ffa565021f934a9acf44bb6b1b60ea19fa9e58950b32357e85a1/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", size = 356157 }, + { url = "https://files.pythonhosted.org/packages/3a/64/1f0471b1e688704a716e07340b85f4145109359951feb08676a1f3b8cec4/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", size = 374826 }, + { url = "https://files.pythonhosted.org/packages/73/4e/082c0c5eba463e29dff1c6b49557f6ad0d6faae4b46832fa9c1e5b69b7ba/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", size = 544549 }, + { url = "https://files.pythonhosted.org/packages/cd/ee/f4af0a62d1ba912c4a3a7f5ec04350946ddd59017f3f3d1c227b20ddf558/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", size = 549245 }, + { url = "https://files.pythonhosted.org/packages/59/42/34601dc773be86a85a9ca47f68301a69fdb019aaae0c1426813f265f5ac0/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", size = 526722 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/280745d5180c9d78df6b53b6e8b65336f8b6adeb958a8fd19c749fded637/rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8", size = 214379 }, +] + [[package]] name = "ruamel-yaml" version = "0.18.6" @@ -1145,6 +2378,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/a8/4abb5a9f58f51e4b1ea386be5ab2e547035bc1ee57200d1eca2f8909a33e/ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8", size = 8618044 }, ] +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + +[[package]] +name = "setuptools" +version = "75.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b8/f21073fde99492b33ca357876430822e4800cdf522011f18041351dfa74b/setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538", size = 1348057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/ae/f19306b5a221f6a436d8f2238d5b80925004093fa3edea59835b514d9057/setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2", size = 1248506 }, +] + [[package]] name = "six" version = "1.16.0" @@ -1154,6 +2405,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -1361,6 +2621,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens", marker = "python_full_version >= '3.10'" }, + { name = "executing", marker = "python_full_version >= '3.10'" }, + { name = "pure-eval", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "standard-imghdr" version = "3.10.14" @@ -1385,6 +2659,7 @@ version = "8.4" source = { virtual = "." } dependencies = [ { name = "fake-useragent", marker = "python_full_version >= '3.10'" }, + { name = "httpx", marker = "python_full_version >= '3.10'" }, { name = "pandas-market-calendars", marker = "python_full_version >= '3.10'" }, { name = "pydantic", marker = "python_full_version >= '3.10'" }, { name = "requests", marker = "python_full_version >= '3.10'" }, @@ -1395,6 +2670,7 @@ dependencies = [ dev = [ { name = "autodoc-pydantic", marker = "python_full_version >= '3.10'" }, { name = "enum-tools", marker = "python_full_version >= '3.10'" }, + { name = "jupyter", marker = "python_full_version >= '3.10'" }, { name = "mypy", marker = "python_full_version >= '3.10'" }, { name = "pytest", marker = "python_full_version >= '3.10'" }, { name = "pytest-asyncio", marker = "python_full_version >= '3.10'" }, @@ -1410,6 +2686,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "fake-useragent", specifier = ">=1.5.1" }, + { name = "httpx", specifier = ">=0.27.2" }, { name = "pandas-market-calendars", specifier = ">=4.4.1" }, { name = "pydantic", specifier = ">=2.9.2" }, { name = "requests", specifier = ">=2.32.3" }, @@ -1420,6 +2697,7 @@ requires-dist = [ dev = [ { name = "autodoc-pydantic", specifier = ">=2.2.0" }, { name = "enum-tools", specifier = ">=0.12.0" }, + { name = "jupyter", specifier = ">=1.1.1" }, { name = "mypy", specifier = ">=1.11.2" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, @@ -1432,6 +2710,32 @@ dev = [ { name = "types-requests", specifier = ">=2.32.0.20240914" }, ] +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "python_full_version >= '3.10' and os_name != 'nt'" }, + { name = "pywinpty", marker = "python_full_version >= '3.10' and os_name == 'nt'" }, + { name = "tornado", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[package]] +name = "tinycss2" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/6f/38d2335a2b70b9982d112bb177e3dbe169746423e33f718bf5e9c7b3ddd3/tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d", size = 67360 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/4d/0db5b8a613d2a59bbc29bc5bb44a2f8070eb9ceab11c50d477502a8a0092/tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7", size = 22532 }, +] + [[package]] name = "tomli" version = "2.0.1" @@ -1450,6 +2754,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/8a/d82202c9f89eab30f9fc05380daae87d617e2ad11571ab23d7c13a29bb54/toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85", size = 56121 }, ] +[[package]] +name = "tornado" +version = "6.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/66/398ac7167f1c7835406888a386f6d0d26ee5dbf197d8a571300be57662d3/tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", size = 500623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/d9/c33be3c1a7564f7d42d87a8d186371a75fd142097076767a5c27da941fef/tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", size = 435924 }, + { url = "https://files.pythonhosted.org/packages/2e/0f/721e113a2fac2f1d7d124b3279a1da4c77622e104084f56119875019ffab/tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", size = 433883 }, + { url = "https://files.pythonhosted.org/packages/13/cf/786b8f1e6fe1c7c675e79657448178ad65e41c1c9765ef82e7f6f765c4c5/tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4", size = 437224 }, + { url = "https://files.pythonhosted.org/packages/e4/8e/a6ce4b8d5935558828b0f30f3afcb2d980566718837b3365d98e34f6067e/tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", size = 436597 }, + { url = "https://files.pythonhosted.org/packages/22/d4/54f9d12668b58336bd30defe0307e6c61589a3e687b05c366f804b7faaf0/tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", size = 436797 }, + { url = "https://files.pythonhosted.org/packages/cf/3f/2c792e7afa7dd8b24fad7a2ed3c2f24a5ec5110c7b43a64cb6095cc106b8/tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", size = 437516 }, + { url = "https://files.pythonhosted.org/packages/71/63/c8fc62745e669ac9009044b889fc531b6f88ac0f5f183cac79eaa950bb23/tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", size = 436958 }, + { url = "https://files.pythonhosted.org/packages/94/d4/f8ac1f5bd22c15fad3b527e025ce219bd526acdbd903f52053df2baecc8b/tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", size = 436882 }, + { url = "https://files.pythonhosted.org/packages/4b/3e/a8124c21cc0bbf144d7903d2a0cadab15cadaf683fa39a0f92bc567f0d4d/tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", size = 438092 }, + { url = "https://files.pythonhosted.org/packages/d9/2f/3f2f05e84a7aff787a96d5fb06821323feb370fe0baed4db6ea7b1088f32/tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", size = 438532 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240906" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/d9/9c9ec2d870af7aa9b722ce4fd5890bb55b1d18898df7f1d069cab194bb2a/types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e", size = 9169 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/4c/5c684b333135a6fb085bb5a5bdfd962937f80bec06745a88fd551e29f4d9/types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6", size = 9693 }, +] + [[package]] name = "types-pytz" version = "2024.2.0.20240913" @@ -1489,6 +2829,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, ] +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + [[package]] name = "urllib3" version = "2.2.3" @@ -1498,6 +2847,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webcolors" +version = "24.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/f8/53150a5bda7e042840b14f0236e1c0a4819d403658e3d453237983addfac/webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d", size = 42392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/33/12020ba99beaff91682b28dc0bbf0345bbc3244a4afbae7644e4fa348f23/webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a", size = 15027 }, +] + [[package]] name = "webencodings" version = "0.5.1" @@ -1507,6 +2874,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +] + [[package]] name = "websockets" version = "13.1" @@ -1599,3 +2975,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/c5/12c6859a2eaa8c53f59a647617a27f1835a226cd7106c601067c53251d98/websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678", size = 159187 }, { url = "https://files.pythonhosted.org/packages/56/27/96a5cd2626d11c8280656c6c71d8ab50fe006490ef9971ccd154e0c42cd2/websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", size = 152134 }, ] + +[[package]] +name = "widgetsnbextension" +version = "4.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, +]