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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,,