flinventory 2.2.3__tar.gz → 2.3.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.
Files changed (25) hide show
  1. {flinventory-2.2.3 → flinventory-2.3.1}/PKG-INFO +1 -1
  2. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/__init__.py +1 -0
  3. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/constant.py +8 -4
  4. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/datacleanup.py +56 -42
  5. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/defaulted_data.py +3 -1
  6. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/generate_labels.py +62 -11
  7. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/inventory_io.py +59 -1
  8. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/sign.py +2 -1
  9. {flinventory-2.2.3 → flinventory-2.3.1}/pyproject.toml +4 -1
  10. {flinventory-2.2.3 → flinventory-2.3.1}/LICENSE +0 -0
  11. {flinventory-2.2.3 → flinventory-2.3.1}/README.md +0 -0
  12. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/__main__.py +0 -0
  13. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/box.py +0 -0
  14. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/font_location.py +0 -0
  15. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/labelprinter_typst.py +0 -0
  16. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/location.py +0 -0
  17. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/signprinter_latex.py +0 -0
  18. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thing.py +0 -0
  19. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thingtemplate_typst/dummyImage.jpg +0 -0
  20. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thingtemplate_typst/label.typ +0 -0
  21. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thingtemplate_typst/out/bowdenzugklemmung1.svg +0 -0
  22. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thingtemplate_typst/out/things.log +0 -0
  23. {flinventory-2.2.3 → flinventory-2.3.1}/flinventory/thingtemplate_typst/thinglist.typ +0 -0
  24. {flinventory-2.2.3 → flinventory-2.3.1}/tests/__init__.py +0 -0
  25. {flinventory-2.2.3 → flinventory-2.3.1}/tests/test_defaulted_data.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flinventory
3
- Version: 2.2.3
3
+ Version: 2.3.1
4
4
  Summary: An inventory system for self-help repair shops
5
5
  Author-Email: flukx <flinventory-flukx@612211124.xyz>
6
6
  License: GPL3
@@ -6,3 +6,4 @@ from .defaulted_data import DefaultedDict
6
6
  from .constant import Options
7
7
  from .thing import Thing
8
8
  from .labelprinter_typst import LabelPrinter
9
+ from .datacleanup import add_printed
@@ -4,6 +4,7 @@
4
4
  By extracting it into a file that is not importing any other one,
5
5
  this can be imported everywhere without circular import problem.
6
6
  """
7
+
7
8
  import os.path
8
9
  import pathlib
9
10
 
@@ -161,14 +162,17 @@ class Options:
161
162
 
162
163
  def __init__(self, options: dict[str, Any]):
163
164
  """Create options from content of option file."""
164
- self.languages = options.get("languages", ["de", "en"])
165
- if isinstance(self.languages, str):
166
- self.languages = [self.languages]
167
- if not isinstance(self.languages, list):
165
+ languages = options.get("languages", ["de", "en"])
166
+ if isinstance(languages, str):
167
+ self.languages: list[str] = [languages]
168
+ elif not isinstance(languages, list) or any(
169
+ not isinstance(language, str) for language in languages
170
+ ):
168
171
  raise ValueError(
169
172
  "Language codes must be a list of strings of "
170
173
  "valid language codes or a single valid language code."
171
174
  )
175
+ self.languages = languages
172
176
  for language in self.languages:
173
177
  if language not in VALID_LANGUAGES:
174
178
  raise ValueError(
@@ -8,6 +8,7 @@ import json
8
8
  import os
9
9
  import logging
10
10
  import string
11
+ from typing import Optional
11
12
 
12
13
  import yaml
13
14
 
@@ -73,7 +74,7 @@ def add_arg_parsers(subparsers):
73
74
  + " are not printed next time."
74
75
  ),
75
76
  )
76
- parser_printed.set_defaults(func=add_printed)
77
+ parser_printed.set_defaults(func=add_printed_from_args)
77
78
 
78
79
  parser_shortcutreplacement = subparsers.add_parser(
79
80
  "shortcut",
@@ -114,7 +115,7 @@ def add_arg_parsers(subparsers):
114
115
  ),
115
116
  )
116
117
  parser_levelname.add_argument("levelname", help="The new levelname.")
117
- parser_levelname.set_defaults(func=set_levelname)
118
+ parser_levelname.set_defaults(func=set_levelname_from_args)
118
119
 
119
120
  parser_schemaname = subparsers.add_parser(
120
121
  "setSchemaname", description="Set the name for a location schema."
@@ -131,10 +132,35 @@ def add_arg_parsers(subparsers):
131
132
  ),
132
133
  )
133
134
  parser_schemaname.add_argument("name", help="The new name.")
134
- parser_schemaname.set_defaults(func=set_schema_name)
135
+ parser_schemaname.set_defaults(func=set_schema_name_from_args)
135
136
 
136
137
 
137
- def get_schema(args) -> tuple[location.Schema, inventory_io.Inventory]:
138
+ def get_schema_from_args(
139
+ args,
140
+ ) -> tuple[location.Schema, inventory_io.Inventory]:
141
+ """Wrapper for get_schema based on command-line arguments.
142
+
143
+ Args:
144
+ args.level: see level_spec in get_schema
145
+ args.directory: directory of the inventory data
146
+ Returns:
147
+ a schema and its surrounding inventory.
148
+ Raises:
149
+ ValueError: if get_schema raises. Error is printed.
150
+ """
151
+ level_spec = args.level
152
+ if not level_spec:
153
+ print("No level specified.")
154
+ return
155
+ inventory = inventory_io.Inventory.from_json_files(directory=args.dataDirectory)
156
+ try:
157
+ return get_schema(inventory, args), inventory
158
+ except ValueError as wrong_spec:
159
+ print(wrong_spec.args[0])
160
+ return
161
+
162
+
163
+ def get_schema(inventory: inventory_io.Inventory, level_spec: str) -> location.Schema:
138
164
  """Get a schema in the inventory.
139
165
 
140
166
  Args:
@@ -146,60 +172,47 @@ def get_schema(args) -> tuple[location.Schema, inventory_io.Inventory]:
146
172
  args.directory: directory of the inventory data
147
173
  Returns:
148
174
  a schema and its surrounding inventory
175
+ Raises:
176
+ ValueError: if the level_spec was invalid. Exception.args[0] contains error message
149
177
  """
150
- level_spec = args.level
151
- if not level_spec:
152
- print("No level specified.")
153
- return
154
- inventory = inventory_io.Inventory.from_json_files(directory=args.dataDirectory)
155
178
  separator = level_spec[0]
156
179
  if separator in string.digits + string.ascii_letters + " ":
157
- print(
180
+ raise ValueError(
158
181
  "Probably separator missing as first character. Use non-digit, non-letter, non-space"
159
182
  )
160
- return
161
183
  schema = inventory.schema
162
184
  for schema_name in level_spec.split(separator)[1:]:
163
185
  print(
164
186
  f"debug: Currently at schema {schema.name if not schema.is_default() else '<default>'}"
165
187
  )
166
188
  if schema_name:
167
- print(f"choose subschema {schema_name}")
189
+ print(f"debug: choose subschema {schema_name}")
168
190
  schema = schema.get_subschema(schema_name)
169
191
  else:
170
- print("choose sub schema default")
192
+ print("debug: choose sub schema default")
171
193
  schema = schema.sub_schema_default
172
- print(schema)
173
- return schema, inventory
194
+ print("debug", schema)
195
+ return schema
174
196
 
175
197
 
176
- def set_levelname(args):
198
+ def set_levelname_from_args(args):
177
199
  """Set the levelname of one location schema."""
178
200
  new_levelname = args.levelname
179
- schema, inventory = get_schema(args)
180
- try:
181
- print(
182
- f"Change level name of {args.level} from {schema.levelname} to {new_levelname}."
183
- )
184
- except location.InvalidLocation:
185
- print(f"Set level name of {args.level} from nothing to {new_levelname}.")
186
- schema.levelname = new_levelname
187
- inventory.save()
201
+ schema, inventory = get_schema_from_args(args)
202
+ inventory.set_levelname(schema, new_levelname)
188
203
 
189
204
 
190
- def set_schema_name(args):
191
- """Set the same of a schema.
205
+ def set_schema_name_from_args(args):
206
+ """Set the name of a schema based on comand-line arguments.
192
207
 
193
208
  Fails on default schemas.
209
+
210
+ Raises:
211
+ ValueError: if level_spec is invalid
212
+ ValueError: if new name is empty except for whitespace
194
213
  """
195
- new_name = args.name
196
- schema, inventory = get_schema(args)
197
- if schema.is_default():
198
- print(f"Cannot change name of default schema {args.level}")
199
- return
200
- print(f"Change name of {args.level} from {schema.name} to {new_name}")
201
- schema.name = new_name
202
- inventory.save()
214
+ schema, inventory = get_schema_from_args(args)
215
+ inventory.set_schema_name(schema, args.name)
203
216
 
204
217
 
205
218
  def example_action(args):
@@ -267,14 +280,15 @@ def use_ids(args):
267
280
  things.save()
268
281
 
269
282
 
270
- def add_printed(args):
271
- """Add to all things the attribute printed=True."""
283
+ def add_printed_from_args(args):
284
+ """Add to all things that would be printed the attribute printed=True. Read invetory from files."""
285
+ add_printed(inventory_io.Inventory.from_json_files(directory=args.dataDirectory))
286
+
287
+
288
+ def add_printed(inventory: inventory_io.Inventory):
289
+ """Add to all things that would be printed the attribute printed=True."""
272
290
  things = (
273
- labelprinter_typst.LabelPrinter()
274
- .sign_things_to_be_printed(
275
- inventory_io.Inventory.from_json_files(directory=args.dataDirectory)
276
- )
277
- .keys()
291
+ labelprinter_typst.LabelPrinter().sign_things_to_be_printed(inventory).keys()
278
292
  )
279
293
  for thing in things:
280
294
  thing.sign["printed"] = True
@@ -22,7 +22,9 @@ from typing import Any, Iterable, Union, Iterator, cast, Callable
22
22
  from . import constant
23
23
 
24
24
  IMMUTABLE_TYPES = (str, int, float, bool, type(None))
25
- Immutable = Union[*IMMUTABLE_TYPES]
25
+ Immutable = Union[
26
+ str, int, float, bool, type(None)
27
+ ] # *IMMUTABLE_TYPES was not accepted
26
28
  Key = Union[str, tuple[str, str]]
27
29
  """Type of keys of DefaultedDict"""
28
30
  Value = Union[Immutable, list[Immutable]]
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  """Create a list of things to hang in the place with all the stuff (e.g. a bike shed)."""
3
+
3
4
  import importlib.resources
4
5
  import json
5
6
 
@@ -9,11 +10,11 @@ import pathlib
9
10
  import argparse
10
11
  import os.path
11
12
  import typst
12
- from typing import cast, Union, Literal
13
+ from typing import cast, Union, Literal, TypeVar
13
14
  import treelib
14
15
 
15
16
  from .box import BoxedThing
16
- from . import inventory_io, labelprinter_typst, thingtemplate_typst
17
+ from . import inventory_io, labelprinter_typst, thingtemplate_typst, constant
17
18
 
18
19
 
19
20
  def add_arg_parsers(subparsers):
@@ -49,9 +50,22 @@ def add_arg_parsers(subparsers):
49
50
  parser_single_label.set_defaults(func=generate_single_label_svg)
50
51
 
51
52
 
52
- def get_thing_list_data(things: inventory_io.Inventory) -> dict[str, str]:
53
+ def get_thing_list_data(
54
+ things: inventory_io.Inventory,
55
+ languages: list[str] | None = None,
56
+ alternatives: bool = True,
57
+ also_without_location: bool = False,
58
+ ) -> dict[str, str]:
53
59
  """Get list[dict] representation of thing list.
54
60
 
61
+ Args:
62
+ things: inventory with listed things
63
+ languages: None for all languages, otherwise list of languages of thing names printed
64
+ Note that this option excludes things with missing names in some languages
65
+ alternatives: whether to list all alternative names.
66
+ Makes the list much longer but also more useful.
67
+ also_without_location: whether to include things that have no box.
68
+ This makes the list longer.
55
69
  Returns:
56
70
  list of dicts. Each dict has "name" and "id", most have "where",
57
71
  many have "name_alt".
@@ -62,6 +76,18 @@ def get_thing_list_data(things: inventory_io.Inventory) -> dict[str, str]:
62
76
  lines: dict[str, list[dict[str, str | None]]] = {}
63
77
 
64
78
  def add_line(front_name: str, new_line: dict[str, str | None]):
79
+ """Add a line to the lines dict.
80
+
81
+ Do not add the line if no "where" is contained in new_line and
82
+ also_without_location is False.
83
+
84
+ Args:
85
+ front_name: string to sort by: what the reader looks for
86
+ new_line: all data for this line on which the typst formatting
87
+ is building on
88
+ """
89
+ if new_line["where"] is None and not also_without_location:
90
+ return
65
91
  if "where" in new_line and not new_line["where"]:
66
92
  del new_line["where"]
67
93
  if front_name in lines:
@@ -69,10 +95,18 @@ def get_thing_list_data(things: inventory_io.Inventory) -> dict[str, str]:
69
95
  else:
70
96
  lines[front_name] = [new_line]
71
97
 
98
+ N = TypeVar("N")
99
+
100
+ def filter_language(name_dict: dict[str, N]) -> dict[str, N]:
101
+ """Only include keys, values which are in languages."""
102
+ if languages is None:
103
+ return name_dict
104
+ return {key: value for key, value in name_dict.items() if key in languages}
105
+
72
106
  for thing in things:
73
107
  if thing.get("category", False):
74
108
  continue
75
- for name in thing.get("name", {}).values():
109
+ for name in filter_language(thing.get("name", {})).values():
76
110
  add_line(
77
111
  name,
78
112
  {
@@ -81,7 +115,9 @@ def get_thing_list_data(things: inventory_io.Inventory) -> dict[str, str]:
81
115
  "where": thing.where if thing.location else None,
82
116
  },
83
117
  )
84
- for lang, names_alt in thing.get("name_alt", {}).items():
118
+ if not alternatives:
119
+ continue
120
+ for lang, names_alt in filter_language(thing.get("name_alt", {})).items():
85
121
  logging.debug("f{(lang, names_alt)=}")
86
122
  for name_alt in names_alt:
87
123
  add_line(
@@ -96,7 +132,7 @@ def get_thing_list_data(things: inventory_io.Inventory) -> dict[str, str]:
96
132
  },
97
133
  )
98
134
  return {
99
- "title": things.schema.name,
135
+ "title": str(things.schema.name),
100
136
  "data": json.dumps(
101
137
  [
102
138
  line
@@ -218,11 +254,26 @@ def generate_labels(options: argparse.Namespace):
218
254
  logging.info("Start creating tree view.")
219
255
  treefile.write(get_tree(all_things))
220
256
 
221
- logging.getLogger("generateLabels").info("Start creating thing list.")
222
- create_listpdf(
223
- get_thing_list_data(all_things),
224
- options.output_dir / options.output_list,
225
- )
257
+ logging.getLogger("generateLabels").info("Start creating thing lists.")
258
+ list_path = options.output_dir / options.output_list
259
+ for language in [None] + all_things.options.languages:
260
+ for alternatives in [True, False]:
261
+ for without_location in [True, False]:
262
+ create_listpdf(
263
+ get_thing_list_data(
264
+ all_things,
265
+ languages=None if language is None else [language],
266
+ alternatives=alternatives,
267
+ also_without_location=without_location,
268
+ ),
269
+ list_path.with_name(
270
+ options.output_list.stem
271
+ + (f"_{language}" if language else "")
272
+ + ("_onlyExisting" if not without_location else "")
273
+ + ("_onlyPrimaryNames" if not alternatives else "")
274
+ + options.output_list.suffix
275
+ ),
276
+ )
226
277
 
227
278
  logging.info("Start creating label pdf.")
228
279
  labelprinter_typst.LabelPrinter().create_labels(
@@ -13,7 +13,7 @@ from typing import Callable, Sequence, Iterable, Optional, Self, Iterator, cast
13
13
 
14
14
  from . import constant
15
15
  from .box import BoxedThing
16
- from .location import Schema
16
+ from .location import Schema, InvalidLocation
17
17
 
18
18
 
19
19
  class Inventory(list[BoxedThing]):
@@ -238,6 +238,64 @@ class Inventory(list[BoxedThing]):
238
238
 
239
239
  return supers.difference({thing})
240
240
 
241
+ def set_levelname(self, schema: Schema, new_levelname: str):
242
+ """Set the levelname of one location schema.
243
+
244
+ Strip off whitespace.
245
+
246
+ Empty levelname raises ValueError if sub schemas exist.
247
+ Otherwise, it removes the possibility of this schema having subschemas.
248
+
249
+ Save the changes to disk.
250
+
251
+ This must be a method of the inventory, because all changed things must be saved afterwards
252
+ and the schema does not know which things are affected.
253
+
254
+ Args:
255
+ schema: schema (level) to be changed
256
+ new_levelname: the new levelname
257
+ Raises:
258
+ ValueError: if the new levelname is empty or whitespace-only and sub schemas exist
259
+ """
260
+ schema_name = "a default schema" if schema.is_default() else schema.name
261
+ new_levelname = new_levelname.strip()
262
+ try:
263
+ print(
264
+ f"debug: Change level name of {schema_name} from {schema.levelname} to {new_levelname}."
265
+ )
266
+ except InvalidLocation:
267
+ print(
268
+ f"debug: Set level name of {schema_name} from nothing to {new_levelname}."
269
+ )
270
+ schema.levelname = new_levelname
271
+ self.save()
272
+
273
+ def set_schema_name(self, schema: Schema, new_name: str):
274
+ """Set the name of a schema.
275
+
276
+ Fails on default schemas.
277
+
278
+ This must be a method of the inventory, because all changed things must be saved afterwards
279
+ and the schema does not know which things are affected.
280
+
281
+ Args:
282
+ inventory: inventory with schema and things (both needed)
283
+ schema: schema (level) to be changed
284
+ new_name: new name of the schema. Is stripped of surrounding whitespace.
285
+ Raises:
286
+ ValueError: if new name is empty except for whitespace
287
+ """
288
+ if schema.is_default():
289
+ raise ValueError(f"Cannot change name of default schema.")
290
+ if not new_name.strip():
291
+ raise ValueError(
292
+ f"The new name '{new_name}' is empty (or only whitespace). This is invalid."
293
+ )
294
+ new_name = new_name.strip()
295
+ print(f"debug: Change schema name from {schema.name} to {new_name}")
296
+ schema.name = new_name
297
+ self.save()
298
+
241
299
 
242
300
  def check_filename_types(filetypes: Sequence[str]) -> Callable[[str], pathlib.Path]:
243
301
  """Create function that checks for being a simple file.
@@ -1,13 +1,14 @@
1
1
  #! /usr/bin/env python
2
2
  """Class sign that contains the info for a sign for a thing."""
3
+
3
4
  import itertools
4
5
 
5
6
  import logging
6
7
  import os.path
7
8
 
8
9
  import pathlib
9
- import yaml
10
10
  from typing import Optional, override
11
+ import yaml
11
12
 
12
13
  from . import constant
13
14
  from . import defaulted_data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flinventory"
3
- version = "2.2.3"
3
+ version = "2.3.1"
4
4
  description = "An inventory system for self-help repair shops"
5
5
  authors = [
6
6
  { name = "flukx", email = "flinventory-flukx@612211124.xyz" },
@@ -60,3 +60,6 @@ include = [
60
60
  { path = "thingtemplate_typst" },
61
61
  ]
62
62
  exclude = "tests"
63
+
64
+ [tool.mypy]
65
+ warn_unreachable = true
File without changes
File without changes