Skip to content

Commit e0d4f9c

Browse files
committed
Fix a bug which makes the API unable to query session in remote context (#2, PR #3)
The fix consists in adding a translation layer between any REST API response when querying Session entities in the Remote dialect. Since all tests were using a bad schema, the mocked data have been fixed too. Additionally, a python 3.6 compatibility module has been added.
1 parent e1bd7bf commit e0d4f9c

12 files changed

Lines changed: 183 additions & 30 deletions

.circleci/config.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,21 @@ shared: &shared
3636
workflows:
3737
standard:
3838
jobs:
39+
- monitor-py36:
40+
filters:
41+
tags:
42+
only: /.*/
3943
- monitor-py37:
40-
filters:
41-
tags:
42-
only: /.*/
44+
filters:
45+
tags:
46+
only: /.*/
4347
- monitor-py38:
4448
filters:
45-
tags:
46-
only: /.*/
49+
tags:
50+
only: /.*/
4751
- deploy:
4852
requires:
53+
- monitor-py36
4954
- monitor-py37
5055
- monitor-py38
5156
filters:
@@ -66,6 +71,10 @@ workflows:
6671

6772
# Jobs definition
6873
jobs:
74+
monitor-py36:
75+
environment:
76+
CI_PYTHON: 'python=3.6'
77+
<<: *shared
6978
monitor-py37:
7079
environment:
7180
CI_PYTHON: 'python=3.7'

docs/source/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
Changelog
33
=========
44

5+
* :release:`1.0.1 <2021-15-10>`
6+
* :bug:`#2` Fix a bug which prevents the api to query sessions from the REST Api.
7+
58
* :release:`1.0.0 <2021-04-01>`
69
* :feature:`0` initial release.

monitor_api/core/entities.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@
33
# SPDX-License-Identifier: MIT
44

55
from monitor_api.enums import Field, MetricScope, CONTEXT_ALL_FIELDS, METRIC_ALL_FIELDS, SESSION_ALL_FIELDS
6-
import datetime
76
from typing import List, Union, Dict, Optional
7+
import datetime
88
import hashlib
99

1010

11+
try:
12+
datetime.datetime.fromisoformat(datetime.datetime.now().isoformat())
13+
api_fromisoformat = datetime.datetime.fromisoformat
14+
except AttributeError:
15+
from monitor_api.py36 import fromisoformat
16+
api_fromisoformat = fromisoformat
17+
18+
1119
class Metric:
1220
"""Basic metric record.
1321
@@ -46,7 +54,7 @@ def __init__(self, context_h: str = None, session_h: str = None, start_time: str
4654
kernel_time: float = None, cpu_usage: float = None, memory_usage: float = None):
4755
self.__c_h = context_h or ''
4856
self.__s_h = session_h or ''
49-
self.__time = datetime.datetime.fromisoformat(start_time)
57+
self.__time = api_fromisoformat(start_time)
5058
self.__item_path = item_path or ''
5159
self.__item = item or ''
5260
self.__variant = variant or ''
@@ -479,7 +487,7 @@ def __init__(self, h: str = None, scm_ref: str = None,
479487
tags: Optional[Union[Dict[str, str], List[Dict[str, str]]]] = None):
480488
self.__h = h or ''
481489
self.__scm = scm_ref or ''
482-
self.__start_date = datetime.datetime.fromisoformat(run_date)
490+
self.__start_date = api_fromisoformat(run_date)
483491
self.__tags = tags or dict()
484492
if type(tags) is list:
485493
# We try to map to a session sent by remote server

monitor_api/dialects/remote.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414
def translate(context: Optional[Dict[str, Any]] = None,
15+
session: Optional[Dict[str, Any]] = None,
1516
metric: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
1617
if context is not None:
1718
d = dict(h=context['h'],
@@ -25,6 +26,12 @@ def translate(context: Optional[Dict[str, Any]] = None,
2526
mac_arch=context['machine_arch'],
2627
sys_info=context['system_info'],
2728
py_info=context['python_info'])
29+
elif session is not None:
30+
d = dict(h=session['session_h'],
31+
run_date=session['run_date'],
32+
tags=session['tags'],
33+
scm_ref=session['scm_ref']
34+
)
2835
else:
2936
d = dict(context_h=metric['context_h'],
3037
session_h=metric['session_h'],
@@ -118,7 +125,7 @@ def get_session_details(self, session_h) -> Optional[Session]:
118125
j = self.__session.get(self._make_url(f'/sessions/{session_h}'))
119126
if j.status_code == HTTPStatus.NO_CONTENT:
120127
return None
121-
return Session(**j.json()['sessions'])
128+
return Session(**translate(session=j.json()['sessions']))
122129
except requests.RequestException:
123130
return None
124131

monitor_api/py36.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from datetime import datetime
2+
from datetime import timedelta
3+
from datetime import timezone
4+
5+
6+
def fromisoformat(date_string):
7+
"""Construct a datetime from the output of datetime.isoformat()."""
8+
if not isinstance(date_string, str):
9+
raise TypeError('fromisoformat: argument must be str')
10+
11+
# Split this at the separator
12+
dstr = date_string[0:10]
13+
tstr = date_string[11:]
14+
15+
try:
16+
date_components = _parse_isoformat_date(dstr)
17+
except ValueError:
18+
raise ValueError(f'Invalid isoformat string: {date_string!r}')
19+
20+
if tstr:
21+
try:
22+
time_components = _parse_isoformat_time(tstr)
23+
except ValueError:
24+
raise ValueError(f'Invalid isoformat string: {date_string!r}')
25+
else:
26+
time_components = [0, 0, 0, 0, None]
27+
28+
return datetime(*(date_components + time_components))
29+
30+
31+
def _parse_isoformat_date(dtstr):
32+
# It is assumed that this function will only be called with a
33+
# string of length exactly 10, and (though this is not used) ASCII-only
34+
year = int(dtstr[0:4])
35+
if dtstr[4] != '-':
36+
raise ValueError('Invalid date separator: %s' % dtstr[4])
37+
38+
month = int(dtstr[5:7])
39+
40+
if dtstr[7] != '-':
41+
raise ValueError('Invalid date separator')
42+
43+
day = int(dtstr[8:10])
44+
45+
return [year, month, day]
46+
47+
48+
def _parse_hh_mm_ss_ff(tstr):
49+
# Parses things of the form HH[:MM[:SS[.fff[fff]]]]
50+
len_str = len(tstr)
51+
52+
time_comps = [0, 0, 0, 0]
53+
pos = 0
54+
for comp in range(0, 3):
55+
if (len_str - pos) < 2:
56+
raise ValueError('Incomplete time component')
57+
58+
time_comps[comp] = int(tstr[pos:pos+2])
59+
60+
pos += 2
61+
next_char = tstr[pos:pos+1]
62+
63+
if not next_char or comp >= 2:
64+
break
65+
66+
if next_char != ':':
67+
raise ValueError('Invalid time separator: %c' % next_char)
68+
69+
pos += 1
70+
71+
if pos < len_str:
72+
if tstr[pos] != '.':
73+
raise ValueError('Invalid microsecond component')
74+
else:
75+
pos += 1
76+
77+
len_remainder = len_str - pos
78+
if len_remainder not in (3, 6):
79+
raise ValueError('Invalid microsecond component')
80+
81+
time_comps[3] = int(tstr[pos:])
82+
if len_remainder == 3:
83+
time_comps[3] *= 1000
84+
85+
return time_comps
86+
87+
88+
def _parse_isoformat_time(tstr):
89+
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
90+
len_str = len(tstr)
91+
if len_str < 2:
92+
raise ValueError('Isoformat time too short')
93+
94+
# This is equivalent to re.search('[+-]', tstr), but faster
95+
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1)
96+
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
97+
98+
time_comps = _parse_hh_mm_ss_ff(timestr)
99+
100+
tzi = None
101+
if tz_pos > 0:
102+
tzstr = tstr[tz_pos:]
103+
104+
# Valid time zone strings are:
105+
# HH:MM len: 5
106+
# HH:MM:SS len: 8
107+
# HH:MM:SS.ffffff len: 15
108+
109+
if len(tzstr) not in (5, 8, 15):
110+
raise ValueError('Malformed time zone string')
111+
112+
tz_comps = _parse_hh_mm_ss_ff(tzstr)
113+
if all(x == 0 for x in tz_comps):
114+
tzi = timezone.utc
115+
else:
116+
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
117+
118+
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
119+
seconds=tz_comps[2], microseconds=tz_comps[3])
120+
121+
tzi = timezone(tzsign * td)
122+
123+
time_comps.append(tzi)
124+
125+
return time_comps

tests/data/test_get_metrics_from_sessions.service.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"/api/v1/sessions/b83742056ad0ed304d821562b7c209ff": {
6868
"data": {
6969
"sessions": {
70-
"h": "b83742056ad0ed304d821562b7c209ff",
70+
"session_h": "b83742056ad0ed304d821562b7c209ff",
7171
"run_date": "2021-02-09T13:48:58.923094",
7272
"scm_ref": "4c3b3ca9e975a0a5eacd5d802d03c4ba690e8e41",
7373
"tags": [

tests/data/test_get_metrics_from_sessions_contexts.service.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"/api/v1/sessions/7768124332f353446557878a89ef6f4c": {
1919
"data": {
2020
"sessions": {
21-
"h": "7768124332f353446557878a89ef6f4c",
21+
"session_h": "7768124332f353446557878a89ef6f4c",
2222
"run_date": "2021-02-09T13:48:58.923094",
2323
"scm_ref": "4c3b3ca9e975a0a5eacd5d802d03c4ba690e8e41",
2424
"tags": [

tests/data/test_get_sessions_from_build.service.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"/api/v1/sessions/601eb85a006d6170e25523a167dbb39e3a1134f7": {
1414
"data": {
1515
"sessions": {
16-
"h": "601eb85a006d6170e25523a167dbb39e3a1134f7",
16+
"session_h": "601eb85a006d6170e25523a167dbb39e3a1134f7",
1717
"run_date": "2021-02-09T13:48:58.923094",
1818
"scm_ref": "40d4766c57e87c33ae2a58f8c3a801d0edc0352e",
1919
"tags": [
@@ -41,7 +41,7 @@
4141
"/api/v1/sessions/4e79321b7086869194e509e2f8dcdf7f58832351": {
4242
"data": {
4343
"sessions": {
44-
"h": "4e79321b7086869194e509e2f8dcdf7f58832351",
44+
"session_h": "4e79321b7086869194e509e2f8dcdf7f58832351",
4545
"run_date": "2021-02-09T13:48:58.923094",
4646
"scm_ref": "606f6cff460dcd8b546d0cbd55b77da475318316",
4747
"tags": [

tests/data/test_get_sessions_from_metrics.service.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"/api/v1/sessions/7768124332f353446557878a89ef6f4c": {
5959
"data": {
6060
"sessions": {
61-
"h": "7768124332f353446557878a89ef6f4c",
61+
"session_h": "7768124332f353446557878a89ef6f4c",
6262
"run_date": "2021-02-09T13:48:58.923094",
6363
"scm_ref": "4c3b3ca9e975a0a5eacd5d802d03c4ba690e8e41",
6464
"tags": [
@@ -86,7 +86,7 @@
8686
"/api/v1/sessions/c4f6fe98a878755644353f2334218677": {
8787
"data": {
8888
"sessions": {
89-
"h": "c4f6fe98a878755644353f2334218677",
89+
"session_h": "c4f6fe98a878755644353f2334218677",
9090
"run_date": "2021-02-09T13:48:58.923094",
9191
"scm_ref": "4c3b3ca9e975a0a5eacd5d802d03c4ba690e8e41",
9292
"tags": [

tests/data/test_session_details.service.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"/api/v1/sessions/b83742056ad0ed304d821562b7c209ff": {
33
"data": {
44
"sessions": {
5-
"h": "b83742056ad0ed304d821562b7c209ff",
5+
"session_h": "b83742056ad0ed304d821562b7c209ff",
66
"run_date": "2021-02-09T13:48:58.923094",
77
"scm_ref": "4c3b3ca9e975a0a5eacd5d802d03c4ba690e8e41",
88
"tags": [

0 commit comments

Comments
 (0)