afp-sdk 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
afp/bindings/__init__.py CHANGED
@@ -8,6 +8,7 @@ from .facade import (
8
8
  MarginAccountRegistry,
9
9
  OracleProvider,
10
10
  ProductRegistry,
11
+ SystemViewer,
11
12
  )
12
13
  from .margin_account import MarginAccount, PositionData, Settlement
13
14
  from .product_registry import (
@@ -16,6 +17,7 @@ from .product_registry import (
16
17
  ProductMetadata,
17
18
  ProductState,
18
19
  )
20
+ from .system_viewer import UserMarginAccountData
19
21
  from .trading_protocol import TradingProtocol
20
22
 
21
23
  __all__ = (
@@ -25,6 +27,7 @@ __all__ = (
25
27
  "MarginAccountRegistry",
26
28
  "OracleProvider",
27
29
  "ProductRegistry",
30
+ "SystemViewer",
28
31
  "TradingProtocol",
29
32
  # Structures
30
33
  "AuctionConfig",
@@ -42,6 +45,7 @@ __all__ = (
42
45
  "ProductMetadata",
43
46
  "Settlement",
44
47
  "Trade",
48
+ "UserMarginAccountData",
45
49
  # Enumerations
46
50
  "ProductState",
47
51
  "Side",
@@ -2,7 +2,6 @@
2
2
 
3
3
  # This module has been generated using pyabigen v0.2.16
4
4
 
5
- import enum
6
5
  import typing
7
6
  from dataclasses import dataclass
8
7
 
@@ -11,12 +10,7 @@ import hexbytes
11
10
  import web3
12
11
  from web3.contract import contract
13
12
 
14
-
15
- class Side(enum.IntEnum):
16
- """Port of `enum Side` on the AuctioneerFacet contract."""
17
-
18
- BID = 0
19
- ASK = 1
13
+ from .clearing_facet import Side
20
14
 
21
15
 
22
16
  @dataclass
@@ -11,6 +11,9 @@ import hexbytes
11
11
  import web3
12
12
  from web3.contract import contract
13
13
 
14
+ if typing.TYPE_CHECKING:
15
+ from .auctioneer_facet import AuctionConfig
16
+
14
17
 
15
18
  class Side(enum.IntEnum):
16
19
  """Port of `enum Side` on the ClearingFacet contract."""
@@ -19,14 +22,6 @@ class Side(enum.IntEnum):
19
22
  ASK = 1
20
23
 
21
24
 
22
- @dataclass
23
- class AuctionConfig:
24
- """Port of `struct AuctionConfig` on the IAuctioneer contract."""
25
-
26
- restoration_buffer: int
27
- liquidation_duration: int
28
-
29
-
30
25
  @dataclass
31
26
  class ClearingConfig:
32
27
  """Port of `struct ClearingConfig` on the IClearing contract."""
@@ -38,7 +33,7 @@ class ClearingConfig:
38
33
  class Config:
39
34
  """Port of `struct Config` on the IClearing contract."""
40
35
 
41
- auction_config: AuctionConfig
36
+ auction_config: "AuctionConfig"
42
37
  clearing_config: ClearingConfig
43
38
 
44
39
 
afp/bindings/facade.py CHANGED
@@ -12,6 +12,7 @@ from . import (
12
12
  mark_price_tracker_facet,
13
13
  oracle_provider,
14
14
  product_registry,
15
+ system_viewer,
15
16
  )
16
17
 
17
18
  # In order to include a facet in the ClearingDiamond facade:
@@ -85,3 +86,15 @@ class ProductRegistry(product_registry.ProductRegistry):
85
86
 
86
87
  def __init__(self, w3: Web3):
87
88
  super().__init__(w3, config.PRODUCT_REGISTRY_ADDRESS)
89
+
90
+
91
+ class SystemViewer(system_viewer.SystemViewer):
92
+ """SystemViewer contract binding.
93
+
94
+ Parameters
95
+ ----------
96
+ w3 : web3.Web3
97
+ """
98
+
99
+ def __init__(self, w3: Web3):
100
+ super().__init__(w3, config.SYSTEM_VIEWER_ADDRESS)
@@ -32,6 +32,16 @@ class FinalSettlementFacet:
32
32
  abi=ABI,
33
33
  )
34
34
 
35
+ @property
36
+ def FSPFinalized(self) -> contract.ContractEvent:
37
+ """Binding for `event FSPFinalized` on the FinalSettlementFacet contract."""
38
+ return self._contract.events.FSPFinalized
39
+
40
+ @property
41
+ def FinalSettlementCloseout(self) -> contract.ContractEvent:
42
+ """Binding for `event FinalSettlementCloseout` on the FinalSettlementFacet contract."""
43
+ return self._contract.events.FinalSettlementCloseout
44
+
35
45
  def closeout_fee_rate(
36
46
  self,
37
47
  ) -> int:
@@ -210,6 +220,50 @@ ABI = typing.cast(
210
220
  "name": "ProductNotInFinalSettlement",
211
221
  "type": "error",
212
222
  },
223
+ {
224
+ "anonymous": False,
225
+ "inputs": [
226
+ {
227
+ "indexed": True,
228
+ "internalType": "bytes32",
229
+ "name": "productID",
230
+ "type": "bytes32",
231
+ },
232
+ {
233
+ "indexed": False,
234
+ "internalType": "uint256",
235
+ "name": "fsp",
236
+ "type": "uint256",
237
+ },
238
+ ],
239
+ "name": "FSPFinalized",
240
+ "type": "event",
241
+ },
242
+ {
243
+ "anonymous": False,
244
+ "inputs": [
245
+ {
246
+ "indexed": True,
247
+ "internalType": "bytes32",
248
+ "name": "productID",
249
+ "type": "bytes32",
250
+ },
251
+ {
252
+ "indexed": False,
253
+ "internalType": "uint256",
254
+ "name": "accountLength",
255
+ "type": "uint256",
256
+ },
257
+ {
258
+ "indexed": False,
259
+ "internalType": "address",
260
+ "name": "closedBy",
261
+ "type": "address",
262
+ },
263
+ ],
264
+ "name": "FinalSettlementCloseout",
265
+ "type": "event",
266
+ },
213
267
  {
214
268
  "inputs": [],
215
269
  "name": "CLOSEOUT_FEE_RATE",
@@ -2,7 +2,6 @@
2
2
 
3
3
  # This module has been generated using pyabigen v0.2.16
4
4
 
5
- import enum
6
5
  import typing
7
6
  from dataclasses import dataclass
8
7
 
@@ -11,76 +10,13 @@ import hexbytes
11
10
  import web3
12
11
  from web3.contract import contract
13
12
 
14
-
15
- class ProductState(enum.IntEnum):
16
- """Port of `enum ProductState` on the SystemViewer contract."""
17
-
18
- NOT_EXIST = 0
19
- PENDING = 1
20
- LIVE = 2
21
- TRADEOUT = 3
22
- FINAL_SETTLEMENT = 4
23
- EXPIRED = 5
24
-
25
-
26
- @dataclass
27
- class Settlement:
28
- """Port of `struct Settlement` on the IMarginAccount contract."""
29
-
30
- position_id: hexbytes.HexBytes
31
- quantity: int
32
- price: int
33
-
34
-
35
- @dataclass
36
- class PositionData:
37
- """Port of `struct PositionData` on the IMarginAccount contract."""
38
-
39
- position_id: hexbytes.HexBytes
40
- quantity: int
41
- cost_basis: int
42
- maintenance_margin: int
43
- pnl: int
44
-
45
-
46
- @dataclass
47
- class ProductMetadata:
48
- """Port of `struct ProductMetadata` on the IProductRegistry contract."""
49
-
50
- builder: eth_typing.ChecksumAddress
51
- symbol: str
52
- description: str
53
-
54
-
55
- @dataclass
56
- class OracleSpecification:
57
- """Port of `struct OracleSpecification` on the IProductRegistry contract."""
58
-
59
- oracle_address: eth_typing.ChecksumAddress
60
- fsv_decimals: int
61
- fsp_alpha: int
62
- fsp_beta: int
63
- fsv_calldata: hexbytes.HexBytes
64
-
65
-
66
- @dataclass
67
- class Product:
68
- """Port of `struct Product` on the IProductRegistry contract."""
69
-
70
- metadata: ProductMetadata
71
- oracle_spec: OracleSpecification
72
- price_quotation: str
73
- collateral_asset: eth_typing.ChecksumAddress
74
- start_time: int
75
- earliest_fsp_submission_time: int
76
- unit_value: int
77
- initial_margin_requirement: int
78
- maintenance_margin_requirement: int
79
- offer_price_buffer: int
80
- auction_bounty: int
81
- tradeout_interval: int
82
- tick_size: int
83
- extended_metadata: str
13
+ from .margin_account import Settlement, PositionData
14
+ from .product_registry import (
15
+ OracleSpecification,
16
+ Product,
17
+ ProductMetadata,
18
+ ProductState,
19
+ )
84
20
 
85
21
 
86
22
  @dataclass
@@ -2,61 +2,14 @@
2
2
 
3
3
  # This module has been generated using pyabigen v0.2.16
4
4
 
5
- import enum
6
5
  import typing
7
- from dataclasses import dataclass
8
6
 
9
7
  import eth_typing
10
8
  import hexbytes
11
9
  import web3
12
10
  from web3.contract import contract
13
11
 
14
-
15
- class Side(enum.IntEnum):
16
- """Port of `enum Side` on the TradingProtocol contract."""
17
-
18
- BID = 0
19
- ASK = 1
20
-
21
-
22
- @dataclass
23
- class IntentData:
24
- """Port of `struct IntentData` on the IClearing contract."""
25
-
26
- nonce: int
27
- trading_protocol_id: eth_typing.ChecksumAddress
28
- product_id: hexbytes.HexBytes
29
- limit_price: int
30
- quantity: int
31
- max_trading_fee_rate: int
32
- good_until: int
33
- side: Side
34
-
35
-
36
- @dataclass
37
- class Intent:
38
- """Port of `struct Intent` on the IClearing contract."""
39
-
40
- margin_account_id: eth_typing.ChecksumAddress
41
- intent_account_id: eth_typing.ChecksumAddress
42
- hash: hexbytes.HexBytes
43
- data: IntentData
44
- signature: hexbytes.HexBytes
45
-
46
-
47
- @dataclass
48
- class Trade:
49
- """Port of `struct Trade` on the IClearing contract."""
50
-
51
- product_id: hexbytes.HexBytes
52
- protocol_id: eth_typing.ChecksumAddress
53
- trade_id: int
54
- price: int
55
- timestamp: int
56
- accounts: typing.List[eth_typing.ChecksumAddress]
57
- quantities: typing.List[int]
58
- fee_rates: typing.List[int]
59
- intents: typing.List[Intent]
12
+ from .clearing_facet import Trade
60
13
 
61
14
 
62
15
  class TradingProtocol:
afp/config.py CHANGED
@@ -9,30 +9,34 @@ FEE_RATE_MULTIPLIER = 10**6
9
9
  FULL_PRECISION_MULTIPLIER = 10**18
10
10
 
11
11
  USER_AGENT = "afp-sdk"
12
+ DEFAULT_EXCHANGE_API_VERSION = 1
12
13
  EXCHANGE_URL = os.getenv(
13
- "AFP_EXCHANGE_URL", "https://afp-exchange-staging.up.railway.app"
14
+ "AFP_EXCHANGE_URL", "https://afp-exchange-stable.up.railway.app"
14
15
  )
15
16
 
16
- CHAIN_ID = int(os.getenv("AFP_CHAIN_ID", 65010004))
17
+ CHAIN_ID = int(os.getenv("AFP_CHAIN_ID", 65000000))
17
18
 
18
19
  CLEARING_DIAMOND_ADDRESS = Web3.to_checksum_address(
19
20
  os.getenv(
20
- "AFP_CLEARING_DIAMOND_ADDRESS", "0xB894bFf368Bf1EA89c18612670B7E072866a5264"
21
+ "AFP_CLEARING_DIAMOND_ADDRESS", "0x5B5411F1548254d25360d71FE40cFc1cC983B2A2"
21
22
  )
22
23
  )
23
24
  MARGIN_ACCOUNT_REGISTRY_ADDRESS = Web3.to_checksum_address(
24
25
  os.getenv(
25
26
  "AFP_MARGIN_ACCOUNT_REGISTRY_ADDRESS",
26
- "0xDA71FdE0E7cfFf445e848EAdB3B2255B68Ed6ef6",
27
+ "0x99f4FA9Cdce7AD227eB84907936a8FeF2095D846",
27
28
  )
28
29
  )
29
30
  ORACLE_PROVIDER_ADDRESS = Web3.to_checksum_address(
30
31
  os.getenv(
31
- "AFP_ORACLE_PROVIDER_ADDRESS", "0x626da921088a5A00C75C208Decbb60E816488481"
32
+ "AFP_ORACLE_PROVIDER_ADDRESS", "0xF2A2A27da33D30B4BF38D7e186E7B0b1e964e55c"
32
33
  )
33
34
  )
34
35
  PRODUCT_REGISTRY_ADDRESS = Web3.to_checksum_address(
35
36
  os.getenv(
36
- "AFP_PRODUCT_REGISTRY_ADDRESS", "0x9b92EAC112c996a513e515Cf8c3BDd83619F730D"
37
+ "AFP_PRODUCT_REGISTRY_ADDRESS", "0x86B3829471929B115367DA0958f56A6AB844b08e"
37
38
  )
38
39
  )
40
+ SYSTEM_VIEWER_ADDRESS = Web3.to_checksum_address(
41
+ os.getenv("AFP_SYSTEM_VIEWER_ADDRESS", "0xfF2DFcC44a95cce96E03EfC33C65c8Be671Bae5B")
42
+ )
afp/exchange.py CHANGED
@@ -116,7 +116,13 @@ class ExchangeClient:
116
116
  yield MarketDepthData.model_validate_json(line)
117
117
 
118
118
  def _send_request(
119
- self, method: str, endpoint: str, stream: bool = False, **kwargs: Any
119
+ self,
120
+ method: str,
121
+ endpoint: str,
122
+ *,
123
+ stream: bool = False,
124
+ api_version: int = config.DEFAULT_EXCHANGE_API_VERSION,
125
+ **kwargs: Any,
120
126
  ) -> Response:
121
127
  kwargs["headers"] = {
122
128
  "Content-Type": "application/json",
@@ -126,7 +132,10 @@ class ExchangeClient:
126
132
 
127
133
  try:
128
134
  response = self._session.request(
129
- method, f"{config.EXCHANGE_URL}{endpoint}", stream=stream, **kwargs
135
+ method,
136
+ f"{config.EXCHANGE_URL}/v{api_version}{endpoint}",
137
+ stream=stream,
138
+ **kwargs,
130
139
  )
131
140
  except requests.exceptions.RequestException as request_exception:
132
141
  raise ExchangeError(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afp-sdk
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Autonomous Futures Protocol Python SDK
5
5
  Keywords: autonity,web3,trading,crypto,prediction,forecast,markets
6
6
  License-Expression: MIT
@@ -38,7 +38,7 @@ pip install afp-sdk
38
38
 
39
39
  The `afp` package consists of the following:
40
40
 
41
- - `afp` top-level module: High-level API for interacting with the Clearing
41
+ - `afp` top-level module: High-level API for interacting with the AFP Clearing
42
42
  System and the AutEx exchange.
43
43
  - `afp.bindings` submodule: Low-level API that provides typed Python bindings
44
44
  for the Clearing System smart contracts.
@@ -47,7 +47,7 @@ The `afp` package consists of the following:
47
47
 
48
48
  ### Preparation
49
49
 
50
- In order to use the AFP system, traders need to prepare the following:
50
+ In order to trade in the AFP system, traders need to prepare the following:
51
51
 
52
52
  - The ID of a product to be traded.
53
53
  - The address of the product's collateral token.
@@ -55,7 +55,7 @@ In order to use the AFP system, traders need to prepare the following:
55
55
  balance in ATN (for paying gas fee) and in the product's collateral token.
56
56
  - An Autonity account for signing intents. The two accounts can be the same.
57
57
  - The address of an Autonity RPC provider. They can be found on
58
- [Chainlist](https://chainlist.org/?search=autonity&testnets=true).
58
+ [Chainlist](https://chainlist.org/?search=autonity).
59
59
 
60
60
  We can store those in the following constants (using random example IDs):
61
61
 
@@ -169,6 +169,22 @@ print(fills)
169
169
 
170
170
  See further code examples in the [examples](./examples/) directory.
171
171
 
172
+ ## Configuration
173
+
174
+ By default the SDK communicates with the AFP Clearing System contracts on
175
+ Autonity Mainnet, and the AutEx Exchange. Connection parameters can be
176
+ overridden with the following environment variables:
177
+
178
+ ```sh
179
+ AFP_EXCHANGE_URL=
180
+ AFP_CHAIN_ID=
181
+ AFP_CLEARING_DIAMOND_ADDRESS=
182
+ AFP_MARGIN_ACCOUNT_REGISTRY_ADDRESS=
183
+ AFP_ORACLE_PROVIDER_ADDRESS=
184
+ AFP_PRODUCT_REGISTRY_ADDRESS=
185
+ AFP_SYSTEM_VIEWER_ADDRESS=
186
+ ```
187
+
172
188
  ## Development
173
189
 
174
190
  The package uses [`uv`](https://docs.astral.sh/uv/) as project manager.
@@ -6,30 +6,30 @@ afp/api/builder.py,sha256=mde8XemEMKL76KgEd5rxg7RLBS6lyIfNOEGmE1_iK0M,7114
6
6
  afp/api/clearing.py,sha256=nMVjayFkf0XYYP3aKAdgR6NaFSG4Cl_QsEuADRuIO7M,12922
7
7
  afp/api/liquidation.py,sha256=CtME5524VVW9mXMMpORyUP205sTVqpLnHIPoTy2MGa8,5439
8
8
  afp/api/trading.py,sha256=HjskW4c13CwAB-AwyIsSBcvyXKKEJk7a9768tqcTzUE,10648
9
- afp/bindings/__init__.py,sha256=5PDdlk0aClsTt2NgbVqP-R-z2QQp99IfXaSB13Pmf5c,1155
10
- afp/bindings/auctioneer_facet.py,sha256=99FmK35trfOIJGqJqyc1NnpG0KavhH0_jNufszZ9VhA,24013
9
+ afp/bindings/__init__.py,sha256=_n9xoogYi8AAlSx_PE-wjnwP1ujVyDUwoRM0BSm243U,1271
10
+ afp/bindings/auctioneer_facet.py,sha256=4p906zdU2lUsqpWlsiLE3dlxTPrlNpqk8DtjiQUWJ8M,23919
11
11
  afp/bindings/bankruptcy_facet.py,sha256=Yg8sLhDU0crsL1ISSy4eoeQEP5jrAuiWBBlco5MtgPc,11370
12
- afp/bindings/clearing_facet.py,sha256=GLBtquybV1FRvDNlYXgpcvlpgw9pvZ5UJbLOYKayPMU,34168
12
+ afp/bindings/clearing_facet.py,sha256=DzRzzP9qXQ7kJMcMRwoLwLJ7S-w-2mid_CbF1fOVHaE,34081
13
13
  afp/bindings/erc20.py,sha256=-jw7ohcEbUL7g3PpK3-a5_7ClUDCDHt1bezm-w7exzo,10822
14
- afp/bindings/facade.py,sha256=FDKY6XLYsK6suDmWSsP5cek8y4ycY4soo9R4udvG1MM,2019
15
- afp/bindings/final_settlement_facet.py,sha256=gYJXiUyZ_-L8i8LHnPUmB3mBhLxHr8GAtKurwsCypa0,7699
14
+ afp/bindings/facade.py,sha256=nZmAp7r26iszxtNyE5imdl_PKbG6vii_3oYJ3Q69iow,2278
15
+ afp/bindings/final_settlement_facet.py,sha256=1U5F3WFvf4GUml3gb90YF9RZK_xCwBsX2zRQ8sWSkR0,9470
16
16
  afp/bindings/margin_account.py,sha256=ffkpf5HEXQCxiWuLpgDcHVAiaUVvbKU1Kt0K8tvgZcA,42461
17
17
  afp/bindings/margin_account_registry.py,sha256=mx9sAU6HuC09d-LAf9bzP57HPLa3M6qXpN7Lj-uiXSc,18800
18
18
  afp/bindings/mark_price_tracker_facet.py,sha256=vnVsAmpts_2Sv9Jmx3JlON8X8FJd9D2jOFhqSwXIA7A,2830
19
19
  afp/bindings/oracle_provider.py,sha256=CMpVAXdUuTP_W9ZE297B-m9MjuW90yCOhNLMVl3kCf0,15863
20
20
  afp/bindings/product_registry.py,sha256=HFWwbFKvXk0A2dZB6KBa0Ul8-vX9uvvtGj0dhRL9UUw,43701
21
- afp/bindings/system_viewer.py,sha256=v5yeogqMsRTjG3aRUd8O1oiQZ4ErEaOTfcctTfmcRu8,42767
22
- afp/bindings/trading_protocol.py,sha256=FSfK4HejLQUb7oNiGmyvyqbkUAG379OXNK2F5SG1Qzg,40642
23
- afp/config.py,sha256=3S7k49n4ZaRko3FX6Hw95r5L-sw2XiAjWtqYiX3Omx0,1017
21
+ afp/bindings/system_viewer.py,sha256=Kwi-Kz2oKh5qWy17N0D2LyrAbkKz_sW9OxeWh6VX_j8,41347
22
+ afp/bindings/trading_protocol.py,sha256=ZloF3REbjFq9v0UGVsM0_Lk0EhfWJKdeJ0PzVEnyZo0,39573
23
+ afp/config.py,sha256=GbfvtsRkRDas4L-s1E-9AB7RitrN9JRQ79R5zfv6Ha0,1190
24
24
  afp/decorators.py,sha256=Ustc15RbXGYIEqDb9lXnd-bdhZJ8ZrztN4h_vjkLsG0,2692
25
25
  afp/enums.py,sha256=4dglBx4Amcu0GrlJn0cIj-rp9uDHZGfDEP_vMAO02xo,485
26
26
  afp/exceptions.py,sha256=RE8IE6akDgbar2PobdpOiBdGZZCYkb4jWf1ozJmdLaI,342
27
- afp/exchange.py,sha256=l8Cartp0R4PPe6O_Wu10YDw-5P_PbaAkd7w-W5QrlWY,5272
27
+ afp/exchange.py,sha256=K_SkVyRvs7ebW82BGbxND1r5UHzb4_TNRfkOOIG-Dvs,5444
28
28
  afp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  afp/schemas.py,sha256=8nlNPq6t_jmYEpa_WWhOIr_yvpQv5tj86uvpGTNHWZY,4996
30
30
  afp/signing.py,sha256=7fohnAvoiDA4ems_3OcVnr16OmYLSg9NnnCSzDplg6s,2235
31
31
  afp/validators.py,sha256=m61ZpP385CxsdDGJavPkXKTXnJAHKLCzedoZ8mL6daI,1818
32
- afp_sdk-0.2.1.dist-info/licenses/LICENSE,sha256=ZdaKItgc2ppfqta2OJV0oHpSJiK87PUxmUkUo-_0SB8,1065
33
- afp_sdk-0.2.1.dist-info/WHEEL,sha256=NHRAbdxxzyL9K3IO2LjmlNqKSyPZnKv2BD16YYVKo18,79
34
- afp_sdk-0.2.1.dist-info/METADATA,sha256=muk0O2vQMBGviKdMsUXxCwmwKUWJqviVSkPuz4y1yMo,5618
35
- afp_sdk-0.2.1.dist-info/RECORD,,
32
+ afp_sdk-0.3.0.dist-info/licenses/LICENSE,sha256=ZdaKItgc2ppfqta2OJV0oHpSJiK87PUxmUkUo-_0SB8,1065
33
+ afp_sdk-0.3.0.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
34
+ afp_sdk-0.3.0.dist-info/METADATA,sha256=YJmCZfdcfYbJxL2DTDKNuoiPZW0SEb13aOVgxtRgzoc,6026
35
+ afp_sdk-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.14
2
+ Generator: uv 0.8.15
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any