Skip to content

Commit 53da153

Browse files
committed
API: Adding iso datetimes to datalake response
-Fix mistake made in tests of bad dict testing. -Fixing epoc to UTC conversion. Handled None case. -Change naming from UTC to iso -Make precision to ms and add Z -Ading pytest -n auto plugin -Better devshell name -Fix Tests to run on new python.
1 parent f61854d commit 53da153

File tree

10 files changed

+119
-28
lines changed

10 files changed

+119
-28
lines changed

.claude/settings.local.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(gh search issues --repo spulec/moto \"S3 content_length\" --limit 10)"
5+
]
6+
}
7+
}

.github/workflows/actions.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ on: [push, pull_request]
33

44
jobs:
55
test-client:
6-
runs-on: ubuntu-20.04
6+
runs-on: ubuntu-latest
7+
timeout-minutes: 30 # Add this line
78
strategy:
89
matrix:
910
python: [3.8, 3.9, "3.10", 3.12]
1011
extras: ["test", "test,queuable,sentry"]
1112
steps:
1213
- name: Setup Python
13-
uses: actions/setup-python@v2.2.2
14+
uses: actions/setup-python@v5
1415
with:
1516
python-version: ${{ matrix.python }}
1617
- name: Check out repository code
@@ -20,13 +21,16 @@ jobs:
2021
- name: Test
2122
working-directory: ./client
2223
run: |
23-
pip install -e .[${{ matrix.extras }}]
24-
py.test
24+
pip --version
25+
pip install --verbose .[${{ matrix.extras }}]
26+
27+
# Run pytest with specific path and import mode
28+
python -m pytest --import-mode=importlib ./datalake/tests/
2529
test-docker:
2630
runs-on: ubuntu-latest
2731
steps:
2832
- name: Check out repository code
29-
uses: actions/checkout@v2
33+
uses: actions/checkout@v4
3034
with:
3135
fetch-depth: 0
3236
- name: Test

Dockerfile

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,32 @@ ENV LC_ALL C.UTF-8
77

88
# TODO: keep requirements in one place
99
RUN pip install \
10+
'boto==2.49.0' \
11+
'boto3==1.35.41' \
12+
'botocore==1.35.64' \
13+
'datalake<2' \
14+
'flake8>=2.5.0,<4.1' \
15+
'freezegun<1' \
16+
'moto<3' \
17+
'pytest<8' \
18+
'responses<0.22.0' \
19+
'tox>4,<5' \
20+
# test requirements
1021
blinker>=1.4 \
11-
boto3>=1.1.3 \
1222
click>=5.1 \
13-
Flask>=0.10.1 \
1423
flask-swagger>=0.2.14 \
24+
Flask>=0.10.1 \
1525
memoized_property>=1.0.1 \
26+
pyinotify>=0.9.4, \
1627
python-dateutil>=2.4.2 \
1728
python-dotenv>=0.1.3 \
1829
pytz>=2015.4 \
19-
sentry-sdk[flask]>=0.19.5 \
30+
raven>=5.0.0 \
2031
requests>=2.5 \
32+
sentry-sdk[flask]>=0.19.5 \
2133
simplejson>=3.3.1 \
22-
six>=1.10.0 \
23-
# test requirements
24-
'flake8>=2.5.0,<4.1' \
25-
'freezegun<1' \
26-
'moto<3' \
27-
'pytest<8' \
28-
'responses<0.22.0' \
29-
pyinotify>=0.9.4, \
30-
raven>=5.0.0 \
31-
'tox>4,<5' \
32-
'datalake<2'
34+
six>=1.10.0
35+
3336

3437
RUN mkdir -p /opt/
3538
COPY . /opt/

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ IMAGE="$(REPO)/$(REPO_PATH):$(VERSION)"
77
docker: version
88
docker build --build-arg VERSION=$(VERSION) -t $(IMAGE) .
99

10-
.PHONY: devshell # Open a developer shell in the docker env
11-
devshell: docker
10+
.PHONY: dev # Open a developer shell in the docker env
11+
dev: docker
1212
docker run --rm -it -v $$PWD:/opt --entrypoint /bin/bash $(IMAGE)
1313

1414
test-client: docker

api/datalake_api/v0.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from flask import current_app as app
1919
import os
2020
import simplejson as json
21+
from datetime import datetime, timezone
22+
import decimal
2123
from .querier import ArchiveQuerier, Cursor, InvalidCursor, \
2224
DEFAULT_LOOKBACK_DAYS
2325
from .fetcher import ArchiveFileFetcher
@@ -29,6 +31,38 @@
2931

3032
_archive_querier = None
3133

34+
35+
def unix_ms_to_utc_iso(unix_ms):
36+
if unix_ms is None:
37+
return unix_ms
38+
unix_ms_to_iso = unix_ms
39+
if isinstance(unix_ms_to_iso, decimal.Decimal):
40+
unix_ms_to_iso = float(unix_ms_to_iso)
41+
iso = datetime.fromtimestamp(
42+
unix_ms_to_iso / 1000.0, tz=timezone.utc
43+
).isoformat(timespec='milliseconds').replace('+00:00', 'Z')
44+
return iso
45+
46+
47+
def add_utc_metadata(metadata):
48+
"""Add ISO-8601 UTC timestamp fields to metadata dict
49+
50+
This function takes a metadata dict and adds start_iso and end_iso fields
51+
based on existing start and end epoch timestamps
52+
iso precision is set to milliseconds
53+
Can be expanded to add any api-level metadata
54+
"""
55+
if not metadata:
56+
return metadata
57+
58+
start_iso = unix_ms_to_utc_iso(metadata['start'])
59+
end_iso = unix_ms_to_utc_iso(metadata['end'])
60+
61+
metadata['start_iso'] = start_iso
62+
metadata['end_iso'] = end_iso
63+
return metadata
64+
65+
3266
def _get_aws_kwargs():
3367
kwargs = dict(
3468
region_name=app.config.get('AWS_REGION'),
@@ -305,6 +339,14 @@ def files_get():
305339
type: string
306340
description: 16-byte blake2 hash of the file
307341
content
342+
start_iso:
343+
type: string
344+
description: the start time of the file in ISO
345+
format UTC iso timezone
346+
end_iso:
347+
type: string
348+
description: the end time of the file in ISO
349+
format UTC iso timezone
308350
309351
next:
310352
type: string
@@ -349,7 +391,10 @@ def files_get():
349391
where=params.get('where'),
350392
cursor=params.get('cursor'))
351393

352-
[r.update(http_url=_get_canonical_http_url(r)) for r in results]
394+
for r in results:
395+
r.update(http_url=_get_canonical_http_url(r))
396+
r['metadata'] = add_utc_metadata(r['metadata'])
397+
353398
response = {
354399
'records': results,
355400
'next': _get_next_url(flask.request, results),
@@ -476,6 +521,7 @@ def file_get_metadata(file_id):
476521
id: DatalakeAPIError
477522
'''
478523
f = _get_file(file_id)
524+
f.metadata = add_utc_metadata(f.metadata)
479525
return Response(json.dumps(f.metadata), content_type='application/json')
480526

481527

@@ -542,6 +588,7 @@ def latest_get(what, where):
542588
params = _validate_latest_params(params)
543589
f = _get_latest(what, where, params.get('lookback', DEFAULT_LOOKBACK_DAYS))
544590
f.update(http_url=_get_canonical_http_url(f))
591+
f['metadata'] = add_utc_metadata(f['metadata'])
545592
return Response(json.dumps(f), content_type='application/json')
546593

547594

api/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_version_from_pyver():
3030
if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv:
3131
raise ImportError('You must install pyver to create a package')
3232
else:
33-
return 'noversion'
33+
return '0.0.0'
3434
version, version_info = pyver.get_version(pkg="datalake_api",
3535
public=True)
3636
return version

api/tests/test_metadata.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations under
1313
# the License.
14+
from datetime import datetime, timezone
15+
from decimal import Decimal
1416
import pytest
1517
import simplejson as json
1618

@@ -32,7 +34,20 @@ def test_get_metadata(metadata_getter, s3_file_maker, random_metadata):
3234
res = metadata_getter('12345')
3335
assert res.status_code == 200
3436
assert res.content_type == 'application/json'
35-
assert json.loads(res.data) == random_metadata
37+
res_data = json.loads(res.data)
38+
for k, v in res_data.items():
39+
if k == 'start_iso' or k == 'end_iso':
40+
k_epoch = k.replace('_iso','')
41+
v_epoch = res_data[k_epoch]
42+
if v is None:
43+
assert v == v_epoch
44+
45+
expected_v_iso = datetime.fromtimestamp(
46+
v_epoch / 1000.0, tz=timezone.utc
47+
).isoformat(timespec='milliseconds').replace('+00:00', 'Z')
48+
assert v == expected_v_iso
49+
else:
50+
assert v == random_metadata[k]
3651

3752

3853
def test_no_such_metadata(s3_bucket_maker, metadata_getter):

client/datalake/tests/test_record.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# License for the specific language governing permissions and limitations under
1313
# the License.
1414

15+
import importlib
16+
import sys
17+
1518
import pytest
1619

1720
from datalake.common import has_s3, DatalakeRecord, InvalidDatalakeMetadata
@@ -117,4 +120,14 @@ def test_record_size_and_create_time(s3_file_maker, random_metadata):
117120
for r in records:
118121
assert r['metadata'] == random_metadata
119122
assert abs(r['create_time'] - now) <= max_tolerable_delta
120-
assert r['size'] == 25
123+
124+
moto_version = importlib.metadata.version('moto')
125+
moto_major = int(moto_version.split('.')[0])
126+
moto_minor = int(moto_version.split('.')[1])
127+
expected_size = 25 if moto_major >= 2 and moto_minor >= 1 else 67
128+
# Moto byte count fix around 2.1.0
129+
130+
print(f"\nDEBUG: Python version: {sys.version}")
131+
print(f"DEBUG: Moto version: {moto_version}")
132+
print(f"DEBUG: Actual file size: {r['size']}")
133+
assert r['size'] == expected_size

client/pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ dynamic = ["version"]
3737
test = [
3838
'pytest<8.0.0',
3939
'pytest-cov>=2.5.1,<4',
40-
'moto[s3]>4,<5',
40+
'moto[s3]>4,<5;python_version>="3.10"',
41+
'moto[s3]<3;python_version<"3.10"',
4142
'twine<4.0.0',
4243
'pip>=20.0.0,<22.0.0',
4344
'wheel<0.38.0',
@@ -73,7 +74,8 @@ distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
7374
# Example formatted version: 1.2.3+42.ge174a1f.dirty
7475

7576
[tool.pytest.ini_options]
76-
addopts = "--cov=planet.mc_client --cov-config .coveragerc"
77+
# Commented out coverage options to fix test execution
78+
#addopts = "--cov=datalake"
7779
markers = [
7880
"slow: marks tests as slow (deselect with '-m \"not slow\"')"
7981
]

ingester/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_version_from_pyver():
3030
if 'sdist' in sys.argv or 'bdist_wheel' in sys.argv:
3131
raise ImportError('You must install pyver to create a package')
3232
else:
33-
return 'noversion'
33+
return '0.0.0'
3434
version, version_info = pyver.get_version(pkg="datalake_ingester",
3535
public=True)
3636
return version

0 commit comments

Comments
 (0)