diff options
| author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
|---|---|---|
| committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
| commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
| tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/isodate | |
| parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
| download | gn-ai-master.tar.gz | |
Diffstat (limited to '.venv/lib/python3.12/site-packages/isodate')
11 files changed, 1438 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/isodate/__init__.py b/.venv/lib/python3.12/site-packages/isodate/__init__.py new file mode 100644 index 00000000..d9cca6a5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/__init__.py @@ -0,0 +1,103 @@ +""" +Import all essential functions and constants to re-export them here for easy +access. + +This module contains also various pre-defined ISO 8601 format strings. +""" + +from isodate.duration import Duration +from isodate.isodates import date_isoformat, parse_date +from isodate.isodatetime import datetime_isoformat, parse_datetime +from isodate.isoduration import duration_isoformat, parse_duration +from isodate.isoerror import ISO8601Error +from isodate.isostrf import ( + D_ALT_BAS, + D_ALT_BAS_ORD, + D_ALT_EXT, + D_ALT_EXT_ORD, + D_DEFAULT, + D_WEEK, + DATE_BAS_COMPLETE, + DATE_BAS_MONTH, + DATE_BAS_ORD_COMPLETE, + DATE_BAS_WEEK, + DATE_BAS_WEEK_COMPLETE, + DATE_CENTURY, + DATE_EXT_COMPLETE, + DATE_EXT_MONTH, + DATE_EXT_ORD_COMPLETE, + DATE_EXT_WEEK, + DATE_EXT_WEEK_COMPLETE, + DATE_YEAR, + DT_BAS_COMPLETE, + DT_BAS_ORD_COMPLETE, + DT_BAS_WEEK_COMPLETE, + DT_EXT_COMPLETE, + DT_EXT_ORD_COMPLETE, + DT_EXT_WEEK_COMPLETE, + TIME_BAS_COMPLETE, + TIME_BAS_MINUTE, + TIME_EXT_COMPLETE, + TIME_EXT_MINUTE, + TIME_HOUR, + TZ_BAS, + TZ_EXT, + TZ_HOUR, + strftime, +) +from isodate.isotime import parse_time, time_isoformat +from isodate.isotzinfo import parse_tzinfo, tz_isoformat +from isodate.tzinfo import LOCAL, UTC, FixedOffset +from isodate.version import version as __version__ + +__all__ = [ + "parse_date", + "date_isoformat", + "parse_time", + "time_isoformat", + "parse_datetime", + "datetime_isoformat", + "parse_duration", + "duration_isoformat", + "ISO8601Error", + "parse_tzinfo", + "tz_isoformat", + "UTC", + "FixedOffset", + "LOCAL", + "Duration", + "strftime", + "DATE_BAS_COMPLETE", + "DATE_BAS_ORD_COMPLETE", + "DATE_BAS_WEEK", + "DATE_BAS_WEEK_COMPLETE", + "DATE_CENTURY", + "DATE_EXT_COMPLETE", + "DATE_EXT_ORD_COMPLETE", + "DATE_EXT_WEEK", + "DATE_EXT_WEEK_COMPLETE", + "DATE_YEAR", + "DATE_BAS_MONTH", + "DATE_EXT_MONTH", + "TIME_BAS_COMPLETE", + "TIME_BAS_MINUTE", + "TIME_EXT_COMPLETE", + "TIME_EXT_MINUTE", + "TIME_HOUR", + "TZ_BAS", + "TZ_EXT", + "TZ_HOUR", + "DT_BAS_COMPLETE", + "DT_EXT_COMPLETE", + "DT_BAS_ORD_COMPLETE", + "DT_EXT_ORD_COMPLETE", + "DT_BAS_WEEK_COMPLETE", + "DT_EXT_WEEK_COMPLETE", + "D_DEFAULT", + "D_WEEK", + "D_ALT_EXT", + "D_ALT_BAS", + "D_ALT_BAS_ORD", + "D_ALT_EXT_ORD", + "__version__", +] diff --git a/.venv/lib/python3.12/site-packages/isodate/duration.py b/.venv/lib/python3.12/site-packages/isodate/duration.py new file mode 100644 index 00000000..bc8a5cb9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/duration.py @@ -0,0 +1,316 @@ +""" +This module defines a Duration class. + +The class Duration allows to define durations in years and months and can be +used as limited replacement for timedelta objects. +""" + +from datetime import timedelta +from decimal import ROUND_FLOOR, Decimal + + +def fquotmod(val, low, high): + """ + A divmod function with boundaries. + + """ + # assumes that all the maths is done with Decimals. + # divmod for Decimal uses truncate instead of floor as builtin + # divmod, so we have to do it manually here. + a, b = val - low, high - low + div = (a / b).to_integral(ROUND_FLOOR) + mod = a - div * b + # if we were not using Decimal, it would look like this. + # div, mod = divmod(val - low, high - low) + mod += low + return int(div), mod + + +def max_days_in_month(year, month): + """ + Determines the number of days of a specific month in a specific year. + """ + if month in (1, 3, 5, 7, 8, 10, 12): + return 31 + if month in (4, 6, 9, 11): + return 30 + if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0): + return 29 + return 28 + + +class Duration: + """ + A class which represents a duration. + + The difference to datetime.timedelta is, that this class handles also + differences given in years and months. + A Duration treats differences given in year, months separately from all + other components. + + A Duration can be used almost like any timedelta object, however there + are some restrictions: + * It is not really possible to compare Durations, because it is unclear, + whether a duration of 1 year is bigger than 365 days or not. + * Equality is only tested between the two (year, month vs. timedelta) + basic components. + + A Duration can also be converted into a datetime object, but this requires + a start date or an end date. + + The algorithm to add a duration to a date is defined at + http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes + """ + + def __init__( + self, + days=0, + seconds=0, + microseconds=0, + milliseconds=0, + minutes=0, + hours=0, + weeks=0, + months=0, + years=0, + ): + """ + Initialise this Duration instance with the given parameters. + """ + if not isinstance(months, Decimal): + months = Decimal(str(months)) + if not isinstance(years, Decimal): + years = Decimal(str(years)) + self.months = months + self.years = years + self.tdelta = timedelta( + days, seconds, microseconds, milliseconds, minutes, hours, weeks + ) + + def __getstate__(self): + return self.__dict__ + + def __setstate__(self, state): + self.__dict__.update(state) + + def __getattr__(self, name): + """ + Provide direct access to attributes of included timedelta instance. + """ + return getattr(self.tdelta, name) + + def __str__(self): + """ + Return a string representation of this duration similar to timedelta. + """ + params = [] + if self.years: + params.append("%d years" % self.years) + if self.months: + fmt = "%d months" + if self.months <= 1: + fmt = "%d month" + params.append(fmt % self.months) + params.append(str(self.tdelta)) + return ", ".join(params) + + def __repr__(self): + """ + Return a string suitable for repr(x) calls. + """ + return "%s.%s(%d, %d, %d, years=%d, months=%d)" % ( + self.__class__.__module__, + self.__class__.__name__, + self.tdelta.days, + self.tdelta.seconds, + self.tdelta.microseconds, + self.years, + self.months, + ) + + def __hash__(self): + """ + Return a hash of this instance so that it can be used in, for + example, dicts and sets. + """ + return hash((self.tdelta, self.months, self.years)) + + def __neg__(self): + """ + A simple unary minus. + + Returns a new Duration instance with all it's negated. + """ + negduration = Duration(years=-self.years, months=-self.months) + negduration.tdelta = -self.tdelta + return negduration + + def __add__(self, other): + """ + Durations can be added with Duration, timedelta, date and datetime + objects. + """ + if isinstance(other, Duration): + newduration = Duration( + years=self.years + other.years, months=self.months + other.months + ) + newduration.tdelta = self.tdelta + other.tdelta + return newduration + try: + # try anything that looks like a date or datetime + # 'other' has attributes year, month, day + # and relies on 'timedelta + other' being implemented + if not (float(self.years).is_integer() and float(self.months).is_integer()): + raise ValueError( + "fractional years or months not supported" " for date calculations" + ) + newmonth = other.month + self.months + carry, newmonth = fquotmod(newmonth, 1, 13) + newyear = other.year + self.years + carry + maxdays = max_days_in_month(newyear, newmonth) + if other.day > maxdays: + newday = maxdays + else: + newday = other.day + newdt = other.replace( + year=int(newyear), month=int(newmonth), day=int(newday) + ) + # does a timedelta + date/datetime + return self.tdelta + newdt + except AttributeError: + # other probably was not a date/datetime compatible object + pass + try: + # try if other is a timedelta + # relies on timedelta + timedelta supported + newduration = Duration(years=self.years, months=self.months) + newduration.tdelta = self.tdelta + other + return newduration + except AttributeError: + # ignore ... other probably was not a timedelta compatible object + pass + # we have tried everything .... return a NotImplemented + return NotImplemented + + __radd__ = __add__ + + def __mul__(self, other): + if isinstance(other, int): + newduration = Duration(years=self.years * other, months=self.months * other) + newduration.tdelta = self.tdelta * other + return newduration + return NotImplemented + + __rmul__ = __mul__ + + def __sub__(self, other): + """ + It is possible to subtract Duration and timedelta objects from Duration + objects. + """ + if isinstance(other, Duration): + newduration = Duration( + years=self.years - other.years, months=self.months - other.months + ) + newduration.tdelta = self.tdelta - other.tdelta + return newduration + try: + # do maths with our timedelta object .... + newduration = Duration(years=self.years, months=self.months) + newduration.tdelta = self.tdelta - other + return newduration + except TypeError: + # looks like timedelta - other is not implemented + pass + return NotImplemented + + def __rsub__(self, other): + """ + It is possible to subtract Duration objects from date, datetime and + timedelta objects. + + TODO: there is some weird behaviour in date - timedelta ... + if timedelta has seconds or microseconds set, then + date - timedelta != date + (-timedelta) + for now we follow this behaviour to avoid surprises when mixing + timedeltas with Durations, but in case this ever changes in + the stdlib we can just do: + return -self + other + instead of all the current code + """ + if isinstance(other, timedelta): + tmpdur = Duration() + tmpdur.tdelta = other + return tmpdur - self + try: + # check if other behaves like a date/datetime object + # does it have year, month, day and replace? + if not (float(self.years).is_integer() and float(self.months).is_integer()): + raise ValueError( + "fractional years or months not supported" " for date calculations" + ) + newmonth = other.month - self.months + carry, newmonth = fquotmod(newmonth, 1, 13) + newyear = other.year - self.years + carry + maxdays = max_days_in_month(newyear, newmonth) + if other.day > maxdays: + newday = maxdays + else: + newday = other.day + newdt = other.replace( + year=int(newyear), month=int(newmonth), day=int(newday) + ) + return newdt - self.tdelta + except AttributeError: + # other probably was not compatible with data/datetime + pass + return NotImplemented + + def __eq__(self, other): + """ + If the years, month part and the timedelta part are both equal, then + the two Durations are considered equal. + """ + if isinstance(other, Duration): + if (self.years * 12 + self.months) == ( + other.years * 12 + other.months + ) and self.tdelta == other.tdelta: + return True + return False + # check if other con be compared against timedelta object + # will raise an AssertionError when optimisation is off + if self.years == 0 and self.months == 0: + return self.tdelta == other + return False + + def __ne__(self, other): + """ + If the years, month part or the timedelta part is not equal, then + the two Durations are considered not equal. + """ + if isinstance(other, Duration): + if (self.years * 12 + self.months) != ( + other.years * 12 + other.months + ) or self.tdelta != other.tdelta: + return True + return False + # check if other can be compared against timedelta object + # will raise an AssertionError when optimisation is off + if self.years == 0 and self.months == 0: + return self.tdelta != other + return True + + def totimedelta(self, start=None, end=None): + """ + Convert this duration into a timedelta object. + + This method requires a start datetime or end datetimem, but raises + an exception if both are given. + """ + if start is None and end is None: + raise ValueError("start or end required") + if start is not None and end is not None: + raise ValueError("only start or end allowed") + if start is not None: + return (start + self) - start + return end - (end - self) diff --git a/.venv/lib/python3.12/site-packages/isodate/isodates.py b/.venv/lib/python3.12/site-packages/isodate/isodates.py new file mode 100644 index 00000000..d32fe25e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isodates.py @@ -0,0 +1,203 @@ +""" +This modules provides a method to parse an ISO 8601:2004 date string to a +python datetime.date instance. + +It supports all basic, extended and expanded formats as described in the ISO +standard. The only limitations it has, are given by the Python datetime.date +implementation, which does not support dates before 0001-01-01. +""" + +import re +from datetime import date, timedelta + +from isodate.isoerror import ISO8601Error +from isodate.isostrf import DATE_EXT_COMPLETE, strftime + +DATE_REGEX_CACHE = {} +# A dictionary to cache pre-compiled regular expressions. +# A set of regular expressions is identified, by number of year digits allowed +# and whether a plus/minus sign is required or not. (This option is changeable +# only for 4 digit years). + + +def build_date_regexps(yeardigits=4, expanded=False): + """ + Compile set of regular expressions to parse ISO dates. The expressions will + be created only if they are not already in REGEX_CACHE. + + It is necessary to fix the number of year digits, else it is not possible + to automatically distinguish between various ISO date formats. + + ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/- + sign is required (expanded format). To support +/- sign for 4 digit years, + the expanded parameter needs to be set to True. + """ + if yeardigits != 4: + expanded = True + if (yeardigits, expanded) not in DATE_REGEX_CACHE: + cache_entry = [] + # ISO 8601 expanded DATE formats allow an arbitrary number of year + # digits with a leading +/- sign. + if expanded: + sign = 1 + else: + sign = 0 + + def add_re(regex_text): + cache_entry.append(re.compile(r"\A" + regex_text + r"\Z")) + + # 1. complete dates: + # YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" % (sign, yeardigits) + ) + # YYYYMMDD or +- YYYYYYMMDD... basic date format + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" % (sign, yeardigits) + ) + # 2. complete week dates: + # YYYY-Www-D or +-YYYYYY-Www-D ... extended week date + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" % (sign, yeardigits) + ) + # YYYYWwwD or +-YYYYYYWwwD ... basic week date + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" + r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" % (sign, yeardigits) + ) + # 3. ordinal dates: + # YYYY-DDD or +-YYYYYY-DDD ... extended format + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"-(?P<day>[0-9]{3})" % (sign, yeardigits) + ) + # YYYYDDD or +-YYYYYYDDD ... basic format + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"(?P<day>[0-9]{3})" % (sign, yeardigits) + ) + # 4. week dates: + # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date + # 4. week dates: + # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"-W(?P<week>[0-9]{2})" % (sign, yeardigits) + ) + # YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" + r"(?P<week>[0-9]{2})" % (sign, yeardigits) + ) + # 5. month dates: + # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month + # 5. month dates: + # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"-(?P<month>[0-9]{2})" % (sign, yeardigits) + ) + # YYYMM or +-YYYYYYMM ... basic incomplete month date format + add_re( + r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" + r"(?P<month>[0-9]{2})" % (sign, yeardigits) + ) + # 6. year dates: + # YYYY or +-YYYYYY ... reduced accuracy specific year + add_re(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" % (sign, yeardigits)) + # 7. century dates: + # YY or +-YYYY ... reduced accuracy specific century + add_re(r"(?P<sign>[+-]){%d}" r"(?P<century>[0-9]{%d})" % (sign, yeardigits - 2)) + + DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry + return DATE_REGEX_CACHE[(yeardigits, expanded)] + + +def parse_date(datestring, yeardigits=4, expanded=False, defaultmonth=1, defaultday=1): + """ + Parse an ISO 8601 date string into a datetime.date object. + + As the datetime.date implementation is limited to dates starting from + 0001-01-01, negative dates (BC) and year 0 can not be parsed by this + method. + + For incomplete dates, this method chooses the first day for it. For + instance if only a century is given, this method returns the 1st of + January in year 1 of this century. + + supported formats: (expanded formats are shown with 6 digits for year) + YYYYMMDD +-YYYYYYMMDD basic complete date + YYYY-MM-DD +-YYYYYY-MM-DD extended complete date + YYYYWwwD +-YYYYYYWwwD basic complete week date + YYYY-Www-D +-YYYYYY-Www-D extended complete week date + YYYYDDD +-YYYYYYDDD basic ordinal date + YYYY-DDD +-YYYYYY-DDD extended ordinal date + YYYYWww +-YYYYYYWww basic incomplete week date + YYYY-Www +-YYYYYY-Www extended incomplete week date + YYYMM +-YYYYYYMM basic incomplete month date + YYY-MM +-YYYYYY-MM incomplete month date + YYYY +-YYYYYY incomplete year date + YY +-YYYY incomplete century date + + @param datestring: the ISO date string to parse + @param yeardigits: how many digits are used to represent a year + @param expanded: if True then +/- signs are allowed. This parameter + is forced to True, if yeardigits != 4 + + @return: a datetime.date instance represented by datestring + @raise ISO8601Error: if this function can not parse the datestring + @raise ValueError: if datestring can not be represented by datetime.date + """ + if yeardigits != 4: + expanded = True + isodates = build_date_regexps(yeardigits, expanded) + for pattern in isodates: + match = pattern.match(datestring) + if match: + groups = match.groupdict() + # sign, century, year, month, week, day, + # FIXME: negative dates not possible with python standard types + sign = (groups["sign"] == "-" and -1) or 1 + if "century" in groups: + return date( + sign * (int(groups["century"]) * 100 + 1), defaultmonth, defaultday + ) + if "month" not in groups: # weekdate or ordinal date + ret = date(sign * int(groups["year"]), 1, 1) + if "week" in groups: + isotuple = ret.isocalendar() + if "day" in groups: + days = int(groups["day"] or 1) + else: + days = 1 + # if first week in year, do weeks-1 + return ret + timedelta( + weeks=int(groups["week"]) - (((isotuple[1] == 1) and 1) or 0), + days=-isotuple[2] + days, + ) + elif "day" in groups: # ordinal date + return ret + timedelta(days=int(groups["day"]) - 1) + else: # year date + return ret.replace(month=defaultmonth, day=defaultday) + # year-, month-, or complete date + if "day" not in groups or groups["day"] is None: + day = defaultday + else: + day = int(groups["day"]) + return date( + sign * int(groups["year"]), int(groups["month"]) or defaultmonth, day + ) + raise ISO8601Error("Unrecognised ISO 8601 date format: %r" % datestring) + + +def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4): + """ + Format date strings. + + This method is just a wrapper around isodate.isostrf.strftime and uses + Date-Extended-Complete as default format. + """ + return strftime(tdate, format, yeardigits) diff --git a/.venv/lib/python3.12/site-packages/isodate/isodatetime.py b/.venv/lib/python3.12/site-packages/isodate/isodatetime.py new file mode 100644 index 00000000..1b208053 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isodatetime.py @@ -0,0 +1,45 @@ +""" +This module defines a method to parse an ISO 8601:2004 date time string. + +For this job it uses the parse_date and parse_time methods defined in date +and time module. +""" + +from datetime import datetime + +from isodate.isodates import parse_date +from isodate.isoerror import ISO8601Error +from isodate.isostrf import DATE_EXT_COMPLETE, TIME_EXT_COMPLETE, TZ_EXT, strftime +from isodate.isotime import parse_time + + +def parse_datetime(datetimestring): + """ + Parses ISO 8601 date-times into datetime.datetime objects. + + This function uses parse_date and parse_time to do the job, so it allows + more combinations of date and time representations, than the actual + ISO 8601:2004 standard allows. + """ + try: + datestring, timestring = datetimestring.split("T") + except ValueError: + raise ISO8601Error( + "ISO 8601 time designator 'T' missing. Unable to" + " parse datetime string %r" % datetimestring + ) + tmpdate = parse_date(datestring) + tmptime = parse_time(timestring) + return datetime.combine(tmpdate, tmptime) + + +def datetime_isoformat( + tdt, format=DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT +): + """ + Format datetime strings. + + This method is just a wrapper around isodate.isostrf.strftime and uses + Extended-Complete as default format. + """ + return strftime(tdt, format) diff --git a/.venv/lib/python3.12/site-packages/isodate/isoduration.py b/.venv/lib/python3.12/site-packages/isodate/isoduration.py new file mode 100644 index 00000000..4f1755bf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isoduration.py @@ -0,0 +1,147 @@ +""" +This module provides an ISO 8601:2004 duration parser. + +It also provides a wrapper to strftime. This wrapper makes it easier to +format timedelta or Duration instances as ISO conforming strings. +""" + +import re +from datetime import timedelta +from decimal import Decimal + +from isodate.duration import Duration +from isodate.isodatetime import parse_datetime +from isodate.isoerror import ISO8601Error +from isodate.isostrf import D_DEFAULT, strftime + +ISO8601_PERIOD_REGEX = re.compile( + r"^(?P<sign>[+-])?" + r"P(?!\b)" + r"(?P<years>[0-9]+([,.][0-9]+)?Y)?" + r"(?P<months>[0-9]+([,.][0-9]+)?M)?" + r"(?P<weeks>[0-9]+([,.][0-9]+)?W)?" + r"(?P<days>[0-9]+([,.][0-9]+)?D)?" + r"((?P<separator>T)(?P<hours>[0-9]+([,.][0-9]+)?H)?" + r"(?P<minutes>[0-9]+([,.][0-9]+)?M)?" + r"(?P<seconds>[0-9]+([,.][0-9]+)?S)?)?$" +) +# regular expression to parse ISO duration strings. + + +def parse_duration(datestring, as_timedelta_if_possible=True): + """ + Parses an ISO 8601 durations into datetime.timedelta or Duration objects. + + If the ISO date string does not contain years or months, a timedelta + instance is returned, else a Duration instance is returned. + + The following duration formats are supported: + -PnnW duration in weeks + -PnnYnnMnnDTnnHnnMnnS complete duration specification + -PYYYYMMDDThhmmss basic alternative complete date format + -PYYYY-MM-DDThh:mm:ss extended alternative complete date format + -PYYYYDDDThhmmss basic alternative ordinal date format + -PYYYY-DDDThh:mm:ss extended alternative ordinal date format + + The '-' is optional. + + Limitations: ISO standard defines some restrictions about where to use + fractional numbers and which component and format combinations are + allowed. This parser implementation ignores all those restrictions and + returns something when it is able to find all necessary components. + In detail: + it does not check, whether only the last component has fractions. + it allows weeks specified with all other combinations + + The alternative format does not support durations with years, months or + days set to 0. + """ + if not isinstance(datestring, str): + raise TypeError("Expecting a string %r" % datestring) + match = ISO8601_PERIOD_REGEX.match(datestring) + if not match: + # try alternative format: + if datestring.startswith("P"): + durdt = parse_datetime(datestring[1:]) + if as_timedelta_if_possible and durdt.year == 0 and durdt.month == 0: + # FIXME: currently not possible in alternative format + # create timedelta + ret = timedelta( + days=durdt.day, + seconds=durdt.second, + microseconds=durdt.microsecond, + minutes=durdt.minute, + hours=durdt.hour, + ) + else: + # create Duration + ret = Duration( + days=durdt.day, + seconds=durdt.second, + microseconds=durdt.microsecond, + minutes=durdt.minute, + hours=durdt.hour, + months=durdt.month, + years=durdt.year, + ) + return ret + raise ISO8601Error("Unable to parse duration string %r" % datestring) + groups = match.groupdict() + for key, val in groups.items(): + if key not in ("separator", "sign"): + if val is None: + groups[key] = "0n" + # print groups[key] + if key in ("years", "months"): + groups[key] = Decimal(groups[key][:-1].replace(",", ".")) + else: + # these values are passed into a timedelta object, + # which works with floats. + groups[key] = float(groups[key][:-1].replace(",", ".")) + if as_timedelta_if_possible and groups["years"] == 0 and groups["months"] == 0: + ret = timedelta( + days=groups["days"], + hours=groups["hours"], + minutes=groups["minutes"], + seconds=groups["seconds"], + weeks=groups["weeks"], + ) + if groups["sign"] == "-": + ret = timedelta(0) - ret + else: + ret = Duration( + years=groups["years"], + months=groups["months"], + days=groups["days"], + hours=groups["hours"], + minutes=groups["minutes"], + seconds=groups["seconds"], + weeks=groups["weeks"], + ) + if groups["sign"] == "-": + ret = Duration(0) - ret + return ret + + +def duration_isoformat(tduration, format=D_DEFAULT): + """ + Format duration strings. + + This method is just a wrapper around isodate.isostrf.strftime and uses + P%P (D_DEFAULT) as default format. + """ + # TODO: implement better decision for negative Durations. + # should be done in Duration class in consistent way with timedelta. + if ( + isinstance(tduration, Duration) + and ( + tduration.years < 0 + or tduration.months < 0 + or tduration.tdelta < timedelta(0) + ) + ) or (isinstance(tduration, timedelta) and (tduration < timedelta(0))): + ret = "-" + else: + ret = "" + ret += strftime(tduration, format) + return ret diff --git a/.venv/lib/python3.12/site-packages/isodate/isoerror.py b/.venv/lib/python3.12/site-packages/isodate/isoerror.py new file mode 100644 index 00000000..068429f2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isoerror.py @@ -0,0 +1,7 @@ +""" +This module defines all exception classes in the whole package. +""" + + +class ISO8601Error(ValueError): + """Raised when the given ISO string can not be parsed.""" diff --git a/.venv/lib/python3.12/site-packages/isodate/isostrf.py b/.venv/lib/python3.12/site-packages/isodate/isostrf.py new file mode 100644 index 00000000..455ce97a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isostrf.py @@ -0,0 +1,189 @@ +""" +This module provides an alternative strftime method. + +The strftime method in this module allows only a subset of Python's strftime +format codes, plus a few additional. It supports the full range of date values +possible with standard Python date/time objects. Furthermore there are several +pr-defined format strings in this module to make ease producing of ISO 8601 +conforming strings. +""" + +import re +from datetime import date, timedelta + +from isodate.duration import Duration +from isodate.isotzinfo import tz_isoformat + +# Date specific format strings +DATE_BAS_COMPLETE = "%Y%m%d" +DATE_EXT_COMPLETE = "%Y-%m-%d" +DATE_BAS_WEEK_COMPLETE = "%YW%W%w" +DATE_EXT_WEEK_COMPLETE = "%Y-W%W-%w" +DATE_BAS_ORD_COMPLETE = "%Y%j" +DATE_EXT_ORD_COMPLETE = "%Y-%j" +DATE_BAS_WEEK = "%YW%W" +DATE_EXT_WEEK = "%Y-W%W" +DATE_BAS_MONTH = "%Y%m" +DATE_EXT_MONTH = "%Y-%m" +DATE_YEAR = "%Y" +DATE_CENTURY = "%C" + +# Time specific format strings +TIME_BAS_COMPLETE = "%H%M%S" +TIME_EXT_COMPLETE = "%H:%M:%S" +TIME_BAS_MINUTE = "%H%M" +TIME_EXT_MINUTE = "%H:%M" +TIME_HOUR = "%H" + +# Time zone formats +TZ_BAS = "%z" +TZ_EXT = "%Z" +TZ_HOUR = "%h" + +# DateTime formats +DT_EXT_COMPLETE = DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT +DT_BAS_COMPLETE = DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS +DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT +DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS +DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + "T" + TIME_EXT_COMPLETE + TZ_EXT +DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + "T" + TIME_BAS_COMPLETE + TZ_BAS + +# Duration formts +D_DEFAULT = "P%P" +D_WEEK = "P%p" +D_ALT_EXT = "P" + DATE_EXT_COMPLETE + "T" + TIME_EXT_COMPLETE +D_ALT_BAS = "P" + DATE_BAS_COMPLETE + "T" + TIME_BAS_COMPLETE +D_ALT_EXT_ORD = "P" + DATE_EXT_ORD_COMPLETE + "T" + TIME_EXT_COMPLETE +D_ALT_BAS_ORD = "P" + DATE_BAS_ORD_COMPLETE + "T" + TIME_BAS_COMPLETE + +STRF_DT_MAP = { + "%d": lambda tdt, yds: "%02d" % tdt.day, + "%f": lambda tdt, yds: "%06d" % tdt.microsecond, + "%H": lambda tdt, yds: "%02d" % tdt.hour, + "%j": lambda tdt, yds: "%03d" + % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1), + "%m": lambda tdt, yds: "%02d" % tdt.month, + "%M": lambda tdt, yds: "%02d" % tdt.minute, + "%S": lambda tdt, yds: "%02d" % tdt.second, + "%w": lambda tdt, yds: "%1d" % tdt.isoweekday(), + "%W": lambda tdt, yds: "%02d" % tdt.isocalendar()[1], + "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + (("%%0%dd" % yds) % tdt.year), + "%C": lambda tdt, yds: (((yds != 4) and "+") or "") + + (("%%0%dd" % (yds - 2)) % (tdt.year / 100)), + "%h": lambda tdt, yds: tz_isoformat(tdt, "%h"), + "%Z": lambda tdt, yds: tz_isoformat(tdt, "%Z"), + "%z": lambda tdt, yds: tz_isoformat(tdt, "%z"), + "%%": lambda tdt, yds: "%", +} + +STRF_D_MAP = { + "%d": lambda tdt, yds: "%02d" % tdt.days, + "%f": lambda tdt, yds: "%06d" % tdt.microseconds, + "%H": lambda tdt, yds: "%02d" % (tdt.seconds / 60 / 60), + "%m": lambda tdt, yds: "%02d" % tdt.months, + "%M": lambda tdt, yds: "%02d" % ((tdt.seconds / 60) % 60), + "%S": lambda tdt, yds: "%02d" % (tdt.seconds % 60), + "%W": lambda tdt, yds: "%02d" % (abs(tdt.days / 7)), + "%Y": lambda tdt, yds: (((yds != 4) and "+") or "") + + (("%%0%dd" % yds) % tdt.years), + "%C": lambda tdt, yds: (((yds != 4) and "+") or "") + + (("%%0%dd" % (yds - 2)) % (tdt.years / 100)), + "%%": lambda tdt, yds: "%", +} + + +def _strfduration(tdt, format, yeardigits=4): + """ + this is the work method for timedelta and Duration instances. + + see strftime for more details. + """ + + def repl(match): + """ + lookup format command and return corresponding replacement. + """ + if match.group(0) in STRF_D_MAP: + return STRF_D_MAP[match.group(0)](tdt, yeardigits) + elif match.group(0) == "%P": + ret = [] + if isinstance(tdt, Duration): + if tdt.years: + ret.append("%sY" % abs(tdt.years)) + if tdt.months: + ret.append("%sM" % abs(tdt.months)) + usecs = abs( + (tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds + ) + seconds, usecs = divmod(usecs, 1000000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + if days: + ret.append("%sD" % days) + if hours or minutes or seconds or usecs: + ret.append("T") + if hours: + ret.append("%sH" % hours) + if minutes: + ret.append("%sM" % minutes) + if seconds or usecs: + if usecs: + ret.append(("%d.%06d" % (seconds, usecs)).rstrip("0")) + else: + ret.append("%d" % seconds) + ret.append("S") + # at least one component has to be there. + return ret and "".join(ret) or "0D" + elif match.group(0) == "%p": + return str(abs(tdt.days // 7)) + "W" + return match.group(0) + + return re.sub("%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p", repl, format) + + +def _strfdt(tdt, format, yeardigits=4): + """ + this is the work method for time and date instances. + + see strftime for more details. + """ + + def repl(match): + """ + lookup format command and return corresponding replacement. + """ + if match.group(0) in STRF_DT_MAP: + return STRF_DT_MAP[match.group(0)](tdt, yeardigits) + return match.group(0) + + return re.sub("%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%", repl, format) + + +def strftime(tdt, format, yeardigits=4): + """Directive Meaning Notes + %d Day of the month as a decimal number [01,31]. + %f Microsecond as a decimal number [0,999999], zero-padded + on the left (1) + %H Hour (24-hour clock) as a decimal number [00,23]. + %j Day of the year as a decimal number [001,366]. + %m Month as a decimal number [01,12]. + %M Minute as a decimal number [00,59]. + %S Second as a decimal number [00,61]. (3) + %w Weekday as a decimal number [0(Monday),6]. + %W Week number of the year (Monday as the first day of the week) + as a decimal number [00,53]. All days in a new year preceding the + first Monday are considered to be in week 0. (4) + %Y Year with century as a decimal number. [0000,9999] + %C Century as a decimal number. [00,99] + %z UTC offset in the form +HHMM or -HHMM (empty string if the + object is naive). (5) + %Z Time zone name (empty string if the object is naive). + %P ISO8601 duration format. + %p ISO8601 duration format in weeks. + %% A literal '%' character. + + """ + if isinstance(tdt, (timedelta, Duration)): + return _strfduration(tdt, format, yeardigits) + return _strfdt(tdt, format, yeardigits) diff --git a/.venv/lib/python3.12/site-packages/isodate/isotime.py b/.venv/lib/python3.12/site-packages/isodate/isotime.py new file mode 100644 index 00000000..f74ef5dd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isotime.py @@ -0,0 +1,155 @@ +""" +This modules provides a method to parse an ISO 8601:2004 time string to a +Python datetime.time instance. + +It supports all basic and extended formats including time zone specifications +as described in the ISO standard. +""" + +import re +from datetime import time +from decimal import ROUND_FLOOR, Decimal + +from isodate.isoerror import ISO8601Error +from isodate.isostrf import TIME_EXT_COMPLETE, TZ_EXT, strftime +from isodate.isotzinfo import TZ_REGEX, build_tzinfo + +TIME_REGEX_CACHE = [] +# used to cache regular expressions to parse ISO time strings. + + +def build_time_regexps(): + """ + Build regular expressions to parse ISO time string. + + The regular expressions are compiled and stored in TIME_REGEX_CACHE + for later reuse. + """ + if not TIME_REGEX_CACHE: + # ISO 8601 time representations allow decimal fractions on least + # significant time component. Command and Full Stop are both valid + # fraction separators. + # The letter 'T' is allowed as time designator in front of a time + # expression. + # Immediately after a time expression, a time zone definition is + # allowed. + # a TZ may be missing (local time), be a 'Z' for UTC or a string of + # +-hh:mm where the ':mm' part can be skipped. + # TZ information patterns: + # '' + # Z + # +-hh:mm + # +-hhmm + # +-hh => + # isotzinfo.TZ_REGEX + def add_re(regex_text): + TIME_REGEX_CACHE.append(re.compile(r"\A" + regex_text + TZ_REGEX + r"\Z")) + + # 1. complete time: + # hh:mm:ss.ss ... extended format + add_re( + r"T?(?P<hour>[0-9]{2}):" + r"(?P<minute>[0-9]{2}):" + r"(?P<second>[0-9]{2}" + r"([,.][0-9]+)?)" + ) + # hhmmss.ss ... basic format + add_re( + r"T?(?P<hour>[0-9]{2})" + r"(?P<minute>[0-9]{2})" + r"(?P<second>[0-9]{2}" + r"([,.][0-9]+)?)" + ) + # 2. reduced accuracy: + # hh:mm.mm ... extended format + add_re(r"T?(?P<hour>[0-9]{2}):" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)") + # hhmm.mm ... basic format + add_re(r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)") + # hh.hh ... basic format + add_re(r"T?(?P<hour>[0-9]{2}" r"([,.][0-9]+)?)") + return TIME_REGEX_CACHE + + +def parse_time(timestring): + """ + Parses ISO 8601 times into datetime.time objects. + + Following ISO 8601 formats are supported: + (as decimal separator a ',' or a '.' is allowed) + hhmmss.ssTZD basic complete time + hh:mm:ss.ssTZD extended complete time + hhmm.mmTZD basic reduced accuracy time + hh:mm.mmTZD extended reduced accuracy time + hh.hhTZD basic reduced accuracy time + TZD is the time zone designator which can be in the following format: + no designator indicates local time zone + Z UTC + +-hhmm basic hours and minutes + +-hh:mm extended hours and minutes + +-hh hours + """ + isotimes = build_time_regexps() + for pattern in isotimes: + match = pattern.match(timestring) + if match: + groups = match.groupdict() + for key, value in groups.items(): + if value is not None: + groups[key] = value.replace(",", ".") + tzinfo = build_tzinfo( + groups["tzname"], + groups["tzsign"], + int(groups["tzhour"] or 0), + int(groups["tzmin"] or 0), + ) + if "second" in groups: + second = Decimal(groups["second"]).quantize( + Decimal(".000001"), rounding=ROUND_FLOOR + ) + microsecond = (second - int(second)) * int(1e6) + # int(...) ... no rounding + # to_integral() ... rounding + return time( + int(groups["hour"]), + int(groups["minute"]), + int(second), + int(microsecond.to_integral()), + tzinfo, + ) + if "minute" in groups: + minute = Decimal(groups["minute"]) + second = Decimal((minute - int(minute)) * 60).quantize( + Decimal(".000001"), rounding=ROUND_FLOOR + ) + microsecond = (second - int(second)) * int(1e6) + return time( + int(groups["hour"]), + int(minute), + int(second), + int(microsecond.to_integral()), + tzinfo, + ) + else: + microsecond, second, minute = 0, 0, 0 + hour = Decimal(groups["hour"]) + minute = (hour - int(hour)) * 60 + second = (minute - int(minute)) * 60 + microsecond = (second - int(second)) * int(1e6) + return time( + int(hour), + int(minute), + int(second), + int(microsecond.to_integral()), + tzinfo, + ) + raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring) + + +def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT): + """ + Format time strings. + + This method is just a wrapper around isodate.isostrf.strftime and uses + Time-Extended-Complete with extended time zone as default format. + """ + return strftime(ttime, format) diff --git a/.venv/lib/python3.12/site-packages/isodate/isotzinfo.py b/.venv/lib/python3.12/site-packages/isodate/isotzinfo.py new file mode 100644 index 00000000..54f36de0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/isotzinfo.py @@ -0,0 +1,91 @@ +""" +This module provides an ISO 8601:2004 time zone info parser. + +It offers a function to parse the time zone offset as specified by ISO 8601. +""" + +import re + +from isodate.isoerror import ISO8601Error +from isodate.tzinfo import UTC, ZERO, FixedOffset + +TZ_REGEX = ( + r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)" +) + +TZ_RE = re.compile(TZ_REGEX) + + +def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0): + """ + create a tzinfo instance according to given parameters. + + tzname: + 'Z' ... return UTC + '' | None ... return None + other ... return FixedOffset + """ + if tzname is None or tzname == "": + return None + if tzname == "Z": + return UTC + tzsign = ((tzsign == "-") and -1) or 1 + return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname) + + +def parse_tzinfo(tzstring): + """ + Parses ISO 8601 time zone designators to tzinfo objects. + + A time zone designator can be in the following format: + no designator indicates local time zone + Z UTC + +-hhmm basic hours and minutes + +-hh:mm extended hours and minutes + +-hh hours + """ + match = TZ_RE.match(tzstring) + if match: + groups = match.groupdict() + return build_tzinfo( + groups["tzname"], + groups["tzsign"], + int(groups["tzhour"] or 0), + int(groups["tzmin"] or 0), + ) + raise ISO8601Error("%s not a valid time zone info" % tzstring) + + +def tz_isoformat(dt, format="%Z"): + """ + return time zone offset ISO 8601 formatted. + The various ISO formats can be chosen with the format parameter. + + if tzinfo is None returns '' + if tzinfo is UTC returns 'Z' + else the offset is rendered to the given format. + format: + %h ... +-HH + %z ... +-HHMM + %Z ... +-HH:MM + """ + tzinfo = dt.tzinfo + if (tzinfo is None) or (tzinfo.utcoffset(dt) is None): + return "" + if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO: + return "Z" + tdelta = tzinfo.utcoffset(dt) + seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds + sign = ((seconds < 0) and "-") or "+" + seconds = abs(seconds) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + if hours > 99: + raise OverflowError("can not handle differences > 99 hours") + if format == "%Z": + return "%s%02d:%02d" % (sign, hours, minutes) + elif format == "%z": + return "%s%02d%02d" % (sign, hours, minutes) + elif format == "%h": + return "%s%02d" % (sign, hours) + raise ValueError('unknown format string "%s"' % format) diff --git a/.venv/lib/python3.12/site-packages/isodate/tzinfo.py b/.venv/lib/python3.12/site-packages/isodate/tzinfo.py new file mode 100644 index 00000000..726c54a3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/tzinfo.py @@ -0,0 +1,166 @@ +""" +This module provides some datetime.tzinfo implementations. + +All those classes are taken from the Python documentation. +""" + +import time +from datetime import timedelta, tzinfo + +ZERO = timedelta(0) +# constant for zero time offset. + + +class Utc(tzinfo): + """UTC + + Universal time coordinated time zone. + """ + + def utcoffset(self, dt): + """ + Return offset from UTC in minutes east of UTC, which is ZERO for UTC. + """ + return ZERO + + def tzname(self, dt): + """ + Return the time zone name corresponding to the datetime object dt, + as a string. + """ + return "UTC" + + def dst(self, dt): + """ + Return the daylight saving time (DST) adjustment, in minutes east + of UTC. + """ + return ZERO + + def __reduce__(self): + """ + When unpickling a Utc object, return the default instance below, UTC. + """ + return _Utc, () + + +UTC = Utc() +# the default instance for UTC. + + +def _Utc(): + """ + Helper function for unpickling a Utc object. + """ + return UTC + + +class FixedOffset(tzinfo): + """ + A class building tzinfo objects for fixed-offset time zones. + + Note that FixedOffset(0, 0, "UTC") or FixedOffset() is a different way to + build a UTC tzinfo object. + """ + + def __init__(self, offset_hours=0, offset_minutes=0, name="UTC"): + """ + Initialise an instance with time offset and name. + The time offset should be positive for time zones east of UTC + and negate for time zones west of UTC. + """ + self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) + self.__name = name + + def utcoffset(self, dt): + """ + Return offset from UTC in minutes of UTC. + """ + return self.__offset + + def tzname(self, dt): + """ + Return the time zone name corresponding to the datetime object dt, as a + string. + """ + return self.__name + + def dst(self, dt): + """ + Return the daylight saving time (DST) adjustment, in minutes east of + UTC. + """ + return ZERO + + def __repr__(self): + """ + Return nicely formatted repr string. + """ + return "<FixedOffset %r>" % self.__name + + +STDOFFSET = timedelta(seconds=-time.timezone) +# locale time zone offset + +# calculate local daylight saving offset if any. +if time.daylight: + DSTOFFSET = timedelta(seconds=-time.altzone) +else: + DSTOFFSET = STDOFFSET + +DSTDIFF = DSTOFFSET - STDOFFSET +# difference between local time zone and local DST time zone + + +class LocalTimezone(tzinfo): + """ + A class capturing the platform's idea of local time. + """ + + def utcoffset(self, dt): + """ + Return offset from UTC in minutes of UTC. + """ + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + """ + Return daylight saving offset. + """ + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + """ + Return the time zone name corresponding to the datetime object dt, as a + string. + """ + return time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + """ + Returns true if DST is active for given datetime object dt. + """ + tt = ( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.weekday(), + 0, + -1, + ) + stamp = time.mktime(tt) + tt = time.localtime(stamp) + return tt.tm_isdst > 0 + + +# the default instance for local time zone. +LOCAL = LocalTimezone() diff --git a/.venv/lib/python3.12/site-packages/isodate/version.py b/.venv/lib/python3.12/site-packages/isodate/version.py new file mode 100644 index 00000000..393e7229 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/isodate/version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '0.7.2' +__version_tuple__ = version_tuple = (0, 7, 2) |
