moat-db-box 0.0.1__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.
- moat_db_box-0.0.1/LICENSE.txt +14 -0
- moat_db_box-0.0.1/PKG-INFO +42 -0
- moat_db_box-0.0.1/README.md +18 -0
- moat_db_box-0.0.1/pyproject.toml +50 -0
- moat_db_box-0.0.1/setup.cfg +4 -0
- moat_db_box-0.0.1/src/moat/db/box/__init__.py +3 -0
- moat_db_box-0.0.1/src/moat/db/box/_main.py +228 -0
- moat_db_box-0.0.1/src/moat/db/box/model.py +152 -0
- moat_db_box-0.0.1/src/moat/db/box/model_.py +67 -0
- moat_db_box-0.0.1/src/moat_db_box.egg-info/PKG-INFO +42 -0
- moat_db_box-0.0.1/src/moat_db_box.egg-info/SOURCES.txt +12 -0
- moat_db_box-0.0.1/src/moat_db_box.egg-info/dependency_links.txt +1 -0
- moat_db_box-0.0.1/src/moat_db_box.egg-info/requires.txt +33 -0
- moat_db_box-0.0.1/src/moat_db_box.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
The code in this repository, and all MoaT submodules it refers to,
|
|
2
|
+
is part of the MoaT project.
|
|
3
|
+
|
|
4
|
+
Unless a submodule's LICENSE.txt states otherwise, all included files are
|
|
5
|
+
licensed under the LGPL V3, as published by the FSF at
|
|
6
|
+
https://www.gnu.org/licenses/lgpl-3.0.html .
|
|
7
|
+
|
|
8
|
+
In addition to the LGPL's terms, the author(s) respectfully ask all users of
|
|
9
|
+
this code to contribute any bug fixes or enhancements. Also, please link back to
|
|
10
|
+
https://M-o-a-T.org.
|
|
11
|
+
|
|
12
|
+
Thank you.
|
|
13
|
+
|
|
14
|
+
Copyright © 2021 ff.: the MoaT contributor(s), as per the git changelog(s).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-db-box
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: MoaT support for managing containers
|
|
5
|
+
Author-email: Matthias Urlichs <matthias@urlichs.de>
|
|
6
|
+
Project-URL: homepage, https://m-o-a-t.org
|
|
7
|
+
Project-URL: repository, https://github.com/M-o-a-T/moat
|
|
8
|
+
Keywords: MoaT
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: Trio
|
|
13
|
+
Classifier: Framework :: AnyIO
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: moat-util~=0.60.9
|
|
19
|
+
Requires-Dist: moat-db~=0.2.3
|
|
20
|
+
Requires-Dist: moat-label~=0.2.2
|
|
21
|
+
Provides-Extra: event
|
|
22
|
+
Requires-Dist: attr; extra == "event"
|
|
23
|
+
Requires-Dist: outcome; extra == "event"
|
|
24
|
+
Provides-Extra: main
|
|
25
|
+
Requires-Dist: asyncclick; extra == "main"
|
|
26
|
+
Requires-Dist: getpass; extra == "main"
|
|
27
|
+
Provides-Extra: msg
|
|
28
|
+
Requires-Dist: msgpack; extra == "msg"
|
|
29
|
+
Provides-Extra: path
|
|
30
|
+
Requires-Dist: simpleeval; extra == "path"
|
|
31
|
+
Provides-Extra: queue
|
|
32
|
+
Requires-Dist: outcome; extra == "queue"
|
|
33
|
+
Provides-Extra: systemd
|
|
34
|
+
Requires-Dist: systemd.daemon; extra == "systemd"
|
|
35
|
+
Provides-Extra: yaml
|
|
36
|
+
Requires-Dist: ruyaml; extra == "yaml"
|
|
37
|
+
Provides-Extra: time
|
|
38
|
+
Requires-Dist: calendar; extra == "time"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: flake8; extra == "dev"
|
|
41
|
+
Requires-Dist: isort; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# MoaT-Box
|
|
2
|
+
|
|
3
|
+
% start synopsis
|
|
4
|
+
|
|
5
|
+
This module manages boxes, i.e. things you can put other things into.
|
|
6
|
+
|
|
7
|
+
"Box" is meant to be inclusive. A room is a box. So is a cupboard, or a row
|
|
8
|
+
of books. Some boxes can be nested, with locations. Some boxes can be moved
|
|
9
|
+
(e.g. a bookcase or a drawer), some can't (the compartments within a drawer)
|
|
10
|
+
|
|
11
|
+
Boxes are typed. You can put a cupboard into a room but not vice versa.
|
|
12
|
+
|
|
13
|
+
Boxes are labelled. A box can have an arbitrary number of labels, though
|
|
14
|
+
most of the time it'll have one, or at most two: one with a barcode and a
|
|
15
|
+
box number, and one with a text that describes the content – or at least
|
|
16
|
+
reminds the owner of it.
|
|
17
|
+
|
|
18
|
+
% end synopsis
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [ "setuptools", "wheel",]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
classifiers = [
|
|
7
|
+
"Intended Audience :: Developers",
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"Framework :: AsyncIO",
|
|
10
|
+
"Framework :: Trio",
|
|
11
|
+
"Framework :: AnyIO",
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"moat-util ~= 0.60.9",
|
|
16
|
+
"moat-db ~= 0.2.3",
|
|
17
|
+
"moat-label ~= 0.2.2",
|
|
18
|
+
]
|
|
19
|
+
version = "0.0.1"
|
|
20
|
+
keywords = [ "MoaT",]
|
|
21
|
+
requires-python = ">=3.11"
|
|
22
|
+
name = "moat-db-box"
|
|
23
|
+
description = "MoaT support for managing containers"
|
|
24
|
+
readme = "README.rst"
|
|
25
|
+
[[project.authors]]
|
|
26
|
+
name = "Matthias Urlichs"
|
|
27
|
+
email = "matthias@urlichs.de"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
homepage = "https://m-o-a-t.org"
|
|
32
|
+
repository = "https://github.com/M-o-a-T/moat"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
event = [ "attr", "outcome",]
|
|
36
|
+
main = [ "asyncclick", "getpass",]
|
|
37
|
+
msg = [ "msgpack",]
|
|
38
|
+
path = [ "simpleeval",]
|
|
39
|
+
queue = [ "outcome",]
|
|
40
|
+
systemd = [ "systemd.daemon",]
|
|
41
|
+
yaml = [ "ruyaml",]
|
|
42
|
+
time = [ "calendar",]
|
|
43
|
+
dev = [ "flake8", "isort",]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools]
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
"*" = ["*.yaml"]
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Support for boxes for storing things.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# The main code must not load any sqlalchemy code.
|
|
6
|
+
# sqlalchemy might not be present.
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import asyncclick as click
|
|
13
|
+
from sqlalchemy import select
|
|
14
|
+
|
|
15
|
+
from moat.util import load_subgroup, option_ng, yprint
|
|
16
|
+
from moat.db import database
|
|
17
|
+
|
|
18
|
+
from .model import Box, BoxTyp
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@load_subgroup(prefix="moat.box", invoke_without_command=False)
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def cli(ctx):
|
|
24
|
+
"""Boxes for storing things."""
|
|
25
|
+
obj = ctx.obj
|
|
26
|
+
|
|
27
|
+
sess = ctx.with_resource(database(obj.cfg.db))
|
|
28
|
+
ctx.with_resource(sess.begin())
|
|
29
|
+
|
|
30
|
+
obj.session = sess
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@cli.group
|
|
34
|
+
@click.option("--name", "-n", type=str, help="Text on the label")
|
|
35
|
+
@click.pass_obj
|
|
36
|
+
def one(obj, name):
|
|
37
|
+
"""
|
|
38
|
+
Manage boxes.
|
|
39
|
+
"""
|
|
40
|
+
obj.name = name
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@one.command(name="show")
|
|
45
|
+
@click.pass_obj
|
|
46
|
+
def show_(obj):
|
|
47
|
+
"""
|
|
48
|
+
Show box details / list all box types.
|
|
49
|
+
|
|
50
|
+
If a name is set, show details for this box.
|
|
51
|
+
Otherwise list all boxes without parents.
|
|
52
|
+
"""
|
|
53
|
+
sess = obj.session
|
|
54
|
+
|
|
55
|
+
if obj.name is None:
|
|
56
|
+
seen = False
|
|
57
|
+
with sess.execute(select(Box).where(Box.container == None)) as boxes: # noqa:E711
|
|
58
|
+
for (box,) in boxes:
|
|
59
|
+
seen = True
|
|
60
|
+
print(box.name)
|
|
61
|
+
if not seen:
|
|
62
|
+
print("No box types defined yet. Use '--help'?", file=sys.stderr)
|
|
63
|
+
else:
|
|
64
|
+
box = sess.one(Box, name=obj.name)
|
|
65
|
+
yprint(box.dump())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def opts(c):
|
|
69
|
+
c = option_ng("--name", "-n", type=str, help="Rename this box")(c)
|
|
70
|
+
c = option_ng("--x", "-x", "pos_x", type=int, help="X position in parent")(c)
|
|
71
|
+
c = option_ng("--y", "-y", "pos_y", type=int, help="Y position in parent")(c)
|
|
72
|
+
c = option_ng("--z", "-z", "pos_z", type=int, help="Z position in parent")(c)
|
|
73
|
+
c = option_ng("--in", "-i", "container", type=str, help="Where is it?")(c)
|
|
74
|
+
c = option_ng("--typ", "-t", "boxtyp", type=str, help="Type of the box")(c)
|
|
75
|
+
return c
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@one.command()
|
|
79
|
+
@opts
|
|
80
|
+
@click.pass_obj
|
|
81
|
+
def add(obj, **kw):
|
|
82
|
+
"""
|
|
83
|
+
Add a box.
|
|
84
|
+
"""
|
|
85
|
+
if obj.name is None:
|
|
86
|
+
raise click.UsageError("The box needs a name!")
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
box = obj.session.one(Box, name=obj.name)
|
|
90
|
+
except KeyError:
|
|
91
|
+
pass
|
|
92
|
+
else:
|
|
93
|
+
print("This box already exists", file=sys.stderr)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
box = Box(name=obj.name)
|
|
96
|
+
obj.session.add(box)
|
|
97
|
+
box.apply(**kw)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@one.command(epilog="Use '-in -' to drop the parent box, -x/-y/-z 0 to clear a position.")
|
|
101
|
+
@opts
|
|
102
|
+
@click.pass_obj
|
|
103
|
+
def set(obj, **kw): # noqa:A001
|
|
104
|
+
"""
|
|
105
|
+
Modify a box.
|
|
106
|
+
"""
|
|
107
|
+
if obj.name is None:
|
|
108
|
+
raise click.UsageError("Which box? Use a name")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
box = obj.session.one(Box, name=obj.name)
|
|
112
|
+
except KeyError:
|
|
113
|
+
print("This box doesn't exist", file=sys.stderr)
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
box.apply(**kw)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@one.command()
|
|
119
|
+
@click.pass_obj
|
|
120
|
+
def delete(obj):
|
|
121
|
+
"""
|
|
122
|
+
Remove a box.
|
|
123
|
+
"""
|
|
124
|
+
if obj.name is None:
|
|
125
|
+
raise click.UsageError("Which box? Use a name")
|
|
126
|
+
try:
|
|
127
|
+
box = obj.session.one(Box, name=obj.name)
|
|
128
|
+
except KeyError:
|
|
129
|
+
print("This box doesn't exist", file=sys.stderr)
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
obj.session.delete(box)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@cli.group(name="typ")
|
|
135
|
+
@click.option("--name", "-n", type=str, help="Name of the box type")
|
|
136
|
+
@click.pass_context
|
|
137
|
+
def typ_(ctx, name):
|
|
138
|
+
"""\
|
|
139
|
+
Manage box types.
|
|
140
|
+
"""
|
|
141
|
+
obj = ctx.obj
|
|
142
|
+
obj.name = name
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@typ_.command(name="show")
|
|
146
|
+
@click.pass_obj
|
|
147
|
+
def typ_show(obj):
|
|
148
|
+
"""
|
|
149
|
+
Show box details / list all box types.
|
|
150
|
+
|
|
151
|
+
If a name is set, show details for this type.
|
|
152
|
+
Otherwise list all known box types.
|
|
153
|
+
"""
|
|
154
|
+
sess = obj.session
|
|
155
|
+
|
|
156
|
+
if obj.name is None:
|
|
157
|
+
seen = False
|
|
158
|
+
with sess.execute(select(BoxTyp)) as boxes:
|
|
159
|
+
for (box,) in boxes:
|
|
160
|
+
seen = True
|
|
161
|
+
print(box.name)
|
|
162
|
+
if not seen:
|
|
163
|
+
print("No box types defined yet. Use '--help'?", file=sys.stderr)
|
|
164
|
+
else:
|
|
165
|
+
box = sess.one(BoxTyp, name=obj.name)
|
|
166
|
+
yprint(box.dump())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def typopts(c):
|
|
170
|
+
c = option_ng("--name", "-n", type=str, help="Rename this type")(c)
|
|
171
|
+
c = option_ng("--x", "-x", "pos_x", type=int, help="Number of X positions for content")(c)
|
|
172
|
+
c = option_ng("--y", "-y", "pos_y", type=int, help="Number of Y positions for content")(c)
|
|
173
|
+
c = option_ng("--z", "-z", "pos_z", type=int, help="Number of Z positions for content")(c)
|
|
174
|
+
c = option_ng("--comment", "-c", "comment", type=str, help="Description of this type")(c)
|
|
175
|
+
c = click.option(
|
|
176
|
+
"--in",
|
|
177
|
+
"-i",
|
|
178
|
+
"parent",
|
|
179
|
+
type=str,
|
|
180
|
+
multiple=True,
|
|
181
|
+
help="Where can you put this thing into?",
|
|
182
|
+
)(c)
|
|
183
|
+
c = click.option("--usable", "-u", is_flag=True, help="Things can be put into this box")(c)
|
|
184
|
+
c = click.option("--unusable", "-U", is_flag=True, help="The box holds fixed subdividers")(c)
|
|
185
|
+
return c
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@typ_.command(name="add")
|
|
189
|
+
@typopts
|
|
190
|
+
@click.pass_obj
|
|
191
|
+
def typ_add(obj, parent, **kw):
|
|
192
|
+
"""
|
|
193
|
+
Add a box type.
|
|
194
|
+
"""
|
|
195
|
+
parent # noqa:B018
|
|
196
|
+
|
|
197
|
+
if obj.name is None:
|
|
198
|
+
raise click.UsageError("The box type needs a name!")
|
|
199
|
+
|
|
200
|
+
bt = BoxTyp(name=obj.name)
|
|
201
|
+
obj.session.add(bt)
|
|
202
|
+
bt.apply(**kw)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@typ_.command(name="set", epilog="Use '-i -NAME' to remove a containing box type.")
|
|
206
|
+
@typopts
|
|
207
|
+
@click.pass_obj
|
|
208
|
+
def typ_set(obj, **kw):
|
|
209
|
+
"""
|
|
210
|
+
Modify a box type.
|
|
211
|
+
"""
|
|
212
|
+
if obj.name is None:
|
|
213
|
+
raise click.UsageError("Which box type? Use a name")
|
|
214
|
+
|
|
215
|
+
bt = obj.session.one(BoxTyp, name=obj.name)
|
|
216
|
+
bt.apply(**kw)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@typ_.command(name="delete")
|
|
220
|
+
@click.pass_obj
|
|
221
|
+
def typ_delete(obj):
|
|
222
|
+
"""
|
|
223
|
+
Remove a box type.
|
|
224
|
+
"""
|
|
225
|
+
if obj.name is None:
|
|
226
|
+
raise click.UsageError("Which box type? Use a name")
|
|
227
|
+
bt = obj.session.one(BoxTyp, name=obj.name)
|
|
228
|
+
obj.session.delete(bt)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema for collecting things
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import Column, ForeignKey, Integer, String, Table, event
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from moat.db.schema import Base
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
boxtyp_tree = Table(
|
|
18
|
+
"boxtyp_tree",
|
|
19
|
+
Base.metadata,
|
|
20
|
+
Column(
|
|
21
|
+
"parent_id",
|
|
22
|
+
Integer,
|
|
23
|
+
ForeignKey("boxtyp.id", name="fk_boxtyp_parent"),
|
|
24
|
+
primary_key=True,
|
|
25
|
+
),
|
|
26
|
+
Column("child_id", Integer, ForeignKey("boxtyp.id", name="fk_boxtyp_child"), primary_key=True),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BoxTyp(Base):
|
|
31
|
+
"One kind of box."
|
|
32
|
+
|
|
33
|
+
name: Mapped[str] = mapped_column(unique=True, type_=String(40))
|
|
34
|
+
comment: Mapped[str] = mapped_column(type_=String(200), nullable=True)
|
|
35
|
+
|
|
36
|
+
parents: Mapped[set[BoxTyp]] = relationship(
|
|
37
|
+
"BoxTyp",
|
|
38
|
+
secondary=boxtyp_tree,
|
|
39
|
+
primaryjoin="BoxTyp.id == boxtyp_tree.c.child_id",
|
|
40
|
+
secondaryjoin="BoxTyp.id == boxtyp_tree.c.parent_id",
|
|
41
|
+
back_populates="children",
|
|
42
|
+
)
|
|
43
|
+
children: Mapped[set[BoxTyp]] = relationship(
|
|
44
|
+
"BoxTyp",
|
|
45
|
+
secondary=boxtyp_tree,
|
|
46
|
+
primaryjoin="BoxTyp.id == boxtyp_tree.c.parent_id",
|
|
47
|
+
secondaryjoin="BoxTyp.id == boxtyp_tree.c.child_id",
|
|
48
|
+
back_populates="parents",
|
|
49
|
+
)
|
|
50
|
+
usable: Mapped[bool] = mapped_column(
|
|
51
|
+
nullable=False,
|
|
52
|
+
default=True,
|
|
53
|
+
server_default="1",
|
|
54
|
+
comment="Can you put things directly into this?",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Possible locations in there
|
|
58
|
+
pos_x: Mapped[int] = mapped_column(nullable=True, comment="Max # of X positions")
|
|
59
|
+
pos_y: Mapped[int] = mapped_column(nullable=True, comment="Max # of Y positions")
|
|
60
|
+
pos_z: Mapped[int] = mapped_column(nullable=True, comment="Max # of Z positions")
|
|
61
|
+
|
|
62
|
+
labeltyp_id: Mapped[int] = mapped_column(
|
|
63
|
+
ForeignKey("labeltyp.id", name="fk_boxtyp_labeltyp"),
|
|
64
|
+
nullable=True,
|
|
65
|
+
comment="Default label",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
boxes: Mapped[set[Box]] = relationship(back_populates="boxtyp")
|
|
69
|
+
|
|
70
|
+
def dump(self) -> dict[str, Any]:
|
|
71
|
+
"Standard info dump"
|
|
72
|
+
res = super().dump()
|
|
73
|
+
res.pop("pos_x", None)
|
|
74
|
+
res.pop("pos_y", None)
|
|
75
|
+
res.pop("pos_z", None)
|
|
76
|
+
if self.pos_x or self.pos_y or self.pos_z:
|
|
77
|
+
res["size"] = [self.pos_x, self.pos_y, self.pos_z]
|
|
78
|
+
if self.parents:
|
|
79
|
+
res["parent"] = [p.name for p in self.parents]
|
|
80
|
+
if self.children:
|
|
81
|
+
res["child"] = [p.name for p in self.children]
|
|
82
|
+
return res
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Box(Base):
|
|
86
|
+
"One particular box, possibly with other boxes inside."
|
|
87
|
+
|
|
88
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
89
|
+
|
|
90
|
+
typ_id: Mapped[int] = mapped_column(ForeignKey("boxtyp.id", name="fk_box_typ"))
|
|
91
|
+
name: Mapped[str] = mapped_column(unique=True, type_=String(40))
|
|
92
|
+
container_id: Mapped[int] = mapped_column(
|
|
93
|
+
ForeignKey("box.id", name="fk_box_container"),
|
|
94
|
+
nullable=True,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
boxtyp: Mapped[BoxTyp] = relationship(back_populates="boxes")
|
|
98
|
+
container: Mapped[Box | None] = relationship(back_populates="boxes", remote_side=[id])
|
|
99
|
+
boxes: Mapped[set[Box]] = relationship(back_populates="container")
|
|
100
|
+
|
|
101
|
+
# location within its parent
|
|
102
|
+
pos_x: Mapped[int] = mapped_column(nullable=True, comment="X position in parent")
|
|
103
|
+
pos_y: Mapped[int] = mapped_column(nullable=True, comment="Y position in parent")
|
|
104
|
+
pos_z: Mapped[int] = mapped_column(nullable=True, comment="Z position in parent")
|
|
105
|
+
|
|
106
|
+
def dump(self) -> dict[str, Any]:
|
|
107
|
+
"Standard info dump"
|
|
108
|
+
res = super().dump()
|
|
109
|
+
res.pop("pos_x", None)
|
|
110
|
+
res.pop("pos_y", None)
|
|
111
|
+
res.pop("pos_z", None)
|
|
112
|
+
if self.pos_x or self.pos_y or self.pos_z:
|
|
113
|
+
res["pos"] = [self.pos_x, self.pos_y, self.pos_z]
|
|
114
|
+
if self.container is not None:
|
|
115
|
+
res["in"] = self.container.name
|
|
116
|
+
if self.labels:
|
|
117
|
+
res["labels"] = [f"{lab.id}:{lab.text}" for lab in self.labels]
|
|
118
|
+
if self.boxes:
|
|
119
|
+
res["content"] = sorted([box.name for box in self.boxes])
|
|
120
|
+
if self.boxtyp:
|
|
121
|
+
res["typ"] = self.boxtyp.name
|
|
122
|
+
return res
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@event.listens_for(Box, "before_insert")
|
|
126
|
+
@event.listens_for(Box, "before_update")
|
|
127
|
+
def validate_box_coords(mapper, connection, model):
|
|
128
|
+
"Validator for box position"
|
|
129
|
+
mapper, connection # noqa:B018
|
|
130
|
+
par = model.container
|
|
131
|
+
if par is not None:
|
|
132
|
+
par = par.boxtyp
|
|
133
|
+
|
|
134
|
+
def chk(p):
|
|
135
|
+
pp = f"pos_{p}"
|
|
136
|
+
ppos = getattr(par, pp, None)
|
|
137
|
+
|
|
138
|
+
if (pos := getattr(model, pp)) is None:
|
|
139
|
+
if ppos is not None and ppos > 1:
|
|
140
|
+
raise ValueError(f"Box {model.name} needs a value for {p}")
|
|
141
|
+
elif pos <= 0:
|
|
142
|
+
raise ValueError(f"Box {model.name} can't set {p} <= 0")
|
|
143
|
+
elif par is None:
|
|
144
|
+
raise ValueError(f"Box {model.name} is not in a sized container")
|
|
145
|
+
elif ppos is None:
|
|
146
|
+
raise ValueError(f"Box {model.name} can't have a value for {p}")
|
|
147
|
+
elif ppos < pos:
|
|
148
|
+
raise ValueError(f"Box {model.name}: {p} is {pos}, max is {ppos}")
|
|
149
|
+
|
|
150
|
+
chk("x")
|
|
151
|
+
chk("y")
|
|
152
|
+
chk("z")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema for collecting things
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from sqlalchemy.orm import relationship
|
|
8
|
+
|
|
9
|
+
from moat.util import NotGiven
|
|
10
|
+
from moat.db.schema import Base
|
|
11
|
+
from moat.db.util import session
|
|
12
|
+
from moat.label.model import Label, LabelTyp
|
|
13
|
+
from moat.thing.model import Thing
|
|
14
|
+
|
|
15
|
+
from .model import Box, BoxTyp
|
|
16
|
+
|
|
17
|
+
BoxTyp.labeltyp = relationship(LabelTyp, back_populates="boxtypes")
|
|
18
|
+
Box.labels = relationship(Label, back_populates="box", collection_class=set)
|
|
19
|
+
Box.things = relationship(Thing, back_populates="container", collection_class=set)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def box_apply(self, container=NotGiven, boxtyp=NotGiven, **kw):
|
|
23
|
+
"?"
|
|
24
|
+
sess = session.get()
|
|
25
|
+
with sess.no_autoflush:
|
|
26
|
+
Base.apply(self, **kw)
|
|
27
|
+
|
|
28
|
+
if container is NotGiven:
|
|
29
|
+
pass
|
|
30
|
+
elif container is None:
|
|
31
|
+
self.container = None
|
|
32
|
+
else:
|
|
33
|
+
self.container = sess.one(Box, name=container)
|
|
34
|
+
|
|
35
|
+
if boxtyp is NotGiven:
|
|
36
|
+
if self.boxtyp is None:
|
|
37
|
+
raise ValueError("New boxes need a type")
|
|
38
|
+
elif boxtyp is None:
|
|
39
|
+
raise ValueError("Boxes need a type")
|
|
40
|
+
else:
|
|
41
|
+
if self.boxtyp is None:
|
|
42
|
+
self.boxtyp = sess.one(BoxTyp, name=boxtyp)
|
|
43
|
+
elif self.boxtyp.name != boxtyp:
|
|
44
|
+
raise ValueError("Box types cannot be changed")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Box.apply = box_apply
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def boxtyp_apply(self, usable, unusable, parent=(), **kw):
|
|
51
|
+
"?"
|
|
52
|
+
Base.apply(self, **kw)
|
|
53
|
+
if usable:
|
|
54
|
+
if unusable:
|
|
55
|
+
raise ValueError("Can't both use and not use this type")
|
|
56
|
+
self.usable = True
|
|
57
|
+
elif unusable:
|
|
58
|
+
self.usable = False
|
|
59
|
+
|
|
60
|
+
for p in parent:
|
|
61
|
+
if p[0] != "-":
|
|
62
|
+
self.parents.add(session.get().one(BoxTyp, name=p))
|
|
63
|
+
else:
|
|
64
|
+
self.parents.remove(session.get().one(BoxTyp, name=p[1:]))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
BoxTyp.apply = boxtyp_apply
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-db-box
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: MoaT support for managing containers
|
|
5
|
+
Author-email: Matthias Urlichs <matthias@urlichs.de>
|
|
6
|
+
Project-URL: homepage, https://m-o-a-t.org
|
|
7
|
+
Project-URL: repository, https://github.com/M-o-a-T/moat
|
|
8
|
+
Keywords: MoaT
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: Trio
|
|
13
|
+
Classifier: Framework :: AnyIO
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: moat-util~=0.60.9
|
|
19
|
+
Requires-Dist: moat-db~=0.2.3
|
|
20
|
+
Requires-Dist: moat-label~=0.2.2
|
|
21
|
+
Provides-Extra: event
|
|
22
|
+
Requires-Dist: attr; extra == "event"
|
|
23
|
+
Requires-Dist: outcome; extra == "event"
|
|
24
|
+
Provides-Extra: main
|
|
25
|
+
Requires-Dist: asyncclick; extra == "main"
|
|
26
|
+
Requires-Dist: getpass; extra == "main"
|
|
27
|
+
Provides-Extra: msg
|
|
28
|
+
Requires-Dist: msgpack; extra == "msg"
|
|
29
|
+
Provides-Extra: path
|
|
30
|
+
Requires-Dist: simpleeval; extra == "path"
|
|
31
|
+
Provides-Extra: queue
|
|
32
|
+
Requires-Dist: outcome; extra == "queue"
|
|
33
|
+
Provides-Extra: systemd
|
|
34
|
+
Requires-Dist: systemd.daemon; extra == "systemd"
|
|
35
|
+
Provides-Extra: yaml
|
|
36
|
+
Requires-Dist: ruyaml; extra == "yaml"
|
|
37
|
+
Provides-Extra: time
|
|
38
|
+
Requires-Dist: calendar; extra == "time"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: flake8; extra == "dev"
|
|
41
|
+
Requires-Dist: isort; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/moat/db/box/__init__.py
|
|
5
|
+
src/moat/db/box/_main.py
|
|
6
|
+
src/moat/db/box/model.py
|
|
7
|
+
src/moat/db/box/model_.py
|
|
8
|
+
src/moat_db_box.egg-info/PKG-INFO
|
|
9
|
+
src/moat_db_box.egg-info/SOURCES.txt
|
|
10
|
+
src/moat_db_box.egg-info/dependency_links.txt
|
|
11
|
+
src/moat_db_box.egg-info/requires.txt
|
|
12
|
+
src/moat_db_box.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
moat-db~=0.2.3
|
|
2
|
+
moat-label~=0.2.2
|
|
3
|
+
moat-util~=0.60.9
|
|
4
|
+
|
|
5
|
+
[dev]
|
|
6
|
+
flake8
|
|
7
|
+
isort
|
|
8
|
+
|
|
9
|
+
[event]
|
|
10
|
+
attr
|
|
11
|
+
outcome
|
|
12
|
+
|
|
13
|
+
[main]
|
|
14
|
+
asyncclick
|
|
15
|
+
getpass
|
|
16
|
+
|
|
17
|
+
[msg]
|
|
18
|
+
msgpack
|
|
19
|
+
|
|
20
|
+
[path]
|
|
21
|
+
simpleeval
|
|
22
|
+
|
|
23
|
+
[queue]
|
|
24
|
+
outcome
|
|
25
|
+
|
|
26
|
+
[systemd]
|
|
27
|
+
systemd.daemon
|
|
28
|
+
|
|
29
|
+
[time]
|
|
30
|
+
calendar
|
|
31
|
+
|
|
32
|
+
[yaml]
|
|
33
|
+
ruyaml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moat
|