Skip to content

Commit 7ef1e06

Browse files
authored
[dateparser] Improve stubs (#15393)
* Functions with `settings` params actually accepts `Settings` (or `dict` only if has `@apply_settings` decorator) * Use `TypeVar` for date params that are used as return objects * Use `collections.abc.Set` instead of `set` since there is an `isinstance` check specifically for `collections.abc.Set` * Annotated many other parameters
1 parent 8ce1e85 commit 7ef1e06

File tree

21 files changed

+340
-193
lines changed

21 files changed

+340
-193
lines changed
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import datetime
2-
from typing import Literal, TypedDict, type_check_only
3-
from typing_extensions import TypeAlias
2+
from typing import Any, Final, Literal, TypedDict, type_check_only
3+
4+
from dateparser.conf import Settings
45

56
from .date import DateDataParser, _DetectLanguagesFunction
67

7-
__version__: str
8+
__version__: Final[str]
89

910
_default_parser: DateDataParser
1011

11-
_Part: TypeAlias = Literal["day", "month", "year"]
12-
_ParserKind: TypeAlias = Literal["timestamp", "relative-time", "custom-formats", "absolute-time", "no-spaces-time"]
13-
1412
@type_check_only
15-
class _Settings(TypedDict, total=False):
13+
class _Settings(TypedDict, total=False): # noqa: Y049
1614
DATE_ORDER: str
1715
PREFER_LOCALE_DATE_ORDER: bool
1816
TIMEZONE: str
@@ -23,14 +21,14 @@ class _Settings(TypedDict, total=False):
2321
PREFER_DATES_FROM: Literal["current_period", "future", "past"]
2422
RELATIVE_BASE: datetime.datetime
2523
STRICT_PARSING: bool
26-
REQUIRE_PARTS: list[_Part]
24+
REQUIRE_PARTS: list[Literal["day", "month", "year"]]
2725
SKIP_TOKENS: list[str]
2826
NORMALIZE: bool
2927
RETURN_TIME_AS_PERIOD: bool
3028
RETURN_TIME_SPAN: bool
3129
DEFAULT_START_OF_WEEK: Literal["monday", "sunday"]
3230
DEFAULT_DAYS_IN_MONTH: int
33-
PARSERS: list[_ParserKind]
31+
PARSERS: list[Literal["timestamp", "relative-time", "custom-formats", "absolute-time", "no-spaces-time"]]
3432
DEFAULT_LANGUAGES: list[str]
3533
LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD: float
3634
CACHE_SIZE_LIMIT: int
@@ -41,6 +39,6 @@ def parse(
4139
languages: list[str] | tuple[str, ...] | set[str] | None = None,
4240
locales: list[str] | tuple[str, ...] | set[str] | None = None,
4341
region: str | None = None,
44-
settings: _Settings | None = None,
42+
settings: Settings | dict[str, Any] | None = None,
4543
detect_languages_function: _DetectLanguagesFunction | None = None,
4644
) -> datetime.datetime | None: ...
Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
from _typeshed import Incomplete
22
from abc import abstractmethod
3+
from typing import ClassVar, Protocol, type_check_only
34

45
from dateparser.conf import Settings
6+
from dateparser.date import DateData
57
from dateparser.parser import _parser
68

9+
# Examples: `hijri_parser.hijri` class or `convertdate.persian` module
10+
@type_check_only
11+
class _CalendarConverter(Protocol):
12+
@classmethod
13+
def to_gregorian(cls, year: int, month: int, day: int) -> tuple[int, int, int]: ...
14+
@classmethod
15+
def from_gregorian(
16+
cls, year: int | None = None, month: int | None = None, day: int | None = None
17+
) -> tuple[int, int, int]: ...
18+
@classmethod
19+
def month_length(cls, year: int, month: int) -> int: ...
20+
21+
# Examples: `hijri_parser.HijriDate` or `jalali_parser.PersianDate`
22+
@type_check_only
23+
class _NonGregorianDate(Protocol):
24+
year: int
25+
month: int
26+
day: int
27+
def __init__(self, year: int, month: int, day: int) -> None: ...
28+
def weekday(self) -> int | None: ...
29+
730
class CalendarBase:
8-
parser: Incomplete
9-
source: Incomplete
10-
def __init__(self, source) -> None: ...
11-
def get_date(self): ...
31+
parser: type[_parser]
32+
source: str
33+
def __init__(self, source: str) -> None: ...
34+
def get_date(self) -> DateData | None: ...
1235

1336
class non_gregorian_parser(_parser):
14-
calendar_converter: Incomplete
15-
default_year: Incomplete
16-
default_month: Incomplete
17-
default_day: Incomplete
18-
non_gregorian_date_cls: Incomplete
37+
calendar_converter: ClassVar[type[_CalendarConverter]]
38+
default_year: ClassVar[int]
39+
default_month: ClassVar[int]
40+
default_day: ClassVar[int]
41+
non_gregorian_date_cls: ClassVar[type[_NonGregorianDate]]
1942
@classmethod
20-
def to_latin(cls, source): ...
43+
def to_latin(cls, source: str) -> str: ...
2144
@abstractmethod
2245
def handle_two_digit_year(self, year: int) -> int: ...
2346
@classmethod
24-
def parse(cls, datestring: str, settings: Settings) -> tuple[Incomplete, Incomplete]: ... # type: ignore[override]
47+
def parse(cls, datestring: str, settings: Settings) -> tuple[Incomplete, str | None]: ... # type: ignore[override]
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from _typeshed import Incomplete
2-
from typing import SupportsIndex
1+
from typing import ClassVar
32

43
from dateparser.calendars import non_gregorian_parser
54

@@ -8,22 +7,22 @@ class hijri:
87
def to_gregorian(cls, year: int | None = None, month: int | None = None, day: int | None = None) -> tuple[int, int, int]: ...
98
@classmethod
109
def from_gregorian(
11-
cls, year: SupportsIndex | None = None, month: SupportsIndex | None = None, day: SupportsIndex | None = None
10+
cls, year: int | None = None, month: int | None = None, day: int | None = None
1211
) -> tuple[int, int, int]: ...
1312
@classmethod
14-
def month_length(cls, year: int, month: int) -> int: ...
13+
def month_length(cls, year: int | None, month: int | None) -> int: ...
1514

1615
class HijriDate:
17-
year: Incomplete
18-
month: Incomplete
19-
day: Incomplete
20-
def __init__(self, year, month, day) -> None: ...
21-
def weekday(self): ...
16+
year: int
17+
month: int
18+
day: int
19+
def __init__(self, year: int, month: int, day: int) -> None: ...
20+
def weekday(self) -> int | None: ...
2221

2322
class hijri_parser(non_gregorian_parser):
24-
calendar_converter: type[hijri]
25-
default_year: int
26-
default_month: int
27-
default_day: int
28-
non_gregorian_date_cls: type[HijriDate]
23+
calendar_converter: ClassVar[type[hijri]]
24+
default_year: ClassVar[int]
25+
default_month: ClassVar[int]
26+
default_day: ClassVar[int]
27+
non_gregorian_date_cls: ClassVar[type[HijriDate]]
2928
def handle_two_digit_year(self, year: int) -> int: ...
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
from _typeshed import Incomplete
1+
from typing import ClassVar
22

33
from dateparser.calendars import non_gregorian_parser
44

55
class PersianDate:
6-
year: Incomplete
7-
month: Incomplete
8-
day: Incomplete
9-
def __init__(self, year, month, day) -> None: ...
10-
def weekday(self): ...
6+
year: int
7+
month: int
8+
day: int
9+
def __init__(self, year: int, month: int, day: int) -> None: ...
10+
def weekday(self) -> int | None: ...
1111

1212
class jalali_parser(non_gregorian_parser):
13-
calendar_converter: Incomplete
14-
default_year: int
15-
default_month: int
16-
default_day: int
17-
non_gregorian_date_cls: type[PersianDate]
13+
# `calendar_converter` is `convertdate.persian` module
14+
default_year: ClassVar[int]
15+
default_month: ClassVar[int]
16+
default_day: ClassVar[int]
17+
non_gregorian_date_cls: ClassVar[type[PersianDate]]
1818
def handle_two_digit_year(self, year: int) -> int: ...

stubs/dateparser/dateparser/conf.pyi

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,45 @@
1-
from typing import Any
2-
from typing_extensions import Self
1+
import datetime
2+
from collections.abc import Callable
3+
from typing import Any, Literal, TypeVar
4+
from typing_extensions import ParamSpec, Self
5+
6+
_P = ParamSpec("_P")
7+
_R = TypeVar("_R")
38

49
class Settings:
5-
def __new__(cls, *args, **kw) -> Self: ...
10+
# Next attributes are optional and may be missing.
11+
# Please keep in sync with _Settings TypedDict
12+
DATE_ORDER: str
13+
PREFER_LOCALE_DATE_ORDER: bool
14+
TIMEZONE: str
15+
TO_TIMEZONE: str
16+
RETURN_AS_TIMEZONE_AWARE: bool
17+
PREFER_MONTH_OF_YEAR: Literal["current", "first", "last"]
18+
PREFER_DAY_OF_MONTH: Literal["current", "first", "last"]
19+
PREFER_DATES_FROM: Literal["current_period", "future", "past"]
20+
RELATIVE_BASE: datetime.datetime
21+
STRICT_PARSING: bool
22+
REQUIRE_PARTS: list[Literal["day", "month", "year"]]
23+
SKIP_TOKENS: list[str]
24+
NORMALIZE: bool
25+
RETURN_TIME_AS_PERIOD: bool
26+
RETURN_TIME_SPAN: bool
27+
DEFAULT_START_OF_WEEK: Literal["monday", "sunday"]
28+
DEFAULT_DAYS_IN_MONTH: int
29+
PARSERS: list[Literal["timestamp", "relative-time", "custom-formats", "absolute-time", "no-spaces-time"]]
30+
DEFAULT_LANGUAGES: list[str]
31+
LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD: float
32+
CACHE_SIZE_LIMIT: int
33+
34+
def __new__(cls, *args, **kwargs) -> Self: ...
635
def __init__(self, settings: dict[str, Any] | None = None) -> None: ...
736
@classmethod
837
def get_key(cls, settings: dict[str, Any] | None = None) -> str: ...
938
def replace(self, mod_settings: dict[str, Any] | None = None, **kwds) -> Self: ...
1039

1140
settings: Settings
1241

13-
def apply_settings(f): ...
42+
def apply_settings(f: Callable[_P, _R]) -> Callable[_P, _R]: ...
1443

1544
class SettingValidationError(ValueError): ...
1645

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Final
22

33
language_order: Final[list[str]]
4-
language_locale_dict: Final[dict[str, str]]
54
language_map: Final[dict[str, list[str]]]
5+
language_locale_dict: Final[dict[str, list[str]]]

stubs/dateparser/dateparser/date.pyi

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,87 @@
1-
import collections
2-
from collections.abc import Callable, Iterable, Iterator
3-
from datetime import datetime, tzinfo
4-
from re import Pattern
5-
from typing import ClassVar, Final, Literal, overload
1+
import re
2+
from _typeshed import Incomplete
3+
from collections import OrderedDict
4+
from collections.abc import Callable, Iterable, Iterator, Set as AbstractSet
5+
from datetime import date, datetime, tzinfo
6+
from typing import Any, ClassVar, Final, Literal, NamedTuple, TypeVar, overload, type_check_only
67
from typing_extensions import TypeAlias
78

8-
from dateparser import _Settings
99
from dateparser.conf import Settings
1010
from dateparser.languages.loader import LocaleDataLoader
1111
from dateparser.languages.locale import Locale
1212

13+
_DateT = TypeVar("_DateT", bound=date)
14+
1315
_DetectLanguagesFunction: TypeAlias = Callable[[str, float], list[str]]
1416
_Period: TypeAlias = Literal["time", "day", "week", "month", "year"]
17+
# Work around attribute and type having the same name.
18+
_Weekday: TypeAlias = Incomplete # Actually it's dateutil._common.weekday class
19+
20+
@type_check_only
21+
class _DateData(NamedTuple):
22+
date_obj: datetime | None
23+
locale: str | None
24+
period: _Period | None
1525

1626
APOSTROPHE_LOOK_ALIKE_CHARS: Final[list[str]]
17-
RE_NBSP: Final[Pattern[str]]
18-
RE_SPACES: Final[Pattern[str]]
19-
RE_TRIM_SPACES: Final[Pattern[str]]
20-
RE_TRIM_COLONS: Final[Pattern[str]]
21-
RE_SANITIZE_SKIP: Final[Pattern[str]]
22-
RE_SANITIZE_RUSSIAN: Final[Pattern[str]]
23-
RE_SANITIZE_PERIOD: Final[Pattern[str]]
24-
RE_SANITIZE_ON: Final[Pattern[str]]
25-
RE_SANITIZE_APOSTROPHE: Final[Pattern[str]]
26-
RE_SEARCH_TIMESTAMP: Final[Pattern[str]]
27-
RE_SANITIZE_CROATIAN: Final[Pattern[str]]
28-
RE_SEARCH_NEGATIVE_TIMESTAMP: Final[Pattern[str]]
27+
RE_NBSP: Final[re.Pattern[str]]
28+
RE_SPACES: Final[re.Pattern[str]]
29+
RE_TRIM_SPACES: Final[re.Pattern[str]]
30+
RE_TRIM_COLONS: Final[re.Pattern[str]]
31+
RE_SANITIZE_SKIP: Final[re.Pattern[str]]
32+
RE_SANITIZE_RUSSIAN: Final[re.Pattern[str]]
33+
RE_SANITIZE_PERIOD: Final[re.Pattern[str]]
34+
RE_SANITIZE_ON: Final[re.Pattern[str]]
35+
RE_SANITIZE_APOSTROPHE: Final[re.Pattern[str]]
36+
RE_SEARCH_TIMESTAMP: Final[re.Pattern[str]]
37+
RE_SANITIZE_CROATIAN: Final[re.Pattern[str]]
38+
RE_SEARCH_NEGATIVE_TIMESTAMP: Final[re.Pattern[str]]
2939

3040
def sanitize_spaces(date_string: str) -> str: ...
31-
def date_range(begin: datetime, end: datetime, **kwargs) -> None: ...
32-
def get_intersecting_periods(low: datetime, high: datetime, period: str = "day") -> None: ...
41+
def date_range(
42+
begin: _DateT,
43+
end: _DateT,
44+
*,
45+
dt1: date | None = None,
46+
dt2: date | None = None,
47+
years: int = 0,
48+
months: int = 0,
49+
days: int = 0,
50+
leapdays: int = 0,
51+
weeks: int = 0,
52+
hours: int = 0,
53+
minutes: int = 0,
54+
seconds: int = 0,
55+
microseconds: int = 0,
56+
weekday: int | _Weekday | None = None,
57+
yearday: int | None = None,
58+
nlyearday: int | None = None,
59+
microsecond: int | None = None,
60+
) -> Iterator[_DateT]: ...
61+
def get_intersecting_periods(
62+
low: _DateT, high: _DateT, period: Literal["year", "month", "week", "day", "hour", "minute", "second", "microsecond"] = "day"
63+
) -> Iterator[_DateT]: ...
3364
def sanitize_date(date_string: str) -> str: ...
34-
def get_date_from_timestamp(date_string: str, settings: Settings, negative: bool = False) -> datetime | None: ...
65+
def get_date_from_timestamp(date_string: str, settings: Settings, negative: bool | None = False) -> datetime | None: ...
3566
def parse_with_formats(date_string: str, date_formats: Iterable[str], settings: Settings) -> DateData: ...
3667

3768
class _DateLocaleParser:
3869
locale: Locale
3970
date_string: str
40-
date_formats: list[str] | tuple[str, ...] | set[str] | None
71+
date_formats: list[str] | tuple[str, ...] | AbstractSet[str] | None
4172
def __init__(
4273
self,
4374
locale: Locale,
4475
date_string: str,
45-
date_formats: list[str] | tuple[str, ...] | set[str] | None,
76+
date_formats: list[str] | tuple[str, ...] | AbstractSet[str] | None,
4677
settings: Settings | None = None,
4778
) -> None: ...
4879
@classmethod
4980
def parse(
5081
cls,
5182
locale: Locale,
5283
date_string: str,
53-
date_formats: list[str] | tuple[str, ...] | set[str] | None = None,
84+
date_formats: list[str] | tuple[str, ...] | AbstractSet[str] | None = None,
5485
settings: Settings | None = None,
5586
) -> DateData: ...
5687
def _parse(self) -> DateData | None: ...
@@ -88,22 +119,26 @@ class DateDataParser:
88119
try_previous_locales: bool
89120
use_given_order: bool
90121
languages: list[str] | None
91-
locales: list[str] | tuple[str, ...] | set[str] | None
122+
locales: list[str] | tuple[str, ...] | AbstractSet[str] | None
92123
region: str
93124
detect_languages_function: _DetectLanguagesFunction | None
94-
previous_locales: collections.OrderedDict[Locale, None]
125+
previous_locales: OrderedDict[Locale, None]
95126
def __init__(
96127
self,
97-
languages: list[str] | tuple[str, ...] | set[str] | None = None,
98-
locales: list[str] | tuple[str, ...] | set[str] | None = None,
128+
languages: list[str] | tuple[str, ...] | AbstractSet[str] | None = None,
129+
locales: list[str] | tuple[str, ...] | AbstractSet[str] | None = None,
99130
region: str | None = None,
100131
try_previous_locales: bool = False,
101132
use_given_order: bool = False,
102-
settings: _Settings | None = None,
133+
settings: Settings | dict[str, Any] | None = None,
103134
detect_languages_function: _DetectLanguagesFunction | None = None,
104135
) -> None: ...
105-
def get_date_data(self, date_string: str, date_formats: list[str] | tuple[str, ...] | set[str] | None = None) -> DateData: ...
106-
def get_date_tuple(self, date_string: str, date_formats: list[str] | tuple[str, ...] | set[str] | None = ...): ...
136+
def get_date_data(
137+
self, date_string: str, date_formats: list[str] | tuple[str, ...] | AbstractSet[str] | None = None
138+
) -> DateData: ...
139+
def get_date_tuple(
140+
self, date_string: str, date_formats: list[str] | tuple[str, ...] | AbstractSet[str] | None = None
141+
) -> _DateData: ...
107142
def _get_applicable_locales(self, date_string: str) -> Iterator[Locale]: ...
108143
def _is_applicable_locale(self, locale: Locale, date_string: str) -> bool: ...
109144
@classmethod

0 commit comments

Comments
 (0)