add calendar module, drop Quart-SQLAlchemy
This commit is contained in:
parent
589d4b3b13
commit
38ff59c76a
6 changed files with 118 additions and 16 deletions
|
|
@ -3,7 +3,8 @@
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
+ `sqlalchemy`: add `unbound_fk()`, `bound_fk()`
|
+ `sqlalchemy`: add `unbound_fk()`, `bound_fk()`
|
||||||
+ Add `timed_cache()`, `TimedDict()`
|
+ Add `timed_cache()`, `TimedDict()`, `age_and_days()`
|
||||||
|
+ Add date conversion utilities
|
||||||
+ Move obsolete stuff to `obsolete` package (includes configparse 0.3 as of now)
|
+ Move obsolete stuff to `obsolete` package (includes configparse 0.3 as of now)
|
||||||
+ Add more exceptions: `NotFoundError()`
|
+ Add more exceptions: `NotFoundError()`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ readme = "README.md"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itsdangerous",
|
"itsdangerous",
|
||||||
"toml",
|
"toml",
|
||||||
"pydantic"
|
"pydantic",
|
||||||
|
"uvloop; os_name=='posix'"
|
||||||
]
|
]
|
||||||
# - further devdependencies below - #
|
# - further devdependencies below - #
|
||||||
|
|
||||||
|
|
@ -43,16 +44,17 @@ flask_sqlalchemy = [
|
||||||
"Flask-SqlAlchemy",
|
"Flask-SqlAlchemy",
|
||||||
]
|
]
|
||||||
peewee = [
|
peewee = [
|
||||||
"peewee>=3.0.0, <4.0"
|
"peewee>=3.0.0"
|
||||||
]
|
]
|
||||||
markdown = [
|
markdown = [
|
||||||
"markdown>=3.0.0"
|
"markdown>=3.0.0"
|
||||||
]
|
]
|
||||||
quart = [
|
quart = [
|
||||||
"Flask>=2.0.0",
|
|
||||||
"Quart",
|
"Quart",
|
||||||
"Quart-Schema",
|
"Quart-Schema"
|
||||||
"uvloop; os_name=='posix'"
|
]
|
||||||
|
quart_sqlalchemy = [
|
||||||
|
"Quart_SQLALchemy>=3.0.0, <4.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
full = [
|
full = [
|
||||||
|
|
@ -60,7 +62,10 @@ full = [
|
||||||
"sakuragasaki46-suou[flask]",
|
"sakuragasaki46-suou[flask]",
|
||||||
"sakuragasaki46-suou[quart]",
|
"sakuragasaki46-suou[quart]",
|
||||||
"sakuragasaki46-suou[peewee]",
|
"sakuragasaki46-suou[peewee]",
|
||||||
"sakuragasaki46-suou[markdown]"
|
"sakuragasaki46-suou[markdown]",
|
||||||
|
"sakuragasaki46-suou[flask-sqlalchemy]"
|
||||||
|
# disabled: quart-sqlalchemy causes issues with anyone else. WON'T BE IMPLEMENTED
|
||||||
|
#"sakuragasaki46-suou[quart-sqlalchemy]"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|
||||||
from .iding import Siq, SiqCache, SiqType, SiqGen
|
from .iding import Siq, SiqCache, SiqType, SiqGen
|
||||||
from .codecs import (StringCase, cb32encode, cb32decode, b32lencode, b32ldecode, b64encode, b64decode, b2048encode, b2048decode,
|
from .codecs import (StringCase, cb32encode, cb32decode, b32lencode, b32ldecode, b64encode, b64decode, b2048encode, b2048decode,
|
||||||
jsonencode, want_bytes, want_str, ssv_list, want_urlsafe)
|
jsonencode, want_bytes, want_str, ssv_list, want_urlsafe, want_urlsafe_bytes)
|
||||||
from .bits import count_ones, mask_shift, split_bits, join_bits, mod_ceil, mod_floor
|
from .bits import count_ones, mask_shift, split_bits, join_bits, mod_ceil, mod_floor
|
||||||
|
from .calendar import want_datetime, want_isodate, want_timestamp, age_and_days
|
||||||
from .configparse import MissingConfigError, MissingConfigWarning, ConfigOptions, ConfigParserConfigSource, ConfigSource, DictConfigSource, ConfigValue, EnvConfigSource
|
from .configparse import MissingConfigError, MissingConfigWarning, ConfigOptions, ConfigParserConfigSource, ConfigSource, DictConfigSource, ConfigValue, EnvConfigSource
|
||||||
from .collections import TimedDict
|
from .collections import TimedDict
|
||||||
from .functools import deprecated, not_implemented, timed_cache
|
from .functools import deprecated, not_implemented, timed_cache
|
||||||
from .classtools import Wanted, Incomplete
|
from .classtools import Wanted, Incomplete
|
||||||
from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem
|
from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem, addattr
|
||||||
from .i18n import I18n, JsonI18n, TomlI18n
|
from .i18n import I18n, JsonI18n, TomlI18n
|
||||||
from .snowflake import Snowflake, SnowflakeGen
|
from .snowflake import Snowflake, SnowflakeGen
|
||||||
from .lex import symbol_table, lex, ilex
|
from .lex import symbol_table, lex, ilex
|
||||||
|
|
@ -37,10 +38,11 @@ __all__ = (
|
||||||
'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n',
|
'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n',
|
||||||
'MissingConfigError', 'MissingConfigWarning', 'PrefixIdentifier', 'Siq', 'SiqCache', 'SiqGen',
|
'MissingConfigError', 'MissingConfigWarning', 'PrefixIdentifier', 'Siq', 'SiqCache', 'SiqGen',
|
||||||
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TimedDict', 'TomlI18n', 'Wanted',
|
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TimedDict', 'TomlI18n', 'Wanted',
|
||||||
'additem', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode',
|
'addattr', 'additem', 'age_and_days', 'b2048decode', 'b2048encode',
|
||||||
'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'count_ones',
|
'b32ldecode', 'b32lencode', 'b64encode', 'b64decode', 'cb32encode',
|
||||||
'deprecated', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', 'lex',
|
'cb32decode', 'count_ones', 'deprecated', 'ilex', 'join_bits',
|
||||||
'ltuple', 'makelist', 'mask_shift', 'mod_ceil', 'mod_floor',
|
'jsonencode', 'kwargs_prefix', 'lex', 'ltuple', 'makelist', 'mask_shift',
|
||||||
'not_implemented', 'rtuple', 'split_bits', 'ssv_list', 'symbol_table',
|
'mod_ceil', 'mod_floor', 'not_implemented', 'rtuple', 'split_bits',
|
||||||
'timed_cache', 'want_bytes', 'want_str', 'want_urlsafe'
|
'ssv_list', 'symbol_table', 'timed_cache', 'want_bytes', 'want_datetime',
|
||||||
|
'want_isodate', 'want_str', 'want_timestamp', 'want_urlsafe', 'want_urlsafe_bytes'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
66
src/suou/calendar.py
Normal file
66
src/suou/calendar.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
"""
|
||||||
|
Calendar utilities (mainly Gregorian oof)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sakuragasaki46.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
See LICENSE for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
This software is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from suou.functools import not_implemented
|
||||||
|
|
||||||
|
|
||||||
|
def want_isodate(d: datetime.datetime | str | float | int, *, tz = None) -> str:
|
||||||
|
"""
|
||||||
|
Convert a date into ISO timestamp (e.g. 2020-01-01T02:03:04)
|
||||||
|
"""
|
||||||
|
if isinstance(d, (int, float)):
|
||||||
|
d = datetime.datetime.fromtimestamp(d, tz=tz)
|
||||||
|
if isinstance(d, str):
|
||||||
|
return d
|
||||||
|
return d.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def want_datetime(d: datetime.datetime | str | float | int, *, tz = None) -> datetime.datetime:
|
||||||
|
"""
|
||||||
|
Convert a date into Python datetime.datetime (e.g. datetime.datetime(2020, 1, 1, 2, 3, 4)).
|
||||||
|
|
||||||
|
If a string is passed, ISO format is assumed
|
||||||
|
"""
|
||||||
|
if isinstance(d, str):
|
||||||
|
d = datetime.datetime.fromisoformat(d)
|
||||||
|
elif isinstance(d, (int, float)):
|
||||||
|
d = datetime.datetime.fromtimestamp(d, tz=tz)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def want_timestamp(d: datetime.datetime | str | float | int, *, tz = None) -> float:
|
||||||
|
"""
|
||||||
|
Convert a date into UNIX timestamp (e.g. 1577840584.0). Returned as a float; decimals are milliseconds.
|
||||||
|
"""
|
||||||
|
if isinstance(d, str):
|
||||||
|
d = want_datetime(d, tz=tz)
|
||||||
|
if isinstance(d, (int, float)):
|
||||||
|
return d
|
||||||
|
return d.timestamp()
|
||||||
|
|
||||||
|
def age_and_days(date: datetime.datetime, now: datetime.datetime | None = None) -> tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Compute age / duration of a timespan in years and days.
|
||||||
|
"""
|
||||||
|
if now is None:
|
||||||
|
now = datetime.date.today()
|
||||||
|
y = now.year - date.year - ((now.month, now.day) < (date.month, date.day))
|
||||||
|
d = (now - datetime.date(date.year + y, date.month, date.day)).days
|
||||||
|
return y, d
|
||||||
|
|
||||||
|
__all__ = ('want_datetime', 'want_timestamp', 'want_isodate', 'age_and_days')
|
||||||
|
|
@ -57,6 +57,7 @@ def want_urlsafe(s: str | bytes) -> str:
|
||||||
Force a Base64 string into its urlsafe representation.
|
Force a Base64 string into its urlsafe representation.
|
||||||
|
|
||||||
Behavior is unchecked and undefined with anything else than Base64 strings.
|
Behavior is unchecked and undefined with anything else than Base64 strings.
|
||||||
|
In particular, this is NOT an URL encoder.
|
||||||
|
|
||||||
Used by b64encode() and b64decode().
|
Used by b64encode() and b64decode().
|
||||||
"""
|
"""
|
||||||
|
|
@ -328,5 +329,5 @@ class StringCase(enum.Enum):
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'cb32encode', 'cb32decode', 'b32lencode', 'b32ldecode', 'b64encode', 'b64decode', 'jsonencode'
|
'cb32encode', 'cb32decode', 'b32lencode', 'b32ldecode', 'b64encode', 'b64decode', 'jsonencode'
|
||||||
'StringCase', 'want_bytes', 'want_str', 'jsondecode', 'ssv_list'
|
'StringCase', 'want_bytes', 'want_str', 'jsondecode', 'ssv_list', 'want_urlsafe', 'want_urlsafe_bytes'
|
||||||
)
|
)
|
||||||
27
tests/test_calendar.py
Normal file
27
tests/test_calendar.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import timezone
|
||||||
|
import datetime
|
||||||
|
from suou.calendar import want_datetime, want_isodate
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestCalendar(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
...
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def test_want_isodate(self):
|
||||||
|
## if test fails, make sure time zone is set to UTC.
|
||||||
|
self.assertEqual(want_isodate(0, tz=timezone.utc), '1970-01-01T00:00:00+00:00')
|
||||||
|
self.assertEqual(want_isodate(86400, tz=timezone.utc), '1970-01-02T00:00:00+00:00')
|
||||||
|
self.assertEqual(want_isodate(1577840584.0, tz=timezone.utc), '2020-01-01T01:03:04+00:00')
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
def test_want_datetime(self):
|
||||||
|
self.assertEqual(want_datetime('2017-04-10T19:00:01', tz=timezone.utc) - want_datetime('2017-04-10T18:00:00', tz=timezone.utc), datetime.timedelta(seconds=3601))
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue