Skip to content

Commit d37796d

Browse files
authored
Merge pull request #56 from ecmwf/develop
1.0.1
2 parents c3dd253 + 1f12a04 commit d37796d

19 files changed

+173
-115
lines changed

.github/workflows/check-publish.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
tags:
99
- '**'
1010

11-
pull_request: ~
11+
# pull_request: ~
1212

1313
workflow_dispatch: ~
1414

@@ -24,7 +24,20 @@ jobs:
2424
- run: isort --check .
2525
- run: black --check .
2626
- run: flake8 .
27-
27+
unit-tests:
28+
name: Unit tests
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
- name: Set up Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version: '3.11'
36+
cache: 'pip'
37+
- name: Install dependencies
38+
run: pip install -r requirements.txt
39+
- name: Run tests
40+
run: pytest tests/unit
2841
docs:
2942
if: |
3043
(github.event_name == 'push' && contains(github.ref, 'refs/tags/')) ||

conftest.py

Whitespace-only changes.

polytope_server/common/auth.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def __init__(self, config):
7272
def authenticate(self, auth_header) -> User:
7373
"""Returns authenticated User, or raises UnauthorizedRequest"""
7474

75+
logging.info("Authenticating user with header:\n{}".format(auth_header))
76+
7577
headers = auth_header.split(",")
7678
if len(headers) == 0:
7779
raise UnauthorizedRequest(
@@ -125,7 +127,7 @@ def authenticate(self, auth_header) -> User:
125127
user.roles = list(set.union(set(user.roles), set(authorizer.get_roles(user))))
126128
user.attributes.update(authorizer.get_attributes(user))
127129

128-
logging.debug("User authenticated:\n {}".format(user.serialize()))
130+
logging.info("User authenticated:\n {}".format(user.serialize()))
129131

130132
return user
131133

polytope_server/common/datasource/mars.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,20 +279,20 @@ def parse_relativedelta(self, time_str):
279279

280280
return relativedelta(days=time_dict["d"], hours=time_dict["h"], minutes=time_dict["m"])
281281

282-
def date_check(self, date, offsets, after=False):
282+
def date_check(self, date, offset, after=False):
283283
"""Process special match rules for DATE constraints"""
284284

285285
date = str(date)
286286

287287
# Default date is -1
288-
if len(str(date)) == 0:
288+
if len(date) == 0:
289289
date = "-1"
290290

291291
now = datetime.today()
292-
offset = now - self.parse_relativedelta(offsets)
292+
offset = now - self.parse_relativedelta(offset)
293293
offset_fmted = offset.strftime("%Y%m%d")
294294

295-
split = str(date).split("/")
295+
split = date.split("/")
296296

297297
# YYYYMMDD
298298
if len(split) == 1:

polytope_server/common/datasource/polytope.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -170,26 +170,7 @@ def match(self, request):
170170

171171
# Check data released
172172
if SCHEDULE_READER is not None and self.obey_schedule:
173-
# Check if step is in feature
174-
if "step" in r:
175-
step = r["step"]
176-
elif r["feature"]["type"] == "timeseries":
177-
step = r["feature"]["range"]["end"]
178-
elif r["feature"]["type"] == "trajectory" and "step" in r["feature"]["axes"]:
179-
# get index of step in axes, then get max step from trajectory
180-
step = r["feature"]["axes"].index("step")
181-
step = r["feature"]["points"][step].max()
182-
else:
183-
raise PolytopeError("'step' not found in request")
184-
SCHEDULE_READER.check_released(
185-
r["date"],
186-
r["class"],
187-
r["stream"],
188-
r.get("domain", "g"),
189-
r["time"],
190-
str(step),
191-
r["type"],
192-
)
173+
SCHEDULE_READER.check_released_polytope_request(r)
193174

194175
def destroy(self, request) -> None:
195176
pass

polytope_server/common/io/fifo.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ def data(self, buffer_size=2 * 1024 * 1024):
5959
if buffer != b"":
6060
yield buffer
6161

62-
# self.delete()
63-
6462
def delete(self):
6563
"""Close and delete FIFO"""
6664
logging.info("Deleting FIFO.")

polytope_server/common/logging.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
# Indexable fields
4242
INDEXABLE_FIELDS = {"request_id": str}
43-
DEFAULT_LOGGING_MODE = "prettyprint" # Changed from "json" to "prettyprint" or "console"
43+
DEFAULT_LOGGING_MODE = "json"
4444
DEFAULT_LOGGING_LEVEL = "INFO"
4545

4646

@@ -120,12 +120,14 @@ def format(self, record):
120120

121121
if self.mode == "logserver":
122122
return self.format_for_logserver(record, result)
123-
elif self.mode == "prettyprint":
123+
if self.mode == "prettyprint":
124124
return json.dumps(
125125
result, indent=2, ensure_ascii=False
126126
) # Added ensure_ascii=False for correct Unicode display
127-
else:
127+
if self.mode == "json":
128128
return json.dumps(result, indent=None)
129+
# default to json
130+
return json.dumps(result, indent=None)
129131

130132

131133
def setup(config, source_name):
@@ -142,7 +144,4 @@ def setup(config, source_name):
142144
logger.addHandler(handler)
143145
logger.setLevel(level)
144146

145-
# Lower the logging level for pymongo
146-
logging.getLogger("pymongo").setLevel(logging.WARNING)
147-
148147
logger.info("Logging Initialized")

polytope_server/common/metric_store/mongodb_metric_store.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ def __init__(self, config=None):
4545
uri = config.get("uri", "mongodb://localhost:27017")
4646
metric_collection = config.get("collection", "metrics")
4747

48+
log_level = config.get("log_level", logging.WARNING)
49+
logging.getLogger("pymongo").setLevel(log_level)
50+
4851
username = config.get("username")
4952
password = config.get("password")
5053

polytope_server/common/request_store/mongodb_request_store.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class MongoRequestStore(request_store.RequestStore):
3737
def __init__(self, config=None, metric_store_config=None):
3838
uri = config.get("uri", "mongodb://localhost:27017")
3939
request_collection = config.get("collection", "requests")
40+
log_level = config.get("log_level", logging.WARNING)
41+
logging.getLogger("pymongo").setLevel(log_level)
4042
username = config.get("username")
4143
password = config.get("password")
4244

polytope_server/common/schedule.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,30 @@ def check_released(
9595
# f"Release time is {release_date}."
9696
)
9797

98+
def check_released_polytope_request(self, req):
99+
# Check if step is in feature
100+
if "step" in req:
101+
step = req["step"]
102+
elif "feature" not in req:
103+
raise PolytopeError("'step' not found in request")
104+
elif req["feature"]["type"] == "timeseries":
105+
step = req["feature"]["range"]["end"]
106+
elif req["feature"]["type"] == "trajectory" and "step" in req["feature"]["axes"]:
107+
# get index of step in axes, then get max step from trajectory
108+
step = req["feature"]["axes"].index("step")
109+
step = max([p[step] for p in req["feature"]["points"]])
110+
else:
111+
raise PolytopeError("'step' not found in request")
112+
self.check_released(
113+
req["date"],
114+
req["class"],
115+
req["stream"],
116+
req.get("domain", "g"),
117+
req["time"],
118+
str(step),
119+
req["type"],
120+
)
121+
98122
def get_release_time_and_delta_day(
99123
self, cclass: str, stream: str, domain: str, time_in: str, step: str, ttype: str
100124
) -> Tuple[Optional[str], Optional[int]]:

polytope_server/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020

2121

2222
# Single-source Polytope version number
23-
__version__ = "0.8.7"
23+
__version__ = "1.0.1"

tests/unit/test_datasource_dummy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@ def test_datasource_dummy_contains_pattern(self):
7171
for x in self.ds.result(self.request):
7272
data += x
7373
assert len(data) == 13
74-
assert data == b"polytopepolyt"
74+
assert data == b"xxxxxxxxxxxxx"

tests/unit/test_fifo.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ class Test:
2929
def setup_method(self, method):
3030
pass
3131

32-
def test_fifo(self):
32+
def test_fifo(self, tmp_path):
3333

34-
fifo = FIFO("test-fifo")
34+
fifo = FIFO("test-fifo", str(tmp_path))
3535

3636
assert not fifo.ready()
3737

@@ -62,21 +62,22 @@ def test_fifo(self):
6262

6363
assert count == 6
6464

65-
# the fifo has been deleted because we finished reading it
65+
fifo.delete()
66+
# the fifo has been deleted
6667
with pytest.raises(OSError):
67-
assert fifo.ready()
68+
assert not fifo.ready()
6869

6970
def read_all(self, fifo, result):
7071
while not fifo.ready():
7172
pass
7273
for x in fifo.data():
7374
result[0] += x
7475

75-
def test_fifo_buffered(self):
76+
def test_fifo_buffered(self, tmp_path):
7677

7778
# write 1 MiB of data, we need two threads
7879

79-
fifo = FIFO("test-fifo")
80+
fifo = FIFO("test-fifo", str(tmp_path))
8081

8182
data = [b""]
8283

tests/unit/test_logging.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
# does it submit to any jurisdiction.
1919
#
2020

21+
import json
2122
import logging
22-
import re
2323

2424
import pytest
2525

@@ -41,33 +41,45 @@ def test_logging_format(self):
4141
import polytope_server.common.logging as mylogging
4242

4343
formatter = mylogging.LogFormatter(mode="json")
44-
record = logging.LogRecord("polytope_server.tests.unit", 10, "/hello/world", 500, "Test Message", None, None)
4544

4645
# Normal record
47-
result = formatter.format(record)
48-
assert re.match(
49-
r'[\s\S]*{[\s\S]*"lvl"[\s\S]*:[\s\S]*"DEBUG"[\s\S]*,[\s\S]*"msg"[\s\S]*:[\s\S]*"Test Message"[\s\S]*,[\s\S]*"pth"[\s\S]*:[\s\S]*"/hello/world:500"[\s\S]*,[\s\S]*"src"[\s\S]*:[\s\S]*"polytope_server.tests.unit"[\s\S]*}[\s\S]*', # noqa
50-
result,
46+
log_message = {
47+
"name": "polytope_server.tests.unit",
48+
"filename": "world",
49+
"lineno": 500,
50+
"levelname": "DEBUG",
51+
"message": "Test Message",
52+
}
53+
record = logging.LogRecord(
54+
log_message["name"],
55+
logging.getLevelNamesMapping()[log_message["levelname"]],
56+
log_message["filename"],
57+
log_message["lineno"],
58+
log_message["message"],
59+
None,
60+
None,
5161
)
62+
result = json.loads(formatter.format(record))
63+
for k in log_message.keys():
64+
assert result[k] == log_message[k]
5265

5366
# Record with valid extra info
5467
record.request_id = "hello"
55-
result = formatter.format(record)
56-
assert re.match(
57-
r'[\s\S]*{[\s\S]*"lvl"[\s\S]*:[\s\S]*"DEBUG"[\s\S]*,[\s\S]*"msg"[\s\S]*:[\s\S]*"Test Message"[\s\S]*,[\s\S]*"pth"[\s\S]*:[\s\S]*"/hello/world:500"[\s\S]*,[\s\S]*"request_id"[\s\S]*:[\s\S]*"hello"[\s\S]*,[\s\S]*"src"[\s\S]*:[\s\S]*"polytope_server.tests.unit"[\s\S]*}[\s\S]*', # noqa
58-
result,
59-
)
68+
log_message["request_id"] = "hello"
69+
result = json.loads(formatter.format(record))
70+
for k in log_message.keys():
71+
assert result[k] == log_message[k]
6072

6173
# Record with invalid extra info (wrong type)
6274
record.request_id = 1234
6375
with pytest.raises(TypeError):
6476
result = formatter.format(record)
6577
del record.request_id
78+
del log_message["request_id"]
6679

6780
# Record with unknown extra info (silently ignores extra)
6881
record.unknown_extra_arg = "hello"
69-
result = formatter.format(record)
70-
assert re.match(
71-
r'[\s\S]*{[\s\S]*"lvl"[\s\S]*:[\s\S]*"DEBUG"[\s\S]*,[\s\S]*"msg"[\s\S]*:[\s\S]*"Test Message"[\s\S]*,[\s\S]*"pth"[\s\S]*:[\s\S]*"/hello/world:500"[\s\S]*,[\s\S]*"src"[\s\S]*:[\s\S]*"polytope_server.tests.unit"[\s\S]*}[\s\S]*', # noqa
72-
result,
73-
)
82+
result = json.loads(formatter.format(record))
83+
for k in log_message.keys():
84+
assert result[k] == log_message[k]
85+
assert "unknown_extra_arg" not in result

tests/unit/test_mars_match.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def setup_method(self, method):
4040

4141
self.mars_config = {
4242
"name": "mars",
43-
"match": {"class": ["od"], "stream": ["oper", "enfo"], "date": {"months": -1}},
43+
"match": {"class": ["od"], "stream": ["oper", "enfo"], "date": "> 30d"},
4444
}
4545
self.request = Request()
4646
self.request.user_request = yaml.dump(
@@ -50,7 +50,7 @@ def setup_method(self, method):
5050
"param": "165.128/166.128/167.128",
5151
"step": "0",
5252
"time": "00",
53-
"date": "20200225/to/20200227/by/3",
53+
"date": "-32",
5454
"type": "an",
5555
"class": "od",
5656
"expver": "0001",

tests/unit/test_mongo_client_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def _verify(
3737
):
3838
mock_mongo.assert_called_once()
3939
args, kwargs = mock_mongo.call_args
40-
assert args[0] == endpoint
40+
assert kwargs["host"] == endpoint
4141
if username:
4242
assert kwargs["username"] == username
4343
if password:

tests/unit/test_polytope_datasource_coercion.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,12 @@ def test_coerce():
5757
"foo": "bar",
5858
},
5959
}
60-
61-
assert Coercion.coerce(request_mars) == request_out
62-
assert Coercion.coerce(request_json) == request_out
60+
for r in [request_json, request_mars]:
61+
r = Coercion.coerce(r)
62+
for key in r:
63+
if isinstance(r[key], list):
64+
r[key] = "/".join(r[key])
65+
assert r == request_out
6366

6467

6568
def test_date_coercion():

0 commit comments

Comments
 (0)