msgspec-extras 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.
- msgspec_extras-0.1.0/.gitattributes +116 -0
- msgspec_extras-0.1.0/.gitignore +21 -0
- msgspec_extras-0.1.0/PKG-INFO +110 -0
- msgspec_extras-0.1.0/README.md +86 -0
- msgspec_extras-0.1.0/msgspec_extras/__init__.py +33 -0
- msgspec_extras-0.1.0/msgspec_extras/_hooks.py +17 -0
- msgspec_extras-0.1.0/msgspec_extras/datetime.py +106 -0
- msgspec_extras-0.1.0/msgspec_extras/numeric.py +24 -0
- msgspec_extras-0.1.0/msgspec_extras/string.py +7 -0
- msgspec_extras-0.1.0/prek.toml +52 -0
- msgspec_extras-0.1.0/pylock.toml +1098 -0
- msgspec_extras-0.1.0/pyproject.toml +131 -0
- msgspec_extras-0.1.0/renovate.json +6 -0
- msgspec_extras-0.1.0/tests/__init__.py +0 -0
- msgspec_extras-0.1.0/tests/conftest.py +0 -0
- msgspec_extras-0.1.0/tests/datetime_test.py +87 -0
- msgspec_extras-0.1.0/tests/numeric_test.py +61 -0
- msgspec_extras-0.1.0/tests/string_test.py +19 -0
- msgspec_extras-0.1.0/uv.lock +975 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Common settings that generally should always be used with your language specific settings
|
|
2
|
+
|
|
3
|
+
# Auto detect text files and perform LF normalization
|
|
4
|
+
* text=auto
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# The above will handle all files NOT found below
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
# Documents
|
|
11
|
+
*.bibtex text diff=bibtex
|
|
12
|
+
*.doc diff=astextplain
|
|
13
|
+
*.DOC diff=astextplain
|
|
14
|
+
*.docx diff=astextplain
|
|
15
|
+
*.DOCX diff=astextplain
|
|
16
|
+
*.dot diff=astextplain
|
|
17
|
+
*.DOT diff=astextplain
|
|
18
|
+
*.pdf diff=astextplain
|
|
19
|
+
*.PDF diff=astextplain
|
|
20
|
+
*.rtf diff=astextplain
|
|
21
|
+
*.RTF diff=astextplain
|
|
22
|
+
*.md text diff=markdown
|
|
23
|
+
*.mdx text diff=markdown
|
|
24
|
+
*.tex text diff=tex
|
|
25
|
+
*.adoc text
|
|
26
|
+
*.textile text
|
|
27
|
+
*.mustache text
|
|
28
|
+
*.csv text eol=crlf
|
|
29
|
+
*.tab text
|
|
30
|
+
*.tsv text
|
|
31
|
+
*.txt text
|
|
32
|
+
*.sql text
|
|
33
|
+
*.epub diff=astextplain
|
|
34
|
+
|
|
35
|
+
# Graphics
|
|
36
|
+
*.png binary
|
|
37
|
+
*.jpg binary
|
|
38
|
+
*.jpeg binary
|
|
39
|
+
*.gif binary
|
|
40
|
+
*.tif binary
|
|
41
|
+
*.tiff binary
|
|
42
|
+
*.ico binary
|
|
43
|
+
# SVG treated as text by default.
|
|
44
|
+
*.svg text
|
|
45
|
+
# If you want to treat it as binary,
|
|
46
|
+
# use the following line instead.
|
|
47
|
+
# *.svg binary
|
|
48
|
+
*.eps binary
|
|
49
|
+
|
|
50
|
+
# Scripts
|
|
51
|
+
*.bash text eol=lf
|
|
52
|
+
*.fish text eol=lf
|
|
53
|
+
*.sh text eol=lf
|
|
54
|
+
*.zsh text eol=lf
|
|
55
|
+
# These are explicitly windows files and should use crlf
|
|
56
|
+
*.bat text eol=crlf
|
|
57
|
+
*.cmd text eol=crlf
|
|
58
|
+
*.ps1 text eol=crlf
|
|
59
|
+
|
|
60
|
+
# Serialisation
|
|
61
|
+
*.json text
|
|
62
|
+
*.toml text
|
|
63
|
+
*.xml text
|
|
64
|
+
*.yaml text
|
|
65
|
+
*.yml text
|
|
66
|
+
|
|
67
|
+
# Archives
|
|
68
|
+
*.7z binary
|
|
69
|
+
*.gz binary
|
|
70
|
+
*.tar binary
|
|
71
|
+
*.tgz binary
|
|
72
|
+
*.zip binary
|
|
73
|
+
|
|
74
|
+
# Text files where line endings should be preserved
|
|
75
|
+
*.patch -text
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Exclude files from exporting
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
.gitattributes export-ignore
|
|
82
|
+
.gitignore export-ignore
|
|
83
|
+
.gitkeep export-ignore
|
|
84
|
+
# Apply override to all files in the directory
|
|
85
|
+
*.md linguist-detectable
|
|
86
|
+
# Basic .gitattributes for a python repo.
|
|
87
|
+
|
|
88
|
+
# Source files
|
|
89
|
+
# ============
|
|
90
|
+
*.pxd text diff=python
|
|
91
|
+
*.py text diff=python
|
|
92
|
+
*.py3 text diff=python
|
|
93
|
+
*.pyw text diff=python
|
|
94
|
+
*.pyx text diff=python
|
|
95
|
+
*.pyz text diff=python
|
|
96
|
+
*.pyi text diff=python
|
|
97
|
+
|
|
98
|
+
# Binary files
|
|
99
|
+
# ============
|
|
100
|
+
*.db binary
|
|
101
|
+
*.p binary
|
|
102
|
+
*.pkl binary
|
|
103
|
+
*.pickle binary
|
|
104
|
+
*.pyc binary export-ignore
|
|
105
|
+
*.pyo binary export-ignore
|
|
106
|
+
*.pyd binary
|
|
107
|
+
|
|
108
|
+
# Jupyter notebook
|
|
109
|
+
*.ipynb text eol=lf
|
|
110
|
+
|
|
111
|
+
# Note: .db, .p, and .pkl files are associated
|
|
112
|
+
# with the python modules ``pickle``, ``dbm.*``,
|
|
113
|
+
# ``shelve``, ``marshal``, ``anydbm``, & ``bsddb``
|
|
114
|
+
# (among others).
|
|
115
|
+
# Fix syntax highlighting on GitHub to allow comments
|
|
116
|
+
.vscode/*.json linguist-language=JSON-with-Comments
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# =====Folders=====
|
|
2
|
+
__pycache__/
|
|
3
|
+
__pypackages__/
|
|
4
|
+
.idea/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.tox/
|
|
8
|
+
.venv/
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
logs/
|
|
12
|
+
site/
|
|
13
|
+
|
|
14
|
+
# =====Files=====
|
|
15
|
+
*.iml
|
|
16
|
+
*.log
|
|
17
|
+
*.txt
|
|
18
|
+
.coverage
|
|
19
|
+
.envrc
|
|
20
|
+
.pdm-python
|
|
21
|
+
.python-version
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msgspec-extras
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Project-URL: Homepage, https://pypi.org/project/msgspec-extras
|
|
5
|
+
Project-URL: Issues, https://codefloe.com/buriedincode/msgspec-extras/issues
|
|
6
|
+
Project-URL: Source, https://codefloe.com/buriedincode/msgspec-extras
|
|
7
|
+
Author-email: BuriedInCode <buriedincode@duckpond.nz>
|
|
8
|
+
Maintainer-email: BuriedInCode <buriedincode@duckpond.nz>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: msgspec>=0.21.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# msgspec-extras
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
28
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
29
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
[](https://github.com/j178/prek)
|
|
33
|
+
[](https://github.com/astral-sh/ruff)
|
|
34
|
+
[](https://github.com/astral-sh/ty)
|
|
35
|
+
|
|
36
|
+
[](https://ci.codefloe.com/repos/1187)
|
|
37
|
+
|
|
38
|
+
A small collection of types that extend [msgspec](https://msgspec.dev)'s built-in type support.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
pdm add msgspec-extras
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Type Support
|
|
47
|
+
|
|
48
|
+
`msgspec-extras` supports **all msgspec native types** - see the full list in the [msgspec documentation](https://msgspec.dev/supported-types).
|
|
49
|
+
It also adds the following custom types.
|
|
50
|
+
|
|
51
|
+
### Datetime
|
|
52
|
+
|
|
53
|
+
- `PastDate` - must be earlier than today
|
|
54
|
+
- `PastDatetime` - must be earlier than now
|
|
55
|
+
- `FutureDate` - must be later than today
|
|
56
|
+
- `FutureDatetime` - must be later than now
|
|
57
|
+
|
|
58
|
+
These types require the `enc_hook`/`dec_hook` to be passed to msgspec's encode/decode calls.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from datetime import datetime, date
|
|
62
|
+
|
|
63
|
+
import msgspec
|
|
64
|
+
from msgspec_extras import PastDate, PastDatetime, FutureDate, FutureDatetime, enc_hook, dec_hook
|
|
65
|
+
|
|
66
|
+
past_date = msgspec.json.decode('"2020-03-21"', type=PastDate, dec_hook=dec_hook)
|
|
67
|
+
past_date_json = msgspec.json.encode(past_date, enc_hook=enc_hook)
|
|
68
|
+
|
|
69
|
+
msgspec.json.decode('"2020-03-21"', type=FutureDate, dec_hook=dec_hook)
|
|
70
|
+
# Raises `msgspec.ValidationError` as the date isn't in the future
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Numeric
|
|
74
|
+
|
|
75
|
+
| Type | Constraint |
|
|
76
|
+
| ------------------ | ---------- |
|
|
77
|
+
| `PositiveInt` | `> 0` |
|
|
78
|
+
| `PositiveFloat` | `> 0` |
|
|
79
|
+
| `NonNegativeInt` | `>= 0` |
|
|
80
|
+
| `NonNegativeFloat` | `>= 0` |
|
|
81
|
+
| `NegativeInt` | `< 0` |
|
|
82
|
+
| `NegativeFloat` | `< 0` |
|
|
83
|
+
| `NonPositiveInt` | `<= 0` |
|
|
84
|
+
| `NonPositiveFloat` | `<= 0` |
|
|
85
|
+
|
|
86
|
+
### String
|
|
87
|
+
|
|
88
|
+
- `HttpUrl` - must be a valid HTTP or HTTPS URL
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
The numeric and string types are just `Annotated` aliases over msgspec's native types (e.g. `Annotated[int, msgspec.Meta(gt=0)]`), so they need no hooks and work as drop-in replacements anywhere you'd use `int`, `float`, or `str`:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import msgspec
|
|
96
|
+
from msgspec_extras import PositiveFloat
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class Product(msgspec.Struct):
|
|
100
|
+
name: str
|
|
101
|
+
price: PositiveFloat
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
product = msgspec.json.decode(b'{"name": "Widget", "price": 5.1}', type=Product)
|
|
105
|
+
msgspec.json.encode(product)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Socials
|
|
109
|
+
|
|
110
|
+
[](https://matrix.to/#/#The-Dev-Environment:matrix.org)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# msgspec-extras
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
4
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
5
|
+
[](https://pypi.org/p/msgspec-extras/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
[](https://github.com/j178/prek)
|
|
9
|
+
[](https://github.com/astral-sh/ruff)
|
|
10
|
+
[](https://github.com/astral-sh/ty)
|
|
11
|
+
|
|
12
|
+
[](https://ci.codefloe.com/repos/1187)
|
|
13
|
+
|
|
14
|
+
A small collection of types that extend [msgspec](https://msgspec.dev)'s built-in type support.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pdm add msgspec-extras
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Type Support
|
|
23
|
+
|
|
24
|
+
`msgspec-extras` supports **all msgspec native types** - see the full list in the [msgspec documentation](https://msgspec.dev/supported-types).
|
|
25
|
+
It also adds the following custom types.
|
|
26
|
+
|
|
27
|
+
### Datetime
|
|
28
|
+
|
|
29
|
+
- `PastDate` - must be earlier than today
|
|
30
|
+
- `PastDatetime` - must be earlier than now
|
|
31
|
+
- `FutureDate` - must be later than today
|
|
32
|
+
- `FutureDatetime` - must be later than now
|
|
33
|
+
|
|
34
|
+
These types require the `enc_hook`/`dec_hook` to be passed to msgspec's encode/decode calls.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from datetime import datetime, date
|
|
38
|
+
|
|
39
|
+
import msgspec
|
|
40
|
+
from msgspec_extras import PastDate, PastDatetime, FutureDate, FutureDatetime, enc_hook, dec_hook
|
|
41
|
+
|
|
42
|
+
past_date = msgspec.json.decode('"2020-03-21"', type=PastDate, dec_hook=dec_hook)
|
|
43
|
+
past_date_json = msgspec.json.encode(past_date, enc_hook=enc_hook)
|
|
44
|
+
|
|
45
|
+
msgspec.json.decode('"2020-03-21"', type=FutureDate, dec_hook=dec_hook)
|
|
46
|
+
# Raises `msgspec.ValidationError` as the date isn't in the future
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Numeric
|
|
50
|
+
|
|
51
|
+
| Type | Constraint |
|
|
52
|
+
| ------------------ | ---------- |
|
|
53
|
+
| `PositiveInt` | `> 0` |
|
|
54
|
+
| `PositiveFloat` | `> 0` |
|
|
55
|
+
| `NonNegativeInt` | `>= 0` |
|
|
56
|
+
| `NonNegativeFloat` | `>= 0` |
|
|
57
|
+
| `NegativeInt` | `< 0` |
|
|
58
|
+
| `NegativeFloat` | `< 0` |
|
|
59
|
+
| `NonPositiveInt` | `<= 0` |
|
|
60
|
+
| `NonPositiveFloat` | `<= 0` |
|
|
61
|
+
|
|
62
|
+
### String
|
|
63
|
+
|
|
64
|
+
- `HttpUrl` - must be a valid HTTP or HTTPS URL
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
The numeric and string types are just `Annotated` aliases over msgspec's native types (e.g. `Annotated[int, msgspec.Meta(gt=0)]`), so they need no hooks and work as drop-in replacements anywhere you'd use `int`, `float`, or `str`:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import msgspec
|
|
72
|
+
from msgspec_extras import PositiveFloat
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Product(msgspec.Struct):
|
|
76
|
+
name: str
|
|
77
|
+
price: PositiveFloat
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
product = msgspec.json.decode(b'{"name": "Widget", "price": 5.1}', type=Product)
|
|
81
|
+
msgspec.json.encode(product)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Socials
|
|
85
|
+
|
|
86
|
+
[](https://matrix.to/#/#The-Dev-Environment:matrix.org)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"FutureDate",
|
|
3
|
+
"FutureDatetime",
|
|
4
|
+
"HttpUrl",
|
|
5
|
+
"NegativeFloat",
|
|
6
|
+
"NegativeInt",
|
|
7
|
+
"NonNegativeFloat",
|
|
8
|
+
"NonNegativeInt",
|
|
9
|
+
"NonPositiveFloat",
|
|
10
|
+
"NonPositiveInt",
|
|
11
|
+
"PastDate",
|
|
12
|
+
"PastDatetime",
|
|
13
|
+
"PositiveFloat",
|
|
14
|
+
"PositiveInt",
|
|
15
|
+
"__version__",
|
|
16
|
+
"dec_hook",
|
|
17
|
+
"enc_hook",
|
|
18
|
+
]
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
from msgspec_extras._hooks import dec_hook, enc_hook
|
|
22
|
+
from msgspec_extras.datetime import FutureDate, FutureDatetime, PastDate, PastDatetime
|
|
23
|
+
from msgspec_extras.numeric import (
|
|
24
|
+
NegativeFloat,
|
|
25
|
+
NegativeInt,
|
|
26
|
+
NonNegativeFloat,
|
|
27
|
+
NonNegativeInt,
|
|
28
|
+
NonPositiveFloat,
|
|
29
|
+
NonPositiveInt,
|
|
30
|
+
PositiveFloat,
|
|
31
|
+
PositiveInt,
|
|
32
|
+
)
|
|
33
|
+
from msgspec_extras.string import HttpUrl
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__all__ = ["dec_hook", "enc_hook"]
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from msgspec_extras.datetime import FutureDate, FutureDatetime, PastDate, PastDatetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dec_hook(typ: type, obj: Any) -> Any: # noqa: ANN401
|
|
9
|
+
if typ in (PastDate, FutureDate, PastDatetime, FutureDatetime):
|
|
10
|
+
return typ(obj)
|
|
11
|
+
raise NotImplementedError(f"Type {typ.__name__} unsupported in dec_hook")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def enc_hook(obj: Any) -> Any: # noqa: ANN401
|
|
15
|
+
if isinstance(obj, (PastDate, FutureDate, PastDatetime, FutureDatetime)):
|
|
16
|
+
return obj.isoformat()
|
|
17
|
+
raise NotImplementedError(f"Encoding objects of type {type(obj).__name__} is unsupported")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
__all__ = ["FutureDate", "FutureDatetime", "PastDate", "PastDatetime"]
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
|
|
5
|
+
from msgspec import ValidationError
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from typing import Self # ty:ignore[unresolved-import]
|
|
9
|
+
except ImportError:
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PastDate(date):
|
|
14
|
+
__slots__ = ()
|
|
15
|
+
|
|
16
|
+
def __new__(cls, value: date | str) -> Self:
|
|
17
|
+
if isinstance(value, date):
|
|
18
|
+
tmp: date = super().__new__(cls, value.year, value.month, value.day)
|
|
19
|
+
elif isinstance(value, str):
|
|
20
|
+
try:
|
|
21
|
+
tmp: date = date.fromisoformat(value)
|
|
22
|
+
except ValueError as err:
|
|
23
|
+
raise ValueError(f"Invalid date format: {value}") from err
|
|
24
|
+
else:
|
|
25
|
+
raise TypeError(f"PastDate must be a date or string, got {type(value).__name__}")
|
|
26
|
+
if tmp < date.today():
|
|
27
|
+
return tmp
|
|
28
|
+
raise ValidationError(f"PastDate must be in the past, got {tmp}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FutureDate(date):
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
def __new__(cls, value: date | str) -> Self:
|
|
35
|
+
if isinstance(value, date):
|
|
36
|
+
tmp: date = super().__new__(cls, value.year, value.month, value.day)
|
|
37
|
+
elif isinstance(value, str):
|
|
38
|
+
try:
|
|
39
|
+
tmp: date = date.fromisoformat(value)
|
|
40
|
+
except ValueError as err:
|
|
41
|
+
raise ValueError(f"Invalid date format: {value}") from err
|
|
42
|
+
else:
|
|
43
|
+
raise TypeError(f"FutureDate must be a date or string, got {type(value).__name__}")
|
|
44
|
+
if tmp > date.today():
|
|
45
|
+
return tmp
|
|
46
|
+
raise ValidationError(f"FutureDate must be in the future, got {tmp}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PastDatetime(datetime):
|
|
50
|
+
__slots__ = ()
|
|
51
|
+
|
|
52
|
+
def __new__(cls, value: datetime | str) -> Self:
|
|
53
|
+
if isinstance(value, datetime):
|
|
54
|
+
tmp: datetime = super().__new__(
|
|
55
|
+
cls,
|
|
56
|
+
value.year,
|
|
57
|
+
value.month,
|
|
58
|
+
value.day,
|
|
59
|
+
value.hour,
|
|
60
|
+
value.minute,
|
|
61
|
+
value.second,
|
|
62
|
+
value.microsecond,
|
|
63
|
+
value.tzinfo,
|
|
64
|
+
)
|
|
65
|
+
elif isinstance(value, str):
|
|
66
|
+
try:
|
|
67
|
+
tmp: datetime = datetime.fromisoformat(value)
|
|
68
|
+
except ValueError as err:
|
|
69
|
+
raise ValueError(f"Invalid datetime format: {value}") from err
|
|
70
|
+
else:
|
|
71
|
+
raise TypeError(
|
|
72
|
+
f"PastDatetime must be a datetime or string, got {type(value).__name__}"
|
|
73
|
+
)
|
|
74
|
+
if tmp < datetime.now():
|
|
75
|
+
return tmp
|
|
76
|
+
raise ValidationError(f"PastDatetime must be in the past, got {tmp}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class FutureDatetime(datetime):
|
|
80
|
+
__slots__ = ()
|
|
81
|
+
|
|
82
|
+
def __new__(cls, value: datetime | str) -> Self:
|
|
83
|
+
if isinstance(value, datetime):
|
|
84
|
+
tmp: datetime = super().__new__(
|
|
85
|
+
cls,
|
|
86
|
+
value.year,
|
|
87
|
+
value.month,
|
|
88
|
+
value.day,
|
|
89
|
+
value.hour,
|
|
90
|
+
value.minute,
|
|
91
|
+
value.second,
|
|
92
|
+
value.microsecond,
|
|
93
|
+
value.tzinfo,
|
|
94
|
+
)
|
|
95
|
+
elif isinstance(value, str):
|
|
96
|
+
try:
|
|
97
|
+
tmp: datetime = datetime.fromisoformat(value)
|
|
98
|
+
except ValueError as err:
|
|
99
|
+
raise ValueError(f"Invalid datetime format: {value}") from err
|
|
100
|
+
else:
|
|
101
|
+
raise TypeError(
|
|
102
|
+
f"FutureDatetime must be a datetime or string, got {type(value).__name__}"
|
|
103
|
+
)
|
|
104
|
+
if tmp > datetime.now():
|
|
105
|
+
return tmp
|
|
106
|
+
raise ValidationError(f"FutureDatetime must be in the future, got {tmp}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"NegativeFloat",
|
|
3
|
+
"NegativeInt",
|
|
4
|
+
"NonNegativeFloat",
|
|
5
|
+
"NonNegativeInt",
|
|
6
|
+
"NonPositiveFloat",
|
|
7
|
+
"NonPositiveInt",
|
|
8
|
+
"PositiveFloat",
|
|
9
|
+
"PositiveInt",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
from typing import Annotated
|
|
13
|
+
|
|
14
|
+
from msgspec import Meta
|
|
15
|
+
|
|
16
|
+
PositiveInt = Annotated[int, Meta(gt=0, description="Integer greater than 0")]
|
|
17
|
+
NegativeInt = Annotated[int, Meta(lt=0, description="Integer less than 0")]
|
|
18
|
+
NonNegativeInt = Annotated[int, Meta(ge=0, description="Integer greater than or equal to 0")]
|
|
19
|
+
NonPositiveInt = Annotated[int, Meta(le=0, description="Integer less than or equal to 0")]
|
|
20
|
+
|
|
21
|
+
PositiveFloat = Annotated[float, Meta(gt=0.0, description="Float greater than 0.0")]
|
|
22
|
+
NegativeFloat = Annotated[float, Meta(lt=0.0, description="Float less than 0.0")]
|
|
23
|
+
NonNegativeFloat = Annotated[float, Meta(ge=0.0, description="Float greater than or equal to 0.0")]
|
|
24
|
+
NonPositiveFloat = Annotated[float, Meta(le=0.0, description="Float less than or equal to 0.0")]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[[repos]]
|
|
2
|
+
hooks = [
|
|
3
|
+
{args = ["--all", "--ignore-case", "--in-place", "--trailing-comma-inline-array"], entry = "toml-sort", exclude = ".*.lock|pylock.toml", id = "toml-sort", language = "system", name = "toml sort", types = ["toml"]},
|
|
4
|
+
{args = ["--number", "--wrap=keep"], entry = "mdformat", id = "mdformat", language = "system", name = "mdformat", types = ["markdown"]},
|
|
5
|
+
{entry = "ruff check .", id = "ruff-check", language = "system", name = "ruff check", pass_filenames = false, types = ["python"]},
|
|
6
|
+
{entry = "ruff format .", id = "ruff-format", language = "system", name = "ruff format", pass_filenames = false, types = ["python"]},
|
|
7
|
+
{entry = "ty check .", id = "ty", language = "system", name = "ty", pass_filenames = false, types = ["python"]},
|
|
8
|
+
]
|
|
9
|
+
repo = "local"
|
|
10
|
+
|
|
11
|
+
[[repos]]
|
|
12
|
+
hooks = [
|
|
13
|
+
{id = "check-ast"},
|
|
14
|
+
{id = "check-builtin-literals"},
|
|
15
|
+
{id = "check-docstring-first"},
|
|
16
|
+
{id = "debug-statements"},
|
|
17
|
+
{id = "forbid-submodules"},
|
|
18
|
+
]
|
|
19
|
+
repo = "https://github.com/pre-commit/pre-commit-hooks"
|
|
20
|
+
rev = "v6.0.0"
|
|
21
|
+
|
|
22
|
+
[[repos]]
|
|
23
|
+
hooks = [
|
|
24
|
+
{args = ["--allow-multiple-documents"], id = "check-yaml"},
|
|
25
|
+
{args = ["--assume-in-merge"], id = "check-merge-conflict"},
|
|
26
|
+
# {args = ["--autofix", "--indent=2", "--no-ensure-ascii"], id = "pretty-format-json"},
|
|
27
|
+
{args = ["--markdown-linebreak-ext=md"], id = "trailing-whitespace"},
|
|
28
|
+
{exclude_types = ["json", "svg", "xml"], id = "end-of-file-fixer"},
|
|
29
|
+
{id = "check-added-large-files"},
|
|
30
|
+
{id = "check-case-conflict"},
|
|
31
|
+
# {id = "check-executables-have-shebangs"},
|
|
32
|
+
# {id = "check-illegal-windows-names"},
|
|
33
|
+
# {id = "check-json"},
|
|
34
|
+
# {id = "check-json5"},
|
|
35
|
+
{id = "check-shebang-scripts-are-executable"},
|
|
36
|
+
# {id = "check-symlinks"},
|
|
37
|
+
{id = "check-toml"},
|
|
38
|
+
{id = "check-vcs-permalinks"},
|
|
39
|
+
# {id = "check-xml"},
|
|
40
|
+
{id = "destroyed-symlinks"},
|
|
41
|
+
{id = "detect-private-key"},
|
|
42
|
+
{id = "fix-byte-order-marker"},
|
|
43
|
+
{id = "mixed-line-ending"},
|
|
44
|
+
]
|
|
45
|
+
repo = "builtin"
|
|
46
|
+
|
|
47
|
+
[[repos]]
|
|
48
|
+
hooks = [
|
|
49
|
+
{id = "check-hooks-apply"},
|
|
50
|
+
{id = "check-useless-excludes"},
|
|
51
|
+
]
|
|
52
|
+
repo = "meta"
|