garf-core 0.0.8__py3-none-any.whl → 0.0.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.
- garf_core/__init__.py +1 -1
- garf_core/api_clients.py +4 -2
- garf_core/base_query.py +1 -1
- garf_core/exceptions.py +1 -29
- garf_core/parsers.py +41 -29
- garf_core/query_editor.py +31 -13
- garf_core/report.py +58 -6
- garf_core/report_fetcher.py +8 -3
- {garf_core-0.0.8.dist-info → garf_core-0.0.10.dist-info}/METADATA +6 -3
- garf_core-0.0.10.dist-info/RECORD +13 -0
- {garf_core-0.0.8.dist-info → garf_core-0.0.10.dist-info}/WHEEL +1 -1
- garf_core-0.0.8.dist-info/RECORD +0 -13
- {garf_core-0.0.8.dist-info → garf_core-0.0.10.dist-info}/entry_points.txt +0 -0
- {garf_core-0.0.8.dist-info → garf_core-0.0.10.dist-info}/top_level.txt +0 -0
garf_core/__init__.py
CHANGED
garf_core/api_clients.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -22,6 +22,8 @@ from collections.abc import Sequence
|
|
22
22
|
import requests
|
23
23
|
from typing_extensions import override
|
24
24
|
|
25
|
+
from garf_core import exceptions
|
26
|
+
|
25
27
|
|
26
28
|
@dataclasses.dataclass
|
27
29
|
class GarfApiRequest:
|
@@ -35,7 +37,7 @@ class GarfApiResponse:
|
|
35
37
|
results: list
|
36
38
|
|
37
39
|
|
38
|
-
class GarfApiError(
|
40
|
+
class GarfApiError(exceptions.GarfError):
|
39
41
|
"""API specific exception."""
|
40
42
|
|
41
43
|
|
garf_core/base_query.py
CHANGED
garf_core/exceptions.py
CHANGED
@@ -22,38 +22,10 @@
|
|
22
22
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
23
23
|
# See the License for the specific language governing permissions and
|
24
24
|
# limitations under the License.
|
25
|
-
"""
|
25
|
+
"""Defines common exceptions for the garf-core library."""
|
26
26
|
|
27
27
|
from __future__ import annotations
|
28
28
|
|
29
29
|
|
30
30
|
class GarfError(Exception):
|
31
31
|
"""Base exception."""
|
32
|
-
|
33
|
-
|
34
|
-
class GarfQueryException(GarfError):
|
35
|
-
"""Base exception for Garf queries."""
|
36
|
-
|
37
|
-
|
38
|
-
class GarfParserException(GarfError):
|
39
|
-
"""Base exception for Garf parsers."""
|
40
|
-
|
41
|
-
|
42
|
-
class GarfCustomizerException(GarfParserException):
|
43
|
-
"""Specifies incorrect customizer."""
|
44
|
-
|
45
|
-
|
46
|
-
class GarfVirtualColumnException(GarfParserException):
|
47
|
-
"""Specifies incorrect virtual column type."""
|
48
|
-
|
49
|
-
|
50
|
-
class GarfFieldException(GarfQueryException):
|
51
|
-
"""Specifies incorrect Google Ads API field."""
|
52
|
-
|
53
|
-
|
54
|
-
class GarfMacroException(GarfQueryException):
|
55
|
-
"""Specifies incorrect macro in Garf query."""
|
56
|
-
|
57
|
-
|
58
|
-
class GarfResourceException(GarfQueryException):
|
59
|
-
"""Specifies incorrect resource name in Google Ads API."""
|
garf_core/parsers.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -11,11 +11,7 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
"""Module for defining various parsing strategy for
|
15
|
-
|
16
|
-
GoogleAdsRowParser parses a single GoogleAdsRow and applies different parsing
|
17
|
-
strategies to each element of the row.
|
18
|
-
"""
|
14
|
+
"""Module for defining various parsing strategy for API response."""
|
19
15
|
|
20
16
|
from __future__ import annotations
|
21
17
|
|
@@ -24,7 +20,7 @@ import contextlib
|
|
24
20
|
import functools
|
25
21
|
import operator
|
26
22
|
from collections.abc import Mapping, MutableSequence
|
27
|
-
from typing import Union
|
23
|
+
from typing import Any, Union
|
28
24
|
|
29
25
|
from typing_extensions import TypeAlias, override
|
30
26
|
|
@@ -34,44 +30,56 @@ ApiRowElement: TypeAlias = Union[int, float, str, bool, list, None]
|
|
34
30
|
|
35
31
|
|
36
32
|
class BaseParser(abc.ABC):
|
37
|
-
|
33
|
+
"""An interface for all parsers to implement."""
|
34
|
+
|
38
35
|
def parse_response(
|
39
|
-
self,
|
36
|
+
self,
|
37
|
+
response: api_clients.GarfApiResponse,
|
38
|
+
query_specification: query_editor.BaseQueryElements,
|
40
39
|
) -> list[list[ApiRowElement]]:
|
41
40
|
"""Parses response."""
|
41
|
+
if not response.results:
|
42
|
+
return [[]]
|
43
|
+
results = []
|
44
|
+
for result in response.results:
|
45
|
+
results.append(self.parse_row(result, query_specification))
|
46
|
+
return results
|
47
|
+
|
48
|
+
@abc.abstractmethod
|
49
|
+
def parse_row(self, row, query_specification):
|
50
|
+
"""Parses single row from response."""
|
42
51
|
|
43
52
|
|
44
53
|
class ListParser(BaseParser):
|
54
|
+
"""Returns API results as is."""
|
55
|
+
|
45
56
|
@override
|
46
|
-
def
|
57
|
+
def parse_row(
|
47
58
|
self,
|
48
|
-
|
59
|
+
row: list,
|
49
60
|
query_specification: query_editor.BaseQueryElements,
|
50
61
|
) -> list[list[ApiRowElement]]:
|
51
|
-
|
52
|
-
return response.results
|
62
|
+
return row
|
53
63
|
|
54
64
|
|
55
65
|
class DictParser(BaseParser):
|
66
|
+
"""Extracts nested dict elements."""
|
67
|
+
|
56
68
|
@override
|
57
|
-
def
|
69
|
+
def parse_row(
|
58
70
|
self,
|
59
|
-
|
71
|
+
row: list,
|
60
72
|
query_specification: query_editor.BaseQueryElements,
|
61
73
|
) -> list[list[ApiRowElement]]:
|
62
|
-
if not
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
results.append(row)
|
72
|
-
return results
|
73
|
-
|
74
|
-
def get_nested_field(self, dictionary, key):
|
74
|
+
if not isinstance(row, Mapping):
|
75
|
+
raise GarfParserError
|
76
|
+
result = []
|
77
|
+
for field in query_specification.fields:
|
78
|
+
result.append(self.get_nested_field(row, field))
|
79
|
+
return result
|
80
|
+
|
81
|
+
def get_nested_field(self, dictionary: dict[str, Any], key: str):
|
82
|
+
"""Returns nested fields from a dictionary."""
|
75
83
|
key = key.split('.')
|
76
84
|
try:
|
77
85
|
return functools.reduce(operator.getitem, key, dictionary)
|
@@ -80,7 +88,11 @@ class DictParser(BaseParser):
|
|
80
88
|
|
81
89
|
|
82
90
|
class NumericConverterDictParser(DictParser):
|
83
|
-
|
91
|
+
"""Extracts nested dict elements with numerical conversions."""
|
92
|
+
|
93
|
+
def get_nested_field(self, dictionary: dict[str, Any], key: str):
|
94
|
+
"""Extract nested field with int/float conversion."""
|
95
|
+
|
84
96
|
def convert_field(value):
|
85
97
|
for type_ in (int, float):
|
86
98
|
with contextlib.suppress(ValueError):
|
garf_core/query_editor.py
CHANGED
@@ -29,6 +29,30 @@ from typing_extensions import Self
|
|
29
29
|
from garf_core import exceptions
|
30
30
|
|
31
31
|
|
32
|
+
class GarfQueryError(exceptions.GarfError):
|
33
|
+
"""Base exception for Garf queries."""
|
34
|
+
|
35
|
+
|
36
|
+
class GarfCustomizerError(GarfQueryError):
|
37
|
+
"""Specifies incorrect customizer."""
|
38
|
+
|
39
|
+
|
40
|
+
class GarfVirtualColumnError(GarfQueryError):
|
41
|
+
"""Specifies incorrect virtual column type."""
|
42
|
+
|
43
|
+
|
44
|
+
class GarfFieldError(GarfQueryError):
|
45
|
+
"""Specifies incorrect fields from API."""
|
46
|
+
|
47
|
+
|
48
|
+
class GarfMacroError(GarfQueryError):
|
49
|
+
"""Specifies incorrect macro in Garf query."""
|
50
|
+
|
51
|
+
|
52
|
+
class GarfResourceError(GarfQueryError):
|
53
|
+
"""Specifies incorrect resource name in the query."""
|
54
|
+
|
55
|
+
|
32
56
|
@dataclasses.dataclass
|
33
57
|
class ProcessedField:
|
34
58
|
"""Helper class to store fields with its customizers.
|
@@ -141,7 +165,7 @@ class VirtualColumn:
|
|
141
165
|
substitute_expression=substitute_expression.replace('.', '_'),
|
142
166
|
)
|
143
167
|
if not _is_quoted_string(field):
|
144
|
-
raise
|
168
|
+
raise GarfFieldError(f"Incorrect field '{field}'.")
|
145
169
|
field = field.replace("'", '').replace('"', '')
|
146
170
|
field = field.format(**macros) if macros else field
|
147
171
|
return VirtualColumn(type='built-in', value=field)
|
@@ -183,9 +207,7 @@ class ExtractedLineElements:
|
|
183
207
|
else:
|
184
208
|
customizer = {}
|
185
209
|
if virtual_column and not alias:
|
186
|
-
raise
|
187
|
-
'Virtual attributes should be aliased'
|
188
|
-
)
|
210
|
+
raise GarfVirtualColumnError('Virtual attributes should be aliased')
|
189
211
|
return ExtractedLineElements(
|
190
212
|
field=_format_type_field_name(field)
|
191
213
|
if not virtual_column and field
|
@@ -209,15 +231,14 @@ class BaseQueryElements:
|
|
209
231
|
"""Contains raw query and parsed elements.
|
210
232
|
|
211
233
|
Attributes:
|
212
|
-
|
213
|
-
|
234
|
+
title: Title of the query that needs to be parsed.
|
235
|
+
text: Text of the query that needs to be parsed.
|
214
236
|
resource_name: Name of Google Ads API reporting resource.
|
215
237
|
fields: Ads API fields that need to be fetched.
|
216
238
|
column_names: Friendly names for fields which are used when saving data
|
217
239
|
column_names: Friendly names for fields which are used when saving data
|
218
240
|
customizers: Attributes of fields that need to be be extracted.
|
219
241
|
virtual_columns: Attributes of fields that need to be be calculated.
|
220
|
-
is_constant_resource: Whether resource considered a constant one.
|
221
242
|
is_builtin_query: Whether query is built-in.
|
222
243
|
"""
|
223
244
|
|
@@ -234,6 +255,7 @@ class BaseQueryElements:
|
|
234
255
|
virtual_columns: dict[str, VirtualColumn] = dataclasses.field(
|
235
256
|
default_factory=dict
|
236
257
|
)
|
258
|
+
is_builtin_query: bool = False
|
237
259
|
|
238
260
|
def __eq__(self, other: BaseQueryElements) -> bool: # noqa: D105
|
239
261
|
return (
|
@@ -381,9 +403,7 @@ class QuerySpecification(CommonParametersMixin, TemplateProcessorMixin):
|
|
381
403
|
try:
|
382
404
|
self.query.text = query_text.format(**self.macros).strip()
|
383
405
|
except KeyError as e:
|
384
|
-
raise
|
385
|
-
f'No value provided for macro {str(e)}.'
|
386
|
-
) from e
|
406
|
+
raise GarfMacroError(f'No value provided for macro {str(e)}.') from e
|
387
407
|
return self
|
388
408
|
|
389
409
|
def remove_comments(self) -> Self:
|
@@ -424,9 +444,7 @@ class QuerySpecification(CommonParametersMixin, TemplateProcessorMixin):
|
|
424
444
|
):
|
425
445
|
self.query.resource_name = str(resource_name[0]).strip()
|
426
446
|
return self
|
427
|
-
raise
|
428
|
-
f'No resource found in query: {self.query.text}'
|
429
|
-
)
|
447
|
+
raise GarfResourceError(f'No resource found in query: {self.query.text}')
|
430
448
|
|
431
449
|
def extract_fields(self) -> Self:
|
432
450
|
for line in self._extract_query_lines():
|
garf_core/report.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -27,7 +27,7 @@ import json
|
|
27
27
|
import warnings
|
28
28
|
from collections import defaultdict
|
29
29
|
from collections.abc import MutableSequence, Sequence
|
30
|
-
from typing import Generator, Literal
|
30
|
+
from typing import Generator, Literal, get_args
|
31
31
|
|
32
32
|
from garf_core import exceptions, parsers, query_editor
|
33
33
|
|
@@ -414,8 +414,8 @@ class GarfReport:
|
|
414
414
|
import polars as pl
|
415
415
|
except ImportError as e:
|
416
416
|
raise ImportError(
|
417
|
-
'Please install garf-
|
418
|
-
'- `pip install garf-
|
417
|
+
'Please install garf-core with Polars support '
|
418
|
+
'- `pip install garf-core[polars]`'
|
419
419
|
) from e
|
420
420
|
return cls(
|
421
421
|
results=df.to_numpy().tolist(), column_names=list(df.schema.keys())
|
@@ -438,11 +438,63 @@ class GarfReport:
|
|
438
438
|
import pandas as pd
|
439
439
|
except ImportError as e:
|
440
440
|
raise ImportError(
|
441
|
-
'Please install garf-
|
442
|
-
'- `pip install garf-
|
441
|
+
'Please install garf-core with Pandas support '
|
442
|
+
'- `pip install garf-core[pandas]`'
|
443
443
|
) from e
|
444
444
|
return cls(results=df.values.tolist(), column_names=list(df.columns.values))
|
445
445
|
|
446
|
+
@classmethod
|
447
|
+
def from_json(cls, json_str: str) -> GarfReport:
|
448
|
+
"""Creates a GarfReport object from a JSON string.
|
449
|
+
|
450
|
+
Args:
|
451
|
+
json_str: JSON string representation of the data.
|
452
|
+
|
453
|
+
Returns:
|
454
|
+
Report build from a json string.
|
455
|
+
|
456
|
+
Raises:
|
457
|
+
TypeError: If any value in the JSON data is not a supported type.
|
458
|
+
ValueError: If `data` is a list but not all dictionaries
|
459
|
+
have the same keys.
|
460
|
+
"""
|
461
|
+
data = json.loads(json_str)
|
462
|
+
|
463
|
+
def validate_value(value):
|
464
|
+
if not isinstance(value, get_args(parsers.ApiRowElement)):
|
465
|
+
raise TypeError(
|
466
|
+
f'Unsupported type {type(value)} for value {value}. '
|
467
|
+
'Expected types: int, float, str, bool, list, or None.'
|
468
|
+
)
|
469
|
+
return value
|
470
|
+
|
471
|
+
# Case 1: `data` is a dictionary
|
472
|
+
if isinstance(data, dict):
|
473
|
+
column_names = list(data.keys())
|
474
|
+
if not data.values():
|
475
|
+
results = []
|
476
|
+
else:
|
477
|
+
results = [[validate_value(value) for value in data.values()]]
|
478
|
+
|
479
|
+
# Case 2: `data` is a list of dictionaries, each representing a row
|
480
|
+
elif isinstance(data, list):
|
481
|
+
column_names = list(data[0].keys()) if data else []
|
482
|
+
for row in data:
|
483
|
+
if not isinstance(row, dict):
|
484
|
+
raise TypeError('All elements in the list must be dictionaries.')
|
485
|
+
if list(row.keys()) != column_names:
|
486
|
+
raise ValueError(
|
487
|
+
'All dictionaries must have consistent keys in the same order.'
|
488
|
+
)
|
489
|
+
results = [
|
490
|
+
[validate_value(value) for value in row.values()] for row in data
|
491
|
+
]
|
492
|
+
else:
|
493
|
+
raise TypeError(
|
494
|
+
'Input JSON must be a dictionary or a list of dictionaries.'
|
495
|
+
)
|
496
|
+
return cls(results=results, column_names=column_names)
|
497
|
+
|
446
498
|
|
447
499
|
class GarfRow:
|
448
500
|
"""Helper class to simplify iteration of GarfReport.
|
garf_core/report_fetcher.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLf
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -126,12 +126,17 @@ class RestApiReportFetcher(ApiReportFetcher):
|
|
126
126
|
self,
|
127
127
|
endpoint: str,
|
128
128
|
parser: parsers.BaseParser = parsers.DictParser,
|
129
|
+
query_specification_builder: query_editor.QuerySpecification = (
|
130
|
+
query_editor.QuerySpecification
|
131
|
+
),
|
132
|
+
**kwargs: str,
|
129
133
|
) -> None:
|
130
134
|
"""Instantiates RestApiReportFetcher.
|
131
135
|
|
132
136
|
Args:
|
133
137
|
endpoint: URL of API endpoint.
|
134
138
|
parser: Type of parser to convert API response.
|
139
|
+
query_specification_builder: Class to perform query parsing.
|
135
140
|
"""
|
136
|
-
|
137
|
-
|
141
|
+
api_client = api_clients.RestApiClient(endpoint)
|
142
|
+
super().__init__(api_client, parser, query_specification_builder, **kwargs)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: garf-core
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.10
|
4
4
|
Summary: Abstracts fetching data from API based on provided SQL-like query.
|
5
5
|
Author-email: "Google Inc. (gTech gPS CSE team)" <no-reply@google.com>
|
6
6
|
License: Apache 2.0
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
13
14
|
Classifier: Intended Audience :: Developers
|
14
15
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
15
16
|
Classifier: Operating System :: OS Independent
|
@@ -17,8 +18,10 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
17
18
|
Requires-Python: >=3.8
|
18
19
|
Description-Content-Type: text/markdown
|
19
20
|
Requires-Dist: python-dateutil
|
20
|
-
Requires-Dist: jinja2
|
21
|
+
Requires-Dist: jinja2
|
21
22
|
Requires-Dist: typing-extensions
|
23
|
+
Requires-Dist: requests
|
24
|
+
Requires-Dist: pyyaml
|
22
25
|
Provides-Extra: pandas
|
23
26
|
Requires-Dist: pandas; extra == "pandas"
|
24
27
|
Provides-Extra: polars
|
@@ -0,0 +1,13 @@
|
|
1
|
+
garf_core/__init__.py,sha256=peAYh1vGVtPtI3ZJM8_HcDmFhOuovynr8ADtcqGfFHM,599
|
2
|
+
garf_core/api_clients.py,sha256=S7Ldmgf0H9Vew2BdfG3Yh6imkhdnr8ix_YHKJmGln-U,2280
|
3
|
+
garf_core/base_query.py,sha256=ZDAw2ojmismXRO0HXEvKDukpS7OAc7390LnM8kvCSCY,1201
|
4
|
+
garf_core/exceptions.py,sha256=Gzvkl2M-rA_XQRAMd3CC62KHeFQE_b6uby0fD0pouw4,1269
|
5
|
+
garf_core/parsers.py,sha256=Uj7aT7roYUofivrBKVKWfcSeG37oYGGLIJ-op2C3hZc,3238
|
6
|
+
garf_core/query_editor.py,sha256=MVfrZdNlJ0EBDPc2ux7XxSArRyOo1QoXvrnKYn6aHTs,16381
|
7
|
+
garf_core/report.py,sha256=xLMKzgcNzmdwLa__rAqlJjBBZZi6ya-9csb1MfbCM74,19588
|
8
|
+
garf_core/report_fetcher.py,sha256=1PtJ8iQwlEcUeW6k1lVZiXh-3hop5x2YHQe-EaKYGNs,4405
|
9
|
+
garf_core-0.0.10.dist-info/METADATA,sha256=p5FyHcQGWfZdVdNPCaCFe_JDZiTvLOxSj7w_EHEA-4g,2425
|
10
|
+
garf_core-0.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
garf_core-0.0.10.dist-info/entry_points.txt,sha256=ODxSZXwWEIXQAjRwBQsBJ6GYw8oPK6DYTo0oDVkKCpo,39
|
12
|
+
garf_core-0.0.10.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
|
13
|
+
garf_core-0.0.10.dist-info/RECORD,,
|
garf_core-0.0.8.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
garf_core/__init__.py,sha256=-2NI7Qm5ndrlcaz3itBBuFXOKu4anul2TdJro2G5oRo,598
|
2
|
-
garf_core/api_clients.py,sha256=FKmwts_QBV_E4aTmrnsZtow0CmSExLibfeSkMSrudU0,2235
|
3
|
-
garf_core/base_query.py,sha256=uEhKP56wcFtMiSwYOxoZ0q7OEvURGVOeRdpSYjxWLho,1201
|
4
|
-
garf_core/exceptions.py,sha256=4qvNN-8GqbbVsrLKxJwBeX1FUtWpKbhUWyvm7anD3iE,1916
|
5
|
-
garf_core/parsers.py,sha256=Y4wwpDXRZky7LBTBklvzk-aDNxgRL5RJg52rSdH5t9Q,2928
|
6
|
-
garf_core/query_editor.py,sha256=EzwjFnbje-RcP9VwWQwzCpy8lipt4tph3Kv6q5Lly5o,15994
|
7
|
-
garf_core/report.py,sha256=xlkZ44cdT_uGENuNeGwy69h7XLtwjdUjyPw3S1m-zc8,17852
|
8
|
-
garf_core/report_fetcher.py,sha256=j1ewWoUvnEGM5vmWihFJ7vSVVEG1t2yzapKhylc-7eQ,4157
|
9
|
-
garf_core-0.0.8.dist-info/METADATA,sha256=Ap-TAWp1wzDtXO6WPUBE1ajGnzQ_YMUIRiIhZbzLcm8,2334
|
10
|
-
garf_core-0.0.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
11
|
-
garf_core-0.0.8.dist-info/entry_points.txt,sha256=ODxSZXwWEIXQAjRwBQsBJ6GYw8oPK6DYTo0oDVkKCpo,39
|
12
|
-
garf_core-0.0.8.dist-info/top_level.txt,sha256=Gj-Zp7fM2turGut5vTJuo5xEcKfow7cTLX3y3WFfNgA,10
|
13
|
-
garf_core-0.0.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|