add bound_fk(), unbound_fk(), TimedDict()
This commit is contained in:
parent
303e9e2b2d
commit
002dbb0579
5 changed files with 148 additions and 4 deletions
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
## 0.5.0
|
||||
|
||||
+ Add `timed_cache()`
|
||||
+ `sqlalchemy`: add `unbound_fk()`, `bound_fk()`
|
||||
+ Add `timed_cache()`, `TimedDict()`
|
||||
+ Move obsolete stuff to `obsolete` package
|
||||
|
||||
## 0.4.0
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from .codecs import (StringCase, cb32encode, cb32decode, b32lencode, b32ldecode,
|
|||
jsonencode, want_bytes, want_str, ssv_list, want_urlsafe)
|
||||
from .bits import count_ones, mask_shift, split_bits, join_bits, mod_ceil, mod_floor
|
||||
from .configparse import MissingConfigError, MissingConfigWarning, ConfigOptions, ConfigParserConfigSource, ConfigSource, DictConfigSource, ConfigValue, EnvConfigSource
|
||||
from .collections import TimedDict
|
||||
from .functools import deprecated, not_implemented, timed_cache
|
||||
from .classtools import Wanted, Incomplete
|
||||
from .itertools import makelist, kwargs_prefix, ltuple, rtuple, additem
|
||||
|
|
@ -35,7 +36,7 @@ __all__ = (
|
|||
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
||||
'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n',
|
||||
'MissingConfigError', 'MissingConfigWarning', 'PrefixIdentifier', 'Siq', 'SiqCache', 'SiqGen',
|
||||
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TomlI18n', 'Wanted',
|
||||
'SiqType', 'Snowflake', 'SnowflakeGen', 'StringCase', 'TimedDict', 'TomlI18n', 'Wanted',
|
||||
'additem', 'b2048decode', 'b2048encode', 'b32ldecode', 'b32lencode',
|
||||
'b64encode', 'b64decode', 'cb32encode', 'cb32decode', 'count_ones',
|
||||
'deprecated', 'ilex', 'join_bits', 'jsonencode', 'kwargs_prefix', 'lex',
|
||||
|
|
|
|||
71
src/suou/collections.py
Normal file
71
src/suou/collections.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""
|
||||
Miscellaneous iterables
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import time
|
||||
from typing import TypeVar
|
||||
|
||||
|
||||
_KT = TypeVar('_KT')
|
||||
|
||||
class TimedDict(dict):
|
||||
_expires: dict[_KT, int]
|
||||
_ttl: int
|
||||
|
||||
def __init__(self, ttl: int, /, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._ttl = ttl
|
||||
self._expires = dict()
|
||||
|
||||
def check_ex(self, key):
|
||||
if super().__contains__(key):
|
||||
ex = self._expires[key]
|
||||
now = int(time.time())
|
||||
if ex < now:
|
||||
del self._expires[key]
|
||||
super().__delitem__(key)
|
||||
elif key in self._expires:
|
||||
del self._expires[key]
|
||||
|
||||
def __getitem__(self, key: _KT, /):
|
||||
self.check_ex(key)
|
||||
return super().__getitem__(key)
|
||||
|
||||
def get(self, key, default=None, /):
|
||||
self.check_ex(key)
|
||||
return super().get(key)
|
||||
|
||||
def __setitem__(self, key: _KT, value, /) -> None:
|
||||
self._expires = int(time.time() + self._ttl)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def setdefault(self, key, default, /):
|
||||
self.check_ex(key)
|
||||
self._expires = int(time.time() + self._ttl)
|
||||
return super().setdefault(key, default)
|
||||
|
||||
def __delitem__(self, key, /):
|
||||
del self._expires[key]
|
||||
super().__delitem__(key)
|
||||
|
||||
def __iter__(self):
|
||||
for k in super():
|
||||
self.check_ex(k)
|
||||
return super().__iter__()
|
||||
|
||||
__all__ = ('TimedDict',)
|
||||
31
src/suou/legal.py
Normal file
31
src/suou/legal.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""
|
||||
TOS / policy building blocks for the lazy.
|
||||
|
||||
XXX DANGER! This is not replacement for legal advice. Contact your lawyer.
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
INDEMNIFY = """
|
||||
You agree to indemnify and hold harmless {0} from any and all claims, damages, liabilities, costs and expenses, including reasonable and unreasonable counsel and attorney’s fees, arising out of any breach of this agreement.
|
||||
"""
|
||||
|
||||
NO_WARRANTY = """
|
||||
Except as represented in this agreement, the {0} is provided “AS IS”. Other than as provided in this agreement, {1} makes no other warranties, express or implied, and hereby disclaims all implied warranties, including any warranty of merchantability and warranty of fitness for a particular purpose.
|
||||
"""
|
||||
|
||||
GOVERNING_LAW = """
|
||||
These terms of services are governed by, and shall be interpreted in accordance with, the laws of {0}. You consent to the sole jurisdiction of {1} for all disputes between You and , and You consent to the sole application of {2} law for all such disputes.
|
||||
"""
|
||||
|
||||
|
|
@ -21,7 +21,8 @@ from functools import wraps
|
|||
from typing import Callable, Iterable, Never, TypeVar
|
||||
import warnings
|
||||
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Date, Dialect, ForeignKey, LargeBinary, Column, MetaData, SmallInteger, String, create_engine, select, text
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, declarative_base as _declarative_base, relationship
|
||||
from sqlalchemy.orm import DeclarativeBase, InstrumentedAttribute, Session, declarative_base as _declarative_base, relationship
|
||||
from sqlalchemy.types import TypeEngine
|
||||
|
||||
from .snowflake import SnowflakeGen
|
||||
from .itertools import kwargs_prefix, makelist
|
||||
|
|
@ -223,6 +224,44 @@ def parent_children(keyword: str, /, **kwargs):
|
|||
|
||||
return parent, child
|
||||
|
||||
|
||||
def unbound_fk(target: str | Column | InstrumentedAttribute, typ: TypeEngine | None = None, **kwargs):
|
||||
"""
|
||||
Shorthand for creating a "unbound" foreign key column from a column name, the referenced column.
|
||||
|
||||
"Unbound" foreign keys are nullable and set to null when referenced object is deleted.
|
||||
|
||||
If target is a string, make sure to pass the column type at typ= (default: IdType aka varbinary(16))!
|
||||
"""
|
||||
if isinstance(target, (Column, InstrumentedAttribute)):
|
||||
target_name = f'{target.table.name}.{target.name}'
|
||||
typ = target.type
|
||||
elif isinstance(target, str):
|
||||
target_name = target
|
||||
if typ is None:
|
||||
typ = IdType
|
||||
|
||||
return Column(typ, ForeignKey(target_name, ondelete='SET NULL'), nullable=True, **kwargs)
|
||||
|
||||
def bound_fk(target: str | Column | InstrumentedAttribute, typ: TypeEngine | None = None, **kwargs):
|
||||
"""
|
||||
Shorthand for creating a "bound" foreign key column from a column name, the referenced column.
|
||||
|
||||
"Bound" foreign keys are not nullable and cascade when referenced object is deleted. It means,
|
||||
parent deleted -> all children deleted.
|
||||
|
||||
If target is a string, make sure to pass the column type at typ= (default: IdType aka varbinary(16))!
|
||||
"""
|
||||
if isinstance(target, (Column, InstrumentedAttribute)):
|
||||
target_name = f'{target.table.name}.{target.name}'
|
||||
typ = target.type
|
||||
elif isinstance(target, str):
|
||||
target_name = target
|
||||
if typ is None:
|
||||
typ = IdType
|
||||
|
||||
return Column(typ, ForeignKey(target_name, ondelete='CASCADE'), nullable=False, **kwargs)
|
||||
|
||||
def want_column(cls: type[DeclarativeBase], col: Column[_T] | str) -> Column[_T]:
|
||||
"""
|
||||
Return a table's column given its name.
|
||||
|
|
@ -238,6 +277,7 @@ def want_column(cls: type[DeclarativeBase], col: Column[_T] | str) -> Column[_T]
|
|||
else:
|
||||
raise TypeError
|
||||
|
||||
## Utilities for use in web apps below
|
||||
|
||||
class AuthSrc(metaclass=ABCMeta):
|
||||
'''
|
||||
|
|
@ -308,5 +348,5 @@ def require_auth_base(cls: type[DeclarativeBase], *, src: AuthSrc, column: str |
|
|||
# Optional dependency: do not import into __init__.py
|
||||
__all__ = (
|
||||
'IdType', 'id_column', 'entity_base', 'declarative_base', 'token_signer', 'match_column', 'match_constraint',
|
||||
'author_pair', 'age_pair', 'require_auth_base', 'want_column'
|
||||
'author_pair', 'age_pair', 'require_auth_base', 'bound_fk', 'unbound_fk', 'want_column'
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue