opengradient 0.5.8__py3-none-any.whl → 0.5.10__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.
opengradient/defaults.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Default variables
2
- DEFAULT_RPC_URL = "https://eth-devnet.opengradient.ai"
2
+ DEFAULT_RPC_URL = "https://ogevmdevnet.opengradient.ai"
3
3
  DEFAULT_API_URL = "https://sdk-devnet.opengradient.ai"
4
4
  DEFAULT_OG_FAUCET_URL = "https://faucet.opengradient.ai/?address="
5
5
  DEFAULT_HUB_SIGNUP_URL = "https://hub.opengradient.ai/signup"
@@ -9,4 +9,6 @@ DEFAULT_BLOCKCHAIN_EXPLORER = "https://explorer.opengradient.ai/tx/"
9
9
  DEFAULT_IMAGE_GEN_HOST = "18.217.25.69"
10
10
  DEFAULT_IMAGE_GEN_PORT = 5125
11
11
  DEFAULT_LLM_SERVER_URL = "http://35.225.197.84:8000"
12
- DEFAULT_OPENGRADIENT_LLM_SERVER_URL = "https://llm.opengradient.ai"
12
+ DEFAULT_OPENGRADIENT_LLM_SERVER_URL = "https://llmogevm.opengradient.ai"
13
+ DEFAULT_OPENGRADIENT_LLM_STREAMING_SERVER_URL = "https://llmogevm.opengradient.ai"
14
+ DEFAULT_NETWORK_FILTER = "og-evm"
opengradient/types.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import time
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum, IntEnum, StrEnum
4
- from typing import Dict, List, Optional, Tuple, Union, DefaultDict
4
+ from typing import Dict, List, Optional, Tuple, Union, DefaultDict, Iterator, AsyncIterator
5
5
  import numpy as np
6
6
 
7
7
 
@@ -165,6 +165,196 @@ class InferenceResult:
165
165
  model_output: Dict[str, np.ndarray]
166
166
 
167
167
 
168
+ @dataclass
169
+ class StreamDelta:
170
+ """
171
+ Represents a delta (incremental change) in a streaming response.
172
+
173
+ Attributes:
174
+ content: Incremental text content (if any)
175
+ role: Message role (appears in first chunk)
176
+ tool_calls: Tool call information (if function calling is used)
177
+ """
178
+ content: Optional[str] = None
179
+ role: Optional[str] = None
180
+ tool_calls: Optional[List[Dict]] = None
181
+
182
+
183
+ @dataclass
184
+ class StreamChoice:
185
+ """
186
+ Represents a choice in a streaming response.
187
+
188
+ Attributes:
189
+ delta: The incremental changes in this chunk
190
+ index: Choice index (usually 0)
191
+ finish_reason: Reason for completion (appears in final chunk)
192
+ """
193
+ delta: StreamDelta
194
+ index: int = 0
195
+ finish_reason: Optional[str] = None
196
+
197
+
198
+ @dataclass
199
+ class StreamUsage:
200
+ """
201
+ Token usage information for a streaming response.
202
+
203
+ Attributes:
204
+ prompt_tokens: Number of tokens in the prompt
205
+ completion_tokens: Number of tokens in the completion
206
+ total_tokens: Total tokens used
207
+ """
208
+ prompt_tokens: int
209
+ completion_tokens: int
210
+ total_tokens: int
211
+
212
+
213
+ @dataclass
214
+ class StreamChunk:
215
+ """
216
+ Represents a single chunk in a streaming LLM response.
217
+
218
+ This follows the OpenAI streaming format but is provider-agnostic.
219
+ Each chunk contains incremental data, with the final chunk including
220
+ usage information.
221
+
222
+ Attributes:
223
+ choices: List of streaming choices (usually contains one choice)
224
+ model: Model identifier
225
+ usage: Token usage information (only in final chunk)
226
+ is_final: Whether this is the final chunk (before [DONE])
227
+ """
228
+ choices: List[StreamChoice]
229
+ model: str
230
+ usage: Optional[StreamUsage] = None
231
+ is_final: bool = False
232
+
233
+ @classmethod
234
+ def from_sse_data(cls, data: Dict) -> "StreamChunk":
235
+ """
236
+ Parse a StreamChunk from SSE data dictionary.
237
+
238
+ Args:
239
+ data: Dictionary parsed from SSE data line
240
+
241
+ Returns:
242
+ StreamChunk instance
243
+ """
244
+ choices = []
245
+ for choice_data in data.get("choices", []):
246
+ delta_data = choice_data.get("delta", {})
247
+ delta = StreamDelta(
248
+ content=delta_data.get("content"),
249
+ role=delta_data.get("role"),
250
+ tool_calls=delta_data.get("tool_calls")
251
+ )
252
+ choice = StreamChoice(
253
+ delta=delta,
254
+ index=choice_data.get("index", 0),
255
+ finish_reason=choice_data.get("finish_reason")
256
+ )
257
+ choices.append(choice)
258
+
259
+ usage = None
260
+ if "usage" in data:
261
+ usage_data = data["usage"]
262
+ usage = StreamUsage(
263
+ prompt_tokens=usage_data.get("prompt_tokens", 0),
264
+ completion_tokens=usage_data.get("completion_tokens", 0),
265
+ total_tokens=usage_data.get("total_tokens", 0)
266
+ )
267
+
268
+ is_final = any(c.finish_reason is not None for c in choices) or usage is not None
269
+
270
+ return cls(
271
+ choices=choices,
272
+ model=data.get("model", "unknown"),
273
+ usage=usage,
274
+ is_final=is_final
275
+ )
276
+
277
+
278
+ @dataclass
279
+ class TextGenerationStream:
280
+ """
281
+ Iterator wrapper for streaming text generation responses.
282
+
283
+ Provides a clean interface for iterating over stream chunks with
284
+ automatic parsing of SSE format.
285
+
286
+ Usage:
287
+ stream = client.llm_chat(..., stream=True)
288
+ for chunk in stream:
289
+ if chunk.choices[0].delta.content:
290
+ print(chunk.choices[0].delta.content, end="")
291
+ """
292
+ _iterator: Union[Iterator[str], AsyncIterator[str]]
293
+ _is_async: bool = False
294
+
295
+ def __iter__(self):
296
+ """Iterate over stream chunks."""
297
+ return self
298
+
299
+ def __next__(self) -> StreamChunk:
300
+ """Get next stream chunk."""
301
+ import json
302
+
303
+ while True:
304
+ try:
305
+ line = next(self._iterator)
306
+ except StopIteration:
307
+ raise
308
+
309
+ if not line or not line.strip():
310
+ continue
311
+
312
+ if not line.startswith("data: "):
313
+ continue
314
+
315
+ data_str = line[6:] # Remove "data: " prefix
316
+
317
+ if data_str.strip() == "[DONE]":
318
+ raise StopIteration
319
+
320
+ try:
321
+ data = json.loads(data_str)
322
+ return StreamChunk.from_sse_data(data)
323
+ except json.JSONDecodeError:
324
+ # Skip malformed chunks
325
+ continue
326
+
327
+ async def __anext__(self) -> StreamChunk:
328
+ """Get next stream chunk (async version)."""
329
+ import json
330
+
331
+ if not self._is_async:
332
+ raise TypeError("Use __next__ for sync iterators")
333
+
334
+ while True:
335
+ try:
336
+ line = await self._iterator.__anext__()
337
+ except StopAsyncIteration:
338
+ raise
339
+
340
+ if not line or not line.strip():
341
+ continue
342
+
343
+ if not line.startswith("data: "):
344
+ continue
345
+
346
+ data_str = line[6:]
347
+
348
+ if data_str.strip() == "[DONE]":
349
+ raise StopAsyncIteration
350
+
351
+ try:
352
+ data = json.loads(data_str)
353
+ return StreamChunk.from_sse_data(data)
354
+ except json.JSONDecodeError:
355
+ continue
356
+
357
+
168
358
  @dataclass
169
359
  class TextGenerationOutput:
170
360
  """
@@ -26,7 +26,7 @@ def read_workflow_wrapper(contract_address: str, format_function: Callable[...,
26
26
  format_function (Callable): Function for formatting the result returned by read_workflow
27
27
  """
28
28
  try:
29
- result = og.read_workflow_result(contract_address)
29
+ result = og.alpha.read_workflow_result(contract_address)
30
30
 
31
31
  formatted_result = format_function(result)
32
32
  block_explorer_link = create_block_explorer_link_smart_contract(contract_address)
@@ -0,0 +1,102 @@
1
+ """
2
+ X402 Authentication handler for httpx streaming requests.
3
+
4
+ This module provides an httpx Auth class that handles x402 payment protocol
5
+ authentication for streaming responses.
6
+ """
7
+
8
+ from typing import Generator, Optional
9
+
10
+ import httpx
11
+ from eth_account.account import LocalAccount
12
+ from x402.clients.base import x402Client
13
+ from x402.types import x402PaymentRequiredResponse
14
+
15
+
16
+ class X402Auth(httpx.Auth):
17
+ """
18
+ httpx Auth handler for x402 payment protocol.
19
+
20
+ This class implements the httpx Auth interface to handle 402 Payment Required
21
+ responses by automatically creating and attaching payment headers.
22
+
23
+ Example:
24
+ async with httpx.AsyncClient(auth=X402Auth(account=wallet_account)) as client:
25
+ response = await client.get("https://api.example.com/paid-resource")
26
+ """
27
+
28
+ requires_response_body = True
29
+
30
+ def __init__(
31
+ self,
32
+ account: LocalAccount,
33
+ max_value: Optional[int] = None,
34
+ network_filter: Optional[str] = None,
35
+ scheme_filter: Optional[str] = None,
36
+ ):
37
+ """
38
+ Initialize X402Auth with an Ethereum account for signing payments.
39
+
40
+ Args:
41
+ account: eth_account LocalAccount instance for signing payments
42
+ max_value: Optional maximum allowed payment amount in base units
43
+ network_filter: Optional network filter for selecting payment requirements
44
+ scheme_filter: Optional scheme filter for selecting payment requirements
45
+ """
46
+ self._account = account
47
+ self._max_value = max_value
48
+ self._network_filter = network_filter
49
+ self._scheme_filter = scheme_filter
50
+ self._x402_client = x402Client(
51
+ account,
52
+ max_value=max_value,
53
+ network_filter=network_filter,
54
+ scheme_filter=scheme_filter,
55
+ )
56
+
57
+ def auth_flow(
58
+ self, request: httpx.Request
59
+ ) -> Generator[httpx.Request, httpx.Response, None]:
60
+ """
61
+ Implement the auth flow for x402 payment handling.
62
+
63
+ This method yields the initial request, and if a 402 response is received,
64
+ it creates a payment header and retries the request.
65
+
66
+ Args:
67
+ request: The initial httpx Request
68
+
69
+ Yields:
70
+ httpx.Request objects (initial and retry with payment header)
71
+ """
72
+ # Send the initial request
73
+ response = yield request
74
+
75
+ # If not a 402, we're done
76
+ if response.status_code != 402:
77
+ return
78
+
79
+ # Handle 402 Payment Required
80
+ try:
81
+ data = response.json()
82
+ payment_response = x402PaymentRequiredResponse(**data)
83
+
84
+ # Select payment requirements
85
+ selected_requirements = self._x402_client.select_payment_requirements(
86
+ payment_response.accepts
87
+ )
88
+
89
+ # Create payment header
90
+ payment_header = self._x402_client.create_payment_header(
91
+ selected_requirements, payment_response.x402_version
92
+ )
93
+
94
+ # Add payment header and retry
95
+ request.headers["X-Payment"] = payment_header
96
+ request.headers["Access-Control-Expose-Headers"] = "X-Payment-Response"
97
+
98
+ yield request
99
+
100
+ except Exception:
101
+ # If payment handling fails, just return the original 402 response
102
+ return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opengradient
3
- Version: 0.5.8
3
+ Version: 0.5.10
4
4
  Summary: Python SDK for OpenGradient decentralized model management & inference services
5
5
  Author-email: OpenGradient <kyle@vannalabs.ai>
6
6
  License-Expression: MIT
@@ -23,7 +23,7 @@ Requires-Dist: requests>=2.32.3
23
23
  Requires-Dist: langchain>=0.3.7
24
24
  Requires-Dist: openai>=1.58.1
25
25
  Requires-Dist: pydantic>=2.9.2
26
- Requires-Dist: og-test-x402==0.0.1
26
+ Requires-Dist: og-test-x402==0.0.9
27
27
  Dynamic: license-file
28
28
 
29
29
  # OpenGradient Python SDK
@@ -35,6 +35,7 @@ A Python SDK for decentralized model management and inference services on the Op
35
35
  - Model management and versioning
36
36
  - Decentralized model inference
37
37
  - Support for LLM inference with various models
38
+ - **Trusted Execution Environment (TEE) inference** with cryptographic attestation
38
39
  - End-to-end verified AI execution
39
40
  - Command-line interface (CLI) for direct access
40
41
 
@@ -46,7 +47,6 @@ Browse and discover AI models on our [Model Hub](https://hub.opengradient.ai/).
46
47
  - Direct integration with the SDK
47
48
 
48
49
  ## Installation
49
-
50
50
  ```bash
51
51
  pip install opengradient
52
52
  ```
@@ -62,7 +62,6 @@ You'll need two accounts to use the SDK:
62
62
  - **OpenGradient account**: Use an existing Ethereum-compatible wallet or create a new one via SDK
63
63
 
64
64
  The easiest way to set up your accounts is through our configuration wizard:
65
-
66
65
  ```bash
67
66
  opengradient config init
68
67
  ```
@@ -73,7 +72,6 @@ This wizard will:
73
72
  - Direct you to our Test Faucet for devnet tokens
74
73
 
75
74
  ### 2. Initialize the SDK
76
-
77
75
  ```python
78
76
  import opengradient as og
79
77
  og.init(private_key="<private_key>", email="<email>", password="<password>")
@@ -82,8 +80,6 @@ og.init(private_key="<private_key>", email="<email>", password="<password>")
82
80
  ### 3. Basic Usage
83
81
 
84
82
  Browse available models on our [Model Hub](https://hub.opengradient.ai/) or create and upload your own:
85
-
86
-
87
83
  ```python
88
84
  # Create and upload a model
89
85
  og.create_model(
@@ -101,20 +97,41 @@ result = og.infer(
101
97
  )
102
98
  ```
103
99
 
104
- ### 4. Examples
100
+ ### 4. TEE (Trusted Execution Environment) Inference
101
+
102
+ OpenGradient supports secure, verifiable inference through TEE for leading LLM providers. Access models from OpenAI, Anthropic, Google, and xAI with cryptographic attestation:
103
+ ```python
104
+ from opengradient import TEE_LLM
105
+
106
+ # Use TEE-enabled models for verifiable AI execution
107
+ result = og.infer(
108
+ model_cid=TEE_LLM.CLAUDE_3_7_SONNET, # or any other TEE_LLM model
109
+ model_inputs={"prompt": "Your prompt here"},
110
+ inference_mode=og.InferenceMode.TEE
111
+ )
112
+ ```
113
+
114
+ **Available TEE Models:**
115
+ The SDK includes models from multiple providers accessible via the `TEE_LLM` enum:
116
+ - **OpenAI**: GPT-4.1, GPT-4o, o4-mini
117
+ - **Anthropic**: Claude 3.7 Sonnet, Claude 3.5 Haiku, Claude 4.0 Sonnet
118
+ - **Google**: Gemini 2.5 Flash, Gemini 2.5 Pro, and more
119
+ - **xAI**: Grok 3 Beta, Grok 4.1 Fast, and other Grok variants
120
+
121
+ For the complete list of available models, check the `TEE_LLM` enum in your IDE autocomplete or see the [API documentation](https://docs.opengradient.ai/).
122
+
123
+ ### 5. Examples
105
124
 
106
125
  See code examples under [examples](./examples).
107
126
 
108
127
  ## CLI Usage
109
128
 
110
129
  The SDK includes a command-line interface for quick operations. First, verify your configuration:
111
-
112
130
  ```bash
113
131
  opengradient config show
114
132
  ```
115
133
 
116
134
  Run a test inference:
117
-
118
135
  ```bash
119
136
  opengradient infer -m QmbUqS93oc4JTLMHwpVxsE39mhNxy6hpf6Py3r9oANr8aZ \
120
137
  --input '{"num_input1":[1.0, 2.0, 3.0], "num_input2":10}'
@@ -124,7 +141,9 @@ opengradient infer -m QmbUqS93oc4JTLMHwpVxsE39mhNxy6hpf6Py3r9oANr8aZ \
124
141
 
125
142
  1. **Off-chain Applications**: Use OpenGradient as a decentralized alternative to centralized AI providers like HuggingFace and OpenAI.
126
143
 
127
- 2. **Model Development**: Manage models on the Model Hub and integrate directly into your development workflow.
144
+ 2. **Verifiable AI Execution**: Leverage TEE inference for cryptographically attested AI outputs, enabling trustless AI applications.
145
+
146
+ 3. **Model Development**: Manage models on the Model Hub and integrate directly into your development workflow.
128
147
 
129
148
  ## Documentation
130
149
 
@@ -140,4 +159,4 @@ If you use [Claude Code](https://claude.ai/code), copy [docs/CLAUDE_SDK_USERS.md
140
159
 
141
160
  - Run `opengradient --help` for CLI command reference
142
161
  - Visit our [documentation](https://docs.opengradient.ai/) for detailed guides
143
- - Join our [community](https://.opengradient.ai/) for support
162
+ - Join our [community](https://opengradient.ai/) for support
@@ -1,17 +1,19 @@
1
- opengradient/__init__.py,sha256=7UkGoQRDtSb0lh3vobxmyJct_uFfm1Re_oz5s0s9dOs,13263
1
+ opengradient/__init__.py,sha256=hRGex2VGwAQ-lqBNphW93D4R3wPHQpQrTkwgGLe2MoA,10255
2
2
  opengradient/account.py,sha256=5wrYpws_1lozjOFjLCTHtxgoxK-LmObDAaVy9eDcJY4,1145
3
- opengradient/cli.py,sha256=4IUKxecZV9la-_nEVxObOIjm6qQ9aEHhq5-m5clzzHc,29901
4
- opengradient/client.py,sha256=nozp80z8KSYQewKQmSVXZQIdVtsSjv53reS3TBRwlXc,63071
5
- opengradient/defaults.py,sha256=yiZnpIOLyHEmZhCEQXgWpT2eJin10UVsivJY6r61xmo,674
3
+ opengradient/alpha.py,sha256=WAtL1GGbEpoeLO89rOMd8-YAgAFJYM1UJlICm6YGsPs,15195
4
+ opengradient/cli.py,sha256=pfgyLfD1MIDifKmGLFsJqBlgvqIcnsIh3zzg7PaIeH4,33670
5
+ opengradient/client.py,sha256=B13xv_iL2ZynCsiojfsXuERzIdbt62UoOLd_s_moCnA,65250
6
+ opengradient/defaults.py,sha256=8faLPwvp_BQdErY_SEjBzvmGVuOBdZ2zKcoryD8SCXk,797
6
7
  opengradient/exceptions.py,sha256=88tfegboGtlehQcwhxsl6ZzhLJWZWlkf_bkHTiCtXpo,3391
7
- opengradient/types.py,sha256=DSkJAcD4fRQ78bG3Ny5-_OqcfptFSIpliS4qKKYE2jU,9026
8
+ opengradient/types.py,sha256=bADakUM6WwdMORGC5HvQvWCezNwIlVc7l0zodPapbhQ,14622
8
9
  opengradient/utils.py,sha256=ZUq4OBIml2vsC0tRqus4Zwb_e3g4woo00apByrafuVw,8058
10
+ opengradient/x402_auth.py,sha256=utfusvfKxPtsq4MtRX03yQ969G7VeatxAQwHLAW77JU,3350
9
11
  opengradient/abi/InferencePrecompile.abi,sha256=reepTHg6Q01UrFP0Gexc-JayplsvOLPfG7jrEZ-cV28,10197
10
12
  opengradient/abi/PriceHistoryInference.abi,sha256=ZB3fZdx1kaFlp2wt1vTbTZZG1k8HPvmNtkG5Q8Bnajw,5098
11
13
  opengradient/abi/WorkflowScheduler.abi,sha256=yEGs76qO4S1z980KL5hBdfyXiJ6k-kERcB1O_o73AEU,416
12
14
  opengradient/abi/inference.abi,sha256=MR5u9npZ-Yx2EqRW17_M-UnGgFF3mMEMepOwaZ-Bkgc,7040
13
15
  opengradient/alphasense/__init__.py,sha256=Ah6IpoPTb6UkY7ImOWLJs3tjlxDJx6vZVR7p5IwP_Ks,292
14
- opengradient/alphasense/read_workflow_tool.py,sha256=ojCf-eMO6e0ib77nqjgEJtXxTxdLZmc_-MvyRemYFY0,3216
16
+ opengradient/alphasense/read_workflow_tool.py,sha256=Y_MKRpBR1dNvCu9gNxcSnT3E_IqxZUAkE-_XziJ0BVY,3222
15
17
  opengradient/alphasense/run_model_tool.py,sha256=wlDqXVHa1xpqQy_hmht_wWegxtqdYgYBXNbRP3qbfwM,6945
16
18
  opengradient/alphasense/types.py,sha256=uxk4JQKbaS2cM3ZiKpdHQb234OJ5ylprNR5vi01QFzA,220
17
19
  opengradient/bin/PriceHistoryInference.bin,sha256=nU2FZpGHIKBZ7NSK9Sr-p9lr-nXja_40ISPN9yckDq8,41276
@@ -25,11 +27,11 @@ opengradient/proto/infer_pb2_grpc.py,sha256=q42_eZ7OZCMTXdWocYA4Ka3B0c3B74dOhfqd
25
27
  opengradient/workflow_models/__init__.py,sha256=pAGRaMZOXmuPnqG5gAQB1FeFLYxQ4F4bmYdrqXIez7c,826
26
28
  opengradient/workflow_models/constants.py,sha256=viIkb_LGcfVprqQNaA80gBTj6cfYam0r6b6MHW9XGFA,740
27
29
  opengradient/workflow_models/types.py,sha256=Z22hF6c8Y4D2GlzVEIBODGwsqSjSrQvUcpZ7R-mIJdI,409
28
- opengradient/workflow_models/utils.py,sha256=ySfpuiOBqLTlfto6ZxZf2vc7K6RGIja0l4eaVm5AOzY,1503
30
+ opengradient/workflow_models/utils.py,sha256=aL2-Hp5J5qlJft6-wx4GnZNOXZ1vjYaTF1uEgYavWdI,1509
29
31
  opengradient/workflow_models/workflow_models.py,sha256=d4C_gs39DAfy4cdY9Ee6GMXpPfzwvKFpmxzK1A7LNgU,3900
30
- opengradient-0.5.8.dist-info/licenses/LICENSE,sha256=xEcvQ3AxZOtDkrqkys2Mm6Y9diEnaSeQRKvxi-JGnNA,1069
31
- opengradient-0.5.8.dist-info/METADATA,sha256=DyqayJvXV39OUn5H9jUqVsKqX9ilHMAaQ2-u4GnqIwM,4215
32
- opengradient-0.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- opengradient-0.5.8.dist-info/entry_points.txt,sha256=yUKTaJx8RXnybkob0J62wVBiCp_1agVbgw9uzsmaeJc,54
34
- opengradient-0.5.8.dist-info/top_level.txt,sha256=oC1zimVLa2Yi1LQz8c7x-0IQm92milb5ax8gHBHwDqU,13
35
- opengradient-0.5.8.dist-info/RECORD,,
32
+ opengradient-0.5.10.dist-info/licenses/LICENSE,sha256=xEcvQ3AxZOtDkrqkys2Mm6Y9diEnaSeQRKvxi-JGnNA,1069
33
+ opengradient-0.5.10.dist-info/METADATA,sha256=Pfj4IQ8soL-1AO5X5PbqcunZF4K0UK3AixX3Lfsh_Bw,5437
34
+ opengradient-0.5.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
35
+ opengradient-0.5.10.dist-info/entry_points.txt,sha256=yUKTaJx8RXnybkob0J62wVBiCp_1agVbgw9uzsmaeJc,54
36
+ opengradient-0.5.10.dist-info/top_level.txt,sha256=oC1zimVLa2Yi1LQz8c7x-0IQm92milb5ax8gHBHwDqU,13
37
+ opengradient-0.5.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5