Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/uid.py: 90%
42 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-18 12:33 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-18 12:33 +0000
1import typing as t
2from viur.core import db
3from viur.core.bones.base import BaseBone, Compute, ComputeInterval, ComputeMethod, UniqueValue, UniqueLockMethod
6def generate_number(db_key: db.Key) -> int:
7 """
8 The generate_number method generates a leading number that is always unique per entry.
9 """
11 def transact(_key: db.Key):
12 # A commit conflict only surfaces when the surrounding transaction commits and is
13 # retried by db.run_in_transaction, so it must propagate out of this function.
14 if db_obj := db.get(_key):
15 db_obj["count"] += 1
16 else:
17 db_obj = db.Entity(_key)
18 db_obj["count"] = 0
19 db.put(db_obj)
20 return db_obj["count"]
22 if db.is_in_transaction(): 22 ↛ 25line 22 didn't jump to line 25 because the condition on line 22 was always true
23 return transact(db_key)
24 else:
25 return db.run_in_transaction(transact, db_key)
28def generate_uid(skel, bone):
29 db_key = db.Key("viur-uids", f"{skel.kindName}-{bone.name}-uid")
30 count_value = generate_number(db_key)
31 if bone.fillchar: 31 ↛ 37line 31 didn't jump to line 37 because the condition on line 31 was always true
32 # The wildcard itself is replaced, so it does not count towards the length of the prefix.
33 length_to_fill = bone.length - (len(bone.pattern) - 1)
34 res = str(count_value).rjust(length_to_fill, bone.fillchar)
35 return bone.pattern.replace("*", res)
36 else:
37 return bone.pattern.replace("*", str(count_value))
40class UidBone(BaseBone):
41 """
42 The "UidBone" represents a data field that contains text values.
43 """
44 type = "uid"
46 def __init__(
47 self,
48 *,
49 generate_fn: t.Callable = generate_uid,
50 fillchar: str = "0",
51 length: int = 13,
52 pattern: str | t.Callable | None = "*",
53 **kwargs
54 ):
55 """
56 Initializes a new UidBone.
58 :param generate_fn: The compute function to calculate the unique value,
59 :param fillchar The char that are filed in when the uid has not the length.
60 :param length: The length allowed for values of this bone.
61 :param pattern: The pattern for this Bone. "*" will be replaced with the uid value.
62 :param kwargs: Inherited arguments from the BaseBone.
63 """
65 super().__init__(
66 compute=Compute(fn=generate_fn, interval=ComputeInterval(ComputeMethod.Once)),
67 unique=UniqueValue(UniqueLockMethod.SameValue, False, "Unique Value already in use"),
68 **kwargs
69 )
70 if self.multiple or self.languages:
71 raise ValueError("UidBone cannot be multiple or translated")
73 if not self.readOnly: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 raise ValueError("UidBone must be read-only")
76 self.fillchar = str(fillchar)
77 self.length = length
78 if isinstance(pattern, t.Callable):
79 pattern = pattern()
80 self.pattern = str(pattern)
81 if self.pattern.count("*") != 1:
82 raise ValueError("Only one wildcard (*) is allowed and required in the pattern")
83 if len(self.fillchar) != 1:
84 raise ValueError("Only one char is allowed as fillchar")
86 def structure(self) -> dict:
87 ret = super().structure() | {
88 "fillchar": self.fillchar,
89 "length": self.length,
90 "pattern": self.pattern
91 }
92 return ret