forked from python-arq/arq
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
150 lines (110 loc) · 3.51 KB
/
Copy pathutils.py
File metadata and controls
150 lines (110 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import asyncio
import logging
import os
from datetime import datetime, timedelta, timezone
from functools import lru_cache
from time import time
from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, Optional, Sequence, overload
from .constants import timezone_env_vars
try:
import pytz
except ImportError: # pragma: no cover
pytz = None # type: ignore
logger = logging.getLogger('arq.utils')
if TYPE_CHECKING:
from .typing import SecondsTimedelta
def as_int(f: float) -> int:
return int(round(f))
def timestamp_ms() -> int:
return as_int(time() * 1000)
def to_unix_ms(dt: datetime) -> int:
"""
convert a datetime to epoch with milliseconds as int
"""
return as_int(dt.timestamp() * 1000)
@lru_cache
def get_tz() -> Optional['pytz.BaseTzInfo']:
if pytz: # pragma: no branch
for timezone_key in timezone_env_vars:
tz_name = os.getenv(timezone_key)
if tz_name:
try:
return pytz.timezone(tz_name)
except KeyError:
logger.warning('unknown timezone: %r', tz_name)
return None
def ms_to_datetime(unix_ms: int) -> datetime:
"""
convert milliseconds to datetime, use the timezone in os.environ
"""
dt = datetime.fromtimestamp(unix_ms / 1000, tz=timezone.utc)
tz = get_tz()
if tz:
dt = dt.astimezone(tz)
return dt
@overload
def to_ms(td: None) -> None:
pass
@overload
def to_ms(td: 'SecondsTimedelta') -> int:
pass
def to_ms(td: Optional['SecondsTimedelta']) -> Optional[int]:
if td is None:
return td
elif isinstance(td, timedelta):
td = td.total_seconds()
return as_int(td * 1000)
@overload
def to_seconds(td: None) -> None:
pass
@overload
def to_seconds(td: 'SecondsTimedelta') -> float:
pass
def to_seconds(td: Optional['SecondsTimedelta']) -> Optional[float]:
if td is None:
return td
elif isinstance(td, timedelta):
return td.total_seconds()
return td
async def poll(step: float = 0.5) -> AsyncGenerator[float, None]:
loop = asyncio.get_event_loop()
start = loop.time()
while True:
before = loop.time()
yield before - start
after = loop.time()
wait = max([0, step - after + before])
await asyncio.sleep(wait)
DEFAULT_CURTAIL = 80
def truncate(s: str, length: int = DEFAULT_CURTAIL) -> str:
"""
Truncate a string and add an ellipsis (three dots) to the end if it was too long
:param s: string to possibly truncate
:param length: length to truncate the string to
"""
if len(s) > length:
s = s[: length - 1] + '…'
return s
def args_to_string(args: Sequence[Any], kwargs: Dict[str, Any]) -> str:
arguments = ''
if args:
arguments = ', '.join(map(repr, args))
if kwargs:
if arguments:
arguments += ', '
arguments += ', '.join(f'{k}={v!r}' for k, v in sorted(kwargs.items()))
return truncate(arguments)
def import_string(dotted_path: str) -> Any:
"""
Taken from pydantic.utils.
"""
from importlib import import_module
try:
module_path, class_name = dotted_path.strip(' ').rsplit('.', 1)
except ValueError as e:
raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e
module = import_module(module_path)
try:
return getattr(module, class_name)
except AttributeError as e:
raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e