-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #299 from 4dn-dcic/dmichaels-20240205
Fixes related to submitr for date/time type and out-of-order references.
- Loading branch information
Showing
14 changed files
with
553 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from collections import namedtuple | ||
from contextlib import contextmanager | ||
import io | ||
import sys | ||
from typing import Optional | ||
|
||
_real_stdout = sys.stdout | ||
_real_stderr = sys.stderr | ||
|
||
|
||
@contextmanager | ||
def captured_output(capture: bool = True): | ||
""" | ||
Context manager to capture any/all output to stdout or stderr, and not actually output it to stdout | ||
or stderr. Yields and object with a get_captured_output() method to get the output captured thus far, | ||
and another uncaptured_print() method to actually print the given output to stdout, even though output | ||
to stdout is being captured. Can be useful, for example, in creating command-line scripts which invoke | ||
code which outputs a lot of info, warning, error, etc to stdout or stderr, and we want to suprress that | ||
output; but with the yielded uncaptured_print() method output specific to the script can actually be | ||
output (to stdout); and/or can also optionally output any/all captured output, e.g. for debugging or | ||
troubleshooting purposes. Disable this capture, without having to restructure your code WRT the usage | ||
of the with-clause with this context manager, pass False as an argument to this context manager. | ||
""" | ||
|
||
original_stdout = _real_stdout | ||
original_stderr = _real_stderr | ||
captured_output = io.StringIO() | ||
|
||
def set_original_output() -> None: | ||
sys.stdout = original_stdout | ||
sys.stderr = original_stderr | ||
|
||
def set_captured_output() -> None: | ||
if capture: | ||
sys.stdout = captured_output | ||
sys.stderr = captured_output | ||
|
||
def uncaptured_print(*args, **kwargs) -> None: | ||
set_original_output() | ||
print(*args, **kwargs) | ||
set_captured_output() | ||
|
||
def uncaptured_input(message: str) -> str: | ||
set_original_output() | ||
value = input(message) | ||
set_captured_output() | ||
return value | ||
|
||
def get_captured_output() -> Optional[str]: | ||
return captured_output.getvalue() if capture else None | ||
|
||
try: | ||
set_captured_output() | ||
Result = namedtuple("Result", ["get_captured_output", "uncaptured_print", "uncaptured_input"]) | ||
yield Result(get_captured_output, uncaptured_print, uncaptured_input) | ||
finally: | ||
set_original_output() | ||
|
||
|
||
@contextmanager | ||
def uncaptured_output(): | ||
original_stdout = sys.stdout | ||
original_stderr = sys.stderr | ||
sys.stdout = _real_stdout | ||
sys.stderr = _real_stderr | ||
try: | ||
yield | ||
finally: | ||
sys.stdout = original_stdout | ||
sys.stderr = original_stderr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
from dcicutils.misc_utils import normalize_spaces | ||
from datetime import datetime, timedelta, timezone | ||
from typing import Optional, Tuple | ||
|
||
|
||
def parse_datetime_string(value: str) -> Optional[datetime]: | ||
""" | ||
Parses the given string into a datetime object and returns it, or if ill-formated then returns None. | ||
The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss" and with an optional timezone | ||
suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the format "YYYY-MM-DD" in which | ||
case a time of "00:00:00" is assumed. If no timezone is specified then the local timezone is assumed. | ||
""" | ||
if not isinstance(value, str) or not (value := normalize_spaces(value)): | ||
return None | ||
tz_hours = -1 | ||
tz_minutes = -1 | ||
if value.rfind("T") > 0: | ||
value = value.replace("T", " ") | ||
if (space := value.find(" ")) > 0 and (value_suffix := value[space + 1:]): | ||
if (plus := value_suffix.rfind("+")) > 0 or (minus := value_suffix.rfind("-")) > 0: | ||
value = normalize_spaces(value[:space] + " " + value_suffix[:(plus if plus > 0 else minus)]) | ||
if value_tz := normalize_spaces(value_suffix[(plus if plus > 0 else minus) + 1:]): | ||
if len(value_tz := value_tz.split(":")) == 2: | ||
value_tz_hours = value_tz[0].strip() | ||
value_tz_minutes = value_tz[1].strip() | ||
else: | ||
value_tz_hours = value_tz[0].strip() | ||
value_tz_minutes = "0" | ||
if value_tz_hours.isdigit() and value_tz_minutes.isdigit(): | ||
tz_hours = int(value_tz_hours) | ||
tz_minutes = int(value_tz_minutes) | ||
if not (plus > 0): | ||
tz_hours = -tz_hours | ||
else: | ||
value = value + " 00:00:00" | ||
if tz_hours < 0 or tz_minutes < 0: | ||
tz_hours, tz_minutes = get_local_timezone_hours_minutes() | ||
try: | ||
dt = datetime.strptime(value, "%Y-%m-%d %H:%M:%S") | ||
tz = timezone(timedelta(hours=tz_hours, minutes=tz_minutes)) | ||
return dt.replace(tzinfo=tz) | ||
except Exception: | ||
return None | ||
|
||
|
||
def parse_date_string(value: str) -> Optional[datetime]: | ||
""" | ||
Parses the given string into a datetime object representing only a date and | ||
returns it, or if ill-formated then returns None. The given string is assumed | ||
to be in the format "YYYY-MM-DD"; if a given string of this format is suffixed | ||
with a space or a "T" and ANYTHING else, then that trailing portion is ignored. | ||
""" | ||
if isinstance(value, str) and (value := normalize_spaces(value)): | ||
if (separator := value.find(" ")) > 0 or (separator := value.find("T")) > 0: | ||
value = value[:separator] | ||
try: | ||
return datetime.strptime(value, "%Y-%m-%d") | ||
except Exception: | ||
pass | ||
|
||
|
||
def normalize_datetime_string(value: str) -> Optional[str]: | ||
""" | ||
Parses the given string into a datetime object and returns a string for that datetime in ISO-8601 format, | ||
or if ill-formated then returns None. The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss" | ||
and with an optional timezone suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the | ||
format "YYYY-MM-DD" in which case a time of "00:00:00" is assumed. If no timezone is specified then | ||
the local timezone is assumed. The returned format looks like this: "2024-02-08T10:37:51-05:00" | ||
""" | ||
dt = parse_datetime_string(value) | ||
return dt.isoformat() if dt else None | ||
|
||
|
||
def normalize_date_string(value: str) -> Optional[str]: | ||
""" | ||
Parses the given string into a datetime object representing only a date and returns a string for that | ||
date in ISO-8601 format, or if ill-formated then returns None. The given string is assumed to be in | ||
the format "YYYY-MM-DD"; but if a given string of this format is suffixed with a space followed by | ||
ANYTHING else, then that trailing portion is ignored. The returned format looks like this: "2024-02-08" | ||
""" | ||
d = parse_date_string(value) | ||
return d.strftime("%Y-%m-%d") if d else None | ||
|
||
|
||
def get_local_timezone_string() -> str: | ||
""" | ||
Returns current/local timezone in format like: "-05:00". | ||
""" | ||
tz_hours, tz_minutes = get_local_timezone_hours_minutes() | ||
return f"{tz_hours:+03d}:{tz_minutes:02d}" | ||
|
||
|
||
def get_local_timezone_hours_minutes() -> Tuple[int, int]: | ||
""" | ||
Returns a tuple with the integer hours and minutes offset for the current/local timezone. | ||
""" | ||
tz_minutes = datetime.now(timezone.utc).astimezone().utcoffset().total_seconds() / 60 | ||
return int(tz_minutes // 60), int(abs(tz_minutes % 60)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.