From 8756097c7a23164472c2bb30a1a0dfed3af60ea7 Mon Sep 17 00:00:00 2001 From: Graeme Holliday Date: Thu, 10 Oct 2024 12:36:05 -0500 Subject: [PATCH] finish change, update tests --- .github/workflows/python-app.yml | 7 +- .gitignore | 3 - .python-version | 1 + Makefile | 6 +- pyproject.toml | 2 +- tastytrade/account.py | 115 +++++++++++++++++++----------- tastytrade/order.py | 117 +++++++++++++++++++++++-------- tastytrade/streamer.py | 21 ++++-- tastytrade/utils.py | 19 ++--- tests/test_account.py | 16 +---- tests/test_streamer.py | 2 +- uv.lock | 64 +++++------------ 12 files changed, 221 insertions(+), 152 deletions(-) create mode 100644 .python-version diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index c524206..7bc850c 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -21,11 +21,10 @@ jobs: uv sync - name: Lint with ruff run: | - uv run ruff check . - - name: Type check with mypy + uv run ruff check tastytrade/ tests/ + - name: Type check with pyright run: | - uv run mypy -p tastytrade - uv run mypy -p tests + uv run pyright tastytrade/ tests/ - name: Test with pytest run: | uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95 diff --git a/.gitignore b/.gitignore index de8a4c3..e4179d2 100644 --- a/.gitignore +++ b/.gitignore @@ -74,9 +74,6 @@ target/ .ipynb_checkpoints *.ipynb -# pyenv -.python-version - # celery beat schedule file celerybeat-schedule diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..cc1923a --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.8 diff --git a/Makefile b/Makefile index e60cee4..1e0c4cd 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,9 @@ install: uv sync lint: - uv run ruff check --fix . - uv run mypy -p tastytrade - uv run mypy -p tests + uv run ruff format tastytrade/ tests/ + uv run ruff check tastytrade/ tests/ + uv run pyright tastytrade/ tests/ test: uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95 diff --git a/pyproject.toml b/pyproject.toml index 5f158c5..19ca258 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,12 +25,12 @@ Documentation = "https://tastyworks-api.readthedocs.io/en/latest" [tool.uv] dev-dependencies = [ - "mypy>=1.11.2", "pytest>=8.3.3", "pytest-aio>=1.5.0", "pytest-cov>=5.0.0", "ruff>=0.6.9", "types-pytz>=2024.2.0.20241003", + "pyright>=1.1.384", ] [tool.setuptools.package-data] diff --git a/tastytrade/account.py b/tastytrade/account.py index 996c493..036afda 100644 --- a/tastytrade/account.py +++ b/tastytrade/account.py @@ -2,7 +2,7 @@ from decimal import Decimal from typing import Any, Dict, List, Literal, Optional, Union -from pydantic import BaseModel, computed_field, field_serializer, model_validator +from pydantic import BaseModel, model_validator from tastytrade.order import ( InstrumentType, @@ -19,7 +19,6 @@ PriceEffect, TastytradeError, TastytradeJsonDataclass, - _get_sign, _set_sign_for, today_in_new_york, validate_response, @@ -66,8 +65,7 @@ class AccountBalance(TastytradeJsonDataclass): long_cryptocurrency_value: Decimal short_cryptocurrency_value: Decimal cryptocurrency_margin_requirement: Decimal - unsettled_cryptocurrency_fiat_amount: Decimal # TODO: work with _amount suffix - unsettled_cryptocurrency_fiat_effect: PriceEffect + unsettled_cryptocurrency_fiat_amount: Decimal closed_loop_available_balance: Decimal equity_offering_margin_requirement: Decimal long_bond_value: Decimal @@ -82,22 +80,17 @@ class AccountBalance(TastytradeJsonDataclass): updated_at: datetime apex_starting_day_margin_equity: Optional[Decimal] = None buying_power_adjustment: Optional[Decimal] = None - buying_power_adjustment_effect: Optional[PriceEffect] = None time_of_day: Optional[str] = None - @computed_field - @property - def pending_cash_effect(self) -> PriceEffect: - return _get_sign(self.pending_cash) - @model_validator(mode="before") @classmethod - def validate_pending_cash(cls, data: Any) -> Any: - return _set_sign_for(data, "pending_cash") - - @field_serializer("pending_cash") - def serialize_pending_cash(self, price: Decimal) -> Decimal: - return abs(price) + def validate_price_effects(cls, data: Any) -> Any: + if isinstance(data, dict): + key = "unsettled-cryptocurrency-fiat-amount" + effect = data.get("unsettled-cryptocurrency-fiat-effect") + if effect == PriceEffect.DEBIT: + data[key] = -abs(Decimal(data[key])) + return _set_sign_for(data, ["pending_cash", "buying_power_adjustment"]) class AccountBalanceSnapshot(TastytradeJsonDataclass): @@ -132,19 +125,27 @@ class AccountBalanceSnapshot(TastytradeJsonDataclass): cash_available_to_withdraw: Decimal day_trade_excess: Decimal pending_cash: Decimal - pending_cash_effect: PriceEffect snapshot_date: date time_of_day: Optional[str] = None long_cryptocurrency_value: Optional[Decimal] = None short_cryptocurrency_value: Optional[Decimal] = None cryptocurrency_margin_requirement: Optional[Decimal] = None unsettled_cryptocurrency_fiat_amount: Optional[Decimal] = None - unsettled_cryptocurrency_fiat_effect: Optional[PriceEffect] = None closed_loop_available_balance: Optional[Decimal] = None equity_offering_margin_requirement: Optional[Decimal] = None long_bond_value: Optional[Decimal] = None bond_margin_requirement: Optional[Decimal] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + if isinstance(data, dict): + key = "unsettled-cryptocurrency-fiat-amount" + effect = data.get("unsettled-cryptocurrency-fiat-effect") + if effect == PriceEffect.DEBIT: + data[key] = -abs(Decimal(data[key])) + return _set_sign_for(data, ["pending_cash"]) + class CurrentPosition(TastytradeJsonDataclass): """ @@ -176,15 +177,22 @@ class CurrentPosition(TastytradeJsonDataclass): deliverable_type: Optional[str] = None average_yearly_market_close_price: Optional[Decimal] = None average_daily_market_close_price: Optional[Decimal] = None - realized_day_gain_effect: Optional[PriceEffect] = None realized_day_gain_date: Optional[date] = None - realized_today_effect: Optional[PriceEffect] = None realized_today_date: Optional[date] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for(data, ["realized_day_gain", "realized_today"]) + class FeesInfo(TastytradeJsonDataclass): total_fees: Decimal - total_fees_effect: PriceEffect + + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for(data, ["total_fees"]) class Lot(TastytradeJsonDataclass): @@ -210,23 +218,32 @@ class MarginReportEntry(TastytradeJsonDataclass): description: str code: str buying_power: Decimal - buying_power_effect: PriceEffect margin_calculation_type: str margin_requirement: Decimal - margin_requirement_effect: PriceEffect expected_price_range_up_percent: Optional[Decimal] = None expected_price_range_down_percent: Optional[Decimal] = None groups: Optional[List[Dict[str, Any]]] = None initial_requirement: Optional[Decimal] = None - initial_requirement_effect: Optional[PriceEffect] = None maintenance_requirement: Optional[Decimal] = None - maintenance_requirement_effect: Optional[PriceEffect] = None point_of_no_return_percent: Optional[Decimal] = None price_increase_percent: Optional[Decimal] = None price_decrease_percent: Optional[Decimal] = None underlying_symbol: Optional[str] = None underlying_type: Optional[str] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "buying_power", + "margin_requirement", + "initial_requirement", + "maintenance_requirement", + ], + ) + class MarginReport(TastytradeJsonDataclass): """ @@ -238,23 +255,32 @@ class MarginReport(TastytradeJsonDataclass): margin_calculation_type: str option_level: str margin_requirement: Decimal - margin_requirement_effect: PriceEffect maintenance_requirement: Decimal - maintenance_requirement_effect: PriceEffect margin_equity: Decimal - margin_equity_effect: PriceEffect option_buying_power: Decimal - option_buying_power_effect: PriceEffect reg_t_margin_requirement: Decimal - reg_t_margin_requirement_effect: PriceEffect reg_t_option_buying_power: Decimal - reg_t_option_buying_power_effect: PriceEffect maintenance_excess: Decimal - maintenance_excess_effect: PriceEffect last_state_timestamp: int groups: List[Union[MarginReportEntry, EmptyDict]] initial_requirement: Optional[Decimal] = None - initial_requirement_effect: Optional[PriceEffect] = None + + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "maintenance_requirement", + "margin_requirement", + "margin_equity", + "maintenance_excess", + "option_buying_power", + "reg_t_margin_requirement", + "reg_t_option_buying_power", + "initial_requirement", + ], + ) class MarginRequirement(TastytradeJsonDataclass): @@ -370,9 +396,7 @@ class Transaction(TastytradeJsonDataclass): executed_at: datetime transaction_date: date value: Decimal - value_effect: PriceEffect net_value: Decimal - net_value_effect: PriceEffect is_estimated_fee: bool symbol: Optional[str] = None instrument_type: Optional[InstrumentType] = None @@ -381,13 +405,9 @@ class Transaction(TastytradeJsonDataclass): quantity: Optional[Decimal] = None price: Optional[Decimal] = None regulatory_fees: Optional[Decimal] = None - regulatory_fees_effect: Optional[PriceEffect] = None clearing_fees: Optional[Decimal] = None - clearing_fees_effect: Optional[PriceEffect] = None commission: Optional[Decimal] = None - commission_effect: Optional[PriceEffect] = None proprietary_index_option_fees: Optional[Decimal] = None - proprietary_index_option_fees_effect: Optional[PriceEffect] = None ext_exchange_order_number: Optional[str] = None ext_global_order_number: Optional[int] = None ext_group_id: Optional[str] = None @@ -400,7 +420,6 @@ class Transaction(TastytradeJsonDataclass): leg_count: Optional[int] = None destination_venue: Optional[str] = None other_charge: Optional[Decimal] = None - other_charge_effect: Optional[PriceEffect] = None other_charge_description: Optional[str] = None reverses_id: Optional[int] = None cost_basis_reconciliation_date: Optional[date] = None @@ -408,6 +427,22 @@ class Transaction(TastytradeJsonDataclass): agency_price: Optional[Decimal] = None principal_price: Optional[Decimal] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "value", + "net_value", + "regulatory_fees", + "clearing_fees", + "proprietary_index_option_fees", + "commission", + "other_charge", + ], + ) + class Account(TastytradeJsonDataclass): """ diff --git a/tastytrade/order.py b/tastytrade/order.py index 2d001c0..3637cc5 100644 --- a/tastytrade/order.py +++ b/tastytrade/order.py @@ -1,10 +1,17 @@ from datetime import date, datetime from decimal import Decimal from enum import Enum -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union + +from pydantic import computed_field, field_serializer, model_validator from tastytrade import VERSION -from tastytrade.utils import PriceEffect, TastytradeJsonDataclass +from tastytrade.utils import ( + PriceEffect, + TastytradeJsonDataclass, + _get_sign, + _set_sign_for, +) class InstrumentType(str, Enum): @@ -225,15 +232,30 @@ class NewOrder(TastytradeJsonDataclass): source: str = f"tastyware/tastytrade:v{VERSION}" legs: List[Leg] gtc_date: Optional[date] = None + #: For a stop/stop limit order. If the latter, use price for the limit price stop_trigger: Optional[Decimal] = None - price: Optional[Decimal] = None # optional for market orders - price_effect: Optional[PriceEffect] = None + #: The price of the order; negative = debit, positive = credit + price: Optional[Decimal] = None + #: The actual notional value of the order. Only for notional market orders! value: Optional[Decimal] = None - value_effect: Optional[PriceEffect] = None partition_key: Optional[str] = None preflight_id: Optional[str] = None rules: Optional[OrderRule] = None + @computed_field + @property + def price_effect(self) -> Optional[PriceEffect]: + return _get_sign(self.price) + + @computed_field + @property + def value_effect(self) -> Optional[PriceEffect]: + return _get_sign(self.value) + + @field_serializer("price", "value") + def serialize_fields(self, field: Optional[Decimal]) -> Optional[Decimal]: + return abs(field) if field else None + class NewComplexOrder(TastytradeJsonDataclass): """ @@ -272,10 +294,8 @@ class PlacedOrder(TastytradeJsonDataclass): size: Optional[Decimal] = None id: Optional[int] = None price: Optional[Decimal] = None - price_effect: Optional[PriceEffect] = None gtc_date: Optional[date] = None value: Optional[Decimal] = None - value_effect: Optional[PriceEffect] = None stop_trigger: Optional[str] = None contingent_status: Optional[str] = None confirmation_status: Optional[str] = None @@ -296,6 +316,11 @@ class PlacedOrder(TastytradeJsonDataclass): preflight_id: Optional[Union[str, int]] = None order_rule: Optional[OrderRule] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for(data, ["price", "value"]) + class PlacedComplexOrder(TastytradeJsonDataclass): """ @@ -321,19 +346,28 @@ class BuyingPowerEffect(TastytradeJsonDataclass): """ change_in_margin_requirement: Decimal - change_in_margin_requirement_effect: PriceEffect change_in_buying_power: Decimal - change_in_buying_power_effect: PriceEffect current_buying_power: Decimal - current_buying_power_effect: PriceEffect new_buying_power: Decimal - new_buying_power_effect: PriceEffect isolated_order_margin_requirement: Decimal - isolated_order_margin_requirement_effect: PriceEffect is_spread: bool impact: Decimal effect: PriceEffect + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "change_in_margin_requirement", + "change_in_buying_power", + "current_buying_power", + "new_buying_power", + "isolated_order_margin_requirement", + ], + ) + class FeeCalculation(TastytradeJsonDataclass): """ @@ -341,15 +375,24 @@ class FeeCalculation(TastytradeJsonDataclass): """ regulatory_fees: Decimal - regulatory_fees_effect: PriceEffect clearing_fees: Decimal - clearing_fees_effect: PriceEffect commission: Decimal - commission_effect: PriceEffect proprietary_index_option_fees: Decimal - proprietary_index_option_fees_effect: PriceEffect total_fees: Decimal - total_fees_effect: PriceEffect + + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "regulatory_fees", + "clearing_fees", + "commission", + "proprietary_index_option_fees", + "total_fees", + ], + ) class PlacedOrderResponse(TastytradeJsonDataclass): @@ -400,17 +443,26 @@ class OrderChainNode(TastytradeJsonDataclass): description: str occurred_at: Optional[datetime] = None total_fees: Optional[Decimal] = None - total_fees_effect: Optional[PriceEffect] = None total_fill_cost: Optional[Decimal] = None - total_fill_cost_effect: Optional[PriceEffect] = None gcd_quantity: Optional[Decimal] = None fill_cost_per_quantity: Optional[Decimal] = None - fill_cost_per_quantity_effect: Optional[PriceEffect] = None order_fill_count: Optional[int] = None roll: Optional[bool] = None legs: Optional[List[OrderChainLeg]] = None entries: Optional[List[OrderChainEntry]] = None + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "total_fees", + "total_fill_cost", + "fill_cost_per_quantity", + ], + ) + class ComputedData(TastytradeJsonDataclass): """ @@ -420,13 +472,9 @@ class ComputedData(TastytradeJsonDataclass): open: bool updated_at: datetime total_fees: Decimal - total_fees_effect: PriceEffect total_commissions: Decimal - total_commissions_effect: PriceEffect realized_gain: Decimal - realized_gain_effect: PriceEffect realized_gain_with_fees: Decimal - realized_gain_with_fees_effect: PriceEffect winner_realized_and_closed: bool winner_realized: bool winner_realized_with_fees: bool @@ -436,16 +484,29 @@ class ComputedData(TastytradeJsonDataclass): started_at_days_to_expiration: int duration: int total_opening_cost: Decimal - total_opening_cost_effect: PriceEffect total_closing_cost: Decimal - total_closing_cost_effect: PriceEffect total_cost: Decimal - total_cost_effect: PriceEffect gcd_open_quantity: Decimal fees_missing: bool open_entries: List[OrderChainEntry] total_cost_per_unit: Optional[Decimal] = None - total_cost_per_unit_effect: Optional[PriceEffect] = None + + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "total_fees", + "total_commissions", + "realized_gain", + "realized_gain_with_fees", + "total_opening_cost", + "total_closing_cost", + "total_cost", + "total_cost_per_unit", + ], + ) class OrderChain(TastytradeJsonDataclass): diff --git a/tastytrade/streamer.py b/tastytrade/streamer.py index 516849d..939f701 100644 --- a/tastytrade/streamer.py +++ b/tastytrade/streamer.py @@ -9,6 +9,7 @@ from typing import Any, AsyncIterator, Dict, List, Optional, Union import websockets +from pydantic import model_validator from websockets import WebSocketClientProtocol from tastytrade import logger @@ -31,10 +32,9 @@ OrderChain, PlacedComplexOrder, PlacedOrder, - PriceEffect, ) from tastytrade.session import Session -from tastytrade.utils import TastytradeError, TastytradeJsonDataclass +from tastytrade.utils import TastytradeError, TastytradeJsonDataclass, _set_sign_for from tastytrade.watchlists import Watchlist CERT_STREAMER_URL = "wss://streamer.cert.tastyworks.com" @@ -73,13 +73,22 @@ class UnderlyingYearGainSummary(TastytradeJsonDataclass): symbol: str instrument_type: InstrumentType fees: Decimal - fees_effect: PriceEffect commissions: Decimal - commissions_effect: PriceEffect yearly_realized_gain: Decimal - yearly_realized_gain_effect: PriceEffect realized_lot_gain: Decimal - realized_lot_gain_effect: PriceEffect + + @model_validator(mode="before") + @classmethod + def validate_price_effects(cls, data: Any) -> Any: + return _set_sign_for( + data, + [ + "fees", + "commissions", + "yearly_realized_gain", + "realized_lot_gain", + ], + ) class SubscriptionType(str, Enum): diff --git a/tastytrade/utils.py b/tastytrade/utils.py index 8e5fc39..f3c6bb1 100644 --- a/tastytrade/utils.py +++ b/tastytrade/utils.py @@ -1,7 +1,7 @@ from datetime import date, datetime, timedelta from decimal import Decimal from enum import Enum -from typing import Any +from typing import Any, List, Optional import pandas_market_calendars as mcal # type: ignore import pytz @@ -242,20 +242,23 @@ def validate_response(response: Response) -> None: raise TastytradeError(error_message) -def _get_sign(value: Decimal) -> PriceEffect: +def _get_sign(value: Optional[Decimal]) -> Optional[PriceEffect]: + if not value: + return None return PriceEffect.DEBIT if value < 0 else PriceEffect.CREDIT -def _set_sign_for(data: Any, property: str) -> Any: +def _set_sign_for(data: Any, properties: List[str]) -> Any: """ Handles setting the sign of a number using the associated "-effect" field. :param data: the raw, unprocessed model object - :param property: the name of the number field + :param properties: the name of the number fields to set """ if isinstance(data, dict): - key = _dasherize(property) - effect = data.get(f"{key}-effect") - if effect == PriceEffect.DEBIT: - data[key] = -abs(Decimal(data[key])) + for property in properties: + key = _dasherize(property) + effect = data.get(f"{key}-effect") + if effect == PriceEffect.DEBIT: + data[key] = -abs(Decimal(data[key])) return data diff --git a/tests/test_account.py b/tests/test_account.py index 2956422..13255f6 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -12,7 +12,6 @@ OrderAction, OrderTimeInForce, OrderType, - PriceEffect, ) @@ -157,8 +156,7 @@ def new_order(session): time_in_force=OrderTimeInForce.DAY, order_type=OrderType.LIMIT, legs=[leg], - price=Decimal(2), - price_effect=PriceEffect.DEBIT, + price=Decimal(-2), ) @@ -191,14 +189,12 @@ def test_place_oco_order(session, account): order_type=OrderType.LIMIT, legs=[closing], price=Decimal("100"), # will never fill - price_effect=PriceEffect.CREDIT, ), NewOrder( time_in_force=OrderTimeInForce.GTC, order_type=OrderType.STOP, legs=[closing], stop_trigger=Decimal("1.5"), # will never fill - price_effect=PriceEffect.CREDIT, ), ] ) @@ -218,8 +214,7 @@ def test_place_otoco_order(session, account): time_in_force=OrderTimeInForce.DAY, order_type=OrderType.LIMIT, legs=[opening], - price=Decimal("2"), # won't fill - price_effect=PriceEffect.DEBIT, + price=Decimal("-2"), # won't fill ), orders=[ NewOrder( @@ -227,14 +222,12 @@ def test_place_otoco_order(session, account): order_type=OrderType.LIMIT, legs=[closing], 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("1.5"), # won't fill - price_effect=PriceEffect.CREDIT, ), ], ) @@ -282,8 +275,7 @@ async def test_place_complex_order_async(session, account): time_in_force=OrderTimeInForce.DAY, order_type=OrderType.LIMIT, legs=[opening], - price=Decimal("2"), # won't fill - price_effect=PriceEffect.DEBIT, + price=Decimal("-2"), # won't fill ), orders=[ NewOrder( @@ -291,14 +283,12 @@ async def test_place_complex_order_async(session, account): order_type=OrderType.LIMIT, legs=[closing], 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("1.5"), # won't fill - price_effect=PriceEffect.CREDIT, ), ], ) diff --git a/tests/test_streamer.py b/tests/test_streamer.py index ea6279a..75a4c37 100644 --- a/tests/test_streamer.py +++ b/tests/test_streamer.py @@ -27,4 +27,4 @@ async def test_dxlink_streamer(session): async for _ in streamer.listen(EventType.QUOTE): break await streamer.unsubscribe_candle(subs[0], "1d") - await streamer.unsubscribe(EventType.QUOTE, subs[1]) + await streamer.unsubscribe(EventType.QUOTE, subs) diff --git a/uv.lock b/uv.lock index ae0ec42..cc5cf22 100644 --- a/uv.lock +++ b/uv.lock @@ -229,51 +229,12 @@ wheels = [ ] [[package]] -name = "mypy" -version = "1.11.2" +name = "nodeenv" +version = "1.9.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/86/5d7cbc4974fd564550b80fbb8103c05501ea11aa7835edf3351d90095896/mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", size = 3078806 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/cd/815368cd83c3a31873e5e55b317551500b12f2d1d7549720632f32630333/mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", size = 10939401 }, - { url = "https://files.pythonhosted.org/packages/f1/27/e18c93a195d2fad75eb96e1f1cbc431842c332e8eba2e2b77eaf7313c6b7/mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", size = 10111697 }, - { url = "https://files.pythonhosted.org/packages/dc/08/cdc1fc6d0d5a67d354741344cc4aa7d53f7128902ebcbe699ddd4f15a61c/mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", size = 12500508 }, - { url = "https://files.pythonhosted.org/packages/64/12/aad3af008c92c2d5d0720ea3b6674ba94a98cdb86888d389acdb5f218c30/mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", size = 13020712 }, - { url = "https://files.pythonhosted.org/packages/03/e6/a7d97cc124a565be5e9b7d5c2a6ebf082379ffba99646e4863ed5bbcb3c3/mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", size = 9567319 }, - { url = "https://files.pythonhosted.org/packages/e2/aa/cc56fb53ebe14c64f1fe91d32d838d6f4db948b9494e200d2f61b820b85d/mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", size = 10859630 }, - { url = "https://files.pythonhosted.org/packages/04/c8/b19a760fab491c22c51975cf74e3d253b8c8ce2be7afaa2490fbf95a8c59/mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", size = 10037973 }, - { url = "https://files.pythonhosted.org/packages/88/57/7e7e39f2619c8f74a22efb9a4c4eff32b09d3798335625a124436d121d89/mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", size = 12416659 }, - { url = "https://files.pythonhosted.org/packages/fc/a6/37f7544666b63a27e46c48f49caeee388bf3ce95f9c570eb5cfba5234405/mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", size = 12897010 }, - { url = "https://files.pythonhosted.org/packages/84/8b/459a513badc4d34acb31c736a0101c22d2bd0697b969796ad93294165cfb/mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", size = 9562873 }, - { url = "https://files.pythonhosted.org/packages/35/3a/ed7b12ecc3f6db2f664ccf85cb2e004d3e90bec928e9d7be6aa2f16b7cdf/mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", size = 10990335 }, - { url = "https://files.pythonhosted.org/packages/04/e4/1a9051e2ef10296d206519f1df13d2cc896aea39e8683302f89bf5792a59/mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", size = 10007119 }, - { url = "https://files.pythonhosted.org/packages/f3/3c/350a9da895f8a7e87ade0028b962be0252d152e0c2fbaafa6f0658b4d0d4/mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", size = 12506856 }, - { url = "https://files.pythonhosted.org/packages/b6/49/ee5adf6a49ff13f4202d949544d3d08abb0ea1f3e7f2a6d5b4c10ba0360a/mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", size = 12952066 }, - { url = "https://files.pythonhosted.org/packages/27/c0/b19d709a42b24004d720db37446a42abadf844d5c46a2c442e2a074d70d9/mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", size = 9664000 }, - { url = "https://files.pythonhosted.org/packages/42/ad/5a8567700410f8aa7c755b0ebd4cacff22468cbc5517588773d65075c0cb/mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", size = 10876550 }, - { url = "https://files.pythonhosted.org/packages/1b/bc/9fc16ea7a27ceb93e123d300f1cfe27a6dd1eac9a8beea4f4d401e737e9d/mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", size = 10068086 }, - { url = "https://files.pythonhosted.org/packages/cd/8f/a1e460f1288405a13352dad16b24aba6dce4f850fc76510c540faa96eda3/mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", size = 12459214 }, - { url = "https://files.pythonhosted.org/packages/c7/74/746b31aef7cc7512dab8bdc2311ef88d63fadc1c453a09c8cab7e57e59bf/mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", size = 12962942 }, - { url = "https://files.pythonhosted.org/packages/28/a4/7fae712240b640d75bb859294ad4776b9960b3216ccb7fa747f578e6c632/mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", size = 9545616 }, - { url = "https://files.pythonhosted.org/packages/16/64/bb5ed751487e2bea0dfaa6f640a7e3bb88083648f522e766d5ef4a76f578/mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", size = 10937294 }, - { url = "https://files.pythonhosted.org/packages/a9/a3/67a0069abed93c3bf3b0bebb8857e2979a02828a4a3fd82f107f8f1143e8/mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", size = 10107707 }, - { url = "https://files.pythonhosted.org/packages/2f/4d/0379daf4258b454b1f9ed589a9dabd072c17f97496daea7b72fdacf7c248/mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d", size = 12498367 }, - { url = "https://files.pythonhosted.org/packages/3b/dc/3976a988c280b3571b8eb6928882dc4b723a403b21735a6d8ae6ed20e82b/mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", size = 13018014 }, - { url = "https://files.pythonhosted.org/packages/83/84/adffc7138fb970e7e2a167bd20b33bb78958370179853a4ebe9008139342/mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", size = 9568056 }, - { url = "https://files.pythonhosted.org/packages/42/3a/bdf730640ac523229dd6578e8a581795720a9321399de494374afc437ec5/mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", size = 2619625 }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] [[package]] @@ -504,6 +465,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/83/2e585d06d49e0320050b3d7d8ae0dfbd1459e976ff9f4b4d8bcca983d474/pyluach-2.2.0-py3-none-any.whl", hash = "sha256:d1eb49d6292087e9290f4661ae01b60c8c933704ec8c9cef82673b349ff96adf", size = 25037 }, ] +[[package]] +name = "pyright" +version = "1.1.384" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/00/a23114619f9d005f4b0f35e037c76cee029174d090a6f73a355749c74f4a/pyright-1.1.384.tar.gz", hash = "sha256:25e54d61f55cbb45f1195ff89c488832d7a45d59f3e132f178fdf9ef6cafc706", size = 21956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/4a/e7f4d71d194ba675f3577d11eebe4e17a592c4d1c3f9986d4b321ba3c809/pyright-1.1.384-py3-none-any.whl", hash = "sha256:f0b6f4db2da38f27aeb7035c26192f034587875f751b847e9ad42ed0c704ac9e", size = 18578 }, +] + [[package]] name = "pytest" version = "8.3.3" @@ -623,7 +597,7 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "mypy" }, + { name = "pyright" }, { name = "pytest" }, { name = "pytest-aio" }, { name = "pytest-cov" }, @@ -641,7 +615,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = ">=1.11.2" }, + { name = "pyright", specifier = ">=1.1.384" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-aio", specifier = ">=1.5.0" }, { name = "pytest-cov", specifier = ">=5.0.0" },