Skip to content

Commit fbd0753

Browse files
committed
[documentation-update] Merge branch 'main' into documentation-update
2 parents 1a420e8 + 80c0ab8 commit fbd0753

File tree

5 files changed

+49
-50
lines changed

5 files changed

+49
-50
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ jobs:
2626
uses: astral-sh/ruff-action@v3
2727
with:
2828
version: "latest"
29+
- name: Run static type checking
30+
run: mypy
2931
- name: Run tests
3032
run: pytest

mano/mano.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from collections.abc import Generator
2-
from datetime import datetime, timedelta
2+
from datetime import timedelta
33
import getpass
44
import json
55
import locale
66
import logging
77
import os
88
import re
9+
from typing import Any
910

1011
import cryptease as crypt
1112
import lxml.html as html
@@ -79,7 +80,6 @@ def interval(x: str) -> int:
7980
raise IntervalError(f"invalid interval '{x}': {e}")
8081

8182
# convert to seconds using datetime
82-
now = datetime.now()
8383
if units == "d":
8484
offset = timedelta(days=value)
8585
elif units == "h":
@@ -89,7 +89,7 @@ def interval(x: str) -> int:
8989
elif units == "s":
9090
offset = timedelta(seconds=value)
9191

92-
return ((now + offset) - now).total_seconds()
92+
return int(offset.total_seconds())
9393

9494

9595
def studies(Keyring: dict[str, str]) -> Generator[tuple[str, str], None, None]:
@@ -183,11 +183,11 @@ def expand_study_id(Keyring: dict[str, str], segment: str) -> tuple[str, str] |
183183
return None
184184
elif len(ids) == 1:
185185
return ids[0]
186-
elif len(ids) > 1:
186+
else:
187187
raise AmbiguousStudyIDError(f'study id is not unique enough {segment}')
188188

189189

190-
def login(Keyring: dict[str, str]) -> dict:
190+
def login(Keyring: dict[str, str]) -> requests.cookies.RequestsCookieJar:
191191
"""
192192
Programmatic login to the Beiwe website (returns cookies)
193193
@@ -227,7 +227,7 @@ def device_settings(Keyring: dict[str, str], study_id: str) -> Generator[tuple[s
227227
tree = html.fromstring(resp.content)
228228
# run xpath expression to get study list
229229
expr = "//div[@class='form-group']/div/input[@class='form-control']"
230-
elements = tree.xpath(expr)
230+
elements: Any = tree.xpath(expr)
231231
if not elements:
232232
raise ScrapeError(f'zero anchor elements returned from expression: {expr}')
233233
# yield each setting name and value

mano/sync.py

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import errno
21
import io
32
import itertools
43
import json
@@ -56,10 +55,10 @@ def backfill(
5655
user_id: str,
5756
output_dir: str,
5857
start_date: str = BACKFILL_START_DATE,
59-
data_streams: list[str] = None,
60-
lock: list = None,
61-
passphrase: str = None,
62-
):
58+
data_streams: list[str] | None = None,
59+
lock: list[str] | None = None,
60+
passphrase: str | None = None,
61+
) -> None:
6362
"""
6463
Backfill a user (participant)
6564
"""
@@ -123,8 +122,11 @@ def backfill(
123122

124123

125124
def download(Keyring: dict[str, str], study_id: str, user_ids: list[str],
126-
data_streams: list[str] = None, time_start: str = None, time_end: str = None,
127-
registry: dict = None, progress: bool = False) -> zipfile.ZipFile:
125+
data_streams: list[str] | None = None,
126+
time_start: str | datetime | None = None,
127+
time_end: str | datetime | None = None,
128+
registry: dict[str, str] | None = None,
129+
progress: int = 0) -> zipfile.ZipFile | None:
128130
"""
129131
Request data archive from Beiwe API
130132
@@ -145,16 +147,18 @@ def download(Keyring: dict[str, str], study_id: str, user_ids: list[str],
145147

146148
# process start_time
147149
if time_start:
148-
time_start: datetime = dateutil.parser.parse(time_start)
150+
if isinstance(time_start, str):
151+
time_start = dateutil.parser.parse(time_start)
149152
else:
150153
epoch = time.gmtime(0)
151-
time_start: datetime = datetime(epoch.tm_year, epoch.tm_mon, epoch.tm_mday)
154+
time_start = datetime(epoch.tm_year, epoch.tm_mon, epoch.tm_mday)
152155

153156
# process end_time
154157
if time_end:
155-
time_end: datetime = dateutil.parser.parse(time_end)
158+
if isinstance(time_end, str):
159+
time_end = dateutil.parser.parse(time_end)
156160
else:
157-
time_end: datetime = datetime.today()
161+
time_end = datetime.today()
158162

159163
# sanity check start and end times
160164
if time_start > time_end:
@@ -225,7 +229,7 @@ def download(Keyring: dict[str, str], study_id: str, user_ids: list[str],
225229
return zf
226230

227231

228-
def _window(timestamp: str, window: int | float) -> tuple[str, str, str]:
232+
def _window(timestamp: str, window: int | float) -> tuple[str, str, str | None]:
229233
"""
230234
Generate a backfill window (start, stop, and resume)
231235
"""
@@ -235,7 +239,7 @@ def _window(timestamp: str, window: int | float) -> tuple[str, str, str]:
235239
# by default, the download window will *stop* at `win_start` + `window`,
236240
# and the next *resume* point will be the same...
237241
win_stop = win_start + timedelta(days=window)
238-
resume = win_stop
242+
resume: datetime | None = win_stop
239243

240244
# ...unless the next projected window stop point extends into the future, in which case the
241245
# window stop point will be set to the present time, but and next resume time will be null
@@ -245,16 +249,15 @@ def _window(timestamp: str, window: int | float) -> tuple[str, str, str]:
245249
resume = None
246250

247251
# convert all timestamps to string representation before returning
248-
win_start = win_start.strftime(mano.TIME_FORMAT)
249-
win_stop = win_stop.strftime(mano.TIME_FORMAT)
250-
if resume:
251-
resume = resume.strftime(mano.TIME_FORMAT)
252+
win_start_str = win_start.strftime(mano.TIME_FORMAT)
253+
win_stop_str = win_stop.strftime(mano.TIME_FORMAT)
254+
resume_str = resume.strftime(mano.TIME_FORMAT) if resume else None
252255

253-
return win_start, win_stop, resume
256+
return win_start_str, win_stop_str, resume_str
254257

255258

256-
def save(Keyring: dict[str, str], archive: zipfile.ZipFile, user_id: str, output_dir: str,
257-
lock: list=None, passphrase=None):
259+
def save(Keyring: dict[str, str], archive: zipfile.ZipFile | None, user_id: str, output_dir: str,
260+
lock: list[str] | None = None, passphrase: str | None = None) -> int:
258261
"""
259262
The order of operations here is important to ensure the ability to reach a state of consistency:
260263
1. Save the file
@@ -330,24 +333,21 @@ def save(Keyring: dict[str, str], archive: zipfile.ZipFile, user_id: str, output
330333
return num_saved
331334

332335

333-
def _makedirs(path: str, umask: int = None, exist_ok: bool = True):
336+
def _makedirs(path: str, umask: int | None = None, exist_ok: bool = True):
334337
"""
335338
Create directories recursively with a temporary umask
336339
"""
337-
if umask is None:
338-
umask = os.umask(umask)
340+
old_umask = None
341+
if umask is not None:
342+
old_umask = os.umask(umask)
339343
try:
340-
os.makedirs(path)
341-
except OSError as e:
342-
if e.errno == errno.EEXIST and exist_ok:
343-
pass
344-
else:
345-
raise e
346-
if umask is None:
347-
os.umask(umask)
344+
os.makedirs(path, exist_ok=exist_ok)
345+
finally:
346+
if old_umask is not None:
347+
os.umask(old_umask)
348348

349349

350-
def _atomic_write(filename: str, content: bytes, overwrite=True, permissions=0o0644):
350+
def _atomic_write(filename: str, content: bytes, overwrite: bool = True, permissions: int = 0o0644):
351351
"""
352352
Write a file by first saving the content to a temporary file first, then
353353
renaming the file. Overwrites silently by default o_o
@@ -376,15 +376,3 @@ def _parse_datatype(member: str, user_id: str):
376376
f'expecting 1 capture group, found {numgroups}: regex="{expr}", string="{member}"'
377377
)
378378
return match.group(1)
379-
380-
381-
# function unused
382-
# def _masked_payload(p: Dict, masked_keys=['registry', 'secret_key', 'access_key']) -> Dict:
383-
# """
384-
# Copy and mask a request payload to safely print to console
385-
# """
386-
# _p = p.copy()
387-
# for k in masked_keys:
388-
# if k in _p and _p[k]:
389-
# _p[k] = "***"
390-
# return _p

mypy.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[mypy]
2+
packages = mano
3+
4+
[mypy-cryptease]
5+
ignore_missing_imports = True

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ Source = "https://github.com/onnela-lab/mano"
4343
[project.optional-dependencies]
4444
dev = [
4545
"build",
46+
"lxml-stubs",
47+
"mypy",
4648
"pytest",
4749
"responses",
4850
"ruff",
4951
"twine",
52+
"types-python-dateutil",
53+
"types-requests",
5054
]
5155

5256
[tool.setuptools.packages.find]

0 commit comments

Comments
 (0)