moat-db-thing 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_thing-0.0.1/LICENSE.txt +14 -0
- moat_db_thing-0.0.1/PKG-INFO +26 -0
- moat_db_thing-0.0.1/README.md +9 -0
- moat_db_thing-0.0.1/pyproject.toml +44 -0
- moat_db_thing-0.0.1/setup.cfg +4 -0
- moat_db_thing-0.0.1/src/moat/db/thing/__init__.py +3 -0
- moat_db_thing-0.0.1/src/moat/db/thing/_main.py +232 -0
- moat_db_thing-0.0.1/src/moat/db/thing/model.py +111 -0
- moat_db_thing-0.0.1/src/moat/db/thing/model_.py +94 -0
- moat_db_thing-0.0.1/src/moat_db_thing.egg-info/PKG-INFO +26 -0
- moat_db_thing-0.0.1/src/moat_db_thing.egg-info/SOURCES.txt +12 -0
- moat_db_thing-0.0.1/src/moat_db_thing.egg-info/dependency_links.txt +1 -0
- moat_db_thing-0.0.1/src/moat_db_thing.egg-info/requires.txt +8 -0
- moat_db_thing-0.0.1/src/moat_db_thing.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,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-db-thing
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: MoaT support for storing info about things
|
|
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.8
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: anyio~=4.2
|
|
19
|
+
Requires-Dist: asyncclick
|
|
20
|
+
Requires-Dist: attrs
|
|
21
|
+
Requires-Dist: msgpack
|
|
22
|
+
Requires-Dist: moat-util~=0.60.9
|
|
23
|
+
Requires-Dist: moat-db~=0.2.3
|
|
24
|
+
Requires-Dist: moat-label~=0.2.2
|
|
25
|
+
Requires-Dist: moat-box~=0.2.2
|
|
26
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
"anyio ~= 4.2",
|
|
16
|
+
"asyncclick",
|
|
17
|
+
"attrs",
|
|
18
|
+
"msgpack",
|
|
19
|
+
"moat-util ~= 0.60.9",
|
|
20
|
+
"moat-db ~= 0.2.3",
|
|
21
|
+
"moat-label ~= 0.2.2",
|
|
22
|
+
"moat-box ~= 0.2.2",
|
|
23
|
+
]
|
|
24
|
+
version = "0.0.1"
|
|
25
|
+
keywords = [ "MoaT",]
|
|
26
|
+
requires-python = ">=3.8"
|
|
27
|
+
name = "moat-db-thing"
|
|
28
|
+
description = "MoaT support for storing info about things"
|
|
29
|
+
readme = "README.rst"
|
|
30
|
+
|
|
31
|
+
[[project.authors]]
|
|
32
|
+
name = "Matthias Urlichs"
|
|
33
|
+
email = "matthias@urlichs.de"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
homepage = "https://m-o-a-t.org"
|
|
37
|
+
repository = "https://github.com/M-o-a-T/moat"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools]
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
"*" = ["*.yaml"]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Support for 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 Thing, ThingTyp
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@load_subgroup(prefix="moat.thing", invoke_without_command=False)
|
|
22
|
+
@click.pass_context
|
|
23
|
+
@click.option("--name", "-n", type=str, help="Name of the thing type")
|
|
24
|
+
def cli(ctx, name):
|
|
25
|
+
"""Things."""
|
|
26
|
+
obj = ctx.obj
|
|
27
|
+
|
|
28
|
+
sess = ctx.with_resource(database(obj.cfg.db))
|
|
29
|
+
ctx.with_resource(sess.begin())
|
|
30
|
+
|
|
31
|
+
obj.session = sess
|
|
32
|
+
obj.name = name
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@cli.group
|
|
36
|
+
@click.option("--name", "-n", type=str, help="Text on the label")
|
|
37
|
+
@click.pass_obj
|
|
38
|
+
def one(obj, name):
|
|
39
|
+
"""
|
|
40
|
+
Manage actual things.
|
|
41
|
+
"""
|
|
42
|
+
obj.name = name
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@one.command(name="show")
|
|
47
|
+
@click.option("--type", "-t", "type_", type=str, help="Type of thing to list")
|
|
48
|
+
@click.pass_obj
|
|
49
|
+
def show_(obj, type_):
|
|
50
|
+
"""
|
|
51
|
+
Show thing details / list all things of a type.
|
|
52
|
+
|
|
53
|
+
If a name is set, show details for this thing.
|
|
54
|
+
Otherwise list all things without storage.
|
|
55
|
+
"""
|
|
56
|
+
sess = obj.session
|
|
57
|
+
|
|
58
|
+
if obj.name is not None:
|
|
59
|
+
thing = sess.one(Thing, name=obj.name)
|
|
60
|
+
yprint(thing.dump())
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
sel = select(Thing)
|
|
64
|
+
if type_ is None:
|
|
65
|
+
sel = sel.where(Thing.container == None) # noqa:E711
|
|
66
|
+
else:
|
|
67
|
+
ttyp = obj.session.one(ThingTyp, name=type_)
|
|
68
|
+
sel = sel.where(Thing.thingtyp == ttyp)
|
|
69
|
+
with sess.execute(select(Thing).where(Thing.container == None)) as things: # noqa:E711
|
|
70
|
+
for (thing,) in things:
|
|
71
|
+
print(thing.name, thing.descr)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def opts(c):
|
|
75
|
+
c = option_ng("--name", "-n", type=str, help="Rename this thing")(c)
|
|
76
|
+
c = option_ng("--x", "-x", "pos_x", type=int, help="X position in parent")(c)
|
|
77
|
+
c = option_ng("--y", "-y", "pos_y", type=int, help="Y position in parent")(c)
|
|
78
|
+
c = option_ng("--z", "-z", "pos_z", type=int, help="Z position in parent")(c)
|
|
79
|
+
c = option_ng("--in", "-i", "container", type=str, help="Where is it?")(c)
|
|
80
|
+
c = option_ng("--typ", "-t", "thingtyp", type=str, help="Type of the thing")(c)
|
|
81
|
+
c = option_ng("--descr", "-d", "descr", type=str, help="Description of the thing")(c)
|
|
82
|
+
c = option_ng("--comment", "-c", type=str, help="Additional comments")(c)
|
|
83
|
+
c = option_ng("--label", "-l", type=str, help="Label for this thing")(c)
|
|
84
|
+
return c
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@one.command()
|
|
88
|
+
@opts
|
|
89
|
+
@click.pass_obj
|
|
90
|
+
def add(obj, **kw):
|
|
91
|
+
"""
|
|
92
|
+
Add a thing.
|
|
93
|
+
"""
|
|
94
|
+
if obj.name is None:
|
|
95
|
+
raise click.UsageError("The thing needs a name!")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
thing = obj.session.one(Thing, name=obj.name)
|
|
99
|
+
except KeyError:
|
|
100
|
+
pass
|
|
101
|
+
else:
|
|
102
|
+
print("This thing already exists", file=sys.stderr)
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
thing = Thing(name=obj.name)
|
|
105
|
+
obj.session.add(thing)
|
|
106
|
+
thing.apply(**kw)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@one.command(epilog="Use '-x/-y/-z 0' to clear a position, '--in -' to remove the location.")
|
|
110
|
+
@opts
|
|
111
|
+
@click.pass_obj
|
|
112
|
+
def set(obj, **kw): # noqa: A001
|
|
113
|
+
"""
|
|
114
|
+
Modify a thing.
|
|
115
|
+
"""
|
|
116
|
+
if obj.name is None:
|
|
117
|
+
raise click.UsageError("Which thing? Use a name")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
thing = obj.session.one(Thing, name=obj.name)
|
|
121
|
+
except KeyError:
|
|
122
|
+
print("This thing doesn't exist", file=sys.stderr)
|
|
123
|
+
sys.exit(1)
|
|
124
|
+
thing.apply(**kw)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@one.command()
|
|
128
|
+
@click.pass_obj
|
|
129
|
+
def delete(obj):
|
|
130
|
+
"""
|
|
131
|
+
Permanently remove a thing.
|
|
132
|
+
"""
|
|
133
|
+
if obj.name is None:
|
|
134
|
+
raise click.UsageError("Which thing? Use a name")
|
|
135
|
+
try:
|
|
136
|
+
thing = obj.session.one(Thing, name=obj.name)
|
|
137
|
+
except KeyError:
|
|
138
|
+
print("This thing doesn't exist", file=sys.stderr)
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
obj.session.delete(thing)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@cli.group(name="typ")
|
|
144
|
+
@click.option("--name", "-n", type=str, help="Name of the type")
|
|
145
|
+
@click.pass_context
|
|
146
|
+
def typ_(ctx, name):
|
|
147
|
+
"""\
|
|
148
|
+
Manage a hierarchy of types of things.
|
|
149
|
+
"""
|
|
150
|
+
obj = ctx.obj
|
|
151
|
+
if obj.name is not None:
|
|
152
|
+
raise click.UsageError("Please use 'thing typ -n NAME'")
|
|
153
|
+
obj.name = name
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@typ_.command(name="show")
|
|
157
|
+
@click.pass_obj
|
|
158
|
+
def typ_show(obj):
|
|
159
|
+
"""
|
|
160
|
+
Show thing details / list all thing types.
|
|
161
|
+
|
|
162
|
+
If a name is set, show details for this type.
|
|
163
|
+
Otherwise list all known thing types.
|
|
164
|
+
"""
|
|
165
|
+
sess = obj.session
|
|
166
|
+
|
|
167
|
+
if obj.name is None:
|
|
168
|
+
seen = False
|
|
169
|
+
with sess.execute(select(ThingTyp)) as things:
|
|
170
|
+
for (thing,) in things:
|
|
171
|
+
seen = True
|
|
172
|
+
print(thing.name)
|
|
173
|
+
if not seen:
|
|
174
|
+
print("No thing types defined yet. Use '--help'?", file=sys.stderr)
|
|
175
|
+
else:
|
|
176
|
+
thing = sess.one(ThingTyp, name=obj.name)
|
|
177
|
+
yprint(thing.dump())
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def typopts(c):
|
|
181
|
+
c = option_ng("--name", "-n", type=str, help="Rename this type")(c)
|
|
182
|
+
c = option_ng("--parent", "-p", type=str, help="Parent of this type")(c)
|
|
183
|
+
c = click.option(
|
|
184
|
+
"--abstract",
|
|
185
|
+
"-a",
|
|
186
|
+
is_flag=True,
|
|
187
|
+
help="This type can't contain a real thing",
|
|
188
|
+
)(c)
|
|
189
|
+
c = click.option("--real", "-A", is_flag=True, help="This type can have a real thing")(c)
|
|
190
|
+
c = option_ng("--comment", "-c", "comment", type=str, help="Description of this type")(c)
|
|
191
|
+
return c
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@typ_.command(name="add")
|
|
195
|
+
@typopts
|
|
196
|
+
@click.pass_obj
|
|
197
|
+
def typ_add(obj, **kw):
|
|
198
|
+
"""
|
|
199
|
+
Add a thing type.
|
|
200
|
+
"""
|
|
201
|
+
if obj.name is None:
|
|
202
|
+
raise click.UsageError("The thing type needs a name!")
|
|
203
|
+
|
|
204
|
+
bt = ThingTyp(name=obj.name)
|
|
205
|
+
obj.session.add(bt)
|
|
206
|
+
bt.apply(**kw)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@typ_.command(name="set", epilog="Use '-i -NAME' to remove a containing thing type.")
|
|
210
|
+
@typopts
|
|
211
|
+
@click.pass_obj
|
|
212
|
+
def typ_set(obj, **kw):
|
|
213
|
+
"""
|
|
214
|
+
Modify a thing type.
|
|
215
|
+
"""
|
|
216
|
+
if obj.name is None:
|
|
217
|
+
raise click.UsageError("Which thing type? Use a name")
|
|
218
|
+
|
|
219
|
+
bt = obj.session.one(ThingTyp, name=obj.name)
|
|
220
|
+
bt.apply(**kw)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@typ_.command(name="delete")
|
|
224
|
+
@click.pass_obj
|
|
225
|
+
def typ_delete(obj):
|
|
226
|
+
"""
|
|
227
|
+
Remove a thing type.
|
|
228
|
+
"""
|
|
229
|
+
if obj.name is None:
|
|
230
|
+
raise click.UsageError("Which thing type? Use a name")
|
|
231
|
+
bt = obj.session.one(ThingTyp, name=obj.name)
|
|
232
|
+
obj.session.delete(bt)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema for collecting things
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import ForeignKey, String, event
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
9
|
+
|
|
10
|
+
from moat.db.schema import Base
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ThingTyp(Base):
|
|
14
|
+
"One kind of thing."
|
|
15
|
+
|
|
16
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
17
|
+
name: Mapped[str] = mapped_column(unique=True, type_=String(40))
|
|
18
|
+
comment: Mapped[str] = mapped_column(type_=String(200), nullable=True)
|
|
19
|
+
|
|
20
|
+
parent_id: Mapped[int] = mapped_column(
|
|
21
|
+
ForeignKey("thingtyp.id", name="fk_thingtyp_typ"),
|
|
22
|
+
nullable=True,
|
|
23
|
+
)
|
|
24
|
+
parent: Mapped[ThingTyp] = relationship(
|
|
25
|
+
"ThingTyp",
|
|
26
|
+
back_populates="children",
|
|
27
|
+
remote_side=[id],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
abstract: Mapped[bool] = mapped_column(
|
|
31
|
+
nullable=False,
|
|
32
|
+
default=False,
|
|
33
|
+
server_default="0",
|
|
34
|
+
comment="Must be False for instantiating things with this type",
|
|
35
|
+
)
|
|
36
|
+
children: Mapped[set[ThingTyp]] = relationship("ThingTyp", back_populates="parent")
|
|
37
|
+
things: Mapped[set[Thing]] = relationship(back_populates="thingtyp")
|
|
38
|
+
|
|
39
|
+
def dump(self): # noqa: D102
|
|
40
|
+
res = super().dump()
|
|
41
|
+
if self.parent:
|
|
42
|
+
res["parent"] = self.parent.name
|
|
43
|
+
if self.children:
|
|
44
|
+
res["child"] = [p.name for p in self.children]
|
|
45
|
+
if self.things:
|
|
46
|
+
res["things"] = len(self.things)
|
|
47
|
+
return res
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Thing(Base):
|
|
51
|
+
"One particular thing"
|
|
52
|
+
|
|
53
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
54
|
+
|
|
55
|
+
typ_id: Mapped[int] = mapped_column(ForeignKey("thingtyp.id", name="fk_thing_typ"))
|
|
56
|
+
name: Mapped[str] = mapped_column(unique=True, type_=String(40))
|
|
57
|
+
descr: Mapped[str] = mapped_column(type_=String(200), nullable=True)
|
|
58
|
+
comment: Mapped[str] = mapped_column(type_=String(200), nullable=True)
|
|
59
|
+
|
|
60
|
+
box_id: Mapped[int] = mapped_column(ForeignKey("box.id", name="fk_thing_box"), nullable=True)
|
|
61
|
+
|
|
62
|
+
thingtyp: Mapped[ThingTyp] = relationship(back_populates="things")
|
|
63
|
+
|
|
64
|
+
# possibly the location within its parent
|
|
65
|
+
pos_x: Mapped[int] = mapped_column(nullable=True, comment="X position in parent")
|
|
66
|
+
pos_y: Mapped[int] = mapped_column(nullable=True, comment="Y position in parent")
|
|
67
|
+
pos_z: Mapped[int] = mapped_column(nullable=True, comment="Z position in parent")
|
|
68
|
+
|
|
69
|
+
def dump(self): # noqa: D102
|
|
70
|
+
res = super().dump()
|
|
71
|
+
res.pop("pos_x", None)
|
|
72
|
+
res.pop("pos_y", None)
|
|
73
|
+
res.pop("pos_z", None)
|
|
74
|
+
if self.pos_x or self.pos_y or self.pos_z:
|
|
75
|
+
res["pos"] = [self.pos_x, self.pos_y, self.pos_z]
|
|
76
|
+
if self.container is not None:
|
|
77
|
+
res["in"] = self.container.name
|
|
78
|
+
if self.labels:
|
|
79
|
+
res["labels"] = [f"{lab.id}:{lab.text}" for lab in self.labels]
|
|
80
|
+
if self.thingtyp:
|
|
81
|
+
res["typ"] = self.thingtyp.name
|
|
82
|
+
return res
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@event.listens_for(Thing, "before_insert")
|
|
86
|
+
@event.listens_for(Thing, "before_update")
|
|
87
|
+
def validate_thing_coords(mapper, connection, model): # noqa: D103
|
|
88
|
+
mapper, connection # noqa:B018
|
|
89
|
+
par = model.container
|
|
90
|
+
if par is not None:
|
|
91
|
+
par = par.boxtyp
|
|
92
|
+
|
|
93
|
+
def chk(p):
|
|
94
|
+
pp = f"pos_{p}"
|
|
95
|
+
ppos = getattr(par, pp, None)
|
|
96
|
+
|
|
97
|
+
if (pos := getattr(model, pp)) is None:
|
|
98
|
+
if ppos is not None and ppos > 1:
|
|
99
|
+
raise ValueError(f"Thing {model.name} needs a value for {p}")
|
|
100
|
+
elif pos <= 0:
|
|
101
|
+
raise ValueError(f"Thing {model.name} can't set {p} <= 0")
|
|
102
|
+
elif par is None:
|
|
103
|
+
raise ValueError(f"Thing {model.name} is not in a sized container")
|
|
104
|
+
elif ppos is None:
|
|
105
|
+
raise ValueError(f"Thing {model.name} can't have a value for {p}")
|
|
106
|
+
elif ppos < pos:
|
|
107
|
+
raise ValueError(f"Thing {model.name}: {p} is {pos}, max is {ppos}")
|
|
108
|
+
|
|
109
|
+
chk("x")
|
|
110
|
+
chk("y")
|
|
111
|
+
chk("z")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema for collecting things
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import func, select
|
|
8
|
+
from sqlalchemy.orm import relationship
|
|
9
|
+
|
|
10
|
+
from moat.util import NotGiven
|
|
11
|
+
from moat.box.model import Box
|
|
12
|
+
from moat.db.schema import Base
|
|
13
|
+
from moat.db.util import session
|
|
14
|
+
from moat.label.model import Label
|
|
15
|
+
|
|
16
|
+
from .model import Thing, ThingTyp
|
|
17
|
+
|
|
18
|
+
Thing.labels = relationship(Label, back_populates="thing", collection_class=set)
|
|
19
|
+
Thing.container = relationship(Box, back_populates="things")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def thing_apply(self, label=NotGiven, container=NotGiven, thingtyp=NotGiven, **kw): # noqa: D103
|
|
23
|
+
sess = session.get()
|
|
24
|
+
with sess.no_autoflush:
|
|
25
|
+
Base.apply(self, **kw)
|
|
26
|
+
|
|
27
|
+
if container is NotGiven:
|
|
28
|
+
pass
|
|
29
|
+
elif container is None or container == "-":
|
|
30
|
+
self.container = None
|
|
31
|
+
else:
|
|
32
|
+
box = sess.one(Box, name=container)
|
|
33
|
+
if not box.boxtype.usable:
|
|
34
|
+
raise ValueError("You can't put anything into a {box.boxtype.name !r}.")
|
|
35
|
+
self.container = box
|
|
36
|
+
|
|
37
|
+
if label is NotGiven:
|
|
38
|
+
pass
|
|
39
|
+
elif label is None or label == "-":
|
|
40
|
+
self.label = None
|
|
41
|
+
else:
|
|
42
|
+
self.label = sess.one(Label, name=label)
|
|
43
|
+
|
|
44
|
+
if thingtyp is NotGiven:
|
|
45
|
+
if self.thingtyp is None:
|
|
46
|
+
raise ValueError("New things need a type")
|
|
47
|
+
elif thingtyp is None:
|
|
48
|
+
raise ValueError("Things need a type")
|
|
49
|
+
else:
|
|
50
|
+
if self.thingtyp is None:
|
|
51
|
+
ttyp = sess.one(ThingTyp, name=thingtyp)
|
|
52
|
+
if ttyp.abstract:
|
|
53
|
+
raise ValueError("Things need a non-abstract type")
|
|
54
|
+
self.thingtyp = ttyp
|
|
55
|
+
elif self.thingtyp.name != thingtyp:
|
|
56
|
+
raise ValueError("Thing types cannot be changed")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
Thing.apply = thing_apply
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def thingtyp_apply(self, parent=NotGiven, abstract=False, real=False, **kw): # noqa: D103
|
|
63
|
+
if abstract:
|
|
64
|
+
if real:
|
|
65
|
+
raise ValueError("A type can't be both 'abstract' and 'real'.")
|
|
66
|
+
kw["abstract"] = True
|
|
67
|
+
elif real:
|
|
68
|
+
kw["abstract"] = False
|
|
69
|
+
|
|
70
|
+
Base.apply(self, **kw)
|
|
71
|
+
|
|
72
|
+
sess = session.get()
|
|
73
|
+
|
|
74
|
+
if parent is NotGiven:
|
|
75
|
+
parent = self.parent.name if self.parent is not None else None
|
|
76
|
+
if parent is None:
|
|
77
|
+
(n,) = sess.execute(
|
|
78
|
+
select(func.count(ThingTyp.id))
|
|
79
|
+
.where(ThingTyp.parent == None) # noqa:E711
|
|
80
|
+
.where(ThingTyp.name != self.name),
|
|
81
|
+
).first()
|
|
82
|
+
if n:
|
|
83
|
+
raise ValueError("Only one top entry allowed")
|
|
84
|
+
else:
|
|
85
|
+
par = sess.one(ThingTyp, name=parent)
|
|
86
|
+
p = par
|
|
87
|
+
while p is not None:
|
|
88
|
+
if p == self:
|
|
89
|
+
raise ValueError("No cycles allowed.")
|
|
90
|
+
p = p.parent
|
|
91
|
+
self.parent = par
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
ThingTyp.apply = thingtyp_apply
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moat-db-thing
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: MoaT support for storing info about things
|
|
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.8
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: anyio~=4.2
|
|
19
|
+
Requires-Dist: asyncclick
|
|
20
|
+
Requires-Dist: attrs
|
|
21
|
+
Requires-Dist: msgpack
|
|
22
|
+
Requires-Dist: moat-util~=0.60.9
|
|
23
|
+
Requires-Dist: moat-db~=0.2.3
|
|
24
|
+
Requires-Dist: moat-label~=0.2.2
|
|
25
|
+
Requires-Dist: moat-box~=0.2.2
|
|
26
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/moat/db/thing/__init__.py
|
|
5
|
+
src/moat/db/thing/_main.py
|
|
6
|
+
src/moat/db/thing/model.py
|
|
7
|
+
src/moat/db/thing/model_.py
|
|
8
|
+
src/moat_db_thing.egg-info/PKG-INFO
|
|
9
|
+
src/moat_db_thing.egg-info/SOURCES.txt
|
|
10
|
+
src/moat_db_thing.egg-info/dependency_links.txt
|
|
11
|
+
src/moat_db_thing.egg-info/requires.txt
|
|
12
|
+
src/moat_db_thing.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moat
|