libres 0.8.0__tar.gz → 0.9.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.
- {libres-0.8.0 → libres-0.9.0}/HISTORY.rst +15 -0
- {libres-0.8.0/src/libres.egg-info → libres-0.9.0}/PKG-INFO +20 -4
- {libres-0.8.0 → libres-0.9.0}/README.rst +2 -2
- {libres-0.8.0 → libres-0.9.0}/pyproject.toml +4 -6
- {libres-0.8.0 → libres-0.9.0}/src/libres/__init__.py +1 -1
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/core.py +5 -5
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/registry.py +1 -1
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/allocation.py +14 -5
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/reservation.py +3 -3
- libres-0.9.0/src/libres/db/models/types/json_type.py +46 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/scheduler.py +8 -4
- {libres-0.8.0 → libres-0.9.0/src/libres.egg-info}/PKG-INFO +20 -4
- {libres-0.8.0 → libres-0.9.0}/tests/test_scheduler.py +1 -1
- libres-0.8.0/src/libres/db/models/types/json_type.py +0 -51
- {libres-0.8.0 → libres-0.9.0}/LICENSE +0 -0
- {libres-0.8.0 → libres-0.9.0}/MANIFEST.in +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/Makefile +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/_static/custom.css +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/_static/favicon.ico +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/_static/logo.svg +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/api.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/concepts.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/conf.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/customizations.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/faq.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/index.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/requirements.txt +0 -0
- {libres-0.8.0 → libres-0.9.0}/docs/under_the_hood.rst +0 -0
- {libres-0.8.0 → libres-0.9.0}/setup.cfg +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/.gitignore +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/__init__.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/exposure.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/session.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/context/settings.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/__init__.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/__init__.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/base.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/other.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/reserved_slot.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/timestamp.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/types/__init__.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/types/utcdatetime.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/models/types/uuid_type.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/db/queries.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/modules/__init__.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/modules/errors.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/modules/events.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/modules/rasterizer.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/modules/utils.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres/py.typed +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres.egg-info/SOURCES.txt +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres.egg-info/dependency_links.txt +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres.egg-info/not-zip-safe +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres.egg-info/requires.txt +0 -0
- {libres-0.8.0 → libres-0.9.0}/src/libres.egg-info/top_level.txt +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_allocation.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_registry.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_reservation.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_reserved_slot.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_session.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_test.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_types.py +0 -0
- {libres-0.8.0 → libres-0.9.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
---------
|
|
3
3
|
|
|
4
|
+
0.9.0 (23.05.2025)
|
|
5
|
+
~~~~~~~~~~~~~~~~~~~
|
|
6
|
+
|
|
7
|
+
- Replaces `JSON` database type with `JSONB`, this means
|
|
8
|
+
Postgres as a backend is not required. You will also need
|
|
9
|
+
to write a migration for existing JSON columns. You may use
|
|
10
|
+
the following recipe using an alembic `Operations` object::
|
|
11
|
+
|
|
12
|
+
operations.alter_column(
|
|
13
|
+
'table_name',
|
|
14
|
+
'column_name',
|
|
15
|
+
type_=JSON,
|
|
16
|
+
postgresql_using='"column_name"::jsonb'
|
|
17
|
+
)
|
|
18
|
+
|
|
4
19
|
0.8.0 (15.01.2025)
|
|
5
20
|
~~~~~~~~~~~~~~~~~~~
|
|
6
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: libres
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: A library to reserve things
|
|
5
5
|
Home-page: http://github.com/seantis/libres/
|
|
6
6
|
Author: Denis Krienbühl
|
|
@@ -51,13 +51,14 @@ Requires-Dist: types-psycopg2; extra == "mypy"
|
|
|
51
51
|
Requires-Dist: types-python-dateutil; extra == "mypy"
|
|
52
52
|
Requires-Dist: types-pytz; extra == "mypy"
|
|
53
53
|
Requires-Dist: typing-extensions; extra == "mypy"
|
|
54
|
+
Dynamic: license-file
|
|
54
55
|
|
|
55
56
|
Libres
|
|
56
57
|
======
|
|
57
58
|
|
|
58
59
|
Libres is a reservations management library to reserve things like tables at
|
|
59
|
-
a restaurant or tickets at an event. It works with Python 3.
|
|
60
|
-
and requires Postgresql 9.
|
|
60
|
+
a restaurant or tickets at an event. It works with Python 3.9+
|
|
61
|
+
and requires Postgresql 9.2+.
|
|
61
62
|
|
|
62
63
|
`Documentation <http://libres.readthedocs.org/en/latest/>`_ | `Source <http://github.com/seantis/libres/>`_ | `Bugs <http://github.com/seantis/libres/issues>`_
|
|
63
64
|
|
|
@@ -141,6 +142,21 @@ After this, create a new release on Github.
|
|
|
141
142
|
Changelog
|
|
142
143
|
---------
|
|
143
144
|
|
|
145
|
+
0.9.0 (23.05.2025)
|
|
146
|
+
~~~~~~~~~~~~~~~~~~~
|
|
147
|
+
|
|
148
|
+
- Replaces `JSON` database type with `JSONB`, this means
|
|
149
|
+
Postgres as a backend is not required. You will also need
|
|
150
|
+
to write a migration for existing JSON columns. You may use
|
|
151
|
+
the following recipe using an alembic `Operations` object::
|
|
152
|
+
|
|
153
|
+
operations.alter_column(
|
|
154
|
+
'table_name',
|
|
155
|
+
'column_name',
|
|
156
|
+
type_=JSON,
|
|
157
|
+
postgresql_using='"column_name"::jsonb'
|
|
158
|
+
)
|
|
159
|
+
|
|
144
160
|
0.8.0 (15.01.2025)
|
|
145
161
|
~~~~~~~~~~~~~~~~~~~
|
|
146
162
|
|
|
@@ -2,8 +2,8 @@ Libres
|
|
|
2
2
|
======
|
|
3
3
|
|
|
4
4
|
Libres is a reservations management library to reserve things like tables at
|
|
5
|
-
a restaurant or tickets at an event. It works with Python 3.
|
|
6
|
-
and requires Postgresql 9.
|
|
5
|
+
a restaurant or tickets at an event. It works with Python 3.9+
|
|
6
|
+
and requires Postgresql 9.2+.
|
|
7
7
|
|
|
8
8
|
`Documentation <http://libres.readthedocs.org/en/latest/>`_ | `Source <http://github.com/seantis/libres/>`_ | `Bugs <http://github.com/seantis/libres/issues>`_
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ branch = true
|
|
|
11
11
|
source = ["src"]
|
|
12
12
|
|
|
13
13
|
[tool.bumpversion]
|
|
14
|
-
current_version = "0.
|
|
14
|
+
current_version = "0.9.0"
|
|
15
15
|
commit = true
|
|
16
16
|
message = "Release {new_version}"
|
|
17
17
|
tag = true
|
|
@@ -36,14 +36,12 @@ replace = """
|
|
|
36
36
|
|
|
37
37
|
[tool.mypy]
|
|
38
38
|
python_version = "3.9"
|
|
39
|
-
follow_imports = "silent"
|
|
40
39
|
namespace_packages = true
|
|
41
40
|
explicit_package_bases = true
|
|
42
|
-
|
|
43
|
-
warn_redundant_casts = true
|
|
41
|
+
strict = true
|
|
44
42
|
warn_unreachable = true
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
# FIXME: remove this exclusion when upgrading to SQlAlchemy 2.0
|
|
44
|
+
untyped_calls_exclude = "sqlalchemy"
|
|
47
45
|
plugins = "sqlmypy"
|
|
48
46
|
mypy_path = "$MYPY_CONFIG_FILE_DIR/src"
|
|
49
47
|
|
|
@@ -61,15 +61,15 @@ class ContextServicesMixin:
|
|
|
61
61
|
|
|
62
62
|
@cached_property
|
|
63
63
|
def is_allocation_exposed(self) -> Callable[[Allocation], bool]:
|
|
64
|
-
return self.context.get_service('exposure').is_allocation_exposed
|
|
64
|
+
return self.context.get_service('exposure').is_allocation_exposed # type: ignore[no-any-return]
|
|
65
65
|
|
|
66
66
|
@cached_property
|
|
67
67
|
def generate_uuid(self) -> Callable[[str], UUID]:
|
|
68
|
-
return self.context.get_service('uuid_generator')
|
|
68
|
+
return self.context.get_service('uuid_generator') # type: ignore[no-any-return]
|
|
69
69
|
|
|
70
70
|
@cached_property
|
|
71
71
|
def validate_email(self) -> Callable[[str], bool]:
|
|
72
|
-
return self.context.get_service('email_validator')
|
|
72
|
+
return self.context.get_service('email_validator') # type: ignore[no-any-return]
|
|
73
73
|
|
|
74
74
|
def clear_cache(self) -> None:
|
|
75
75
|
""" Clears the cache of the mixin. """
|
|
@@ -91,12 +91,12 @@ class ContextServicesMixin:
|
|
|
91
91
|
|
|
92
92
|
@property
|
|
93
93
|
def session_provider(self) -> SessionProvider:
|
|
94
|
-
return self.context.get_service('session_provider')
|
|
94
|
+
return self.context.get_service('session_provider') # type: ignore[no-any-return]
|
|
95
95
|
|
|
96
96
|
@property
|
|
97
97
|
def session(self) -> Session:
|
|
98
98
|
""" Returns the current session. """
|
|
99
|
-
return self.session_provider.session()
|
|
99
|
+
return self.session_provider.session() # type: ignore[no-any-return]
|
|
100
100
|
|
|
101
101
|
def close(self) -> None:
|
|
102
102
|
""" Closes the current session. """
|
|
@@ -98,7 +98,7 @@ class Registry:
|
|
|
98
98
|
if not hasattr(self.local, 'current_context'):
|
|
99
99
|
self.local.current_context = self.master_context
|
|
100
100
|
|
|
101
|
-
return self.local.current_context
|
|
101
|
+
return self.local.current_context # type: ignore[no-any-return]
|
|
102
102
|
|
|
103
103
|
def is_existing_context(self, name: str) -> bool:
|
|
104
104
|
return name in self.contexts
|
|
@@ -34,14 +34,18 @@ if TYPE_CHECKING:
|
|
|
34
34
|
from collections.abc import Iterator
|
|
35
35
|
from sedate.types import TzInfoOrName
|
|
36
36
|
from sqlalchemy.orm import Query
|
|
37
|
+
from typing import NamedTuple
|
|
37
38
|
from typing_extensions import Self
|
|
38
39
|
|
|
39
|
-
from libres.db.models import
|
|
40
|
+
from libres.db.models import ReservedSlot
|
|
40
41
|
from libres.modules.rasterizer import Raster
|
|
41
42
|
|
|
42
43
|
_OptDT1 = TypeVar('_OptDT1', 'datetime | None', datetime, None)
|
|
43
44
|
_OptDT2 = TypeVar('_OptDT2', 'datetime | None', datetime, None)
|
|
44
45
|
|
|
46
|
+
class _ReservationIdRow(NamedTuple):
|
|
47
|
+
id: int
|
|
48
|
+
|
|
45
49
|
|
|
46
50
|
class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
47
51
|
"""Describes a timespan within which one or many timeslots can be
|
|
@@ -114,7 +118,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
114
118
|
timezone: Column[str | None] = Column(types.String())
|
|
115
119
|
|
|
116
120
|
#: Custom data reserved for the user
|
|
117
|
-
data: Column[Any | None] = Column(
|
|
121
|
+
data: Column[dict[str, Any] | None] = Column(
|
|
118
122
|
JSON(),
|
|
119
123
|
nullable=True
|
|
120
124
|
)
|
|
@@ -470,7 +474,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
470
474
|
return self.display_start(timezone), self.display_end(timezone)
|
|
471
475
|
|
|
472
476
|
@property
|
|
473
|
-
def pending_reservations(self) -> Query[
|
|
477
|
+
def pending_reservations(self) -> Query[_ReservationIdRow]:
|
|
474
478
|
""" Returns the pending reservations query for this allocation.
|
|
475
479
|
As the pending reservations target the group and not a specific
|
|
476
480
|
allocation this function returns the same value for masters and
|
|
@@ -482,6 +486,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
482
486
|
)
|
|
483
487
|
|
|
484
488
|
Reservation = self.models.Reservation # noqa: N806
|
|
489
|
+
query: Query[_ReservationIdRow]
|
|
485
490
|
query = object_session(self).query(Reservation.id)
|
|
486
491
|
query = query.filter(Reservation.target == self.group)
|
|
487
492
|
query = query.filter(Reservation.status == 'pending')
|
|
@@ -794,7 +799,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
794
799
|
if self.is_master:
|
|
795
800
|
return self
|
|
796
801
|
else:
|
|
797
|
-
query
|
|
802
|
+
# FIXME: This should either query `self.__class__` or
|
|
803
|
+
# we need to return `Allocation` rather than `Self`
|
|
804
|
+
query: Query[Self] = object_session(self).query(Allocation)
|
|
798
805
|
query = query.filter(Allocation._start == self._start)
|
|
799
806
|
query = query.filter(Allocation.resource == self.mirror_of)
|
|
800
807
|
|
|
@@ -818,7 +825,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
818
825
|
assert self.is_master
|
|
819
826
|
return [self]
|
|
820
827
|
|
|
821
|
-
query
|
|
828
|
+
# FIXME: This should either query `self.__class__` or
|
|
829
|
+
# we need to return `Allocation` rather than `Self`
|
|
830
|
+
query: Query[Self] = object_session(self).query(Allocation)
|
|
822
831
|
query = query.filter(Allocation.mirror_of == self.mirror_of)
|
|
823
832
|
query = query.filter(Allocation._start == self._start)
|
|
824
833
|
|
|
@@ -96,7 +96,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
|
|
|
96
96
|
nullable=False
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
-
data: Column[Any | None] = deferred(
|
|
99
|
+
data: Column[dict[str, Any] | None] = deferred(
|
|
100
100
|
Column(
|
|
101
101
|
JSON(),
|
|
102
102
|
nullable=True
|
|
@@ -148,7 +148,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
|
|
|
148
148
|
# order by date
|
|
149
149
|
query = query.order_by(Allocation._start)
|
|
150
150
|
|
|
151
|
-
return query
|
|
151
|
+
return query # type: ignore[no-any-return]
|
|
152
152
|
|
|
153
153
|
def display_start(
|
|
154
154
|
self,
|
|
@@ -212,4 +212,4 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
|
|
|
212
212
|
# A reservation is deemed autoapprovable if no allocation
|
|
213
213
|
# requires explicit approval
|
|
214
214
|
|
|
215
|
-
return object_session(self).query(~query.exists()).scalar()
|
|
215
|
+
return object_session(self).query(~query.exists()).scalar() # type: ignore[no-any-return]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy.ext.mutable import MutableDict
|
|
4
|
+
from sqlalchemy.types import TypeDecorator
|
|
5
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from sqlalchemy.engine import Dialect
|
|
12
|
+
|
|
13
|
+
_Base = TypeDecorator[dict[str, Any]]
|
|
14
|
+
else:
|
|
15
|
+
_Base = TypeDecorator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JSON(_Base):
|
|
19
|
+
""" A JSONB based type that coerces None's to empty dictionaries.
|
|
20
|
+
|
|
21
|
+
That is, this JSONB column cannot be `'null'::jsonb`. It could
|
|
22
|
+
still be `NULL` though, if it's nullable and never explicitly
|
|
23
|
+
set. But on the Python end you should always see a dictionary.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
impl = JSONB
|
|
28
|
+
|
|
29
|
+
def process_bind_param( # type:ignore[override]
|
|
30
|
+
self,
|
|
31
|
+
value: dict[str, Any] | None,
|
|
32
|
+
dialect: Dialect
|
|
33
|
+
) -> dict[str, Any]:
|
|
34
|
+
|
|
35
|
+
return {} if value is None else value
|
|
36
|
+
|
|
37
|
+
def process_result_value(
|
|
38
|
+
self,
|
|
39
|
+
value: dict[str, Any] | None,
|
|
40
|
+
dialect: Dialect
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
|
|
43
|
+
return {} if value is None else value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
MutableDict.associate_with(JSON) # type:ignore[no-untyped-call]
|
|
@@ -306,7 +306,10 @@ class Scheduler(ContextServicesMixin):
|
|
|
306
306
|
) -> list[tuple[datetime, datetime]]:
|
|
307
307
|
|
|
308
308
|
query = self.allocations_by_group(group)
|
|
309
|
-
dates_query = query.with_entities(
|
|
309
|
+
dates_query: Query[tuple[datetime, datetime]] = query.with_entities(
|
|
310
|
+
Allocation._start,
|
|
311
|
+
Allocation._end
|
|
312
|
+
)
|
|
310
313
|
return dates_query.all()
|
|
311
314
|
|
|
312
315
|
def allocation_mirrors_by_master(
|
|
@@ -336,7 +339,7 @@ class Scheduler(ContextServicesMixin):
|
|
|
336
339
|
query = self.allocations_by_ids(ids)
|
|
337
340
|
query = query.filter(Allocation.approve_manually == True)
|
|
338
341
|
|
|
339
|
-
return self.session.query(query.exists()).scalar()
|
|
342
|
+
return self.session.query(query.exists()).scalar() # type: ignore[no-any-return]
|
|
340
343
|
|
|
341
344
|
def allocate(
|
|
342
345
|
self,
|
|
@@ -1273,8 +1276,9 @@ class Scheduler(ContextServicesMixin):
|
|
|
1273
1276
|
# reservation twice on a single session
|
|
1274
1277
|
if session_id:
|
|
1275
1278
|
found = self.queries.reservations_by_session(session_id)
|
|
1276
|
-
|
|
1277
|
-
|
|
1279
|
+
found_set: set[tuple[UUID, datetime | None]] = set(
|
|
1280
|
+
found.with_entities(Reservation.target, Reservation.start)
|
|
1281
|
+
)
|
|
1278
1282
|
|
|
1279
1283
|
for reservation in reservations:
|
|
1280
1284
|
if (reservation.target, reservation.start) in found_set:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: libres
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: A library to reserve things
|
|
5
5
|
Home-page: http://github.com/seantis/libres/
|
|
6
6
|
Author: Denis Krienbühl
|
|
@@ -51,13 +51,14 @@ Requires-Dist: types-psycopg2; extra == "mypy"
|
|
|
51
51
|
Requires-Dist: types-python-dateutil; extra == "mypy"
|
|
52
52
|
Requires-Dist: types-pytz; extra == "mypy"
|
|
53
53
|
Requires-Dist: typing-extensions; extra == "mypy"
|
|
54
|
+
Dynamic: license-file
|
|
54
55
|
|
|
55
56
|
Libres
|
|
56
57
|
======
|
|
57
58
|
|
|
58
59
|
Libres is a reservations management library to reserve things like tables at
|
|
59
|
-
a restaurant or tickets at an event. It works with Python 3.
|
|
60
|
-
and requires Postgresql 9.
|
|
60
|
+
a restaurant or tickets at an event. It works with Python 3.9+
|
|
61
|
+
and requires Postgresql 9.2+.
|
|
61
62
|
|
|
62
63
|
`Documentation <http://libres.readthedocs.org/en/latest/>`_ | `Source <http://github.com/seantis/libres/>`_ | `Bugs <http://github.com/seantis/libres/issues>`_
|
|
63
64
|
|
|
@@ -141,6 +142,21 @@ After this, create a new release on Github.
|
|
|
141
142
|
Changelog
|
|
142
143
|
---------
|
|
143
144
|
|
|
145
|
+
0.9.0 (23.05.2025)
|
|
146
|
+
~~~~~~~~~~~~~~~~~~~
|
|
147
|
+
|
|
148
|
+
- Replaces `JSON` database type with `JSONB`, this means
|
|
149
|
+
Postgres as a backend is not required. You will also need
|
|
150
|
+
to write a migration for existing JSON columns. You may use
|
|
151
|
+
the following recipe using an alembic `Operations` object::
|
|
152
|
+
|
|
153
|
+
operations.alter_column(
|
|
154
|
+
'table_name',
|
|
155
|
+
'column_name',
|
|
156
|
+
type_=JSON,
|
|
157
|
+
postgresql_using='"column_name"::jsonb'
|
|
158
|
+
)
|
|
159
|
+
|
|
144
160
|
0.8.0 (15.01.2025)
|
|
145
161
|
~~~~~~~~~~~~~~~~~~~
|
|
146
162
|
|
|
@@ -1821,7 +1821,7 @@ def test_data_coding(scheduler):
|
|
|
1821
1821
|
scheduler.allocate((start, end), data=None)
|
|
1822
1822
|
scheduler.commit()
|
|
1823
1823
|
|
|
1824
|
-
assert scheduler.managed_allocations().first().data
|
|
1824
|
+
assert scheduler.managed_allocations().first().data == {}
|
|
1825
1825
|
|
|
1826
1826
|
|
|
1827
1827
|
def test_no_reservations_to_confirm(scheduler):
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from json import loads, dumps
|
|
4
|
-
from sqlalchemy.types import TypeDecorator, TEXT
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from typing import Any
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from sqlalchemy.engine import Dialect
|
|
11
|
-
|
|
12
|
-
_Base = TypeDecorator[Any]
|
|
13
|
-
else:
|
|
14
|
-
_Base = TypeDecorator
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class JSON(_Base):
|
|
18
|
-
"""Like the default JSON, but using the json serializer from the dialect
|
|
19
|
-
(postgres) each time the value is read, even if it never left the ORM. The
|
|
20
|
-
default json type will only do it when the record is read from the
|
|
21
|
-
database.
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
# Use TEXT for now to stay compatible with Postgres 9.1. In the future
|
|
26
|
-
# this will be replaced by JSON (or JSONB) though that requires that we
|
|
27
|
-
# require a later Postgres release. For now we stay backwards compatible
|
|
28
|
-
# with a version that's still widely used (9.1).
|
|
29
|
-
impl = TEXT
|
|
30
|
-
|
|
31
|
-
def process_bind_param(
|
|
32
|
-
self,
|
|
33
|
-
value: Any,
|
|
34
|
-
dialect: Dialect
|
|
35
|
-
) -> str | None:
|
|
36
|
-
|
|
37
|
-
if value is not None:
|
|
38
|
-
value = (dialect._json_serializer or dumps)(value) # type:ignore
|
|
39
|
-
|
|
40
|
-
return value
|
|
41
|
-
|
|
42
|
-
def process_result_value(
|
|
43
|
-
self,
|
|
44
|
-
value: str | None,
|
|
45
|
-
dialect: Dialect
|
|
46
|
-
) -> Any | None:
|
|
47
|
-
|
|
48
|
-
if value is not None:
|
|
49
|
-
value = (dialect._json_deserializer or loads)(value) # type:ignore
|
|
50
|
-
|
|
51
|
-
return value
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|