arthur-common 2.1.58__py3-none-any.whl → 2.1.60__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.
Potentially problematic release.
This version of arthur-common might be problematic. Click here for more details.
- arthur_common/aggregations/aggregator.py +3 -1
- arthur_common/aggregations/functions/agentic_aggregations.py +36 -21
- arthur_common/models/connectors.py +9 -0
- arthur_common/models/metrics.py +16 -2
- arthur_common/models/shield.py +11 -11
- arthur_common/tools/duckdb_data_loader.py +4 -2
- {arthur_common-2.1.58.dist-info → arthur_common-2.1.60.dist-info}/METADATA +11 -3
- {arthur_common-2.1.58.dist-info → arthur_common-2.1.60.dist-info}/RECORD +9 -9
- {arthur_common-2.1.58.dist-info → arthur_common-2.1.60.dist-info}/WHEEL +0 -0
|
@@ -172,7 +172,9 @@ class SketchAggregationFunction(AggregationFunction, ABC):
|
|
|
172
172
|
groups = data.groupby(dim_columns, dropna=False)
|
|
173
173
|
for _, group in groups:
|
|
174
174
|
calculated_metrics.append(
|
|
175
|
-
SketchAggregationFunction._group_to_series(
|
|
175
|
+
SketchAggregationFunction._group_to_series(
|
|
176
|
+
group, timestamp_col, dim_columns, value_col
|
|
177
|
+
),
|
|
176
178
|
)
|
|
177
179
|
|
|
178
180
|
return calculated_metrics
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Annotated
|
|
3
|
+
from typing import Annotated, Any
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
@@ -27,7 +27,10 @@ TOOL_SCORE_NO_TOOL_VALUE = 2
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# TODO: create TypedDict for span
|
|
31
|
+
def extract_spans_with_metrics_and_agents(
|
|
32
|
+
root_spans: list[str | dict[str, Any]],
|
|
33
|
+
) -> list[tuple[dict[str, Any], str]]:
|
|
31
34
|
"""Recursively extract all spans with metrics and their associated agent names from the span tree.
|
|
32
35
|
|
|
33
36
|
Returns:
|
|
@@ -35,14 +38,21 @@ def extract_spans_with_metrics_and_agents(root_spans):
|
|
|
35
38
|
"""
|
|
36
39
|
spans_with_metrics_and_agents = []
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
# TODO: Improve function so it won't modify variable outside of its scope
|
|
42
|
+
def traverse_spans(
|
|
43
|
+
spans: list[str | dict[str, Any]],
|
|
44
|
+
current_agent_name: str = "unknown",
|
|
45
|
+
) -> None:
|
|
46
|
+
for span_to_parse in spans:
|
|
47
|
+
if isinstance(span_to_parse, str):
|
|
48
|
+
parsed_span = json.loads(span_to_parse)
|
|
49
|
+
else:
|
|
50
|
+
parsed_span = span_to_parse
|
|
41
51
|
|
|
42
52
|
# Update current agent name if this span is an AGENT
|
|
43
|
-
if
|
|
53
|
+
if parsed_span.get("span_kind") == "AGENT":
|
|
44
54
|
try:
|
|
45
|
-
raw_data =
|
|
55
|
+
raw_data = parsed_span.get("raw_data", {})
|
|
46
56
|
if isinstance(raw_data, str):
|
|
47
57
|
raw_data = json.loads(raw_data)
|
|
48
58
|
|
|
@@ -52,29 +62,31 @@ def extract_spans_with_metrics_and_agents(root_spans):
|
|
|
52
62
|
current_agent_name = agent_name
|
|
53
63
|
except (json.JSONDecodeError, KeyError, TypeError):
|
|
54
64
|
logger.error(
|
|
55
|
-
f"Error parsing attributes from span (span_id: {
|
|
65
|
+
f"Error parsing attributes from span (span_id: {parsed_span.get('span_id')}) in trace {parsed_span.get('trace_id')}",
|
|
56
66
|
)
|
|
57
67
|
|
|
58
68
|
# Check if this span has metrics
|
|
59
|
-
if
|
|
60
|
-
spans_with_metrics_and_agents.append(
|
|
69
|
+
if parsed_span.get("metric_results", []):
|
|
70
|
+
spans_with_metrics_and_agents.append(
|
|
71
|
+
(parsed_span, current_agent_name),
|
|
72
|
+
)
|
|
61
73
|
|
|
62
74
|
# Recursively traverse children with the current agent name
|
|
63
|
-
if
|
|
64
|
-
traverse_spans(
|
|
75
|
+
if children_span := parsed_span.get("children", []):
|
|
76
|
+
traverse_spans(children_span, current_agent_name)
|
|
65
77
|
|
|
66
78
|
traverse_spans(root_spans)
|
|
67
79
|
return spans_with_metrics_and_agents
|
|
68
80
|
|
|
69
81
|
|
|
70
|
-
def determine_relevance_pass_fail(score):
|
|
82
|
+
def determine_relevance_pass_fail(score: float | None) -> str | None:
|
|
71
83
|
"""Determine pass/fail for relevance scores using global threshold"""
|
|
72
84
|
if score is None:
|
|
73
85
|
return None
|
|
74
86
|
return "pass" if score >= RELEVANCE_SCORE_THRESHOLD else "fail"
|
|
75
87
|
|
|
76
88
|
|
|
77
|
-
def determine_tool_pass_fail(score):
|
|
89
|
+
def determine_tool_pass_fail(score: int | None) -> str | None:
|
|
78
90
|
"""Determine pass/fail for tool scores using global threshold"""
|
|
79
91
|
if score is None:
|
|
80
92
|
return None
|
|
@@ -177,7 +189,7 @@ class AgenticMetricsOverTimeAggregation(SketchAggregationFunction):
|
|
|
177
189
|
|
|
178
190
|
for metric_result in metric_results:
|
|
179
191
|
metric_type = metric_result.get("metric_type")
|
|
180
|
-
details = json.loads(metric_result.get("details",
|
|
192
|
+
details = json.loads(metric_result.get("details", "{}"))
|
|
181
193
|
|
|
182
194
|
if metric_type == "ToolSelection":
|
|
183
195
|
tool_selection = details.get("tool_selection", {})
|
|
@@ -430,7 +442,7 @@ class AgenticRelevancePassFailCountAggregation(NumericAggregationFunction):
|
|
|
430
442
|
|
|
431
443
|
for metric_result in metric_results:
|
|
432
444
|
metric_type = metric_result.get("metric_type")
|
|
433
|
-
details = json.loads(metric_result.get("details",
|
|
445
|
+
details = json.loads(metric_result.get("details", "{}"))
|
|
434
446
|
|
|
435
447
|
if metric_type in ["QueryRelevance", "ResponseRelevance"]:
|
|
436
448
|
relevance_data = details.get(
|
|
@@ -555,7 +567,7 @@ class AgenticToolPassFailCountAggregation(NumericAggregationFunction):
|
|
|
555
567
|
|
|
556
568
|
for metric_result in metric_results:
|
|
557
569
|
if metric_result.get("metric_type") == "ToolSelection":
|
|
558
|
-
details = json.loads(metric_result.get("details",
|
|
570
|
+
details = json.loads(metric_result.get("details", "{}"))
|
|
559
571
|
tool_selection = details.get("tool_selection", {})
|
|
560
572
|
|
|
561
573
|
tool_selection_score = tool_selection.get("tool_selection")
|
|
@@ -723,10 +735,13 @@ class AgenticLLMCallCountAggregation(NumericAggregationFunction):
|
|
|
723
735
|
root_spans = json.loads(root_spans)
|
|
724
736
|
|
|
725
737
|
# Count LLM spans in the tree
|
|
726
|
-
def count_llm_spans(spans):
|
|
738
|
+
def count_llm_spans(spans: list[str | dict[str, Any]]) -> int:
|
|
727
739
|
count = 0
|
|
728
|
-
for
|
|
729
|
-
|
|
740
|
+
for span_to_parse in spans:
|
|
741
|
+
if isinstance(span_to_parse, str):
|
|
742
|
+
span = json.loads(span_to_parse)
|
|
743
|
+
else:
|
|
744
|
+
span = span_to_parse
|
|
730
745
|
|
|
731
746
|
# Check if this span is an LLM span
|
|
732
747
|
if span.get("span_kind") == "LLM":
|
|
@@ -830,7 +845,7 @@ class AgenticToolSelectionAndUsageByAgentAggregation(NumericAggregationFunction)
|
|
|
830
845
|
|
|
831
846
|
for metric_result in metric_results:
|
|
832
847
|
if metric_result.get("metric_type") == "ToolSelection":
|
|
833
|
-
details = json.loads(metric_result.get("details",
|
|
848
|
+
details = json.loads(metric_result.get("details", "{}"))
|
|
834
849
|
tool_selection = details.get("tool_selection", {})
|
|
835
850
|
|
|
836
851
|
tool_selection_score = tool_selection.get("tool_selection")
|
|
@@ -38,6 +38,15 @@ ODBC_CONNECTOR_DRIVER_FIELD = "driver"
|
|
|
38
38
|
ODBC_CONNECTOR_TABLE_NAME_FIELD = "table_name"
|
|
39
39
|
ODBC_CONNECTOR_DIALECT_FIELD = "dialect"
|
|
40
40
|
|
|
41
|
+
# Snowflake connector constants
|
|
42
|
+
SNOWFLAKE_CONNECTOR_ACCOUNT_FIELD = "account"
|
|
43
|
+
SNOWFLAKE_CONNECTOR_SCHEMA_FIELD = "schema"
|
|
44
|
+
SNOWFLAKE_CONNECTOR_WAREHOUSE_FIELD = "warehouse"
|
|
45
|
+
SNOWFLAKE_CONNECTOR_ROLE_FIELD = "role"
|
|
46
|
+
SNOWFLAKE_CONNECTOR_AUTHENTICATOR_FIELD = "authenticator"
|
|
47
|
+
SNOWFLAKE_CONNECTOR_PRIVATE_KEY_FIELD = "private_key"
|
|
48
|
+
SNOWFLAKE_CONNECTOR_PRIVATE_KEY_PASSPHRASE_FIELD = "private_key_passphrase"
|
|
49
|
+
|
|
41
50
|
|
|
42
51
|
# dataset (connector type dependent) constants
|
|
43
52
|
SHIELD_DATASET_TASK_ID_FIELD = "task_id"
|
arthur_common/models/metrics.py
CHANGED
|
@@ -122,6 +122,20 @@ class BaseAggregationParameterSchema(BaseModel):
|
|
|
122
122
|
description="Description of the parameter.",
|
|
123
123
|
)
|
|
124
124
|
|
|
125
|
+
@field_validator("parameter_key")
|
|
126
|
+
@classmethod
|
|
127
|
+
def validate_parameter_key_allowed_characters(cls, v: str) -> str:
|
|
128
|
+
if not v.replace("_", "").isalpha():
|
|
129
|
+
raise ValueError("Parameter key can only contain letters and underscores.")
|
|
130
|
+
return v
|
|
131
|
+
|
|
132
|
+
@field_validator("friendly_name")
|
|
133
|
+
@classmethod
|
|
134
|
+
def validate_friendly_name_allowed_characters(cls, v: str) -> str:
|
|
135
|
+
if not v.replace("_", "").replace(" ", "").isalpha():
|
|
136
|
+
raise ValueError("Friendly name can only contain letters and underscores.")
|
|
137
|
+
return v
|
|
138
|
+
|
|
125
139
|
|
|
126
140
|
class MetricsParameterSchema(BaseAggregationParameterSchema):
|
|
127
141
|
# specific to default metrics/Python metrics—not available to custom aggregations
|
|
@@ -195,7 +209,7 @@ class MetricsColumnParameterSchema(MetricsParameterSchema, BaseColumnParameterSc
|
|
|
195
209
|
|
|
196
210
|
class MetricsColumnListParameterSchema(
|
|
197
211
|
MetricsParameterSchema,
|
|
198
|
-
|
|
212
|
+
BaseColumnBaseParameterSchema,
|
|
199
213
|
):
|
|
200
214
|
# list column parameter schema specific to default metrics
|
|
201
215
|
parameter_type: Literal["column_list"] = "column_list"
|
|
@@ -298,7 +312,7 @@ class ReportedCustomAggregation(BaseReportedAggregation):
|
|
|
298
312
|
|
|
299
313
|
@field_validator("dimension_columns")
|
|
300
314
|
@classmethod
|
|
301
|
-
def validate_dimension_columns_length(cls, v: list[str]) -> str:
|
|
315
|
+
def validate_dimension_columns_length(cls, v: list[str]) -> list[str]:
|
|
302
316
|
if len(v) > 1:
|
|
303
317
|
raise ValueError("Only one dimension column can be specified.")
|
|
304
318
|
return v
|
arthur_common/models/shield.py
CHANGED
|
@@ -32,7 +32,7 @@ class MetricType(str, Enum):
|
|
|
32
32
|
RESPONSE_RELEVANCE = "ResponseRelevance"
|
|
33
33
|
TOOL_SELECTION = "ToolSelection"
|
|
34
34
|
|
|
35
|
-
def __str__(self):
|
|
35
|
+
def __str__(self) -> str:
|
|
36
36
|
return self.value
|
|
37
37
|
|
|
38
38
|
|
|
@@ -575,20 +575,20 @@ class NewMetricRequest(BaseModel):
|
|
|
575
575
|
},
|
|
576
576
|
)
|
|
577
577
|
|
|
578
|
-
@field_validator("type")
|
|
579
|
-
def validate_metric_type(cls, value):
|
|
580
|
-
if value not in MetricType:
|
|
581
|
-
raise ValueError(
|
|
582
|
-
f"Invalid metric type: {value}. Valid types are: {', '.join([t.value for t in MetricType])}",
|
|
583
|
-
)
|
|
584
|
-
return value
|
|
585
|
-
|
|
586
578
|
@model_validator(mode="before")
|
|
587
|
-
def set_config_type(cls, values):
|
|
579
|
+
def set_config_type(cls, values: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
588
580
|
if not isinstance(values, dict):
|
|
589
581
|
return values
|
|
590
582
|
|
|
591
|
-
|
|
583
|
+
try:
|
|
584
|
+
metric_type = MetricType(values.get("type", "empty_value"))
|
|
585
|
+
except ValueError:
|
|
586
|
+
raise HTTPException(
|
|
587
|
+
status_code=400,
|
|
588
|
+
detail=f"Invalid metric type: {values.get('type', 'empty_value')}. Must be one of {[t.value for t in MetricType]}",
|
|
589
|
+
headers={"full_stacktrace": "false"},
|
|
590
|
+
)
|
|
591
|
+
|
|
592
592
|
config_values = values.get("config")
|
|
593
593
|
|
|
594
594
|
# Map metric types to their corresponding config classes
|
|
@@ -16,6 +16,8 @@ from arthur_common.models.schema_definitions import (
|
|
|
16
16
|
DType,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
MAX_JSON_OBJECT_SIZE = 1024 * 1024 * 1024 # 1GB
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
class ColumnFormat(BaseModel):
|
|
21
23
|
source_name: str
|
|
@@ -104,9 +106,9 @@ class DuckDBOperator:
|
|
|
104
106
|
stringified_schema = ", ".join([f"{kv}" for kv in key_value_pairs])
|
|
105
107
|
stringified_schema = f"{{ {stringified_schema} }}"
|
|
106
108
|
|
|
107
|
-
read_stmt = f"read_json('memory://inferences.json', format='array', columns={stringified_schema})"
|
|
109
|
+
read_stmt = f"read_json('memory://inferences.json', format='array', columns={stringified_schema}, maximum_object_size={MAX_JSON_OBJECT_SIZE})"
|
|
108
110
|
else:
|
|
109
|
-
read_stmt = "read_json_auto('memory://inferences.json')"
|
|
111
|
+
read_stmt = f"read_json_auto('memory://inferences.json', maximum_object_size={MAX_JSON_OBJECT_SIZE})"
|
|
110
112
|
|
|
111
113
|
conn.sql(
|
|
112
114
|
f"CREATE OR REPLACE TEMP TABLE {table_name} AS SELECT * FROM {read_stmt}",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: arthur-common
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.60
|
|
4
4
|
Summary: Utility code common to Arthur platform components.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Arthur
|
|
@@ -43,14 +43,14 @@ pip install arthur-common
|
|
|
43
43
|
|
|
44
44
|
## Requirements
|
|
45
45
|
|
|
46
|
-
- Python 3.
|
|
46
|
+
- Python 3.13
|
|
47
47
|
|
|
48
48
|
## Development
|
|
49
49
|
|
|
50
50
|
To set up the development environment, ensure you have [Poetry](https://python-poetry.org/) installed, then run:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
poetry env use 3.
|
|
53
|
+
poetry env use 3.13
|
|
54
54
|
poetry install
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -62,6 +62,14 @@ This project uses [pytest](https://pytest.org/) for testing. To run the tests, e
|
|
|
62
62
|
poetry run pytest
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Release process
|
|
66
|
+
1. Merge changes into **main** branch
|
|
67
|
+
2. Go to **Actions** -> **Arthur Common Version Bump**
|
|
68
|
+
3. Manually trigger workflow there, it will create a PR with version bumping
|
|
69
|
+
4. Go to **Pull requests** and check PR for version bump, accept it if everything is okay
|
|
70
|
+
5. Version bump commit will be merged to **main** branch and it will start release process
|
|
71
|
+
6. Update package version in your project (arthur-engine)
|
|
72
|
+
|
|
65
73
|
## License
|
|
66
74
|
|
|
67
75
|
This project is licensed under the MIT License.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
arthur_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
arthur_common/aggregations/__init__.py,sha256=vISWyciQAtksa71OKeHNP-QyFGd1NzBKq_LBsG0QSG8,67
|
|
3
|
-
arthur_common/aggregations/aggregator.py,sha256=
|
|
3
|
+
arthur_common/aggregations/aggregator.py,sha256=AhyNqBDEbKtS3ZrnSIT9iZ1SK_TAuiUNg9s9loDvek0,8007
|
|
4
4
|
arthur_common/aggregations/functions/README.md,sha256=MkZoTAJ94My96R5Z8GAxud7S6vyR0vgVi9gqdt9a4XY,5460
|
|
5
5
|
arthur_common/aggregations/functions/__init__.py,sha256=HqC3UNRURX7ZQHgamTrQvfA8u_FiZGZ4I4eQW7Ooe5o,1299
|
|
6
|
-
arthur_common/aggregations/functions/agentic_aggregations.py,sha256=
|
|
6
|
+
arthur_common/aggregations/functions/agentic_aggregations.py,sha256=dFstRA-kqfGeGO-KpA0YPAipoZhmxxM7KTEr8NAUl-U,33998
|
|
7
7
|
arthur_common/aggregations/functions/categorical_count.py,sha256=wc1ovL8JoiSeoSTk9h1fgrLj1QuQeYYZmEqgffGc2cw,5328
|
|
8
8
|
arthur_common/aggregations/functions/confusion_matrix.py,sha256=Zac-biMeIVyLRcMXWmENgYq8X4I7Trm8gOE5NRLGKU0,22108
|
|
9
9
|
arthur_common/aggregations/functions/inference_count.py,sha256=SrRfxQVnX-wRTZ1zbqUKupPdACvfKeUpZDidZs45ZUY,4079
|
|
@@ -22,23 +22,23 @@ arthur_common/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
22
22
|
arthur_common/config/config.py,sha256=fcpjOYjPKu4Duk63CuTHrOWKQKAlAhVUR60kF_2_Xog,1247
|
|
23
23
|
arthur_common/config/settings.yaml,sha256=0CrygUwJzC5mGcO5Xnvv2ttp-P7LIsx682jllYA96NQ,161
|
|
24
24
|
arthur_common/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
arthur_common/models/connectors.py,sha256=
|
|
25
|
+
arthur_common/models/connectors.py,sha256=Ro56ASPAFy-j_qYyxnweNRUueTrCmv92MT2U_i7L7rI,2263
|
|
26
26
|
arthur_common/models/datasets.py,sha256=oO-HgZ_OZW-E9DlQYwxkw2T31jwZEqYaB3NvkbYAiYI,527
|
|
27
|
-
arthur_common/models/metrics.py,sha256=
|
|
27
|
+
arthur_common/models/metrics.py,sha256=N5IoQZplllMO54YMptX-SCjOefiftWOoM5-Wt8WY1Es,11934
|
|
28
28
|
arthur_common/models/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
arthur_common/models/schema_definitions.py,sha256=4FSbL51RvOgeikNnVfCVSXmYDNzkyqtEKC2a6FjwRqI,16879
|
|
30
|
-
arthur_common/models/shield.py,sha256=
|
|
30
|
+
arthur_common/models/shield.py,sha256=S5E6_0T_r75k6YXQntwu_2kzbpGCuhLu3b_uh3FQyHY,24413
|
|
31
31
|
arthur_common/models/task_job_specs.py,sha256=xYej0vtHE5zvBQ-ka9Rn4N1lQtR1XXgbGVzhzemiL64,3509
|
|
32
32
|
arthur_common/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
arthur_common/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
arthur_common/tools/aggregation_analyzer.py,sha256=UfMtvFWXV2Dqly8S6nneGgomuvEGN-1tBz81tfkMcAE,11206
|
|
35
35
|
arthur_common/tools/aggregation_loader.py,sha256=3CF46bNi-GdJBNOXkjYfCQ1Aung8lf65L532sdWmR_s,2351
|
|
36
|
-
arthur_common/tools/duckdb_data_loader.py,sha256=
|
|
36
|
+
arthur_common/tools/duckdb_data_loader.py,sha256=OwuvppwcBB9qQxyWr86mH7Gz2FBIuyDl0UpQ7TulhlU,11220
|
|
37
37
|
arthur_common/tools/duckdb_utils.py,sha256=1i-kRXu95gh4Sf9Osl2LFUpdb0yZifOjLDtIgSfSmfs,1197
|
|
38
38
|
arthur_common/tools/functions.py,sha256=FWL4eWO5-vLp86WudT-MGUKvf2B8f02IdoXQFKd6d8k,1093
|
|
39
39
|
arthur_common/tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
arthur_common/tools/schema_inferer.py,sha256=Ur4CXGAkd6ZMSU0nMNrkOEElsBopHXq0lctTV8X92W8,5188
|
|
41
41
|
arthur_common/tools/time_utils.py,sha256=4gfiu9NXfvPZltiVNLSIQGylX6h2W0viNi9Kv4bKyfw,1410
|
|
42
|
-
arthur_common-2.1.
|
|
43
|
-
arthur_common-2.1.
|
|
44
|
-
arthur_common-2.1.
|
|
42
|
+
arthur_common-2.1.60.dist-info/METADATA,sha256=EK5y-Fy8mKi798zO6j2TXRNdm81pBs08gzAwgezvPjo,2038
|
|
43
|
+
arthur_common-2.1.60.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
44
|
+
arthur_common-2.1.60.dist-info/RECORD,,
|
|
File without changes
|