colibri-stateless 0.7.2__cp38-cp38-win_amd64.whl → 0.7.4__cp38-cp38-win_amd64.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.
colibri/__init__.py CHANGED
@@ -6,8 +6,8 @@ Python bindings for the Colibri stateless Ethereum proof library.
6
6
 
7
7
  import atexit
8
8
  from .storage import ColibriStorage, DefaultStorage, MemoryStorage
9
- from .types import MethodType, ColibriError
10
- from .testing import MockStorage, MockRequestHandler
9
+ from .types import MethodType, ColibriError, RPCError
10
+ from .testing import MockStorage, MockRequestHandler, MockProofData, TestHelper
11
11
 
12
12
  # Try to import native module
13
13
  try:
@@ -24,21 +24,20 @@ _global_storage = None
24
24
  _storage_registered = False
25
25
 
26
26
  def _register_global_storage(storage: ColibriStorage = None):
27
- """Register global storage once for the entire module"""
27
+ """Register global storage (can be changed, but affects all Colibri instances)"""
28
28
  global _global_storage, _storage_registered
29
29
 
30
- if _storage_registered:
31
- # Already registered - check if it's the same type
32
- if storage is not None and type(storage) != type(_global_storage):
33
- import warnings
34
- warnings.warn(
35
- f"Storage already registered as {type(_global_storage).__name__}, "
36
- f"ignoring {type(storage).__name__}. C storage is global across all instances.",
37
- RuntimeWarning
38
- )
39
- return _global_storage
30
+ # Allow storage re-registration for testing
31
+ # Note: This affects ALL Colibri instances as C storage is global
32
+ if _storage_registered and storage is not None:
33
+ if type(storage) != type(_global_storage):
34
+ # Storage type is changing - re-register
35
+ _storage_registered = False
40
36
 
41
37
  if storage is None:
38
+ if _storage_registered:
39
+ # Return existing storage
40
+ return _global_storage
42
41
  storage = DefaultStorage()
43
42
 
44
43
  _global_storage = storage
@@ -79,6 +78,9 @@ __all__ = [
79
78
  "MemoryStorage",
80
79
  "MethodType",
81
80
  "ColibriError",
81
+ "RPCError",
82
82
  "MockStorage",
83
83
  "MockRequestHandler",
84
+ "MockProofData",
85
+ "TestHelper",
84
86
  ]
Binary file
colibri/client.py CHANGED
@@ -154,7 +154,7 @@ class Colibri:
154
154
  type_int = native.get_method_support(self.chain_id, method)
155
155
  return MethodType(type_int)
156
156
  except (ValueError, TypeError):
157
- return MethodType.UNKNOWN
157
+ return MethodType.UNDEFINED
158
158
 
159
159
  # Fallback implementation for testing
160
160
  proofable_methods = {
@@ -173,7 +173,7 @@ class Colibri:
173
173
  elif method.startswith("eth_"):
174
174
  return MethodType.UNPROOFABLE
175
175
  else:
176
- return MethodType.NOT_SUPPORTED
176
+ return MethodType.UNDEFINED
177
177
 
178
178
  async def create_proof(self, method: str, params: List[Any]) -> bytes:
179
179
  """
@@ -281,27 +281,12 @@ class Colibri:
281
281
  if not status_json:
282
282
  raise VerificationError("Verification execution returned null")
283
283
 
284
- # Debug: Print the raw JSON response from C
285
- # DEBUG: Raw C JSON response available for debugging
286
-
284
+ # Parse JSON response from C library
287
285
  try:
288
286
  status = json.loads(status_json)
289
287
  except json.JSONDecodeError as e:
290
- # Workaround: Try to fix trailing comma issue from C
291
- if "," in status_json and status_json.rstrip().endswith(",}"):
292
- # Workaround: Fix trailing comma in C JSON
293
- fixed_json = status_json.replace(",}", "}")
294
- print(f" Fixed JSON: {repr(fixed_json)}")
295
- try:
296
- status = json.loads(fixed_json)
297
- # JSON parsing successful after fix
298
- except json.JSONDecodeError as e2:
299
- print(f"Still invalid after fix: {e2}")
300
- raise VerificationError(f"Invalid JSON in verification response: {e2}")
301
- else:
302
- # JSON parse error occurred
303
- print(f" Raw response: {status_json}")
304
- raise VerificationError(f"Invalid JSON in verification response: {e}")
288
+ # JSON parsing failed - this indicates a bug in the C library
289
+ raise VerificationError(f"Invalid JSON from C library: {e}") from e
305
290
 
306
291
  if status["status"] == "success":
307
292
  return status.get("result")
@@ -358,7 +343,7 @@ class Colibri:
358
343
  # Local methods use empty proof
359
344
  return await self.verify_proof(b"", method, params)
360
345
 
361
- elif method_type == MethodType.NOT_SUPPORTED:
346
+ elif method_type == MethodType.NOT_SUPPORTED or method_type == MethodType.UNDEFINED:
362
347
  raise ColibriError(f"Method {method} is not supported")
363
348
 
364
349
  else:
colibri/testing.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Testing utilities and mock implementations for Colibri Python bindings
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import json
6
8
  from pathlib import Path
7
9
  from typing import Any, Dict, List, Optional, Union
@@ -11,6 +13,115 @@ from .storage import ColibriStorage
11
13
  from .types import DataRequest, ColibriError
12
14
 
13
15
 
16
+ class MockProofData:
17
+ """Utility class for creating mock proof data for testing"""
18
+
19
+ @staticmethod
20
+ def create_proof(method: str, params: List[Any], result: Any) -> bytes:
21
+ """
22
+ Create mock proof data for a given method, params, and result.
23
+
24
+ @param method The RPC method name
25
+ @param params The RPC method parameters
26
+ @param result The expected result
27
+ @return Mock proof data as bytes
28
+ """
29
+ proof_dict = {
30
+ "method": method,
31
+ "params": params,
32
+ "result": result,
33
+ "mock": True
34
+ }
35
+ return json.dumps(proof_dict).encode('utf-8')
36
+
37
+ @staticmethod
38
+ def create_empty_proof() -> bytes:
39
+ """
40
+ Create an empty proof (used for LOCAL methods).
41
+
42
+ @return Empty bytes
43
+ """
44
+ return b""
45
+
46
+
47
+ class TestHelper:
48
+ """Helper utilities for setting up test scenarios"""
49
+
50
+ @staticmethod
51
+ def setup_eth_get_balance_mock(
52
+ handler: 'MockRequestHandler',
53
+ address: str,
54
+ block: str,
55
+ balance: str
56
+ ) -> None:
57
+ """
58
+ Setup a mock response for eth_getBalance.
59
+
60
+ @param handler The mock request handler
61
+ @param address The Ethereum address
62
+ @param block The block number or tag
63
+ @param balance The balance to return (hex string)
64
+ """
65
+ response = {
66
+ "jsonrpc": "2.0",
67
+ "id": 1,
68
+ "result": balance
69
+ }
70
+ handler.add_response("eth_getBalance", [address, block], response)
71
+
72
+ @staticmethod
73
+ def setup_eth_get_block_mock(
74
+ handler: 'MockRequestHandler',
75
+ block_hash: str,
76
+ block_data: Dict[str, Any]
77
+ ) -> None:
78
+ """
79
+ Setup a mock response for eth_getBlockByHash.
80
+
81
+ @param handler The mock request handler
82
+ @param block_hash The block hash
83
+ @param block_data The block data to return
84
+ """
85
+ response = {
86
+ "jsonrpc": "2.0",
87
+ "id": 1,
88
+ "result": block_data
89
+ }
90
+ handler.add_response("eth_getBlockByHash", [block_hash, False], response)
91
+
92
+ @staticmethod
93
+ def setup_proof_mock(
94
+ handler: 'MockRequestHandler',
95
+ method: str,
96
+ params: List[Any],
97
+ proof_data: Optional[bytes] = None
98
+ ) -> None:
99
+ """
100
+ Setup a mock proof response for a method.
101
+
102
+ @param handler The mock request handler
103
+ @param method The RPC method name
104
+ @param params The RPC method parameters
105
+ @param proof_data Optional custom proof data (generates default if None)
106
+ """
107
+ if proof_data is None:
108
+ proof_data = MockProofData.create_proof(method, params, "mock_result")
109
+ handler.add_response(method, params, proof_data)
110
+
111
+ @staticmethod
112
+ def create_mock_storage_with_data(preset_data: Dict[str, bytes]) -> MockStorage:
113
+ """
114
+ Create a MockStorage instance with preset data.
115
+
116
+ @param preset_data Dictionary of key-value pairs to preset in storage
117
+ @return MockStorage instance with preset data
118
+ """
119
+ storage = MockStorage()
120
+ for key, value in preset_data.items():
121
+ storage.set(key, value)
122
+ return storage
123
+
124
+
14
125
  class MockStorage(ColibriStorage):
15
126
  """Mock storage implementation for testing"""
16
127
 
@@ -32,18 +143,139 @@ class MockStorage(ColibriStorage):
32
143
  self.delete_calls.append(key)
33
144
  self._data.pop(key, None)
34
145
 
146
+ def size(self) -> int:
147
+ """Return the number of items in storage"""
148
+ return len(self._data)
149
+
150
+ def preset_data(self, data: Dict[str, bytes]) -> None:
151
+ """
152
+ Preset storage with initial data without tracking calls.
153
+
154
+ @param data Dictionary of key-value pairs to preset
155
+ """
156
+ self._data.update(data)
157
+
158
+ def clear_data(self) -> None:
159
+ """Clear all data from storage without tracking calls"""
160
+ self._data.clear()
161
+
162
+ def clear_calls(self) -> None:
163
+ """Clear the history of tracked calls"""
164
+ self.get_calls.clear()
165
+ self.set_calls.clear()
166
+ self.delete_calls.clear()
167
+
35
168
 
36
169
  class MockRequestHandler:
37
170
  """Mock HTTP request handler for testing"""
38
171
 
39
172
  def __init__(self):
40
173
  self._responses: Dict[str, Union[bytes, Dict[str, Any]]] = {}
174
+ self._method_responses: Dict[str, Union[bytes, Dict[str, Any]]] = {}
175
+ self._default_response: Optional[bytes] = None
41
176
  self.request_calls: List[DataRequest] = []
42
177
 
178
+ def _make_key(self, method: str, params: List[Any]) -> str:
179
+ """Create a unique key for method + params combination"""
180
+ return f"{method}:{json.dumps(params, sort_keys=True)}"
181
+
182
+ def add_response(
183
+ self,
184
+ method: str,
185
+ params: List[Any],
186
+ response: Union[bytes, Dict[str, Any], str]
187
+ ) -> None:
188
+ """
189
+ Add a mock response for a specific method and params combination.
190
+
191
+ @param method The RPC method name
192
+ @param params The RPC method parameters
193
+ @param response The response to return (can be bytes, dict, or string)
194
+ """
195
+ key = self._make_key(method, params)
196
+ self._responses[key] = response
197
+
198
+ def add_method_response(
199
+ self,
200
+ method: str,
201
+ response: Union[bytes, Dict[str, Any], str]
202
+ ) -> None:
203
+ """
204
+ Add a mock response for any call to a method (ignoring params).
205
+
206
+ @param method The RPC method name
207
+ @param response The response to return
208
+ """
209
+ self._method_responses[method] = response
210
+
211
+ def set_default_response(self, response: bytes) -> None:
212
+ """
213
+ Set a default response for any unmatched requests.
214
+
215
+ @param response The default response to return
216
+ """
217
+ self._default_response = response
218
+
219
+ def clear_responses(self) -> None:
220
+ """Clear all configured responses"""
221
+ self._responses.clear()
222
+ self._method_responses.clear()
223
+ self._default_response = None
224
+
225
+ def clear_calls(self) -> None:
226
+ """Clear the history of request calls"""
227
+ self.request_calls.clear()
228
+
229
+ def get_calls_for_method(self, method: str) -> List[DataRequest]:
230
+ """
231
+ Get all request calls for a specific method.
232
+
233
+ @param method The RPC method name
234
+ @return List of matching requests
235
+ """
236
+ return [
237
+ req for req in self.request_calls
238
+ if req.payload and req.payload.get("method") == method
239
+ ]
240
+
43
241
  async def handle_request(self, request: DataRequest) -> bytes:
44
- """Handle a mock HTTP request"""
242
+ """
243
+ Handle a mock HTTP request.
244
+
245
+ @param request The data request to handle
246
+ @return Mock response data
247
+ """
45
248
  self.request_calls.append(request)
46
- return b'{"result": "mock_response"}'
249
+
250
+ # Try to find a matching response
251
+ if request.payload and "method" in request.payload:
252
+ method = request.payload["method"]
253
+ params = request.payload.get("params", [])
254
+
255
+ # First try exact match (method + params)
256
+ key = self._make_key(method, params)
257
+ if key in self._responses:
258
+ response = self._responses[key]
259
+ if isinstance(response, bytes):
260
+ return response
261
+ return json.dumps(response).encode('utf-8')
262
+
263
+ # Then try method-only match
264
+ if method in self._method_responses:
265
+ response = self._method_responses[method]
266
+ if isinstance(response, bytes):
267
+ return response
268
+ return json.dumps(response).encode('utf-8')
269
+
270
+ # Use default response if available
271
+ if self._default_response is not None:
272
+ return self._default_response
273
+
274
+ # No response configured
275
+ raise ColibriError(
276
+ f"No mock response configured for request: "
277
+ f"method={request.payload.get('method') if request.payload else 'unknown'}"
278
+ )
47
279
 
48
280
 
49
281
  class FileBasedMockStorage(ColibriStorage):
@@ -54,6 +286,46 @@ class FileBasedMockStorage(ColibriStorage):
54
286
  self._cache: Dict[str, Optional[bytes]] = {}
55
287
  self._access_count: Dict[str, int] = {}
56
288
  self._max_access_per_key = 5 # Prevent infinite loops
289
+
290
+ def _find_file_with_truncation(self, filename: str) -> Optional[Path]:
291
+ """
292
+ Find a file, handling filesystem truncation (macOS has 255 char limit).
293
+
294
+ @param filename The full filename to search for
295
+ @return The path to the file if found, None otherwise
296
+ """
297
+ file_path = self.test_data_dir / filename
298
+
299
+ # Try exact match first
300
+ if file_path.exists():
301
+ return file_path
302
+
303
+ # If not found and filename is long, try to find truncated versions
304
+ if len(filename) > 200:
305
+ # Get the extension
306
+ parts = filename.rsplit('.', 1)
307
+ if len(parts) == 2:
308
+ base_name, extension = parts
309
+
310
+ # Search for files that start with the same prefix and have same extension
311
+ for prefix_len in [250, 240, 230, 220, 200, 150, 100]:
312
+ if len(base_name) > prefix_len:
313
+ prefix = base_name[:prefix_len]
314
+ pattern = f"{prefix}*.{extension}"
315
+ matching_files = list(self.test_data_dir.glob(pattern))
316
+ if matching_files:
317
+ return matching_files[0]
318
+ else:
319
+ # No extension, just search by prefix
320
+ for prefix_len in [250, 240, 230, 220, 200, 150, 100]:
321
+ if len(filename) > prefix_len:
322
+ prefix = filename[:prefix_len]
323
+ pattern = f"{prefix}*"
324
+ matching_files = list(self.test_data_dir.glob(pattern))
325
+ if matching_files:
326
+ return matching_files[0]
327
+
328
+ return None
57
329
 
58
330
  def get(self, key: str) -> Optional[bytes]:
59
331
  # CRITICAL: Return None immediately to break infinite loops
@@ -70,9 +342,9 @@ class FileBasedMockStorage(ColibriStorage):
70
342
  # Return cached value
71
343
  return self._cache[key]
72
344
 
73
- # Load from file
74
- file_path = self.test_data_dir / key
75
- if file_path.exists():
345
+ # Load from file (with truncation handling)
346
+ file_path = self._find_file_with_truncation(key)
347
+ if file_path:
76
348
  data = file_path.read_bytes()
77
349
  # Load file from storage
78
350
  self._cache[key] = data
@@ -99,6 +371,40 @@ class FileBasedMockRequestHandler:
99
371
  self._request_count = 0
100
372
  self._max_requests = 50 # Prevent infinite request loops
101
373
 
374
+ def _find_file_with_truncation(self, filename: str) -> Optional[Path]:
375
+ """
376
+ Find a file, handling filesystem truncation (macOS has 255 char limit).
377
+
378
+ @param filename The full filename to search for
379
+ @return The path to the file if found, None otherwise
380
+ """
381
+ file_path = self.test_data_dir / filename
382
+
383
+ # Try exact match first
384
+ if file_path.exists():
385
+ return file_path
386
+
387
+ # If not found and filename is long, try to find truncated versions
388
+ # macOS typically truncates at 255 characters
389
+ if len(filename) > 200: # If it's potentially truncated
390
+ # Get the extension
391
+ parts = filename.rsplit('.', 1)
392
+ if len(parts) == 2:
393
+ base_name, extension = parts
394
+
395
+ # Search for files that start with the same prefix and have same extension
396
+ # Use progressively shorter prefixes to find the truncated file
397
+ for prefix_len in [250, 240, 230, 220, 200, 150, 100]:
398
+ if len(base_name) > prefix_len:
399
+ prefix = base_name[:prefix_len]
400
+ pattern = f"{prefix}*.{extension}"
401
+ matching_files = list(self.test_data_dir.glob(pattern))
402
+ if matching_files:
403
+ # Return the first match
404
+ return matching_files[0]
405
+
406
+ return None
407
+
102
408
  async def handle_request(self, request: DataRequest) -> bytes:
103
409
  """Handle mock HTTP request by loading from file"""
104
410
 
@@ -106,20 +412,39 @@ class FileBasedMockRequestHandler:
106
412
  if self._request_count > self._max_requests:
107
413
  raise Exception(f"Too many requests ({self._request_count}) - possible infinite loop")
108
414
 
109
- # Convert request to filename (simplified)
415
+ # Convert request to filename base (without extension) - matching C implementation
110
416
  if request.url:
111
- filename = request.url.replace('/', '_').replace('?', '_').replace('=', '_').replace('&', '_')
112
- filename = filename + '.' + request.encoding
417
+ # Sanitize URL to create filename base
418
+ base_name = request.url
419
+ # Replace problematic characters with underscore (matching C implementation)
420
+ for char in ['/', '.', ',', ' ', ':', '=', '?', '"', '&', '[', ']', '{', '}']:
421
+ base_name = base_name.replace(char, '_')
113
422
  elif request.payload and 'method' in request.payload:
114
- method = request.payload['method']
115
- filename = method + '.' + request.encoding
423
+ # For RPC requests, use method name and parameters
424
+ import json
425
+ method = request.payload.get('method', '')
426
+ params = request.payload.get('params', [])
427
+ base_name = method
428
+ for param in params:
429
+ param_str = param if isinstance(param, str) else json.dumps(param)
430
+ base_name += '_' + param_str
431
+ # Sanitize the base name
432
+ for char in ['/', '.', ',', ' ', ':', '=', '?', '"', '&', '[', ']', '{', '}']:
433
+ base_name = base_name.replace(char, '_')
116
434
  else:
117
- filename = 'unknown.' + request.encoding
435
+ base_name = 'unknown'
118
436
 
119
- # Look for mock response file
437
+ # CRITICAL: Truncate to maximum length BEFORE adding extension (matching C: C4_MAX_MOCKNAME_LEN = 100)
438
+ MAX_MOCKNAME_LEN = 100
439
+ if len(base_name) > MAX_MOCKNAME_LEN:
440
+ base_name = base_name[:MAX_MOCKNAME_LEN]
120
441
 
121
- file_path = self.test_data_dir / filename
122
- if file_path.exists():
442
+ # Add file extension based on encoding type
443
+ filename = base_name + '.' + request.encoding
444
+
445
+ # Look for mock response file (with truncation handling)
446
+ file_path = self._find_file_with_truncation(filename)
447
+ if file_path:
123
448
  data = file_path.read_bytes()
124
449
  # Found mock response file
125
450
  return data
colibri/types.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional
8
8
 
9
9
  class MethodType(IntEnum):
10
10
  """Enum for RPC method support types"""
11
- UNKNOWN = 0
11
+ UNDEFINED = 0 # Method is not defined/recognized
12
12
  PROOFABLE = 1
13
13
  UNPROOFABLE = 2
14
14
  NOT_SUPPORTED = 3
@@ -21,7 +21,7 @@ class MethodType(IntEnum):
21
21
  def description(self) -> str:
22
22
  """Human-readable description of the method type"""
23
23
  descriptions = {
24
- MethodType.UNKNOWN: "Unknown method type",
24
+ MethodType.UNDEFINED: "Method not defined/recognized",
25
25
  MethodType.PROOFABLE: "Method supports proof generation",
26
26
  MethodType.UNPROOFABLE: "Method doesn't support proofs, direct RPC call",
27
27
  MethodType.NOT_SUPPORTED: "Method not supported",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: colibri-stateless
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Python bindings for Colibri stateless Ethereum proof library
5
5
  Home-page: https://github.com/corpus-core/colibri-stateless
6
6
  Author: corpus.core
@@ -54,7 +54,7 @@ The colibri client is a stateless and trustless ethereum client, which is optimi
54
54
  ### Installation
55
55
 
56
56
  ```bash
57
- pip install colibri-stateless
57
+ python3 -m pip install colibri-stateless
58
58
  ```
59
59
 
60
60
  ### Basic Usage
@@ -113,11 +113,39 @@ cd colibri-stateless/bindings/python
113
113
  # Build native extension
114
114
  ./build.sh
115
115
 
116
- # Install in development mode
116
+ # Option 1: Use virtual environment (recommended)
117
+ python3 -m venv venv
118
+ source venv/bin/activate
117
119
  pip install -e .
120
+ pip install -r requirements-dev.txt
118
121
 
119
122
  # Run tests
120
- python -m pytest tests/
123
+ pytest tests/ -v
124
+
125
+ # Deactivate when done
126
+ deactivate
127
+ ```
128
+
129
+ **Alternative without virtual environment:**
130
+
131
+ ```bash
132
+ # Install test dependencies with --user flag
133
+ python3 -m pip install --user pytest pytest-asyncio aiohttp
134
+
135
+ # Run tests directly with PYTHONPATH
136
+ PYTHONPATH=src python3 -m pytest tests/ -v
137
+ ```
138
+
139
+ ### Quick Debug Build
140
+
141
+ For faster iteration during development:
142
+
143
+ ```bash
144
+ # Build in debug mode
145
+ ./build_debug.sh
146
+
147
+ # Run tests without installation
148
+ PYTHONPATH=src python3 -m pytest tests/ -v
121
149
  ```
122
150
 
123
151
  ### Integration Tests
@@ -0,0 +1,11 @@
1
+ colibri/__init__.py,sha256=99QBzNVvVAO8P3Bd40ud_R8WJ82dkSPEMyTKhPSzKWo,2506
2
+ colibri/_native.cp38-win_amd64.pyd,sha256=k1AUyslZSsuhT6VAp0jvosUrux-eLbAOmAe9XUw0vKQ,887808
3
+ colibri/client.py,sha256=kgwSwY9kbqxln8yinXsYMRZnc2HPwldB02AQjbZE2wo,20955
4
+ colibri/py.typed,sha256=fDUY3AqEcQHC01aGMGdCqP2qwZXb9ILFNzjSSBBwh1U,61
5
+ colibri/storage.py,sha256=evl2zA4kbZd7sQFpvr2pfI4dNH0c94AvFvQw1VEy5do,4579
6
+ colibri/testing.py,sha256=ti2FwuMGQ6TxKrSAB_rZ2ralpmLAxQX8r8OB5vzJtAg,22148
7
+ colibri/types.py,sha256=QNwehmdN9xdJi3R_4QMwBklw4l1rPQ6LxpheNnGw8lQ,4049
8
+ colibri_stateless-0.7.4.dist-info/METADATA,sha256=p_1BRsXeBCqEW0bHAjOrbIUGh79hOO0_eAEqMiPtrnE,6217
9
+ colibri_stateless-0.7.4.dist-info/WHEEL,sha256=2M046GvC9RLU1f1TWyM-2sB7cRKLhAC7ucAFK8l8f24,99
10
+ colibri_stateless-0.7.4.dist-info/top_level.txt,sha256=7YrQNaIHaKEkVXyESwWV8heheMQY94YJfVoepEYjWRk,8
11
+ colibri_stateless-0.7.4.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- colibri/__init__.py,sha256=5r_j9C9M-XUEojWhTSSmn0A85UZN5ztazAdc2MSM6Nk,2442
2
- colibri/_native.cp38-win_amd64.pyd,sha256=vdvW7CRqZUDBzz-gV8nNYgtH-AME3usu8jF5GwaAL_o,887808
3
- colibri/client.py,sha256=8ERKG2-QM-ZImqFUvpcnQxlmiqUmNX61auQPACNuOco,21893
4
- colibri/py.typed,sha256=fDUY3AqEcQHC01aGMGdCqP2qwZXb9ILFNzjSSBBwh1U,61
5
- colibri/storage.py,sha256=evl2zA4kbZd7sQFpvr2pfI4dNH0c94AvFvQw1VEy5do,4579
6
- colibri/testing.py,sha256=nCvlOSD1ydzv-lv7bLd8UJNtvnlRCPhKKF7KHtZrf5o,10016
7
- colibri/types.py,sha256=9gwXeUkcCPUwKaMFRyCO0vEtoJLyw8ZKNvouCnrtpTQ,3995
8
- colibri_stateless-0.7.2.dist-info/METADATA,sha256=3p_KPydAmdUZkDWzMbf6_bvQfnqqa5MU7uYdgxcevWI,5609
9
- colibri_stateless-0.7.2.dist-info/WHEEL,sha256=2M046GvC9RLU1f1TWyM-2sB7cRKLhAC7ucAFK8l8f24,99
10
- colibri_stateless-0.7.2.dist-info/top_level.txt,sha256=7YrQNaIHaKEkVXyESwWV8heheMQY94YJfVoepEYjWRk,8
11
- colibri_stateless-0.7.2.dist-info/RECORD,,