tango-python 1.1.1__py3-none-any.whl → 1.1.2__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.
- tango/__init__.py +1 -1
- tango/client.py +28 -3
- tango/shapes/factory.py +12 -76
- tango/shapes/generator.py +12 -27
- tango/shapes/parser.py +17 -16
- tango/shapes/schema.py +3 -2
- {tango_python-1.1.1.dist-info → tango_python-1.1.2.dist-info}/METADATA +1 -1
- {tango_python-1.1.1.dist-info → tango_python-1.1.2.dist-info}/RECORD +11 -11
- {tango_python-1.1.1.dist-info → tango_python-1.1.2.dist-info}/WHEEL +1 -1
- {tango_python-1.1.1.dist-info → tango_python-1.1.2.dist-info}/entry_points.txt +0 -0
- {tango_python-1.1.1.dist-info → tango_python-1.1.2.dist-info}/licenses/LICENSE +0 -0
tango/__init__.py
CHANGED
tango/client.py
CHANGED
|
@@ -2081,11 +2081,36 @@ class TangoClient:
|
|
|
2081
2081
|
data = self._get(f"/api/entities/{key}/", params)
|
|
2082
2082
|
return self._parse_response_with_shape(data, shape, Entity, flat, flat_lists)
|
|
2083
2083
|
|
|
2084
|
-
def get_entity_budget_flows(
|
|
2085
|
-
|
|
2084
|
+
def get_entity_budget_flows(
|
|
2085
|
+
self,
|
|
2086
|
+
uei: str,
|
|
2087
|
+
page: int = 1,
|
|
2088
|
+
limit: int = 25,
|
|
2089
|
+
fiscal_year: int | None = None,
|
|
2090
|
+
) -> PaginatedResponse[dict[str, Any]]:
|
|
2091
|
+
"""Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`).
|
|
2092
|
+
|
|
2093
|
+
Standard page/limit pagination (default 25, max 100). Each result row
|
|
2094
|
+
is a hand-built dict from the backend (no shape system).
|
|
2095
|
+
|
|
2096
|
+
Args:
|
|
2097
|
+
uei: Entity UEI. Required.
|
|
2098
|
+
page: Page number.
|
|
2099
|
+
limit: Results per page (max 100).
|
|
2100
|
+
fiscal_year: Optional fiscal year filter.
|
|
2101
|
+
"""
|
|
2086
2102
|
if not uei:
|
|
2087
2103
|
raise TangoValidationError("UEI is required")
|
|
2088
|
-
|
|
2104
|
+
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
2105
|
+
if fiscal_year is not None:
|
|
2106
|
+
params["fiscal_year"] = fiscal_year
|
|
2107
|
+
data = self._get(f"/api/entities/{uei}/budget-flows/", params)
|
|
2108
|
+
return PaginatedResponse(
|
|
2109
|
+
count=int(data.get("count", 0)),
|
|
2110
|
+
next=data.get("next"),
|
|
2111
|
+
previous=data.get("previous"),
|
|
2112
|
+
results=list(data.get("results") or []),
|
|
2113
|
+
)
|
|
2089
2114
|
|
|
2090
2115
|
# Forecast endpoints
|
|
2091
2116
|
def list_forecasts(
|
tango/shapes/factory.py
CHANGED
|
@@ -23,7 +23,7 @@ import logging
|
|
|
23
23
|
from collections.abc import Callable
|
|
24
24
|
from datetime import date, datetime
|
|
25
25
|
from decimal import Decimal
|
|
26
|
-
from typing import Any
|
|
26
|
+
from typing import Any, cast
|
|
27
27
|
|
|
28
28
|
from tango.exceptions import ModelInstantiationError
|
|
29
29
|
from tango.shapes.generator import TypeGenerator
|
|
@@ -542,38 +542,6 @@ class ModelFactory:
|
|
|
542
542
|
# Value is not a dict - might be a primitive or None
|
|
543
543
|
result[result_field_name] = value
|
|
544
544
|
|
|
545
|
-
elif field_spec.is_wildcard:
|
|
546
|
-
# Wildcard on nested field - use full model type
|
|
547
|
-
# This is handled at the top level, but we need to handle it here too
|
|
548
|
-
# for nested wildcards like recipient(*)
|
|
549
|
-
if field_schema.nested_model:
|
|
550
|
-
if field_schema.is_list:
|
|
551
|
-
if isinstance(value, list):
|
|
552
|
-
nested_instances = []
|
|
553
|
-
for item in value:
|
|
554
|
-
if isinstance(item, dict):
|
|
555
|
-
# Parse all fields from the nested model
|
|
556
|
-
nested_instance = self._parse_nested_wildcard(
|
|
557
|
-
item, field_schema.nested_model
|
|
558
|
-
)
|
|
559
|
-
nested_instances.append(nested_instance)
|
|
560
|
-
else:
|
|
561
|
-
nested_instances.append(item)
|
|
562
|
-
result[result_field_name] = nested_instances
|
|
563
|
-
else:
|
|
564
|
-
result[result_field_name] = value
|
|
565
|
-
else:
|
|
566
|
-
if isinstance(value, dict):
|
|
567
|
-
nested_instance = self._parse_nested_wildcard(
|
|
568
|
-
value, field_schema.nested_model
|
|
569
|
-
)
|
|
570
|
-
result[result_field_name] = nested_instance
|
|
571
|
-
else:
|
|
572
|
-
result[result_field_name] = value
|
|
573
|
-
else:
|
|
574
|
-
# Not a nested model, just use the value
|
|
575
|
-
result[result_field_name] = value
|
|
576
|
-
|
|
577
545
|
else:
|
|
578
546
|
# Simple field - parse using appropriate parser
|
|
579
547
|
parsed_value = self._parse_field(
|
|
@@ -661,7 +629,7 @@ class ModelFactory:
|
|
|
661
629
|
raise ModelInstantiationError(
|
|
662
630
|
f"Could not resolve nested model '{nested_model}'"
|
|
663
631
|
)
|
|
664
|
-
return model_class
|
|
632
|
+
return cast(type, model_class)
|
|
665
633
|
except ImportError as err:
|
|
666
634
|
raise ModelInstantiationError(
|
|
667
635
|
f"Could not import models module to resolve '{nested_model}'"
|
|
@@ -704,41 +672,6 @@ class ModelFactory:
|
|
|
704
672
|
# Recursively create nested instance
|
|
705
673
|
return self.create_instance(data, nested_shape, resolved_model, nested_type)
|
|
706
674
|
|
|
707
|
-
def _parse_nested_wildcard(
|
|
708
|
-
self, data: dict[str, Any], nested_model: type | str
|
|
709
|
-
) -> dict[str, Any]:
|
|
710
|
-
"""Parse nested object with wildcard (all fields)
|
|
711
|
-
|
|
712
|
-
Args:
|
|
713
|
-
data: Nested object data
|
|
714
|
-
nested_model: Model class or string name for the nested object
|
|
715
|
-
|
|
716
|
-
Returns:
|
|
717
|
-
Dictionary with all parsed fields
|
|
718
|
-
"""
|
|
719
|
-
# Resolve nested model if it's a string
|
|
720
|
-
resolved_model = self._resolve_nested_model(nested_model)
|
|
721
|
-
|
|
722
|
-
# Ensure model is registered
|
|
723
|
-
if not self.schema_registry.is_registered(resolved_model):
|
|
724
|
-
self.schema_registry.register(resolved_model)
|
|
725
|
-
|
|
726
|
-
# Get model schema
|
|
727
|
-
model_schema = self.schema_registry.get_schema(resolved_model)
|
|
728
|
-
|
|
729
|
-
# Parse all fields
|
|
730
|
-
result: dict[str, Any] = {}
|
|
731
|
-
for field_name, value in data.items():
|
|
732
|
-
if field_name in model_schema:
|
|
733
|
-
field_schema = model_schema[field_name]
|
|
734
|
-
parsed_value = self._parse_field(field_name, value, field_schema.type, field_schema)
|
|
735
|
-
result[field_name] = parsed_value
|
|
736
|
-
else:
|
|
737
|
-
# Field not in schema, include as-is
|
|
738
|
-
result[field_name] = value
|
|
739
|
-
|
|
740
|
-
return result
|
|
741
|
-
|
|
742
675
|
def _parse_field(self, field_name: str, value: Any, field_type: type, field_schema: Any) -> Any:
|
|
743
676
|
"""Parse a single field value using appropriate parser
|
|
744
677
|
|
|
@@ -778,7 +711,7 @@ class ModelFactory:
|
|
|
778
711
|
return value
|
|
779
712
|
|
|
780
713
|
def validate_data(
|
|
781
|
-
self, data: dict[str, Any], shape_spec: ShapeSpec, base_model: type
|
|
714
|
+
self, data: dict[str, Any], shape_spec: ShapeSpec, base_model: type | str
|
|
782
715
|
) -> list[str]:
|
|
783
716
|
"""Validate that data matches the shape specification
|
|
784
717
|
|
|
@@ -803,11 +736,15 @@ class ModelFactory:
|
|
|
803
736
|
errors: list[str] = []
|
|
804
737
|
|
|
805
738
|
if not isinstance(data, dict):
|
|
806
|
-
errors.append(
|
|
739
|
+
errors.append( # type: ignore[unreachable]
|
|
740
|
+
f"Expected dictionary data, got {type(data).__name__}"
|
|
741
|
+
)
|
|
807
742
|
return errors
|
|
808
743
|
|
|
809
|
-
# Ensure model is registered
|
|
810
|
-
|
|
744
|
+
# Ensure model is registered. String model names are expected to be
|
|
745
|
+
# pre-registered (explicit schemas); only concrete classes can be
|
|
746
|
+
# auto-registered via introspection.
|
|
747
|
+
if isinstance(base_model, type) and not self.schema_registry.is_registered(base_model):
|
|
811
748
|
self.schema_registry.register(base_model)
|
|
812
749
|
|
|
813
750
|
# Get model schema
|
|
@@ -826,9 +763,8 @@ class ModelFactory:
|
|
|
826
763
|
|
|
827
764
|
# Check if field exists in schema
|
|
828
765
|
if field_spec.name not in model_schema:
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
)
|
|
766
|
+
model_name = base_model.__name__ if isinstance(base_model, type) else base_model
|
|
767
|
+
errors.append(f"Field '{field_spec.name}' does not exist in {model_name} schema")
|
|
832
768
|
continue
|
|
833
769
|
|
|
834
770
|
field_schema = model_schema[field_spec.name]
|
tango/shapes/generator.py
CHANGED
|
@@ -20,7 +20,7 @@ Examples:
|
|
|
20
20
|
import logging
|
|
21
21
|
import threading
|
|
22
22
|
from collections import OrderedDict
|
|
23
|
-
from typing import Any, get_args, get_origin, get_type_hints
|
|
23
|
+
from typing import Any, cast, get_args, get_origin, get_type_hints
|
|
24
24
|
|
|
25
25
|
from tango.exceptions import TypeGenerationError
|
|
26
26
|
from tango.shapes.models import ShapeSpec
|
|
@@ -250,7 +250,10 @@ class TypeGenerator:
|
|
|
250
250
|
|
|
251
251
|
field_schema = model_schema[field_spec.name]
|
|
252
252
|
|
|
253
|
-
# Determine field type
|
|
253
|
+
# Determine field type. The value is a heterogeneous mix of type
|
|
254
|
+
# objects, parameterized generics (list[...]), and union objects,
|
|
255
|
+
# so it is intentionally typed as Any.
|
|
256
|
+
field_type: Any
|
|
254
257
|
if field_spec.nested_fields:
|
|
255
258
|
# Generate nested type
|
|
256
259
|
if not field_schema.nested_model:
|
|
@@ -275,25 +278,7 @@ class TypeGenerator:
|
|
|
275
278
|
|
|
276
279
|
# Handle optional types
|
|
277
280
|
if field_schema.is_optional:
|
|
278
|
-
field_type = field_type | None
|
|
279
|
-
|
|
280
|
-
annotations[field_name] = field_type
|
|
281
|
-
|
|
282
|
-
elif field_spec.is_wildcard:
|
|
283
|
-
# Wildcard on nested field - use full model type
|
|
284
|
-
if field_schema.nested_model:
|
|
285
|
-
# Resolve nested model if it's a string
|
|
286
|
-
field_type = self._resolve_nested_model(field_schema.nested_model)
|
|
287
|
-
else:
|
|
288
|
-
field_type = field_schema.type
|
|
289
|
-
|
|
290
|
-
# Handle list types
|
|
291
|
-
if field_schema.is_list:
|
|
292
|
-
field_type = list[field_type] # type: ignore
|
|
293
|
-
|
|
294
|
-
# Handle optional types
|
|
295
|
-
if field_schema.is_optional:
|
|
296
|
-
field_type = field_type | None # type: ignore
|
|
281
|
+
field_type = field_type | None
|
|
297
282
|
|
|
298
283
|
annotations[field_name] = field_type
|
|
299
284
|
|
|
@@ -303,11 +288,11 @@ class TypeGenerator:
|
|
|
303
288
|
|
|
304
289
|
# Handle list types
|
|
305
290
|
if field_schema.is_list:
|
|
306
|
-
field_type = list[field_type]
|
|
291
|
+
field_type = list[field_type]
|
|
307
292
|
|
|
308
293
|
# Handle optional types
|
|
309
294
|
if field_schema.is_optional:
|
|
310
|
-
field_type = field_type | None
|
|
295
|
+
field_type = field_type | None
|
|
311
296
|
|
|
312
297
|
annotations[field_name] = field_type
|
|
313
298
|
|
|
@@ -329,7 +314,7 @@ class TypeGenerator:
|
|
|
329
314
|
field_type = field_schema.type
|
|
330
315
|
# Handle optional types
|
|
331
316
|
if field_schema.is_optional:
|
|
332
|
-
field_type = field_type | None
|
|
317
|
+
field_type = field_type | None
|
|
333
318
|
annotations[auto_field] = field_type
|
|
334
319
|
|
|
335
320
|
# Create TypedDict dynamically
|
|
@@ -414,7 +399,7 @@ class TypeGenerator:
|
|
|
414
399
|
model_class = getattr(models, nested_model, None)
|
|
415
400
|
if model_class is None:
|
|
416
401
|
raise TypeGenerationError(f"Could not resolve nested model '{nested_model}'")
|
|
417
|
-
return model_class
|
|
402
|
+
return cast(type, model_class)
|
|
418
403
|
except ImportError as err:
|
|
419
404
|
raise TypeGenerationError(
|
|
420
405
|
f"Could not import models module to resolve '{nested_model}'"
|
|
@@ -555,7 +540,7 @@ class TypeGenerator:
|
|
|
555
540
|
|
|
556
541
|
# Handle basic types
|
|
557
542
|
if hasattr(type_annotation, "__name__"):
|
|
558
|
-
type_name = type_annotation.__name__
|
|
543
|
+
type_name = str(type_annotation.__name__)
|
|
559
544
|
else:
|
|
560
545
|
type_name = str(type_annotation)
|
|
561
546
|
|
|
@@ -576,7 +561,7 @@ class TypeGenerator:
|
|
|
576
561
|
if args:
|
|
577
562
|
formatted_args = [self._format_type_annotation(arg) for arg in args]
|
|
578
563
|
return f"{origin.__name__}[{', '.join(formatted_args)}]"
|
|
579
|
-
return origin.__name__
|
|
564
|
+
return str(origin.__name__)
|
|
580
565
|
|
|
581
566
|
return type_name
|
|
582
567
|
|
tango/shapes/parser.py
CHANGED
|
@@ -110,7 +110,7 @@ def _suggest_field_correction(invalid_field: str, valid_fields: list[str]) -> st
|
|
|
110
110
|
|
|
111
111
|
# Check for common prefix
|
|
112
112
|
best_match = None
|
|
113
|
-
best_score = 0
|
|
113
|
+
best_score = 0.0
|
|
114
114
|
|
|
115
115
|
for field in valid_fields:
|
|
116
116
|
# Count common prefix length
|
|
@@ -167,6 +167,13 @@ class ShapeParser:
|
|
|
167
167
|
self._schema_registry = schema_registry
|
|
168
168
|
self._schema_registry_initialized = schema_registry is not None
|
|
169
169
|
|
|
170
|
+
def _ensure_registry(self) -> SchemaRegistry:
|
|
171
|
+
"""Return the schema registry, lazily creating it on first use."""
|
|
172
|
+
if self._schema_registry is None:
|
|
173
|
+
self._schema_registry = SchemaRegistry()
|
|
174
|
+
self._schema_registry_initialized = True
|
|
175
|
+
return self._schema_registry
|
|
176
|
+
|
|
170
177
|
def parse(self, shape: str) -> ShapeSpec:
|
|
171
178
|
"""Parse a shape string into a ShapeSpec
|
|
172
179
|
|
|
@@ -544,25 +551,22 @@ class ShapeParser:
|
|
|
544
551
|
>>> spec = parser.parse("invalid_field")
|
|
545
552
|
>>> parser.validate(spec, Contract) # Raises ShapeValidationError
|
|
546
553
|
"""
|
|
547
|
-
|
|
548
|
-
if not self._schema_registry_initialized:
|
|
549
|
-
self._schema_registry = SchemaRegistry()
|
|
550
|
-
self._schema_registry_initialized = True
|
|
554
|
+
registry = self._ensure_registry()
|
|
551
555
|
|
|
552
556
|
# Ensure model is registered
|
|
553
|
-
if not
|
|
554
|
-
|
|
557
|
+
if not registry.is_registered(model_class):
|
|
558
|
+
registry.register(model_class)
|
|
555
559
|
|
|
556
560
|
# Validate each field
|
|
557
561
|
for field_spec in shape_spec.fields:
|
|
558
562
|
self._validate_field_spec(field_spec, model_class)
|
|
559
563
|
|
|
560
|
-
def _validate_field_spec(self, field_spec: FieldSpec, model_class: type) -> None:
|
|
564
|
+
def _validate_field_spec(self, field_spec: FieldSpec, model_class: type | str) -> None:
|
|
561
565
|
"""Validate a single field specification against a model
|
|
562
566
|
|
|
563
567
|
Args:
|
|
564
568
|
field_spec: Field specification to validate
|
|
565
|
-
model_class: Model class to validate against
|
|
569
|
+
model_class: Model class (or registered model name) to validate against
|
|
566
570
|
|
|
567
571
|
Raises:
|
|
568
572
|
ShapeValidationError: If field is invalid
|
|
@@ -571,20 +575,17 @@ class ShapeParser:
|
|
|
571
575
|
if field_spec.is_wildcard:
|
|
572
576
|
return
|
|
573
577
|
|
|
574
|
-
|
|
575
|
-
if not self._schema_registry_initialized:
|
|
576
|
-
self._schema_registry = SchemaRegistry()
|
|
577
|
-
self._schema_registry_initialized = True
|
|
578
|
+
registry = self._ensure_registry()
|
|
578
579
|
|
|
579
580
|
# Validate field exists in model
|
|
580
581
|
try:
|
|
581
|
-
field_schema =
|
|
582
|
+
field_schema = registry.validate_field(model_class, field_spec.name)
|
|
582
583
|
except ShapeValidationError as e:
|
|
583
584
|
# Enhance error message with suggestions
|
|
584
585
|
model_name = (
|
|
585
586
|
model_class.__name__ if hasattr(model_class, "__name__") else str(model_class)
|
|
586
587
|
)
|
|
587
|
-
model_schema =
|
|
588
|
+
model_schema = registry.get_schema(model_class)
|
|
588
589
|
valid_fields = list(model_schema.keys())
|
|
589
590
|
|
|
590
591
|
error_msg = f"Field '{field_spec.name}' does not exist in {model_name}."
|
|
@@ -630,7 +631,7 @@ class ShapeParser:
|
|
|
630
631
|
error_msg += "\n\nNested selections are only valid for object fields like 'recipient', 'agency', 'location', etc."
|
|
631
632
|
|
|
632
633
|
# Find some nested fields as examples
|
|
633
|
-
model_schema =
|
|
634
|
+
model_schema = registry.get_schema(model_class)
|
|
634
635
|
nested_examples = [
|
|
635
636
|
name for name, schema in model_schema.items() if schema.nested_model
|
|
636
637
|
]
|
tango/shapes/schema.py
CHANGED
|
@@ -8,6 +8,7 @@ the dynamic-only model approach. These schemas define field types, nested models
|
|
|
8
8
|
list indicators independently of the dataclass definitions.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import builtins
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from typing import Any, get_args, get_origin, get_type_hints
|
|
13
14
|
|
|
@@ -33,10 +34,10 @@ class FieldSchema:
|
|
|
33
34
|
"""
|
|
34
35
|
|
|
35
36
|
name: str
|
|
36
|
-
type: type
|
|
37
|
+
type: builtins.type
|
|
37
38
|
is_optional: bool
|
|
38
39
|
is_list: bool
|
|
39
|
-
nested_model: type | None = None
|
|
40
|
+
nested_model: builtins.type | str | None = None
|
|
40
41
|
|
|
41
42
|
def __repr__(self) -> str:
|
|
42
43
|
"""String representation for debugging"""
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
tango/__init__.py,sha256=
|
|
2
|
-
tango/client.py,sha256=
|
|
1
|
+
tango/__init__.py,sha256=gg4U46hFhUJwgvbGtOdP3vgooi8US-fLXvpBZ060_H8,1912
|
|
2
|
+
tango/client.py,sha256=9-IfMZwerx4uRsPlDI38uQDv77TNl2qShCHVtP4t7EU,144839
|
|
3
3
|
tango/exceptions.py,sha256=aRvDm0dUCEtNDfRVYCX7SEDdd1WlIVVY6sN78Tzo-a0,3114
|
|
4
4
|
tango/models.py,sha256=QqUPtO7HJJDUaJDAMUkzvqR7pj1YIr8BetbS4palxc0,34261
|
|
5
5
|
tango/shapes/__init__.py,sha256=7ea1WU74jp4znhNw-gXruag6m6eyPZtbVgbDFmFUWro,1072
|
|
6
6
|
tango/shapes/explicit_schemas.py,sha256=8HgxKpAi2U80JP_MnABnW1JpYJA05aoOZSJsJIcyIWQ,71421
|
|
7
|
-
tango/shapes/factory.py,sha256=
|
|
8
|
-
tango/shapes/generator.py,sha256=
|
|
7
|
+
tango/shapes/factory.py,sha256=uDPhnp6VVzrLBKodxteZy5AcU43juEv-qKVRWth51j0,31550
|
|
8
|
+
tango/shapes/generator.py,sha256=xzakpwTwqTMUr89I06hp54-FIYZziviWHk2Glscg-LM,22775
|
|
9
9
|
tango/shapes/models.py,sha256=h3pIhOqrrdlN953Y6r0oney5HFbKPOD-frRndRWimJ0,3018
|
|
10
|
-
tango/shapes/parser.py,sha256
|
|
11
|
-
tango/shapes/schema.py,sha256=
|
|
10
|
+
tango/shapes/parser.py,sha256=AecNx5oT_hV_pf2AWZnsR8Y2aLmCtjfgS9-znA2Pesc,27429
|
|
11
|
+
tango/shapes/schema.py,sha256=ZdiNuqWCRhmpyLag8xtlQcoyyqvkpDqrr7xu2YbhaOY,14176
|
|
12
12
|
tango/shapes/types.py,sha256=27jrAE0VIdrKaLjR_FK71hfIIGX2Tg3ex7REEBV1TFE,1301
|
|
13
13
|
tango/webhooks/__init__.py,sha256=3bbiiGoB3s5iqqmQceroN0-MCSm-ZOZQx3M6JAknIUo,774
|
|
14
14
|
tango/webhooks/cli.py,sha256=f_vQbJ2AeSQjKnQo7MxHFyUB2SAKcgHYmGQULS0isqQ,13858
|
|
15
15
|
tango/webhooks/receiver.py,sha256=5yhsVhlLyoxmOCGvmbynWAIlDB2OaCPVf1H4GA1SxmU,9279
|
|
16
16
|
tango/webhooks/signing.py,sha256=92Ee-0B6PR7ZkvY3Np3gzl88-mtfKkh-I7lxqCe2lGw,2374
|
|
17
17
|
tango/webhooks/simulate.py,sha256=g2Osa0FYU5mJuon07T2aUCtmkUoTEzsY261tlp76fF0,3165
|
|
18
|
-
tango_python-1.1.
|
|
19
|
-
tango_python-1.1.
|
|
20
|
-
tango_python-1.1.
|
|
21
|
-
tango_python-1.1.
|
|
22
|
-
tango_python-1.1.
|
|
18
|
+
tango_python-1.1.2.dist-info/METADATA,sha256=-wGAubnmnT1ysNyUThjVuYWx3p9IjDPf-4FQrxy756U,20599
|
|
19
|
+
tango_python-1.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
20
|
+
tango_python-1.1.2.dist-info/entry_points.txt,sha256=kGLUbglUjuaAqEFvOZ1QuSW0vWb6VeSpCIFKaOFkKoQ,50
|
|
21
|
+
tango_python-1.1.2.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
|
|
22
|
+
tango_python-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|