dcicutils 8.8.3.1b14__py3-none-any.whl → 8.8.3.1b16__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,6 +24,8 @@ def captured_output(capture: bool = True, encoding: Optional[str] = None):
24
24
 
25
25
  original_stdout = _real_stdout
26
26
  original_stderr = _real_stderr
27
+ # FYI: This encoding business with _EncodedStringIO was introduced (circa April 2024)
28
+ # when ran into issues unit testing progress_bar which outputs those funny block characters.
27
29
  captured_output = io.StringIO() if not encoding else _EncodedStringIO(encoding)
28
30
 
29
31
  def set_original_output() -> None:
@@ -3,6 +3,22 @@ from datetime import datetime, timedelta, timezone
3
3
  from dateutil import parser as datetime_parser
4
4
  from typing import Optional, Tuple, Union
5
5
 
6
+ TIMEZONE_LOCAL = datetime.now().astimezone().tzinfo # type: datetime.timezone
7
+ TIMEZONE_LOCAL_NAME = TIMEZONE_LOCAL.tzname(None) # type: str
8
+ TIMEZONE_LOCAL_OFFSET = TIMEZONE_LOCAL.utcoffset(None) # type: datetime.timedelta
9
+ TIMEZONE_LOCAL_OFFSET_TOTAL_MINUTES = int(TIMEZONE_LOCAL_OFFSET.total_seconds()) // 60 # type: int
10
+ TIMEZONE_LOCAL_OFFSET_HOURS = TIMEZONE_LOCAL_OFFSET_TOTAL_MINUTES // 60 # type: int
11
+ TIMEZONE_LOCAL_OFFSET_MINUTES = TIMEZONE_LOCAL_OFFSET_TOTAL_MINUTES % 60 # type: int
12
+ TIMEZONE_LOCAL_SUFFIX = f"{TIMEZONE_LOCAL_OFFSET_HOURS:+03d}:{TIMEZONE_LOCAL_OFFSET_MINUTES:02d}" # type: str
13
+
14
+ TIMEZONE_UTC = timezone.utc # type: datetime.timezone
15
+ TIMEZONE_UTC_NAME = TIMEZONE_UTC.tzname(None) # type: str
16
+ TIMEZONE_UTC_OFFSET = timedelta(0) # type: datetime.timedelta
17
+ TIMEZONE_UTC_OFFSET_TOTAL_MINUTES = 0 # type: int
18
+ TIMEZONE_UTC_OFFSET_HOURS = 0 # type: int
19
+ TIMEZONE_UTC_OFFSET_MINUTES = 0 # type: int
20
+ TIMEZONE_UTC_SUFFIX = "Z" # type: str
21
+
6
22
 
7
23
  def parse_datetime_string(value: str) -> Optional[datetime]:
8
24
  """
@@ -83,46 +99,60 @@ def normalize_date_string(value: str) -> Optional[str]:
83
99
  return d.strftime("%Y-%m-%d") if d else None
84
100
 
85
101
 
86
- def get_timezone(hours: int, minutes: Optional[int] = None) -> timezone:
102
+ def get_timezone(hours_or_timedelta: Union[int, timedelta], minutes: Optional[int] = None) -> timezone:
103
+ try:
104
+ if isinstance(hours_or_timedelta, timedelta):
105
+ return timezone(hours_or_timedelta)
106
+ return timezone(timedelta(hours=hours_or_timedelta, minutes=minutes or 0))
107
+ except Exception:
108
+ return TIMEZONE_LOCAL
109
+
110
+
111
+ def get_timezone_offset(tz: timezone) -> timedelta:
87
112
  try:
88
- return timezone(timedelta(hours=hours, minutes=minutes or 0))
113
+ return tz.utcoffset(None)
89
114
  except Exception:
90
- return timezone.utc
115
+ return TIMEZONE_LOCAL_OFFSET
91
116
 
92
117
 
93
118
  def get_timezone_hours_minutes(tz: timezone) -> Tuple[int, int]:
94
119
  """
95
120
  Returns a tuple with the integer hours and minutes offset for the given timezone.
121
+ If negative then only the hours is negative; the mintutes is always positive;
122
+ this is okay because there are no timezones less than one hour from UTC.
96
123
  """
97
- tz_minutes = datetime.now(tz).utcoffset().total_seconds() / 60
98
- return int(tz_minutes // 60), int(abs(tz_minutes % 60))
124
+ tz_offset = get_timezone_offset(tz)
125
+ tz_offset_total_minutes = int(tz_offset.total_seconds()) // 60
126
+ tz_offset_hours = tz_offset_total_minutes // 60
127
+ tz_offset_minutes = abs(tz_offset_total_minutes % 60)
128
+ return tz_offset_hours, tz_offset_minutes
99
129
 
100
130
 
101
131
  def get_utc_timezone() -> timezone:
102
- return timezone.utc
132
+ return TIMEZONE_UTC
103
133
 
104
134
 
105
135
  def get_local_timezone() -> timezone:
106
136
  """
107
137
  Returns current/local timezone as a datetime.timezone object.
108
138
  """
109
- return datetime.now().astimezone().tzinfo
139
+ return TIMEZONE_LOCAL
110
140
 
111
141
 
112
142
  def get_local_timezone_string() -> str:
113
143
  """
114
144
  Returns current/local timezone in format like: "-05:00".
115
145
  """
116
- tz_hours, tz_minutes = get_local_timezone_hours_minutes()
117
- return f"{tz_hours:+03d}:{tz_minutes:02d}"
146
+ return TIMEZONE_LOCAL_SUFFIX
118
147
 
119
148
 
120
149
  def get_local_timezone_hours_minutes() -> Tuple[int, int]:
121
150
  """
122
151
  Returns a tuple with the integer hours and minutes offset for the current/local timezone.
152
+ If negative then only the hours is negative; the mintutes is always positive;
153
+ this is okay because there are no timezones less than one hour from UTC.
123
154
  """
124
- tz_minutes = datetime.now(timezone.utc).astimezone().utcoffset().total_seconds() / 60
125
- return int(tz_minutes // 60), int(abs(tz_minutes % 60))
155
+ return TIMEZONE_LOCAL_OFFSET_HOURS, TIMEZONE_LOCAL_OFFSET_MINUTES
126
156
 
127
157
 
128
158
  def parse_datetime(value: str, utc: bool = False, tz: Optional[timezone] = None) -> Optional[datetime]:
@@ -154,14 +184,16 @@ def parse_datetime(value: str, utc: bool = False, tz: Optional[timezone] = None)
154
184
 
155
185
  def format_datetime(value: datetime,
156
186
  utc: bool = False,
157
- iso: bool = False,
158
- ms: bool = False,
159
187
  tz: Optional[Union[timezone, bool]] = None,
188
+ iso: bool = False,
160
189
  notz: bool = False,
161
190
  noseconds: bool = False,
191
+ ms: bool = False,
162
192
  verbose: bool = False,
163
193
  noseparator: bool = False,
164
- noday: bool = False) -> Optional[str]:
194
+ noday: bool = False,
195
+ nodate: bool = False,
196
+ notime: bool = False) -> str:
165
197
  """
166
198
  Returns the given datetime as a string in "YYYY:MM:DD hh:mm:ss tz" format, for
167
199
  example "2024-04-17 15:42:26 EDT". If the given notz argument is True then omits
@@ -173,9 +205,11 @@ def format_datetime(value: datetime,
173
205
  one; if the given utc argument is True then it will be UTC; or if the given tz
174
206
  argument is a datetime.timezone it will be in that timezone.
175
207
  """
208
+ if nodate is True and notime is True:
209
+ return ""
176
210
  if not isinstance(value, datetime):
177
211
  if not isinstance(value, str) or not (value := parse_datetime(value)):
178
- return None
212
+ return ""
179
213
  try:
180
214
  if utc is True:
181
215
  tz = timezone.utc
@@ -195,19 +229,70 @@ def format_datetime(value: datetime,
195
229
  value = value.replace(microsecond=0)
196
230
  if noseconds is True:
197
231
  if notz is True:
198
- return value.strftime(f"%Y-%m-%dT%H:%M")
232
+ if nodate is True:
233
+ return value.strftime(f"%H:%M")
234
+ elif notime is True:
235
+ return value.strftime(f"%Y-%m-%d")
236
+ else:
237
+ return value.strftime(f"%Y-%m-%dT%H:%M")
199
238
  tz = value.strftime("%z")
200
239
  tz = tz[:3] + ":" + tz[3:]
201
- return value.strftime(f"%Y-%m-%dT%H:%M") + tz
202
- return value.isoformat()
240
+ if nodate is True:
241
+ return value.strftime(f"%H:%M") + tz
242
+ elif notime is True:
243
+ return value.strftime(f"%Y-%m-%d") + tz
244
+ else:
245
+ return value.strftime(f"%Y-%m-%dT%H:%M") + tz
246
+ if nodate is True:
247
+ return value.strftime(f"%H:%M:%S{f'.%f' if ms is True else ''}")
248
+ elif notime is True:
249
+ return value.strftime(f"%Y-%m-%d")
250
+ else:
251
+ return value.isoformat()
203
252
  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'}")
253
+ if nodate is True:
254
+ return value.strftime(
255
+ f"%-I:%M{'' if noseconds is True else ':%S'}"
256
+ f"{f'.%f' if ms is True else ''} %p{'' if notz is True else ' %Z'}")
257
+ elif notime is True:
258
+ return value.strftime(f"{'' if noday is True else '%A, '}%B %-d, %Y")
259
+ else:
260
+ return value.strftime(
261
+ f"{'' if noday is True else '%A, '}%B %-d, %Y{'' if noseparator is True else ' |'}"
262
+ f" %-I:%M{'' if noseconds is True else ':%S'}"
263
+ f"{f'.%f' if ms is True else ''} %p{'' if notz is True else ' %Z'}")
208
264
  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'}")
265
+ if nodate is True:
266
+ return value.strftime(
267
+ f"%H:%M{'' if noseconds is True else ':%S'}"
268
+ f"{f'.%f' if ms is True else ''}{'' if notz is True else ' %Z'}")
269
+ elif notime is True:
270
+ return value.strftime(f"%Y-%m-%d")
271
+ else:
272
+ return value.strftime(
273
+ f"%Y-%m-%d %H:%M{'' if noseconds is True else ':%S'}"
274
+ f"{f'.%f' if ms is True else ''}{'' if notz is True else ' %Z'}")
212
275
  except Exception:
213
276
  return None
277
+
278
+
279
+ def format_date(value: datetime,
280
+ utc: bool = False,
281
+ iso: bool = False,
282
+ tz: Optional[Union[timezone, bool]] = None,
283
+ verbose: bool = False,
284
+ noday: bool = False) -> str:
285
+ return format_datetime(value, utc=utc, iso=iso, tz=tz, verbose=verbose, noday=noday, notime=True)
286
+
287
+
288
+ def format_time(value: datetime,
289
+ utc: bool = False,
290
+ iso: bool = False,
291
+ ms: bool = False,
292
+ tz: Optional[Union[timezone, bool]] = None,
293
+ notz: bool = False,
294
+ noseconds: bool = False,
295
+ verbose: bool = False,
296
+ noday: bool = False) -> str:
297
+ return format_datetime(value, utc=utc, iso=iso, ms=ms, tz=tz, notz=notz,
298
+ noseconds=noseconds, verbose=verbose, nodate=True)
@@ -174,6 +174,18 @@ class StructuredDataSet:
174
174
  result = []
175
175
  if self._norefs:
176
176
  for ref in self._resolved_refs:
177
+ # The structure of this self._resolved_refs is setup in Schema._map_function_ref,
178
+ # which is called whenever a reference (linkTo) is encountered. It is a set of
179
+ # tuples containing three items: [0] the ref path, [1] its uuid (if applicable),
180
+ # and [2] its src. The src identifies the place where this ref occurred and is a
181
+ # dictionary containing file, type, column, and row properties. For this case, of
182
+ # norefs (i.e. unchecked refs), the uuid ([1]) is None because we are skipping
183
+ # ref resolution. But the src is actually a *string* dump of the dictionary, only
184
+ # because dictionaries cannot be put in a set (which is what _resolved_refs is);
185
+ # this dump is also done in Schema._map_function_ref (should probably change this
186
+ # to be a list to avoid this - TODO); we only even store this src info for this
187
+ # norefs case, as not really needed otherwise. This is just to support the
188
+ # useful-for-troublehsooting options --info --refs for smaht-submitr.
177
189
  if len(ref) >= 3 and (ref_path := ref[0]) and (ref_src := load_json(ref[2])):
178
190
  if existing_ref := [item for item in result if item.get("path") == ref_path]:
179
191
  existing_ref[0]["srcs"].append(ref_src)
@@ -681,9 +693,11 @@ class Schema(SchemaBase):
681
693
  # Here the caller has specified the (StructuredDataSet) norefs option
682
694
  # which means we do not check for the existence of references at all.
683
695
  if value:
684
- # Dump the src as a JSON string because a dictionary cannot be added to a set; note
685
- # that this is ONLY used for smaht-submitr/submit-metadata-bundle --info --refs.
686
- # This info can be gotten at using StructureDataSet.unchecked_refs.
696
+ # Dump the src as a JSON string because a dictionary cannot be added to a set;
697
+ # this is ONLY used for smaht-submitr/submit-metadata-bundle --info --refs.
698
+ # This info exposed via StructureDataSet.unchecked_refs. TODO: Should probably
699
+ # make this not a set type so we dont' have to do this dump (and corresponding
700
+ # load, in StructureDataSet.unchecked_refs).
687
701
  self._resolved_refs.add((f"/{link_to}/{value}", None,
688
702
  json.dumps(src) if isinstance(src, dict) else None))
689
703
  return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.8.3.1b14
3
+ Version: 8.8.3.1b16
4
4
  Summary: Utility package for interacting with the 4DN Data Portal and other 4DN resources
5
5
  Home-page: https://github.com/4dn-dcic/utils
6
6
  License: MIT
@@ -2,7 +2,7 @@ dcicutils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  dcicutils/base.py,sha256=gxNEv3DSVUfoX3NToWw7pcCdguxsJ75NDqsPi3wdFG4,5115
3
3
  dcicutils/beanstalk_utils.py,sha256=nHMWfFnZAXFiJh60oVouwbAPMKsQfHnDtkwz_PDE6S4,51434
4
4
  dcicutils/bundle_utils.py,sha256=ZVQcqlt7Yly8-YbL3A-5DW859_hMWpTL6dXtknEYZIw,34669
5
- dcicutils/captured_output.py,sha256=2pzkq7vtC47zwsSeXS6cJNxC_LXpUbw6ArudKlJHdb0,3108
5
+ dcicutils/captured_output.py,sha256=0hP7sPwleMaYXQAvCfJOxG8Z8T_JJYy8ADp8A5ZoblE,3295
6
6
  dcicutils/cloudformation_utils.py,sha256=MtWJrSTXyiImgbPHgRvfH9bWso20ZPLTFJAfhDQSVj4,13786
7
7
  dcicutils/codebuild_utils.py,sha256=CKpmhJ-Z8gYbkt1I2zyMlKtFdsg7T8lqrx3V5ieta-U,1155
8
8
  dcicutils/command_utils.py,sha256=JExll5TMqIcmuiGvuS8q4XDUvoEfi2oSH0E2FVF6suU,15285
@@ -12,7 +12,7 @@ dcicutils/contribution_utils.py,sha256=vYLS1JUB3sKd24BUxZ29qUBqYeQBLK9cwo8x3k64u
12
12
  dcicutils/creds_utils.py,sha256=xrLekD49Ex0GOpL9n7LlJA4gvNcY7txTVFOSYD7LvEU,11113
13
13
  dcicutils/data_readers.py,sha256=6EMrY7TjDE8H7bA_TCWtpLQP7slJ0YTL77_dNh6e7sg,7626
14
14
  dcicutils/data_utils.py,sha256=k2OxOlsx7AJ6jF-YNlMyGus_JqSUBe4_n1s65Mv1gQQ,3098
15
- dcicutils/datetime_utils.py,sha256=S3SCmpqv_Z0CS8_yqZsoN4aL1Epu8jwKs6DDYs96OdM,9427
15
+ dcicutils/datetime_utils.py,sha256=STFFakdCIwtuRLA1PCbieeXniTPY8Rchg5Psv1CUJ84,13342
16
16
  dcicutils/deployment_utils.py,sha256=sKv8Jb-_Zw2aH3OAywRlsre-Cqm3a7fEGG3_1PT-r-s,69908
17
17
  dcicutils/diff_utils.py,sha256=sQx-yz56DHAcQWOChYbAG3clXu7TbiZKlw-GggeveO0,8118
18
18
  dcicutils/docker_utils.py,sha256=30gUiqz7X9rJwSPXTPn4ewjQibUgoSJqhP9o9vn5X-A,1747
@@ -63,7 +63,7 @@ dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19
63
63
  dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
64
64
  dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
65
65
  dcicutils/ssl_certificate_utils.py,sha256=F0ifz_wnRRN9dfrfsz7aCp4UDLgHEY8LaK7PjnNvrAQ,9707
66
- dcicutils/structured_data.py,sha256=Ho97KAqPgfn9QVfIMpjp0gS1FXLAnEJu6usdVi2lGHc,59927
66
+ dcicutils/structured_data.py,sha256=BQuIMv6OPySsn6YxtXE2Er-zLE2QJuCYhEQ3V0u_UXY,61238
67
67
  dcicutils/submitr/progress_constants.py,sha256=5bxyX77ql8qEJearfHEvsvXl7D0GuUODW0T65mbRmnE,2895
68
68
  dcicutils/submitr/ref_lookup_strategy.py,sha256=Js2cVznTmgjciLWBPLCvMiwLIHXjDn3jww-gJPjYuFw,3467
69
69
  dcicutils/task_utils.py,sha256=MF8ujmTD6-O2AC2gRGPHyGdUrVKgtr8epT5XU8WtNjk,8082
@@ -72,8 +72,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
72
72
  dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
73
73
  dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
74
74
  dcicutils/zip_utils.py,sha256=rnjNv_k6L9jT2SjDSgVXp4BEJYLtz9XN6Cl2Fy-tqnM,2027
75
- dcicutils-8.8.3.1b14.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
76
- dcicutils-8.8.3.1b14.dist-info/METADATA,sha256=Od4jg67J1tt_7cxv1n04p8PGmpjbnlfi9-EKsMS-GKk,3357
77
- dcicutils-8.8.3.1b14.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
78
- dcicutils-8.8.3.1b14.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
79
- dcicutils-8.8.3.1b14.dist-info/RECORD,,
75
+ dcicutils-8.8.3.1b16.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
76
+ dcicutils-8.8.3.1b16.dist-info/METADATA,sha256=911IvUnQQ4KuLpV7KUYEOz9rduwKZVugINSficjzMIY,3357
77
+ dcicutils-8.8.3.1b16.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
78
+ dcicutils-8.8.3.1b16.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
79
+ dcicutils-8.8.3.1b16.dist-info/RECORD,,