dcicutils 8.8.3.1b12__tar.gz → 8.8.3.1b14__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/PKG-INFO +1 -1
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/data_readers.py +11 -0
- dcicutils-8.8.3.1b14/dcicutils/datetime_utils.py +213 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/progress_bar.py +3 -1
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/pyproject.toml +1 -1
- dcicutils-8.8.3.1b12/dcicutils/datetime_utils.py +0 -98
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/LICENSE.txt +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/README.rst +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/__init__.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/base.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/beanstalk_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/bundle_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/captured_output.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/cloudformation_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/codebuild_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/command_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/common.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/contribution_scripts.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/contribution_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/creds_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/data_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/deployment_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/diff_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/docker_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ecr_scripts.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ecr_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ecs_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/env_base.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/env_manager.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/env_scripts.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/env_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/env_utils_legacy.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/es_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/exceptions.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ff_mocks.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ff_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/file_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/function_cache_decorator.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/glacier_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/jh_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/kibana/dashboards.json +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/kibana/readme.md +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/lang_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/c4-infrastructure.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/c4-python-infrastructure.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-common-server.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-common.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-pipeline.jsonc +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/log_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/misc_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/obfuscation_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/opensearch_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/portal_object_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/portal_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/project_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/qa_checkers.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/qa_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/redis_tools.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/redis_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/s3_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/schema_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/scripts/publish_to_pypi.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/scripts/run_license_checker.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/scripts/view_portal_object.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/secrets_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/sheet_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/snapshot_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/ssl_certificate_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/structured_data.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/submitr/progress_constants.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/submitr/ref_lookup_strategy.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/task_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/tmpfile_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/trace_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/validation_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/variant_utils.py +0 -0
- {dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/zip_utils.py +0 -0
@@ -66,6 +66,13 @@ class RowReader(abc.ABC):
|
|
66
66
|
else:
|
67
67
|
return value
|
68
68
|
|
69
|
+
@property
|
70
|
+
def nrows(self) -> int:
|
71
|
+
nrows = 0
|
72
|
+
for row in self:
|
73
|
+
nrows += 1
|
74
|
+
return nrows
|
75
|
+
|
69
76
|
def open(self) -> None:
|
70
77
|
pass
|
71
78
|
|
@@ -192,6 +199,10 @@ class Excel:
|
|
192
199
|
return True
|
193
200
|
return False
|
194
201
|
|
202
|
+
@property
|
203
|
+
def nsheets(self) -> int:
|
204
|
+
return len(self.sheet_names)
|
205
|
+
|
195
206
|
def __del__(self) -> None:
|
196
207
|
if (workbook := self._workbook) is not None:
|
197
208
|
self._workbook = None
|
@@ -0,0 +1,213 @@
|
|
1
|
+
from dcicutils.misc_utils import normalize_spaces
|
2
|
+
from datetime import datetime, timedelta, timezone
|
3
|
+
from dateutil import parser as datetime_parser
|
4
|
+
from typing import Optional, Tuple, Union
|
5
|
+
|
6
|
+
|
7
|
+
def parse_datetime_string(value: str) -> Optional[datetime]:
|
8
|
+
"""
|
9
|
+
Parses the given string into a datetime object and returns it, or if ill-formated then returns None.
|
10
|
+
The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss" and with an optional timezone
|
11
|
+
suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the format "YYYY-MM-DD" in which
|
12
|
+
case a time of "00:00:00" is assumed. If no timezone is specified then the local timezone is assumed.
|
13
|
+
"""
|
14
|
+
if not isinstance(value, str) or not (value := normalize_spaces(value)):
|
15
|
+
return None
|
16
|
+
tz_hours = -1
|
17
|
+
tz_minutes = -1
|
18
|
+
if value.rfind("T") > 0:
|
19
|
+
value = value.replace("T", " ")
|
20
|
+
if (space := value.find(" ")) > 0 and (value_suffix := value[space + 1:]):
|
21
|
+
if (plus := value_suffix.rfind("+")) > 0 or (minus := value_suffix.rfind("-")) > 0:
|
22
|
+
value = normalize_spaces(value[:space] + " " + value_suffix[:(plus if plus > 0 else minus)])
|
23
|
+
if value_tz := normalize_spaces(value_suffix[(plus if plus > 0 else minus) + 1:]):
|
24
|
+
if len(value_tz := value_tz.split(":")) == 2:
|
25
|
+
value_tz_hours = value_tz[0].strip()
|
26
|
+
value_tz_minutes = value_tz[1].strip()
|
27
|
+
else:
|
28
|
+
value_tz_hours = value_tz[0].strip()
|
29
|
+
value_tz_minutes = "0"
|
30
|
+
if value_tz_hours.isdigit() and value_tz_minutes.isdigit():
|
31
|
+
tz_hours = int(value_tz_hours)
|
32
|
+
tz_minutes = int(value_tz_minutes)
|
33
|
+
if not (plus > 0):
|
34
|
+
tz_hours = -tz_hours
|
35
|
+
else:
|
36
|
+
value = value + " 00:00:00"
|
37
|
+
if tz_hours < 0 or tz_minutes < 0:
|
38
|
+
tz_hours, tz_minutes = get_local_timezone_hours_minutes()
|
39
|
+
try:
|
40
|
+
dt = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
|
41
|
+
tz = timezone(timedelta(hours=tz_hours, minutes=tz_minutes))
|
42
|
+
return dt.replace(tzinfo=tz)
|
43
|
+
except Exception:
|
44
|
+
return None
|
45
|
+
|
46
|
+
|
47
|
+
def parse_date_string(value: str) -> Optional[datetime]:
|
48
|
+
"""
|
49
|
+
Parses the given string into a datetime object representing only a date and
|
50
|
+
returns it, or if ill-formated then returns None. The given string is assumed
|
51
|
+
to be in the format "YYYY-MM-DD"; if a given string of this format is suffixed
|
52
|
+
with a space or a "T" and ANYTHING else, then that trailing portion is ignored.
|
53
|
+
"""
|
54
|
+
if isinstance(value, str) and (value := normalize_spaces(value)):
|
55
|
+
if (separator := value.find(" ")) > 0 or (separator := value.find("T")) > 0:
|
56
|
+
value = value[:separator]
|
57
|
+
try:
|
58
|
+
return datetime.strptime(value, "%Y-%m-%d")
|
59
|
+
except Exception:
|
60
|
+
pass
|
61
|
+
|
62
|
+
|
63
|
+
def normalize_datetime_string(value: str) -> Optional[str]:
|
64
|
+
"""
|
65
|
+
Parses the given string into a datetime object and returns a string for that datetime in ISO-8601 format,
|
66
|
+
or if ill-formated then returns None. The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss"
|
67
|
+
and with an optional timezone suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the
|
68
|
+
format "YYYY-MM-DD" in which case a time of "00:00:00" is assumed. If no timezone is specified then
|
69
|
+
the local timezone is assumed. The returned format looks like this: "2024-02-08T10:37:51-05:00"
|
70
|
+
"""
|
71
|
+
dt = parse_datetime_string(value)
|
72
|
+
return dt.isoformat() if dt else None
|
73
|
+
|
74
|
+
|
75
|
+
def normalize_date_string(value: str) -> Optional[str]:
|
76
|
+
"""
|
77
|
+
Parses the given string into a datetime object representing only a date and returns a string for that
|
78
|
+
date in ISO-8601 format, or if ill-formated then returns None. The given string is assumed to be in
|
79
|
+
the format "YYYY-MM-DD"; but if a given string of this format is suffixed with a space followed by
|
80
|
+
ANYTHING else, then that trailing portion is ignored. The returned format looks like this: "2024-02-08"
|
81
|
+
"""
|
82
|
+
d = parse_date_string(value)
|
83
|
+
return d.strftime("%Y-%m-%d") if d else None
|
84
|
+
|
85
|
+
|
86
|
+
def get_timezone(hours: int, minutes: Optional[int] = None) -> timezone:
|
87
|
+
try:
|
88
|
+
return timezone(timedelta(hours=hours, minutes=minutes or 0))
|
89
|
+
except Exception:
|
90
|
+
return timezone.utc
|
91
|
+
|
92
|
+
|
93
|
+
def get_timezone_hours_minutes(tz: timezone) -> Tuple[int, int]:
|
94
|
+
"""
|
95
|
+
Returns a tuple with the integer hours and minutes offset for the given timezone.
|
96
|
+
"""
|
97
|
+
tz_minutes = datetime.now(tz).utcoffset().total_seconds() / 60
|
98
|
+
return int(tz_minutes // 60), int(abs(tz_minutes % 60))
|
99
|
+
|
100
|
+
|
101
|
+
def get_utc_timezone() -> timezone:
|
102
|
+
return timezone.utc
|
103
|
+
|
104
|
+
|
105
|
+
def get_local_timezone() -> timezone:
|
106
|
+
"""
|
107
|
+
Returns current/local timezone as a datetime.timezone object.
|
108
|
+
"""
|
109
|
+
return datetime.now().astimezone().tzinfo
|
110
|
+
|
111
|
+
|
112
|
+
def get_local_timezone_string() -> str:
|
113
|
+
"""
|
114
|
+
Returns current/local timezone in format like: "-05:00".
|
115
|
+
"""
|
116
|
+
tz_hours, tz_minutes = get_local_timezone_hours_minutes()
|
117
|
+
return f"{tz_hours:+03d}:{tz_minutes:02d}"
|
118
|
+
|
119
|
+
|
120
|
+
def get_local_timezone_hours_minutes() -> Tuple[int, int]:
|
121
|
+
"""
|
122
|
+
Returns a tuple with the integer hours and minutes offset for the current/local timezone.
|
123
|
+
"""
|
124
|
+
tz_minutes = datetime.now(timezone.utc).astimezone().utcoffset().total_seconds() / 60
|
125
|
+
return int(tz_minutes // 60), int(abs(tz_minutes % 60))
|
126
|
+
|
127
|
+
|
128
|
+
def parse_datetime(value: str, utc: bool = False, tz: Optional[timezone] = None) -> Optional[datetime]:
|
129
|
+
"""
|
130
|
+
Parses the given string into a datetime, if possible, and returns that value,
|
131
|
+
or None if not able to parse. The timezone of the returned datetime will be the
|
132
|
+
local timezone; or if the given utc argument is True then it will be UTC; or if the
|
133
|
+
given tz argument is a datetime.timezone then return datetime will be in that timezone.
|
134
|
+
"""
|
135
|
+
if isinstance(value, datetime):
|
136
|
+
return value
|
137
|
+
elif not isinstance(value, str):
|
138
|
+
return None
|
139
|
+
try:
|
140
|
+
# This dateutil.parser handles quite a wide variety of formats and suits our needs.
|
141
|
+
value = datetime_parser.parse(value)
|
142
|
+
if utc is True:
|
143
|
+
# If the given utc argument is True then it trumps any tz argument if given.
|
144
|
+
tz = timezone.utc
|
145
|
+
if value.tzinfo is not None:
|
146
|
+
# The given value had an explicit timezone specified.
|
147
|
+
if isinstance(tz, timezone):
|
148
|
+
return value.astimezone(tz)
|
149
|
+
return value
|
150
|
+
return value.replace(tzinfo=tz if isinstance(tz, timezone) else get_local_timezone())
|
151
|
+
except Exception:
|
152
|
+
return None
|
153
|
+
|
154
|
+
|
155
|
+
def format_datetime(value: datetime,
|
156
|
+
utc: bool = False,
|
157
|
+
iso: bool = False,
|
158
|
+
ms: bool = False,
|
159
|
+
tz: Optional[Union[timezone, bool]] = None,
|
160
|
+
notz: bool = False,
|
161
|
+
noseconds: bool = False,
|
162
|
+
verbose: bool = False,
|
163
|
+
noseparator: bool = False,
|
164
|
+
noday: bool = False) -> Optional[str]:
|
165
|
+
"""
|
166
|
+
Returns the given datetime as a string in "YYYY:MM:DD hh:mm:ss tz" format, for
|
167
|
+
example "2024-04-17 15:42:26 EDT". If the given notz argument is True then omits
|
168
|
+
the timezone; if the noseconds argument is given the omits the seconds. If the given
|
169
|
+
verbose argument is True then returns a really verbose version of the datetime, for
|
170
|
+
example "Wednesday, April 17, 2024 | 15:42:26 EDT"; if the noseparator argument is
|
171
|
+
True then omits the "|" separator; if the noday argument is True then omits the day
|
172
|
+
of week part. The timezone of the returned datetime string will default to the local
|
173
|
+
one; if the given utc argument is True then it will be UTC; or if the given tz
|
174
|
+
argument is a datetime.timezone it will be in that timezone.
|
175
|
+
"""
|
176
|
+
if not isinstance(value, datetime):
|
177
|
+
if not isinstance(value, str) or not (value := parse_datetime(value)):
|
178
|
+
return None
|
179
|
+
try:
|
180
|
+
if utc is True:
|
181
|
+
tz = timezone.utc
|
182
|
+
elif not isinstance(tz, timezone):
|
183
|
+
tz = get_local_timezone()
|
184
|
+
if tz is True:
|
185
|
+
notz = False
|
186
|
+
elif tz is False:
|
187
|
+
notz = True
|
188
|
+
if noseconds is True:
|
189
|
+
ms = False
|
190
|
+
value = value.astimezone(tz)
|
191
|
+
if iso:
|
192
|
+
if notz is True:
|
193
|
+
value = value.replace(tzinfo=None)
|
194
|
+
if not (ms is True):
|
195
|
+
value = value.replace(microsecond=0)
|
196
|
+
if noseconds is True:
|
197
|
+
if notz is True:
|
198
|
+
return value.strftime(f"%Y-%m-%dT%H:%M")
|
199
|
+
tz = value.strftime("%z")
|
200
|
+
tz = tz[:3] + ":" + tz[3:]
|
201
|
+
return value.strftime(f"%Y-%m-%dT%H:%M") + tz
|
202
|
+
return value.isoformat()
|
203
|
+
if verbose:
|
204
|
+
return value.strftime(
|
205
|
+
f"{'' if noday is True else '%A, '}%B %-d, %Y{'' if noseparator is True else ' |'}"
|
206
|
+
f" %-I:%M{'' if noseconds is True else ':%S'}"
|
207
|
+
f"{f'.%f' if ms is True else ''} %p{'' if notz is True else ' %Z'}")
|
208
|
+
else:
|
209
|
+
return value.strftime(
|
210
|
+
f"%Y-%m-%d %H:%M{'' if noseconds is True else ':%S'}"
|
211
|
+
f"{f'.%f' if ms is True else ''}{'' if notz is True else ' %Z'}")
|
212
|
+
except Exception:
|
213
|
+
return None
|
@@ -50,6 +50,7 @@ class ProgressBar:
|
|
50
50
|
def __init__(self, total: Optional[int] = None,
|
51
51
|
description: Optional[str] = None,
|
52
52
|
use_byte_size_for_rate: bool = False,
|
53
|
+
use_ascii: bool = False,
|
53
54
|
catch_interrupt: bool = True,
|
54
55
|
interrupt: Optional[Callable] = None,
|
55
56
|
interrupt_continue: Optional[Callable] = None,
|
@@ -66,6 +67,7 @@ class ProgressBar:
|
|
66
67
|
self._tidy_output_hack = (tidy_output_hack is True)
|
67
68
|
self._stop_requested = False
|
68
69
|
self._use_byte_size_for_rate = (use_byte_size_for_rate is True and self._tidy_output_hack)
|
70
|
+
self._use_ascii = (use_ascii is True)
|
69
71
|
# Interrupt handling. We do not do the actual (signal) interrupt setup
|
70
72
|
# in self._initialize as that could be called from a (sub) thread; and in
|
71
73
|
# Python we can only set a signal (SIGINT in our case) on the main thread.
|
@@ -103,7 +105,7 @@ class ProgressBar:
|
|
103
105
|
else:
|
104
106
|
bar_format = "{l_bar}{bar}| {n_fmt}/{total_fmt} | {rate_fmt} | {elapsed}{postfix} | ETA: {remaining} "
|
105
107
|
self._bar = TQDM(total=self._total, desc=self._description,
|
106
|
-
dynamic_ncols=True, bar_format=bar_format, unit="", file=sys.stdout)
|
108
|
+
dynamic_ncols=True, bar_format=bar_format, unit="", file=sys.stdout, ascii=self._use_ascii)
|
107
109
|
self._started = time.time()
|
108
110
|
if self._disabled:
|
109
111
|
self._bar.disable = True
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "dcicutils"
|
3
|
-
version = "8.8.3.
|
3
|
+
version = "8.8.3.1b14" # TODO: To become 8.8.4
|
4
4
|
description = "Utility package for interacting with the 4DN Data Portal and other 4DN resources"
|
5
5
|
authors = ["4DN-DCIC Team <support@4dnucleome.org>"]
|
6
6
|
license = "MIT"
|
@@ -1,98 +0,0 @@
|
|
1
|
-
from dcicutils.misc_utils import normalize_spaces
|
2
|
-
from datetime import datetime, timedelta, timezone
|
3
|
-
from typing import Optional, Tuple
|
4
|
-
|
5
|
-
|
6
|
-
def parse_datetime_string(value: str) -> Optional[datetime]:
|
7
|
-
"""
|
8
|
-
Parses the given string into a datetime object and returns it, or if ill-formated then returns None.
|
9
|
-
The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss" and with an optional timezone
|
10
|
-
suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the format "YYYY-MM-DD" in which
|
11
|
-
case a time of "00:00:00" is assumed. If no timezone is specified then the local timezone is assumed.
|
12
|
-
"""
|
13
|
-
if not isinstance(value, str) or not (value := normalize_spaces(value)):
|
14
|
-
return None
|
15
|
-
tz_hours = -1
|
16
|
-
tz_minutes = -1
|
17
|
-
if value.rfind("T") > 0:
|
18
|
-
value = value.replace("T", " ")
|
19
|
-
if (space := value.find(" ")) > 0 and (value_suffix := value[space + 1:]):
|
20
|
-
if (plus := value_suffix.rfind("+")) > 0 or (minus := value_suffix.rfind("-")) > 0:
|
21
|
-
value = normalize_spaces(value[:space] + " " + value_suffix[:(plus if plus > 0 else minus)])
|
22
|
-
if value_tz := normalize_spaces(value_suffix[(plus if plus > 0 else minus) + 1:]):
|
23
|
-
if len(value_tz := value_tz.split(":")) == 2:
|
24
|
-
value_tz_hours = value_tz[0].strip()
|
25
|
-
value_tz_minutes = value_tz[1].strip()
|
26
|
-
else:
|
27
|
-
value_tz_hours = value_tz[0].strip()
|
28
|
-
value_tz_minutes = "0"
|
29
|
-
if value_tz_hours.isdigit() and value_tz_minutes.isdigit():
|
30
|
-
tz_hours = int(value_tz_hours)
|
31
|
-
tz_minutes = int(value_tz_minutes)
|
32
|
-
if not (plus > 0):
|
33
|
-
tz_hours = -tz_hours
|
34
|
-
else:
|
35
|
-
value = value + " 00:00:00"
|
36
|
-
if tz_hours < 0 or tz_minutes < 0:
|
37
|
-
tz_hours, tz_minutes = get_local_timezone_hours_minutes()
|
38
|
-
try:
|
39
|
-
dt = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
|
40
|
-
tz = timezone(timedelta(hours=tz_hours, minutes=tz_minutes))
|
41
|
-
return dt.replace(tzinfo=tz)
|
42
|
-
except Exception:
|
43
|
-
return None
|
44
|
-
|
45
|
-
|
46
|
-
def parse_date_string(value: str) -> Optional[datetime]:
|
47
|
-
"""
|
48
|
-
Parses the given string into a datetime object representing only a date and
|
49
|
-
returns it, or if ill-formated then returns None. The given string is assumed
|
50
|
-
to be in the format "YYYY-MM-DD"; if a given string of this format is suffixed
|
51
|
-
with a space or a "T" and ANYTHING else, then that trailing portion is ignored.
|
52
|
-
"""
|
53
|
-
if isinstance(value, str) and (value := normalize_spaces(value)):
|
54
|
-
if (separator := value.find(" ")) > 0 or (separator := value.find("T")) > 0:
|
55
|
-
value = value[:separator]
|
56
|
-
try:
|
57
|
-
return datetime.strptime(value, "%Y-%m-%d")
|
58
|
-
except Exception:
|
59
|
-
pass
|
60
|
-
|
61
|
-
|
62
|
-
def normalize_datetime_string(value: str) -> Optional[str]:
|
63
|
-
"""
|
64
|
-
Parses the given string into a datetime object and returns a string for that datetime in ISO-8601 format,
|
65
|
-
or if ill-formated then returns None. The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss"
|
66
|
-
and with an optional timezone suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the
|
67
|
-
format "YYYY-MM-DD" in which case a time of "00:00:00" is assumed. If no timezone is specified then
|
68
|
-
the local timezone is assumed. The returned format looks like this: "2024-02-08T10:37:51-05:00"
|
69
|
-
"""
|
70
|
-
dt = parse_datetime_string(value)
|
71
|
-
return dt.isoformat() if dt else None
|
72
|
-
|
73
|
-
|
74
|
-
def normalize_date_string(value: str) -> Optional[str]:
|
75
|
-
"""
|
76
|
-
Parses the given string into a datetime object representing only a date and returns a string for that
|
77
|
-
date in ISO-8601 format, or if ill-formated then returns None. The given string is assumed to be in
|
78
|
-
the format "YYYY-MM-DD"; but if a given string of this format is suffixed with a space followed by
|
79
|
-
ANYTHING else, then that trailing portion is ignored. The returned format looks like this: "2024-02-08"
|
80
|
-
"""
|
81
|
-
d = parse_date_string(value)
|
82
|
-
return d.strftime("%Y-%m-%d") if d else None
|
83
|
-
|
84
|
-
|
85
|
-
def get_local_timezone_string() -> str:
|
86
|
-
"""
|
87
|
-
Returns current/local timezone in format like: "-05:00".
|
88
|
-
"""
|
89
|
-
tz_hours, tz_minutes = get_local_timezone_hours_minutes()
|
90
|
-
return f"{tz_hours:+03d}:{tz_minutes:02d}"
|
91
|
-
|
92
|
-
|
93
|
-
def get_local_timezone_hours_minutes() -> Tuple[int, int]:
|
94
|
-
"""
|
95
|
-
Returns a tuple with the integer hours and minutes offset for the current/local timezone.
|
96
|
-
"""
|
97
|
-
tz_minutes = datetime.now(timezone.utc).astimezone().utcoffset().total_seconds() / 60
|
98
|
-
return int(tz_minutes // 60), int(abs(tz_minutes % 60))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/c4-infrastructure.jsonc
RENAMED
File without changes
|
File without changes
|
File without changes
|
{dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-common.jsonc
RENAMED
File without changes
|
{dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-gpl-pipeline.jsonc
RENAMED
File without changes
|
{dcicutils-8.8.3.1b12 → dcicutils-8.8.3.1b14}/dcicutils/license_policies/park-lab-pipeline.jsonc
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|