Skip to content

Commit

Permalink
downgrade to Python 3.9
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeutschler committed Sep 24, 2024
1 parent 1f3ca0d commit 042368e
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 69 deletions.
4 changes: 2 additions & 2 deletions datespan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from datespan.date_span_set import DateSpanSet

__author__ = "Thomas Zeutschler"
__version__ = "0.2.8"
__version__ = "0.2.9"
__license__ = "MIT"
VERSION = __version__

Expand All @@ -21,7 +21,7 @@
]


def parse(datespan_text: str, parser_info: parserinfo | None = None) -> DateSpanSet:
def parse(datespan_text: str, parser_info: parserinfo = None) -> DateSpanSet:
"""
Creates a new DateSpanSet instance and parses the given text into a set of DateSpan objects.
Expand Down
53 changes: 28 additions & 25 deletions datespan/date_span.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DateSpan:
"""The Maximum datetime that can be safely represented by a DateSpan. Aligned with the maximum supported datetime of Numpy and Pandas."""


def __init__(self, start=None, end=None, message: str | None = None):
def __init__(self, start=None, end=None, message: str = None):
"""
Initializes a new DateSpan with the given start and end date. If only one date is given, the DateSpan will
represent a single point in time. If no date is given, the DateSpan will be undefined.
Expand All @@ -46,20 +46,23 @@ class to parse multipart date spans.
"""
self._arg_start = start
self._arg_end = end if end is not None else start
self._message: str | None = message
self._message: str = message

if isinstance(start, datetime | None) and isinstance(end, datetime | None):
self._start: datetime | None = start
self._end: datetime | None = end if end is not None else start
if isinstance(start, datetime) and isinstance(end, datetime):
self._start: datetime = start
self._end: datetime = end if end is not None else start
self._start, self._end = self._swap()
elif start is None and end is None:
self._start: datetime = None
self._end: datetime = None
else:
try:
self._start, self._end = self._parse(start, end)
except ValueError as e:
raise e

@property
def message(self) -> str | None:
def message(self) -> str:
"""Returns the message of the DateSpan."""
return self._message

Expand Down Expand Up @@ -95,7 +98,7 @@ def __getitem__(self, item):
return self._end
raise IndexError("Index out of range. DateSpan only supports index 0 and 1.")

def contains(self, other: datetime | DateSpan | float) -> bool:
def contains(self, other) -> bool:
"""Returns True if the DateSpan contains the given date, DateSpan or float timestamp."""
return self.__contains__(other)

Expand Down Expand Up @@ -168,7 +171,7 @@ def intersect(self, other: DateSpan) -> DateSpan:
return DateSpan(max(self._start, other._start), min(self._end, other._end))
return DateSpan.undefined()

def subtract(self, other: DateSpan, allow_split: bool = False) -> DateSpan | (DateSpan, DateSpan):
def subtract(self, other: DateSpan, allow_split: bool = False):
"""
Returns a new DateSpan that is the subtraction of the DateSpan with the given DateSpan.
If there is no overlap, the DateSpan will be returned unchanged.
Expand Down Expand Up @@ -212,7 +215,7 @@ def subtract(self, other: DateSpan, allow_split: bool = False) -> DateSpan | (Da
# overlap at the end
return DateSpan(self._start, other._start - timedelta(microseconds=1))

def with_time(self, time: datetime | time, text: str | None = None) -> DateSpan:
def with_time(self, time, text: str = None) -> DateSpan:
"""
Returns a new DateSpan with the start and end date set to the given date and time.
If text is provided, the DateSpan will be adjusted to the time span specified in the text,
Expand Down Expand Up @@ -483,9 +486,9 @@ def is_full_day(self) -> bool:
return (self._start == self._begin_of_day(self._start) and
self._end == self._end_of_day(self._end))

def replace(self, year: int | None = None, month: int | None = None, day: int | None = None,
hour: int | None = None,
minute: int | None = None, second: int | None = None, microsecond: int | None = None) -> DateSpan:
def replace(self, year: int = None, month: int = None, day: int = None,
hour: int = None,
minute: int = None, second: int = None, microsecond: int = None) -> DateSpan:
"""
Returns a new DateSpan with the start and end date replaced by the given datetime parts.
"""
Expand Down Expand Up @@ -592,9 +595,9 @@ def shift_end(self, years: int = 0, months: int = 0, days: int = 0, hours: int =
return DateSpan(result.start, result.start)
return result

def set_start(self, year: int | None = None, month: int | None = None, day: int | None = None,
hour: int | None = None, minute: int | None = None, second: int | None = None,
microsecond: int | None = None, ) -> DateSpan:
def set_start(self, year: int = None, month: int = None, day: int = None,
hour: int = None, minute: int = None, second: int = None,
microsecond: int = None, ) -> DateSpan:
"""
Sets the start date of the DateSpan to a specific time or date. Only the given fragments will be set.
Invalid day values, e.g. set February to 31st, will be automatically adjusted.
Expand All @@ -603,9 +606,9 @@ def set_start(self, year: int | None = None, month: int | None = None, day: int
"""
return DateSpan(self._set(self._start, year, month, day, hour, minute, second, microsecond), self._end)

def set_end(self, year: int | None = None, month: int | None = None, day: int | None = None,
hour: int | None = None, minute: int | None = None, second: int | None = None,
microsecond: int | None = None, ) -> DateSpan:
def set_end(self, year: int = None, month: int = None, day: int = None,
hour: int = None, minute: int = None, second: int = None,
microsecond: int = None, ) -> DateSpan:
"""
Sets the end date of the DateSpan to a specific time or date. Only the given fragments will be set.
Invalid day values, e.g. set February to 31st, will be automatically adjusted.
Expand All @@ -614,9 +617,9 @@ def set_end(self, year: int | None = None, month: int | None = None, day: int |
"""
return DateSpan(self._start, self._set(self._end, year, month, day, hour, minute, second, microsecond))

def set(self, year: int | None = None, month: int | None = None, day: int | None = None,
hour: int | None = None, minute: int | None = None, second: int | None = None,
microsecond: int | None = None, ) -> DateSpan:
def set(self, year: int = None, month: int = None, day: int = None,
hour: int = None, minute: int = None, second: int = None,
microsecond: int = None, ) -> DateSpan:
"""
Sets the start and end date of the DateSpan to a specific time or date. Only the given fragments will be set.
Invalid day values, e.g. set February to 31st, will be automatically adjusted.
Expand All @@ -627,9 +630,9 @@ def set(self, year: int | None = None, month: int | None = None, day: int | None
return DateSpan(self._set(self._start, year, month, day, hour, minute, second, microsecond),
self._set(self._end, year, month, day, hour, minute, second, microsecond))

def _set(self, dt: datetime, year: int | None = None, month: int | None = None, day: int | None = None,
hour: int | None = None, minute: int | None = None, second: int | None = None,
microsecond: int | None = None, ) -> datetime | None:
def _set(self, dt: datetime, year: int = None, month: int = None, day: int = None,
hour: int = None, minute: int = None, second: int = None,
microsecond: int = None, ) -> datetime:
"""
Sets a datetime to a specific time or date. Only the given fragments will be set.
Invalid day values, e.g. set February to 31st, will be automatically
Expand Down Expand Up @@ -732,7 +735,7 @@ def undefined(cls) -> DateSpan:
return DateSpan(None, None)

@classmethod
def _monday(cls, base: datetime | None = None, offset_weeks: int = 0, offset_years: int = 0, offset_months: int = 0,
def _monday(cls, base: datetime = None, offset_weeks: int = 0, offset_years: int = 0, offset_months: int = 0,
offset_days: int = 0) -> DateSpan:
# Monday is 0 and Sunday is 6
if base is None:
Expand Down
77 changes: 38 additions & 39 deletions datespan/date_span_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import uuid
from datetime import datetime, date, time
from typing import Any
from typing import Any, Union

from dateutil.parser import parserinfo

Expand All @@ -20,7 +20,7 @@ class DateSpanSet:
"""


def __init__(self, definition: Any | None = None, parser_info: parserinfo | None = None):
def __init__(self, definition: Any = None, parser_info: parserinfo = None):
"""
Initializes a new DateSpanSet based on a given set of date span set definition.
The date span set definition can be a string, a DateSpan, datetime, date or time object or a list of these.
Expand All @@ -37,7 +37,7 @@ def __init__(self, definition: Any | None = None, parser_info: parserinfo | None
"""
self._spans: list[DateSpan] = []
self._definition = definition
self._parser_info: parserinfo | None = parser_info
self._parser_info: parserinfo = parser_info
self._iter_index = 0

if definition is not None:
Expand All @@ -51,14 +51,14 @@ def _initialize(self, definition: Any):
expressions = []

# collect available definitions
if isinstance(definition, DateSpan | str | datetime | time | date):
if isinstance(definition, (DateSpan, str, datetime, time, date)):
expressions.append(definition)

elif isinstance(definition, DateSpanSet):
self._definition = definition._definition
expressions.extend(definition._spans)

elif isinstance(definition, list | tuple):
elif isinstance(definition, (list, tuple)):
definitions = []
for item in definition:
if isinstance(item, DateSpan):
Expand All @@ -67,7 +67,7 @@ def _initialize(self, definition: Any):
elif isinstance(item, DateSpanSet):
definitions.append(str(item._definition))
expressions.extend(item._spans)
elif isinstance(item, datetime | time | date):
elif isinstance(item, (datetime, time, date)):
definitions.append(str(item))
expressions.append(item)
elif isinstance(item, str):
Expand All @@ -85,7 +85,7 @@ def _initialize(self, definition: Any):
self._spans.append(exp)
elif isinstance(exp, str):
self._parse(exp)
elif isinstance(exp, datetime | date | time):
elif isinstance(exp, (datetime, date, time)):
self._spans.append(DateSpan(exp))
else:
raise ValueError(f"Objects of type '{type(exp)}' are not supported for DateSpanSet.")
Expand Down Expand Up @@ -198,14 +198,14 @@ def spans(self) -> list[DateSpan]:
return self._spans

@property
def start(self) -> datetime | None:
def start(self) -> datetime:
"""Returns the start datetime of the first DateSpan object in the set."""
if len(self._spans) > 0:
return self._spans[0].start
return None

@property
def end(self) -> datetime | None:
def end(self) -> datetime:
""" Returns the end datetime of the last DateSpan object in the set."""
if len(self._spans) > 0:
return self._spans[-1].end
Expand All @@ -219,13 +219,13 @@ def clone(self) -> DateSpanSet:
dss._parser_info = self._parser_info
return dss

def add(self, other: DateSpanSet | DateSpan | str):
def add(self, other):
""" Adds a new DateSpan object to the DateSpanSet."""
merged = self.merge(other)
self._spans = merged._spans
self._definition = merged._definition

def remove(self, other: DateSpanSet | DateSpan | str):
def remove(self, other):
""" Removes a DateSpan object from the DateSpanSet."""
self._spans = self.intersect(other)._spans

Expand All @@ -246,7 +246,7 @@ def shift(self, years: int = 0, months: int = 0, days: int = 0, hours: int = 0,

# region Class Methods
@classmethod
def parse(cls, datespan_text: str, parser_info: parserinfo | None = None) -> DateSpanSet:
def parse(cls, datespan_text: str, parser_info: parserinfo = None) -> DateSpanSet:
"""
Creates a new DateSpanSet instance and parses the given text into a set of DateSpan objects.
Expand All @@ -266,7 +266,7 @@ def parse(cls, datespan_text: str, parser_info: parserinfo | None = None) -> Dat
return cls(definition=datespan_text, parser_info=parser_info)

@classmethod
def try_parse(cls, datespan_text: str, parser_info: parserinfo | None = None) -> DateSpanSet | None:
def try_parse(cls, datespan_text: str, parser_info: parserinfo = None) -> DateSpanSet:
"""
Creates a new DateSpanSet instance and parses the given text into a set of DateSpan objects. If
the text cannot be parsed, None is returned.
Expand Down Expand Up @@ -328,7 +328,7 @@ def to_sql(self, column: str, line_breaks: bool = False, add_comment: bool = Tru
return "OR\n".join(filters)
return " OR ".join(filters) + inline_comment

def to_function(self, return_sourceCde: bool = False) -> callable | str:
def to_function(self, return_sourceCde: bool = False):
"""
Generate a compiled Python function that can be directly used as a filter function
within Python, Pandas or other. The lambda function will return True if the input
Expand Down Expand Up @@ -456,7 +456,7 @@ def to_tuples(self) -> list[tuple[datetime, datetime]]:
""" Returns a list of tuples with start and end dates of all DateSpan objects in the DateSpanSet."""
return [(ds.start, ds.end) for ds in self._spans]

def filter(self, data: Any, column: str | None = None, return_mask: bool = False,
def filter(self, data: Any, column: str = None, return_mask: bool = False,
return_index: bool = False) -> Any:
"""
Filters the given data object, e.g. a Pandas DataFrame or Series, based on the date spans of the DateSpanSet.
Expand Down Expand Up @@ -485,31 +485,30 @@ def filter(self, data: Any, column: str | None = None, return_mask: bool = False
"""
class_name = f"{data.__class__.__module__}.{data.__class__.__qualname__}"
match class_name:
case "pandas.core.frame.DataFrame":
if column is None:
raise ValueError("A column name must be provided to filter a Pandas DataFrame.")
mask = self.to_df_lambda()(data[column])
if return_mask:
return mask
elif return_index:
return data[mask].index.to_numpy()
return data[mask]

case "pandas.core.series.Series":
mask = self.to_df_lambda()(data)
if return_mask:
return mask
elif return_index:
return data[mask].index.to_numpy()
return data[mask]
case _:
raise ValueError(f"Objects of type '{class_name}' are not yet supported for filtering.")
if class_name == "pandas.core.frame.DataFrame":
if column is None:
raise ValueError("A column name must be provided to filter a Pandas DataFrame.")
mask = self.to_df_lambda()(data[column])
if return_mask:
return mask
elif return_index:
return data[mask].index.to_numpy()
return data[mask]

elif class_name == "pandas.core.series.Series":
mask = self.to_df_lambda()(data)
if return_mask:
return mask
elif return_index:
return data[mask].index.to_numpy()
return data[mask]
else:
raise ValueError(f"Objects of type '{class_name}' are not yet supported for filtering.")

# endregion

# region Set Operations
def merge(self, other: DateSpanSet | DateSpan | str) -> DateSpanSet:
def merge(self, other) -> DateSpanSet:
"""
Merges the current DateSpanSet with another DateSpanSet, DateSpan or a string representing a data span.
The resulting DateSpanSet will contain date spans representing all data spans of the current and the other
Expand All @@ -529,7 +528,7 @@ def merge(self, other: DateSpanSet | DateSpan | str) -> DateSpanSet:
return DateSpanSet([self, DateSpanSet(other)])
raise ValueError(f"Objects of type '{type(other)}' are not supported for DateSpanSet merging.")

def intersect(self, other: DateSpanSet | DateSpan | str) -> DateSpanSet:
def intersect(self, other) -> DateSpanSet:
"""
Intersects the current DateSpanSet with another DateSpanSet, DateSpan or a string representing a data span.
The resulting DateSpanSet will contain data spans that represent the current DataSpanSet minus the date spans
Expand All @@ -543,7 +542,7 @@ def intersect(self, other: DateSpanSet | DateSpan | str) -> DateSpanSet:
"""
raise NotImplementedError()

def subtract(self, other: DateSpanSet | DateSpan | str) -> DateSpanSet:
def subtract(self, other) -> DateSpanSet:
"""
Subtracts a DateSpanSet, DateSpan or a string representing a data span from the current DateSpanSet.
So, the resulting DateSpanSet will contain data spans that represent the current DataSpanSet minus
Expand Down Expand Up @@ -621,7 +620,7 @@ def _merge_all(self):

self._spans = merged

def _parse(self, text: str | None = None):
def _parse(self, text: str = None):
"""
Parses the given text into a set of DateSpan objects.
"""
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -24,7 +25,7 @@ classifiers = [
readme = "README.md"
dynamic = ["version"]
license = {file = "LICENSE"}
requires-python = ">= 3.10"
requires-python = ">= 3.9"
authors = [
{name = "Thomas Zeutschler"},
{email = "cubedpandas@gmail.com"},
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# datespan - Copyright (c)2024, Thomas Zeutschler, MIT license

python-dateutil
Loading

0 comments on commit 042368e

Please sign in to comment.