infdate 0.1.0__tar.gz
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.
- infdate-0.1.0/.gitignore +140 -0
- infdate-0.1.0/.gitlab-ci.yml +32 -0
- infdate-0.1.0/LICENSE +18 -0
- infdate-0.1.0/PKG-INFO +42 -0
- infdate-0.1.0/README.md +18 -0
- infdate-0.1.0/pyproject.toml +33 -0
- infdate-0.1.0/reports/docs/build-info.json +8 -0
- infdate-0.1.0/reports/docs/build-info.md +9 -0
- infdate-0.1.0/reports/test-junit.xml +1 -0
- infdate-0.1.0/src/infdate/__init__.py +282 -0
- infdate-0.1.0/src/infdate/__main__.py +8 -0
- infdate-0.1.0/src/infdate/py.typed +0 -0
- infdate-0.1.0/tests/__init__.py +0 -0
- infdate-0.1.0/tests/test_infdate.py +341 -0
- infdate-0.1.0/tools/ci_get_pypi_token.py +28 -0
- infdate-0.1.0/tools/codestyle.sh +13 -0
- infdate-0.1.0/tools/pytest.sh +2 -0
infdate-0.1.0/.gitignore
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
2
|
+
__pycache__/
|
3
|
+
*.py[cod]
|
4
|
+
*$py.class
|
5
|
+
|
6
|
+
# C extensions
|
7
|
+
*.so
|
8
|
+
|
9
|
+
# Distribution / packaging
|
10
|
+
.Python
|
11
|
+
build/
|
12
|
+
develop-eggs/
|
13
|
+
dist/
|
14
|
+
downloads/
|
15
|
+
eggs/
|
16
|
+
.eggs/
|
17
|
+
lib/
|
18
|
+
lib64/
|
19
|
+
parts/
|
20
|
+
sdist/
|
21
|
+
var/
|
22
|
+
wheels/
|
23
|
+
pip-wheel-metadata/
|
24
|
+
share/python-wheels/
|
25
|
+
*.egg-info/
|
26
|
+
.installed.cfg
|
27
|
+
*.egg
|
28
|
+
MANIFEST
|
29
|
+
|
30
|
+
# PyInstaller
|
31
|
+
# Usually these files are written by a python script from a template
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33
|
+
*.manifest
|
34
|
+
*.spec
|
35
|
+
|
36
|
+
# Installer logs
|
37
|
+
pip-log.txt
|
38
|
+
pip-delete-this-directory.txt
|
39
|
+
|
40
|
+
# Unit test / coverage reports
|
41
|
+
htmlcov/
|
42
|
+
.tox/
|
43
|
+
.nox/
|
44
|
+
.coverage
|
45
|
+
.coverage.*
|
46
|
+
.cache
|
47
|
+
nosetests.xml
|
48
|
+
coverage.xml
|
49
|
+
*.cover
|
50
|
+
*.py,cover
|
51
|
+
.hypothesis/
|
52
|
+
.pytest_cache/
|
53
|
+
|
54
|
+
# Translations
|
55
|
+
*.pot
|
56
|
+
|
57
|
+
# Django stuff:
|
58
|
+
*.log
|
59
|
+
local_settings.py
|
60
|
+
db.sqlite3
|
61
|
+
db.sqlite3-journal
|
62
|
+
|
63
|
+
# Flask stuff:
|
64
|
+
instance/
|
65
|
+
.webassets-cache
|
66
|
+
|
67
|
+
# Scrapy stuff:
|
68
|
+
.scrapy
|
69
|
+
|
70
|
+
# Sphinx documentation
|
71
|
+
docs/_build/
|
72
|
+
|
73
|
+
# PyBuilder
|
74
|
+
target/
|
75
|
+
|
76
|
+
# Jupyter Notebook
|
77
|
+
.ipynb_checkpoints
|
78
|
+
|
79
|
+
# IPython
|
80
|
+
profile_default/
|
81
|
+
ipython_config.py
|
82
|
+
|
83
|
+
# pyenv
|
84
|
+
.python-version
|
85
|
+
|
86
|
+
# pipenv
|
87
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
88
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
89
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
90
|
+
# install all needed dependencies.
|
91
|
+
#Pipfile.lock
|
92
|
+
|
93
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
94
|
+
__pypackages__/
|
95
|
+
|
96
|
+
# Celery stuff
|
97
|
+
celerybeat-schedule
|
98
|
+
celerybeat.pid
|
99
|
+
|
100
|
+
# SageMath parsed files
|
101
|
+
*.sage.py
|
102
|
+
|
103
|
+
# Environments
|
104
|
+
.env
|
105
|
+
.venv
|
106
|
+
env/
|
107
|
+
venv/
|
108
|
+
ENV/
|
109
|
+
env.bak/
|
110
|
+
venv.bak/
|
111
|
+
|
112
|
+
# Spyder project settings
|
113
|
+
.spyderproject
|
114
|
+
.spyproject
|
115
|
+
|
116
|
+
# Rope project settings
|
117
|
+
.ropeproject
|
118
|
+
|
119
|
+
# mkdocs documentation
|
120
|
+
/site
|
121
|
+
|
122
|
+
# mypy
|
123
|
+
.mypy_cache/
|
124
|
+
.dmypy.json
|
125
|
+
dmypy.json
|
126
|
+
|
127
|
+
# Pyre type checker
|
128
|
+
.pyre/
|
129
|
+
|
130
|
+
# pytest and coverage output
|
131
|
+
testreport.xml
|
132
|
+
coverage.xml
|
133
|
+
|
134
|
+
# Morast extracted overrides and results
|
135
|
+
docs/reference/
|
136
|
+
.morast/overrides/*+extracted.md
|
137
|
+
|
138
|
+
# uv lock file
|
139
|
+
uv.lock
|
140
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# ==============================================================================
|
2
|
+
# Python pipeline for infdate
|
3
|
+
# using components
|
4
|
+
# ==============================================================================
|
5
|
+
|
6
|
+
workflow:
|
7
|
+
rules:
|
8
|
+
- if: $CI_PIPELINE_SOURCE != "merge_request_event"
|
9
|
+
|
10
|
+
|
11
|
+
include:
|
12
|
+
- component: $CI_SERVER_FQDN/blackstream-x/generic-components/meta-gitlab-security@~latest
|
13
|
+
- component: $CI_SERVER_FQDN/blackstream-x/python-components/meta-uv-pipeline@feature/uv-version
|
14
|
+
inputs:
|
15
|
+
uv-version: "0.7.13"
|
16
|
+
mypy-args: "--exclude tools/"
|
17
|
+
python-version: "3.13"
|
18
|
+
python-variant: "alpine"
|
19
|
+
pylint-args: "--disable=fixme --report=yes"
|
20
|
+
pytest-rules:
|
21
|
+
- if: $CI_COMMIT_TAG == null
|
22
|
+
pytest-matrix:
|
23
|
+
- TEST_PYTHON_VERSION: ["3.10", "3.11", "3.12", "3.14-rc"]
|
24
|
+
pypi-project-name: infdate
|
25
|
+
|
26
|
+
|
27
|
+
stages:
|
28
|
+
- codestyle
|
29
|
+
- test
|
30
|
+
- pre-build
|
31
|
+
- build
|
32
|
+
- upload
|
infdate-0.1.0/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
MIT No Attribution
|
2
|
+
|
3
|
+
Copyright 2025 Rainer Schwarzbach
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
13
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
14
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
15
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
16
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
17
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
18
|
+
SOFTWARE.
|
infdate-0.1.0/PKG-INFO
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: infdate
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Date object wrapper supporting infinity
|
5
|
+
Project-URL: Homepage, https://gitlab.com/blackstream-x/infdate
|
6
|
+
Project-URL: CI, https://gitlab.com/blackstream-x/infdate/-/pipelines
|
7
|
+
Project-URL: Bug Tracker, https://gitlab.com/blackstream-x/infdate/-/issues
|
8
|
+
Project-URL: Repository, https://gitlab.com/blackstream-x/infdate.git
|
9
|
+
Author-email: Rainer Schwarzbach <rainer@blackstream.de>
|
10
|
+
License-File: LICENSE
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Operating System :: OS Independent
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
22
|
+
Requires-Python: >=3.11
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
|
25
|
+
# infdate
|
26
|
+
|
27
|
+
_Python module for date calculations implementing a concept of infinity_
|
28
|
+
|
29
|
+
The **Date** class provided in this package wraps the standard library’s
|
30
|
+
**datetime.date** class and adds the capability to specify dates in positive
|
31
|
+
(after everything else) or negative (before everything else) infinity,
|
32
|
+
and to do calculations (add days, or subtract days or other **Date** instances)
|
33
|
+
with these objects.
|
34
|
+
|
35
|
+
For easier usage, differences are expressed as integers (1 = one day)
|
36
|
+
or floats (inf and -inf _only_).
|
37
|
+
|
38
|
+
These capabilities can come handy when dealing with API representations of dates,
|
39
|
+
eg. in GitLab’s [Personal Access Tokens API].
|
40
|
+
|
41
|
+
* * *
|
42
|
+
[Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
|
infdate-0.1.0/README.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# infdate
|
2
|
+
|
3
|
+
_Python module for date calculations implementing a concept of infinity_
|
4
|
+
|
5
|
+
The **Date** class provided in this package wraps the standard library’s
|
6
|
+
**datetime.date** class and adds the capability to specify dates in positive
|
7
|
+
(after everything else) or negative (before everything else) infinity,
|
8
|
+
and to do calculations (add days, or subtract days or other **Date** instances)
|
9
|
+
with these objects.
|
10
|
+
|
11
|
+
For easier usage, differences are expressed as integers (1 = one day)
|
12
|
+
or floats (inf and -inf _only_).
|
13
|
+
|
14
|
+
These capabilities can come handy when dealing with API representations of dates,
|
15
|
+
eg. in GitLab’s [Personal Access Tokens API].
|
16
|
+
|
17
|
+
* * *
|
18
|
+
[Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
|
@@ -0,0 +1,33 @@
|
|
1
|
+
[project]
|
2
|
+
name = "infdate"
|
3
|
+
version = "0.1.0"
|
4
|
+
description = "Date object wrapper supporting infinity"
|
5
|
+
readme = "README.md"
|
6
|
+
authors = [
|
7
|
+
{ name = "Rainer Schwarzbach", email = "rainer@blackstream.de" }
|
8
|
+
]
|
9
|
+
requires-python = ">=3.11"
|
10
|
+
classifiers = [
|
11
|
+
"Development Status :: 4 - Beta",
|
12
|
+
"License :: OSI Approved :: MIT License",
|
13
|
+
"Operating System :: OS Independent",
|
14
|
+
"Programming Language :: Python :: 3",
|
15
|
+
"Programming Language :: Python :: 3.11",
|
16
|
+
"Programming Language :: Python :: 3.12",
|
17
|
+
"Programming Language :: Python :: 3.13",
|
18
|
+
"Programming Language :: Python :: 3.14",
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
21
|
+
"Intended Audience :: Developers",
|
22
|
+
]
|
23
|
+
dependencies = []
|
24
|
+
|
25
|
+
[project.urls]
|
26
|
+
Homepage = "https://gitlab.com/blackstream-x/infdate"
|
27
|
+
CI = "https://gitlab.com/blackstream-x/infdate/-/pipelines"
|
28
|
+
"Bug Tracker" = "https://gitlab.com/blackstream-x/infdate/-/issues"
|
29
|
+
Repository = "https://gitlab.com/blackstream-x/infdate.git"
|
30
|
+
|
31
|
+
[build-system]
|
32
|
+
requires = ["hatchling"]
|
33
|
+
build-backend = "hatchling.build"
|
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"ci_pipeline_created_at": "2025-06-23T16:10:13Z",
|
3
|
+
"ci_pipeline_id": "1884836730",
|
4
|
+
"ci_pipeline_url": "https://gitlab.com/blackstream-x/infdate/-/pipelines/1884836730",
|
5
|
+
"ci_project_title": "infdate",
|
6
|
+
"ci_project_url": "https://gitlab.com/blackstream-x/infdate",
|
7
|
+
"ci_commit_sha": "de24ba8b624157e46f55f13115ee5c9fcfa5ba7e"
|
8
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Build Information
|
2
|
+
|
3
|
+
_provided by [glsr-present](https://pypi.org/project/glsr-present/) 0.3.6_
|
4
|
+
|
5
|
+
[infdate](https://gitlab.com/blackstream-x/infdate)
|
6
|
+
built with pipeline
|
7
|
+
[1884836730](https://gitlab.com/blackstream-x/infdate/-/pipelines/1884836730)
|
8
|
+
(build started 2025-06-23T16:10:13Z)
|
9
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="16" time="0.453" timestamp="2025-06-23T16:10:55.667613+00:00" hostname="runner-jhcjxvh-project-71015407-concurrent-0"><testcase classname="tests.test_infdate.Date" name="test_arbitrary_date" time="0.003" /><testcase classname="tests.test_infdate.Date" name="test_float_init" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_ge" time="0.054" /><testcase classname="tests.test_infdate.Date" name="test_gt" time="0.044" /><testcase classname="tests.test_infdate.Date" name="test_hashable" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_le" time="0.053" /><testcase classname="tests.test_infdate.Date" name="test_lt" time="0.043" /><testcase classname="tests.test_infdate.Date" name="test_max" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_min" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_nan_init" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_ne" time="0.044" /><testcase classname="tests.test_infdate.Date" name="test_replace" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_str" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_sub_date" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_sub_or_add_days" time="0.001" /><testcase classname="tests.test_infdate.Date" name="test_today" time="0.001" /></testsuite></testsuites>
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
"""
|
4
|
+
infdate: a wrapper around standard library’s datetime.date objects,
|
5
|
+
capable of representing positive and negative infinity
|
6
|
+
"""
|
7
|
+
|
8
|
+
import datetime
|
9
|
+
import math
|
10
|
+
|
11
|
+
from typing import final, overload, Any, Final, TypeVar
|
12
|
+
|
13
|
+
INFINITY: Final = math.inf
|
14
|
+
NEGATIVE_INFINITY: Final = -math.inf
|
15
|
+
|
16
|
+
INFINITE_DATE_DISPLAY: Final = "<inf>"
|
17
|
+
NEGATIVE_INFINITE_DATE_DISPLAY: Final = "<-inf>"
|
18
|
+
|
19
|
+
ISO_DATE_FORMAT: Final = "%Y-%m-%d"
|
20
|
+
ISO_DATETIME_FORMAT_UTC: Final = f"{ISO_DATE_FORMAT}T%H:%M:%S.%f%Z"
|
21
|
+
|
22
|
+
D = TypeVar("D", bound="Date")
|
23
|
+
|
24
|
+
|
25
|
+
class DateMeta(type):
|
26
|
+
"""Date metaclass"""
|
27
|
+
|
28
|
+
@property
|
29
|
+
def min(cls: type[D], /) -> D: # type: ignore[misc]
|
30
|
+
"""Minimum possible Date"""
|
31
|
+
return cls(NEGATIVE_INFINITY)
|
32
|
+
|
33
|
+
@property
|
34
|
+
def max(cls: type[D], /) -> D: # type: ignore[misc]
|
35
|
+
"""Maximum possible Date"""
|
36
|
+
return cls(INFINITY)
|
37
|
+
|
38
|
+
|
39
|
+
class Date(metaclass=DateMeta):
|
40
|
+
"""Date object capable of representing negative or positive infinity"""
|
41
|
+
|
42
|
+
resolution = 1
|
43
|
+
|
44
|
+
@overload
|
45
|
+
def __init__(self: D, year_or_strange_number: float, /) -> None: ...
|
46
|
+
@overload
|
47
|
+
def __init__(
|
48
|
+
self: D, year_or_strange_number: int, month: int, day: int, /
|
49
|
+
) -> None: ...
|
50
|
+
@final
|
51
|
+
def __init__(
|
52
|
+
self: D, year_or_strange_number: int | float, /, month: int = 0, day: int = 0
|
53
|
+
) -> None:
|
54
|
+
"""Create a date-like object"""
|
55
|
+
if isinstance(year_or_strange_number, int):
|
56
|
+
self.__wrapped_date_obj: datetime.date | None = datetime.date(
|
57
|
+
int(year_or_strange_number), month, day
|
58
|
+
)
|
59
|
+
self.__ordinal: float | int = self.__wrapped_date_obj.toordinal()
|
60
|
+
elif math.isnan(year_or_strange_number):
|
61
|
+
raise ValueError("Cannot instantiate from NaN")
|
62
|
+
elif year_or_strange_number in (
|
63
|
+
INFINITY,
|
64
|
+
NEGATIVE_INFINITY,
|
65
|
+
):
|
66
|
+
self.__ordinal = year_or_strange_number
|
67
|
+
self.__wrapped_date_obj = None
|
68
|
+
else:
|
69
|
+
raise ValueError("Cannot instantiate from a regular deterministic float")
|
70
|
+
#
|
71
|
+
|
72
|
+
def toordinal(self: D) -> float | int:
|
73
|
+
"""to ordinal (almost like datetime.date.toordinal())"""
|
74
|
+
return self.__ordinal
|
75
|
+
|
76
|
+
def get_date_object(self: D) -> datetime.date:
|
77
|
+
"""Return the wrapped date object"""
|
78
|
+
if isinstance(self.__wrapped_date_obj, datetime.date):
|
79
|
+
return self.__wrapped_date_obj
|
80
|
+
#
|
81
|
+
raise ValueError("Non-deterministic date")
|
82
|
+
|
83
|
+
@property
|
84
|
+
def year(self: D) -> int:
|
85
|
+
"""shortcut: year"""
|
86
|
+
return self.get_date_object().year
|
87
|
+
|
88
|
+
@property
|
89
|
+
def month(self: D) -> int:
|
90
|
+
"""shortcut: month"""
|
91
|
+
return self.get_date_object().month
|
92
|
+
|
93
|
+
@property
|
94
|
+
def day(self: D) -> int:
|
95
|
+
"""shortcut: day"""
|
96
|
+
return self.get_date_object().day
|
97
|
+
|
98
|
+
def replace(self: D, /, year: int = 0, month: int = 0, day: int = 0) -> D:
|
99
|
+
"""Return a copy with year, month, and/or date replaced"""
|
100
|
+
internal_object = self.get_date_object()
|
101
|
+
return self.factory(
|
102
|
+
internal_object.replace(
|
103
|
+
year=year or internal_object.year,
|
104
|
+
month=month or internal_object.month,
|
105
|
+
day=day or internal_object.day,
|
106
|
+
)
|
107
|
+
)
|
108
|
+
|
109
|
+
def isoformat(self: D) -> str:
|
110
|
+
"""Date representation in ISO format"""
|
111
|
+
return self.strftime(ISO_DATE_FORMAT)
|
112
|
+
|
113
|
+
def strftime(self: D, fmt: str, /) -> str:
|
114
|
+
"""String representation of the date"""
|
115
|
+
try:
|
116
|
+
date_object = self.get_date_object()
|
117
|
+
except ValueError as error:
|
118
|
+
if self.__ordinal == INFINITY:
|
119
|
+
return INFINITE_DATE_DISPLAY
|
120
|
+
#
|
121
|
+
if self.__ordinal == NEGATIVE_INFINITY:
|
122
|
+
return NEGATIVE_INFINITE_DATE_DISPLAY
|
123
|
+
#
|
124
|
+
raise error from error
|
125
|
+
#
|
126
|
+
return date_object.strftime(fmt or ISO_DATE_FORMAT)
|
127
|
+
|
128
|
+
__format__ = strftime
|
129
|
+
|
130
|
+
def __bool__(self: D) -> bool:
|
131
|
+
"""True if a real date is wrapped"""
|
132
|
+
return self.__wrapped_date_obj is not None
|
133
|
+
|
134
|
+
def __hash__(self: D) -> int:
|
135
|
+
"""hash value"""
|
136
|
+
return hash(f"date with ordinal {self.__ordinal}")
|
137
|
+
|
138
|
+
def __add__(self: D, delta: int | float, /) -> D:
|
139
|
+
"""Add other, respecting maybe-nondeterministic values"""
|
140
|
+
for observed_item in (delta, self.__ordinal):
|
141
|
+
for infinity_form in (INFINITY, NEGATIVE_INFINITY):
|
142
|
+
if observed_item == infinity_form:
|
143
|
+
return self.factory(infinity_form)
|
144
|
+
#
|
145
|
+
#
|
146
|
+
#
|
147
|
+
return self.fromordinal(int(self.__ordinal) + int(delta))
|
148
|
+
|
149
|
+
@overload
|
150
|
+
def __sub__(self: D, other: int | float, /) -> D: ...
|
151
|
+
@overload
|
152
|
+
def __sub__(self: D, other: D, /) -> int | float: ...
|
153
|
+
@final
|
154
|
+
def __sub__(self: D, other: D | int | float, /) -> D | int | float:
|
155
|
+
"""subtract other, respecting possibly nondeterministic values"""
|
156
|
+
if isinstance(other, (int, float)):
|
157
|
+
return self + -other
|
158
|
+
#
|
159
|
+
return self.__ordinal - other.toordinal()
|
160
|
+
|
161
|
+
def __lt__(self: D, other: D, /) -> bool:
|
162
|
+
"""Rich comparison: less"""
|
163
|
+
return self.__ordinal < other.toordinal()
|
164
|
+
|
165
|
+
def __le__(self: D, other: D, /) -> bool:
|
166
|
+
"""Rich comparison: less or equal"""
|
167
|
+
return self < other or self == other
|
168
|
+
|
169
|
+
def __gt__(self: D, other: D, /) -> bool:
|
170
|
+
"""Rich comparison: greater"""
|
171
|
+
return self.__ordinal > other.toordinal()
|
172
|
+
|
173
|
+
def __ge__(self: D, other: D, /) -> bool:
|
174
|
+
"""Rich comparison: greater or equal"""
|
175
|
+
return self > other or self == other
|
176
|
+
|
177
|
+
def __eq__(self: D, other, /) -> bool:
|
178
|
+
"""Rich comparison: equals"""
|
179
|
+
return self.__ordinal == other.toordinal()
|
180
|
+
|
181
|
+
def __ne__(self: D, other, /) -> bool:
|
182
|
+
"""Rich comparison: does not equal"""
|
183
|
+
return self.__ordinal != other.toordinal()
|
184
|
+
|
185
|
+
def __repr__(self: D, /) -> str:
|
186
|
+
"""String representation of the object"""
|
187
|
+
try:
|
188
|
+
return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"
|
189
|
+
except ValueError:
|
190
|
+
return f"{self.__class__.__name__}({repr(self.__ordinal)})"
|
191
|
+
#
|
192
|
+
|
193
|
+
def __str__(self: D, /) -> str:
|
194
|
+
"""String representation of the date"""
|
195
|
+
return self.isoformat()
|
196
|
+
|
197
|
+
@classmethod
|
198
|
+
def today(cls: type[D], /) -> D:
|
199
|
+
"""Today as Date object"""
|
200
|
+
return cls.factory(datetime.date.today())
|
201
|
+
|
202
|
+
@classmethod
|
203
|
+
def fromisoformat(
|
204
|
+
cls: type[D],
|
205
|
+
source: str,
|
206
|
+
/,
|
207
|
+
) -> D:
|
208
|
+
"""Create an instance from an iso format representation"""
|
209
|
+
lower_source_stripped = source.strip().lower()
|
210
|
+
if lower_source_stripped == INFINITE_DATE_DISPLAY:
|
211
|
+
return cls(INFINITY)
|
212
|
+
#
|
213
|
+
if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
|
214
|
+
return cls(NEGATIVE_INFINITY)
|
215
|
+
#
|
216
|
+
return cls.factory(datetime.date.fromisoformat(lower_source_stripped))
|
217
|
+
|
218
|
+
@classmethod
|
219
|
+
def fromisocalendar(
|
220
|
+
cls: type[D],
|
221
|
+
/,
|
222
|
+
year: int,
|
223
|
+
week: int,
|
224
|
+
day: int,
|
225
|
+
) -> D:
|
226
|
+
"""Create an instance from an iso calendar date"""
|
227
|
+
return cls.factory(datetime.date.fromisocalendar(year, week, day))
|
228
|
+
|
229
|
+
@classmethod
|
230
|
+
def fromordinal(
|
231
|
+
cls: type[D],
|
232
|
+
source: float | int,
|
233
|
+
/,
|
234
|
+
) -> D:
|
235
|
+
"""Create an instance from a date ordinal"""
|
236
|
+
if isinstance(source, int):
|
237
|
+
return cls.factory(datetime.date.fromordinal(source))
|
238
|
+
#
|
239
|
+
if source in (NEGATIVE_INFINITY, INFINITY):
|
240
|
+
return cls(source)
|
241
|
+
#
|
242
|
+
raise ValueError(f"Invalid source for .fromordinal(): {source!r}")
|
243
|
+
|
244
|
+
@classmethod
|
245
|
+
def from_api_data(
|
246
|
+
cls: type[D],
|
247
|
+
source: Any,
|
248
|
+
/,
|
249
|
+
*,
|
250
|
+
fmt: str = ISO_DATETIME_FORMAT_UTC,
|
251
|
+
past_bound: bool = False,
|
252
|
+
) -> D:
|
253
|
+
"""Create an instance from string or another type,
|
254
|
+
assuming infinity in the latter case
|
255
|
+
"""
|
256
|
+
if isinstance(source, str):
|
257
|
+
return cls.factory(datetime.datetime.strptime(source, fmt))
|
258
|
+
#
|
259
|
+
return cls(NEGATIVE_INFINITY if past_bound else INFINITY)
|
260
|
+
|
261
|
+
@overload
|
262
|
+
@classmethod
|
263
|
+
def factory(cls: type[D], source: datetime.date | datetime.datetime, /) -> D: ...
|
264
|
+
@overload
|
265
|
+
@classmethod
|
266
|
+
def factory(cls: type[D], source: float, /) -> D: ...
|
267
|
+
@final
|
268
|
+
@classmethod
|
269
|
+
def factory(
|
270
|
+
cls: type[D], source: datetime.date | datetime.datetime | float, /
|
271
|
+
) -> D:
|
272
|
+
"""Create a new instance from a datetime.date or datetime.datetime object,
|
273
|
+
from
|
274
|
+
"""
|
275
|
+
if isinstance(source, (datetime.date, datetime.datetime)):
|
276
|
+
return cls(source.year, source.month, source.day)
|
277
|
+
#
|
278
|
+
return cls(source)
|
279
|
+
|
280
|
+
|
281
|
+
BEFORE_BIG_BANG: Final = Date.max
|
282
|
+
SAINT_GLINGLIN: Final = Date.min
|
File without changes
|
File without changes
|
@@ -0,0 +1,341 @@
|
|
1
|
+
# -None*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
"""
|
4
|
+
Tests for the infdate module
|
5
|
+
"""
|
6
|
+
|
7
|
+
import datetime
|
8
|
+
import math
|
9
|
+
import secrets
|
10
|
+
import unittest
|
11
|
+
|
12
|
+
|
13
|
+
import infdate
|
14
|
+
|
15
|
+
|
16
|
+
MAX_ORDINAL = datetime.date.max.toordinal()
|
17
|
+
|
18
|
+
|
19
|
+
def random_deterministic_date() -> infdate.Date:
|
20
|
+
"""Helper function: create a random deterministic Date"""
|
21
|
+
return infdate.Date.fromordinal(secrets.randbelow(MAX_ORDINAL) + 1)
|
22
|
+
|
23
|
+
|
24
|
+
class VerboseTestCase(unittest.TestCase):
|
25
|
+
"""Testcase showinf maximum differences"""
|
26
|
+
|
27
|
+
def setUp(self):
|
28
|
+
"""set maxDiff"""
|
29
|
+
self.maxDiff = None # pylint: disable=invalid-name ; name from unittest module
|
30
|
+
|
31
|
+
|
32
|
+
class Date(VerboseTestCase):
|
33
|
+
"""Date objects"""
|
34
|
+
|
35
|
+
def test_max(self):
|
36
|
+
"""Date.max"""
|
37
|
+
max_date = infdate.Date.max
|
38
|
+
with self.subTest("ordinal"):
|
39
|
+
self.assertEqual(max_date.toordinal(), math.inf)
|
40
|
+
#
|
41
|
+
with self.subTest("bool"):
|
42
|
+
self.assertFalse(max_date)
|
43
|
+
#
|
44
|
+
for attribute in ("year", "month", "day"):
|
45
|
+
with self.subTest("failure", attribute=attribute):
|
46
|
+
self.assertRaisesRegex(
|
47
|
+
ValueError, "^Non-deterministic date$", getattr, max_date, attribute
|
48
|
+
)
|
49
|
+
#
|
50
|
+
#
|
51
|
+
with self.subTest("repr"):
|
52
|
+
self.assertEqual(repr(max_date), "Date(inf)")
|
53
|
+
#
|
54
|
+
with self.subTest("isoformat"):
|
55
|
+
self.assertEqual(max_date.isoformat(), "<inf>")
|
56
|
+
#
|
57
|
+
|
58
|
+
def test_min(self):
|
59
|
+
"""Date.min"""
|
60
|
+
min_date = infdate.Date.min
|
61
|
+
with self.subTest("ordinal"):
|
62
|
+
self.assertEqual(min_date.toordinal(), -math.inf)
|
63
|
+
#
|
64
|
+
with self.subTest("bool"):
|
65
|
+
self.assertFalse(min_date)
|
66
|
+
#
|
67
|
+
for attribute in ("year", "month", "day"):
|
68
|
+
with self.subTest("failure", attribute=attribute):
|
69
|
+
self.assertRaisesRegex(
|
70
|
+
ValueError, "^Non-deterministic date$", getattr, min_date, attribute
|
71
|
+
)
|
72
|
+
#
|
73
|
+
#
|
74
|
+
with self.subTest("repr"):
|
75
|
+
self.assertEqual(repr(min_date), "Date(-inf)")
|
76
|
+
#
|
77
|
+
with self.subTest("isoformat"):
|
78
|
+
self.assertEqual(min_date.isoformat(), "<-inf>")
|
79
|
+
#
|
80
|
+
|
81
|
+
def test_nan_init(self):
|
82
|
+
"""Try to initialize with NaN"""
|
83
|
+
self.assertRaisesRegex(
|
84
|
+
ValueError, "^Cannot instantiate from NaN$", infdate.Date, math.nan
|
85
|
+
)
|
86
|
+
|
87
|
+
def test_float_init(self):
|
88
|
+
"""Try to initialize with a regular float"""
|
89
|
+
self.assertRaisesRegex(
|
90
|
+
ValueError,
|
91
|
+
"^Cannot instantiate from a regular deterministic float$",
|
92
|
+
infdate.Date,
|
93
|
+
12.345,
|
94
|
+
)
|
95
|
+
|
96
|
+
def test_arbitrary_date(self):
|
97
|
+
"""arbitrary date"""
|
98
|
+
some_date = infdate.Date(2023, 5, 23)
|
99
|
+
expected_ordinal = datetime.date(2023, 5, 23).toordinal()
|
100
|
+
with self.subTest("ordinal"):
|
101
|
+
self.assertEqual(some_date.toordinal(), expected_ordinal)
|
102
|
+
#
|
103
|
+
with self.subTest("bool"):
|
104
|
+
self.assertTrue(some_date)
|
105
|
+
#
|
106
|
+
for attribute, expected_value in (("year", 2023), ("month", 5), ("day", 23)):
|
107
|
+
with self.subTest(
|
108
|
+
"success", attribute=attribute, expected_value=expected_value
|
109
|
+
):
|
110
|
+
self.assertEqual(getattr(some_date, attribute), expected_value)
|
111
|
+
#
|
112
|
+
#
|
113
|
+
with self.subTest("repr"):
|
114
|
+
self.assertEqual(repr(some_date), "Date(2023, 5, 23)")
|
115
|
+
#
|
116
|
+
with self.subTest("isoformat"):
|
117
|
+
self.assertEqual(some_date.isoformat(), "2023-05-23")
|
118
|
+
#
|
119
|
+
|
120
|
+
def test_replace(self):
|
121
|
+
""".replace() method"""
|
122
|
+
min_date = infdate.Date.min
|
123
|
+
with self.subTest("failure"):
|
124
|
+
self.assertRaisesRegex(
|
125
|
+
ValueError, "^Non-deterministic date$", min_date.replace, month=6
|
126
|
+
)
|
127
|
+
#
|
128
|
+
some_date = infdate.Date(1234, 5, 6)
|
129
|
+
old_year = 1234
|
130
|
+
old_month = 5
|
131
|
+
old_day = 6
|
132
|
+
for new_year in (1, 2000, 5000, 9999):
|
133
|
+
with self.subTest("replaced", new_year=new_year):
|
134
|
+
new_date = some_date.replace(year=new_year)
|
135
|
+
self.assertEqual(new_date.year, new_year)
|
136
|
+
self.assertEqual(new_date.month, old_month)
|
137
|
+
self.assertEqual(new_date.day, old_day)
|
138
|
+
#
|
139
|
+
#
|
140
|
+
for new_month in (1, 4, 7, 12):
|
141
|
+
with self.subTest("replaced", new_month=new_month):
|
142
|
+
new_date = some_date.replace(month=new_month)
|
143
|
+
self.assertEqual(new_date.year, old_year)
|
144
|
+
self.assertEqual(new_date.month, new_month)
|
145
|
+
self.assertEqual(new_date.day, old_day)
|
146
|
+
#
|
147
|
+
#
|
148
|
+
for new_day in (1, 10, 16, 31):
|
149
|
+
with self.subTest("replaced", new_day=new_day):
|
150
|
+
new_date = some_date.replace(day=new_day)
|
151
|
+
self.assertEqual(new_date.year, old_year)
|
152
|
+
self.assertEqual(new_date.month, old_month)
|
153
|
+
self.assertEqual(new_date.day, new_day)
|
154
|
+
#
|
155
|
+
#
|
156
|
+
|
157
|
+
def test_hashable(self):
|
158
|
+
"""hash(date_instance) capability; Date instances are usable as dict keys"""
|
159
|
+
isaac = infdate.Date(1643, 1, 4)
|
160
|
+
ada = infdate.Date(1815, 12, 10)
|
161
|
+
birthdays = {isaac: "Newton", ada: "Lovelace"}
|
162
|
+
self.assertEqual(birthdays[isaac], "Newton")
|
163
|
+
self.assertEqual(birthdays[ada], "Lovelace")
|
164
|
+
|
165
|
+
def test_sub_or_add_days(self):
|
166
|
+
"""date_instance +/- number of days capability"""
|
167
|
+
bernoulli = infdate.Date(1655, 1, 6)
|
168
|
+
self.assertEqual(bernoulli + 60, infdate.Date(1655, 3, 7))
|
169
|
+
self.assertEqual(bernoulli - 60, infdate.Date(1654, 11, 7))
|
170
|
+
self.assertEqual(bernoulli - math.inf, infdate.Date.min)
|
171
|
+
self.assertEqual(bernoulli + math.inf, infdate.Date.max)
|
172
|
+
self.assertEqual(infdate.Date.max - 1, infdate.Date.max)
|
173
|
+
self.assertEqual(infdate.Date.max + 1, infdate.Date.max)
|
174
|
+
self.assertEqual(infdate.Date.min - 1, infdate.Date.min)
|
175
|
+
self.assertEqual(infdate.Date.min + 1, infdate.Date.min)
|
176
|
+
self.assertEqual(infdate.Date.max - math.inf, infdate.Date.min)
|
177
|
+
self.assertEqual(infdate.Date.max + math.inf, infdate.Date.max)
|
178
|
+
self.assertEqual(infdate.Date.min - math.inf, infdate.Date.min)
|
179
|
+
self.assertEqual(infdate.Date.min + math.inf, infdate.Date.max)
|
180
|
+
self.assertRaises(ValueError, bernoulli.__add__, math.nan)
|
181
|
+
self.assertRaises(ValueError, bernoulli.__sub__, math.nan)
|
182
|
+
# Adding math.nan to or subtrating it from the infinity dates
|
183
|
+
# does not raise an error because infinity is checked first,
|
184
|
+
# before calculating a result
|
185
|
+
self.assertEqual(infdate.Date.min + math.nan, infdate.Date.min)
|
186
|
+
self.assertEqual(infdate.Date.max - math.nan, infdate.Date.max)
|
187
|
+
|
188
|
+
def test_sub_date(self):
|
189
|
+
"""date_instance - date_instance capability"""
|
190
|
+
chernobyl = infdate.Date(1986, 4, 26)
|
191
|
+
fukushima = infdate.Date(2011, 3, 11)
|
192
|
+
self.assertEqual(fukushima - chernobyl, 9085)
|
193
|
+
self.assertEqual(chernobyl - fukushima, -9085)
|
194
|
+
self.assertEqual(infdate.Date.max - fukushima, math.inf)
|
195
|
+
self.assertEqual(chernobyl - infdate.Date.min, math.inf)
|
196
|
+
self.assertEqual(infdate.Date.min - fukushima, -math.inf)
|
197
|
+
self.assertEqual(chernobyl - infdate.Date.max, -math.inf)
|
198
|
+
self.assertEqual(infdate.Date.min - infdate.Date.max, -math.inf)
|
199
|
+
self.assertEqual(infdate.Date.max - infdate.Date.min, math.inf)
|
200
|
+
# subtracting infinite dates from themselves results in NaN
|
201
|
+
self.assertTrue(math.isnan(infdate.Date.max - infdate.Date.max))
|
202
|
+
self.assertTrue(math.isnan(infdate.Date.min - infdate.Date.min))
|
203
|
+
|
204
|
+
# pylint: disable=comparison-with-itself ; to show lt/gt ↔ le/ge difference
|
205
|
+
|
206
|
+
def test_lt(self):
|
207
|
+
"""less than"""
|
208
|
+
mindate = infdate.Date.min
|
209
|
+
maxdate = infdate.Date.max
|
210
|
+
for iteration in range(1, 1001):
|
211
|
+
random_date = random_deterministic_date()
|
212
|
+
with self.subTest(
|
213
|
+
"compared to <-inf>", iteration=iteration, random_date=random_date
|
214
|
+
):
|
215
|
+
self.assertTrue(mindate < random_date)
|
216
|
+
self.assertFalse(random_date < mindate)
|
217
|
+
#
|
218
|
+
with self.subTest(
|
219
|
+
"compared to <inf>", iteration=iteration, random_date=random_date
|
220
|
+
):
|
221
|
+
self.assertTrue(random_date < maxdate)
|
222
|
+
self.assertFalse(maxdate < random_date)
|
223
|
+
#
|
224
|
+
with self.subTest(
|
225
|
+
"compared to itself", iteration=iteration, random_date=random_date
|
226
|
+
):
|
227
|
+
self.assertFalse(random_date < random_date)
|
228
|
+
#
|
229
|
+
#
|
230
|
+
|
231
|
+
def test_le(self):
|
232
|
+
"""less than or equal"""
|
233
|
+
mindate = infdate.Date.min
|
234
|
+
maxdate = infdate.Date.max
|
235
|
+
for iteration in range(1, 1001):
|
236
|
+
random_date = random_deterministic_date()
|
237
|
+
with self.subTest(
|
238
|
+
"compared to <-inf>", iteration=iteration, random_date=random_date
|
239
|
+
):
|
240
|
+
self.assertTrue(mindate <= random_date)
|
241
|
+
self.assertFalse(random_date <= mindate)
|
242
|
+
#
|
243
|
+
with self.subTest(
|
244
|
+
"compared to <inf>", iteration=iteration, random_date=random_date
|
245
|
+
):
|
246
|
+
self.assertTrue(random_date <= maxdate)
|
247
|
+
self.assertFalse(maxdate <= random_date)
|
248
|
+
#
|
249
|
+
with self.subTest(
|
250
|
+
"compared to itself", iteration=iteration, random_date=random_date
|
251
|
+
):
|
252
|
+
self.assertTrue(random_date <= random_date)
|
253
|
+
#
|
254
|
+
#
|
255
|
+
|
256
|
+
def test_gt(self):
|
257
|
+
"""greater than"""
|
258
|
+
mindate = infdate.Date.min
|
259
|
+
maxdate = infdate.Date.max
|
260
|
+
for iteration in range(1, 1001):
|
261
|
+
random_date = random_deterministic_date()
|
262
|
+
with self.subTest(
|
263
|
+
"compared to <-inf>", iteration=iteration, random_date=random_date
|
264
|
+
):
|
265
|
+
self.assertFalse(mindate > random_date)
|
266
|
+
self.assertTrue(random_date > mindate)
|
267
|
+
#
|
268
|
+
with self.subTest(
|
269
|
+
"compared to <inf>", iteration=iteration, random_date=random_date
|
270
|
+
):
|
271
|
+
self.assertFalse(random_date > maxdate)
|
272
|
+
self.assertTrue(maxdate > random_date)
|
273
|
+
#
|
274
|
+
with self.subTest(
|
275
|
+
"compared to itself", iteration=iteration, random_date=random_date
|
276
|
+
):
|
277
|
+
self.assertFalse(random_date > random_date)
|
278
|
+
#
|
279
|
+
#
|
280
|
+
|
281
|
+
def test_ge(self):
|
282
|
+
"""greater than or equal"""
|
283
|
+
mindate = infdate.Date.min
|
284
|
+
maxdate = infdate.Date.max
|
285
|
+
for iteration in range(1, 1001):
|
286
|
+
random_date = random_deterministic_date()
|
287
|
+
with self.subTest(
|
288
|
+
"compared to <-inf>", iteration=iteration, random_date=random_date
|
289
|
+
):
|
290
|
+
self.assertFalse(mindate >= random_date)
|
291
|
+
self.assertTrue(random_date >= mindate)
|
292
|
+
#
|
293
|
+
with self.subTest(
|
294
|
+
"compared to <inf>", iteration=iteration, random_date=random_date
|
295
|
+
):
|
296
|
+
self.assertFalse(random_date >= maxdate)
|
297
|
+
self.assertTrue(maxdate >= random_date)
|
298
|
+
#
|
299
|
+
with self.subTest(
|
300
|
+
"compared to itself", iteration=iteration, random_date=random_date
|
301
|
+
):
|
302
|
+
self.assertTrue(random_date <= random_date)
|
303
|
+
#
|
304
|
+
#
|
305
|
+
|
306
|
+
def test_ne(self):
|
307
|
+
"""not equal"""
|
308
|
+
mindate = infdate.Date.min
|
309
|
+
maxdate = infdate.Date.max
|
310
|
+
for iteration in range(1, 1001):
|
311
|
+
random_date = random_deterministic_date()
|
312
|
+
with self.subTest(
|
313
|
+
"compared to <-inf>", iteration=iteration, random_date=random_date
|
314
|
+
):
|
315
|
+
self.assertTrue(mindate != random_date)
|
316
|
+
self.assertTrue(random_date != mindate)
|
317
|
+
#
|
318
|
+
with self.subTest(
|
319
|
+
"compared to <inf>", iteration=iteration, random_date=random_date
|
320
|
+
):
|
321
|
+
self.assertTrue(random_date != maxdate)
|
322
|
+
self.assertTrue(maxdate != random_date)
|
323
|
+
#
|
324
|
+
with self.subTest(
|
325
|
+
"compared to itself", iteration=iteration, random_date=random_date
|
326
|
+
):
|
327
|
+
self.assertFalse(random_date != random_date)
|
328
|
+
#
|
329
|
+
#
|
330
|
+
|
331
|
+
def test_str(self):
|
332
|
+
"""hash(date_instance) capability; Date instances are usable as dict keys"""
|
333
|
+
isaac = infdate.Date(1643, 1, 4)
|
334
|
+
self.assertEqual(str(isaac), "1643-01-04")
|
335
|
+
|
336
|
+
def test_today(self):
|
337
|
+
""".today() classmethod"""
|
338
|
+
today = datetime.date.today()
|
339
|
+
self.assertEqual(
|
340
|
+
infdate.Date.today(), infdate.Date(today.year, today.month, today.day)
|
341
|
+
)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# /// script
|
4
|
+
# dependencies = [
|
5
|
+
# "requests",
|
6
|
+
# ]
|
7
|
+
# ///
|
8
|
+
|
9
|
+
"""
|
10
|
+
Script for outputting athe PYPI token, compare
|
11
|
+
<https://stefan.sofa-rockers.org/2024/11/14/gitlab-trusted-publisher/>
|
12
|
+
but eliminating the need for curl in the container image
|
13
|
+
|
14
|
+
Run using "uv run tools/ci_get_pypi_token.py"
|
15
|
+
"""
|
16
|
+
|
17
|
+
import os
|
18
|
+
|
19
|
+
import requests
|
20
|
+
|
21
|
+
|
22
|
+
if __name__ == "__main__":
|
23
|
+
response = requests.post(
|
24
|
+
os.environ.get("PYPI_OIDC_URL"),
|
25
|
+
json={"token": os.environ.get("PYPI_ID_TOKEN")},
|
26
|
+
timeout=10,
|
27
|
+
)
|
28
|
+
print(response.json()["token"])
|
@@ -0,0 +1,13 @@
|
|
1
|
+
echo -e "--- mypy ---\n"
|
2
|
+
uvx mypy --exclude tools/ .
|
3
|
+
echo -e "\n--- pylint src ---"
|
4
|
+
uvx pylint src
|
5
|
+
echo -e "--- pylint tests ---"
|
6
|
+
PYTHONPATH=src uvx pylint tests
|
7
|
+
echo -e "--- ruff check ---\n"
|
8
|
+
uvx ruff check
|
9
|
+
echo -e "\n--- ruff format ---\n"
|
10
|
+
uvx ruff format
|
11
|
+
echo -e "\n--- ty check ---\n"
|
12
|
+
# --ignore unresolved-import
|
13
|
+
uvx ty check src tests
|