flinventory 2.2.4__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.4 → flinventory-2.3.1}/PKG-INFO +1 -1
  2. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/__init__.py +1 -0
  3. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/constant.py +8 -4
  4. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/datacleanup.py +12 -68
  5. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/defaulted_data.py +3 -1
  6. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/generate_labels.py +62 -11
  7. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/inventory_io.py +59 -1
  8. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/sign.py +2 -1
  9. {flinventory-2.2.4 → flinventory-2.3.1}/pyproject.toml +4 -1
  10. {flinventory-2.2.4 → flinventory-2.3.1}/LICENSE +0 -0
  11. {flinventory-2.2.4 → flinventory-2.3.1}/README.md +0 -0
  12. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/__main__.py +0 -0
  13. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/box.py +0 -0
  14. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/font_location.py +0 -0
  15. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/labelprinter_typst.py +0 -0
  16. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/location.py +0 -0
  17. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/signprinter_latex.py +0 -0
  18. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thing.py +0 -0
  19. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thingtemplate_typst/dummyImage.jpg +0 -0
  20. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thingtemplate_typst/label.typ +0 -0
  21. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thingtemplate_typst/out/bowdenzugklemmung1.svg +0 -0
  22. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thingtemplate_typst/out/things.log +0 -0
  23. {flinventory-2.2.4 → flinventory-2.3.1}/flinventory/thingtemplate_typst/thinglist.typ +0 -0
  24. {flinventory-2.2.4 → flinventory-2.3.1}/tests/__init__.py +0 -0
  25. {flinventory-2.2.4 → 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.4
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,9 +8,9 @@ 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
- from typing import Optional
14
14
 
15
15
  from . import constant, labelprinter_typst
16
16
  from . import inventory_io
@@ -74,7 +74,7 @@ def add_arg_parsers(subparsers):
74
74
  + " are not printed next time."
75
75
  ),
76
76
  )
77
- parser_printed.set_defaults(func=add_printed)
77
+ parser_printed.set_defaults(func=add_printed_from_args)
78
78
 
79
79
  parser_shortcutreplacement = subparsers.add_parser(
80
80
  "shortcut",
@@ -199,38 +199,7 @@ def set_levelname_from_args(args):
199
199
  """Set the levelname of one location schema."""
200
200
  new_levelname = args.levelname
201
201
  schema, inventory = get_schema_from_args(args)
202
- set_levelname(inventory, schema, new_levelname)
203
-
204
-
205
- def set_levelname(
206
- inventory: inventory_io.Inventory, schema: location.Schema, new_levelname: str
207
- ):
208
- """Set the levelname of one location schema.
209
-
210
- Strip off whitespace.
211
-
212
- Empty levelname raises ValueError if sub schemas exist.
213
- Otherwise, it removes the possibility of this schema having subschemas.
214
-
215
- Save the changes to disk.
216
-
217
- Args:
218
- inventory: inventory with schema and things (both needed)
219
- schema: schema (level) to be changed
220
- new_levelname: the new levelname
221
- Raises:
222
- ValueError: if the new levelname is empty or whitespace-only and sub schemas exist
223
- """
224
- schema_name = "a default schema" if schema.is_default() else schema.name
225
- new_levelname = new_levelname.strip()
226
- try:
227
- print(
228
- f"Change level name of {schema_name} from {schema.levelname} to {new_levelname}."
229
- )
230
- except location.InvalidLocation:
231
- print(f"Set level name of {schema_name} from nothing to {new_levelname}.")
232
- schema.levelname = new_levelname
233
- inventory.save()
202
+ inventory.set_levelname(schema, new_levelname)
234
203
 
235
204
 
236
205
  def set_schema_name_from_args(args):
@@ -243,33 +212,7 @@ def set_schema_name_from_args(args):
243
212
  ValueError: if new name is empty except for whitespace
244
213
  """
245
214
  schema, inventory = get_schema_from_args(args)
246
- set_schema_name(inventory, schema, args.name)
247
-
248
-
249
- def set_schema_name(
250
- inventory: inventory_io.Inventory, schema: location.Schema, new_name: str
251
- ):
252
- """Set the name of a schema.
253
-
254
- Fails on default schemas.
255
-
256
- Args:
257
- inventory: inventory with schema and things (both needed)
258
- schema: schema (level) to be changed
259
- new_name: new name of the schema. Is stripped of surrounding whitespace.
260
- Raises:
261
- ValueError: if new name is empty except for whitespace
262
- """
263
- if schema.is_default():
264
- raise ValueError(f"Cannot change name of default schema.")
265
- if not new_name.strip():
266
- raise ValueError(
267
- f"The new name '{new_name}' is empty (or only whitespace). This is invalid."
268
- )
269
- new_name = new_name.strip()
270
- print(f"debug: Change schema name from {schema.name} to {new_name}")
271
- schema.name = new_name
272
- inventory.save()
215
+ inventory.set_schema_name(schema, args.name)
273
216
 
274
217
 
275
218
  def example_action(args):
@@ -337,14 +280,15 @@ def use_ids(args):
337
280
  things.save()
338
281
 
339
282
 
340
- def add_printed(args):
341
- """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."""
342
290
  things = (
343
- labelprinter_typst.LabelPrinter()
344
- .sign_things_to_be_printed(
345
- inventory_io.Inventory.from_json_files(directory=args.dataDirectory)
346
- )
347
- .keys()
291
+ labelprinter_typst.LabelPrinter().sign_things_to_be_printed(inventory).keys()
348
292
  )
349
293
  for thing in things:
350
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.4"
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