1from __future__ import annotations
2
3import re
4
5from plain import postgres
6from plain.exceptions import ValidationError
7from plain.postgres import types
8
9__all__ = ["Flag", "FlagResult"]
10
11
12def validate_flag_name(value: str) -> None:
13 if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value):
14 raise ValidationError(f"{value} is not a valid Python identifier name")
15
16
17@postgres.register_model
18class FlagResult(postgres.Model):
19 created_at = types.DateTimeField(create_now=True)
20 updated_at = types.DateTimeField(create_now=True, update_now=True)
21 flag: Flag = types.ForeignKeyField("Flag", on_delete=postgres.CASCADE)
22 key = types.TextField(max_length=255)
23 value = types.JSONField()
24
25 query: postgres.QuerySet[FlagResult] = postgres.QuerySet()
26
27 model_options = postgres.Options(
28 constraints=[
29 postgres.UniqueConstraint(
30 fields=["flag", "key"], name="plainflags_flagresult_unique_key"
31 ),
32 ],
33 )
34
35 def __str__(self) -> str:
36 return self.key
37
38
39@postgres.register_model
40class Flag(postgres.Model):
41 created_at = types.DateTimeField(create_now=True)
42 updated_at = types.DateTimeField(create_now=True, update_now=True)
43 name = types.TextField(max_length=255, validators=[validate_flag_name])
44
45 # Optional description that can be filled in after the flag is used/created
46 description = types.TextField(required=False)
47
48 # To manually disable a flag before completing deleting
49 # (good to disable first to make sure the code doesn't use the flag anymore)
50 enabled = types.BooleanField(default=True)
51
52 # To provide an easier way to see if a flag is still being used
53 used_at = types.DateTimeField(required=False, allow_null=True)
54
55 query: postgres.QuerySet[Flag] = postgres.QuerySet()
56
57 model_options = postgres.Options(
58 constraints=[
59 postgres.UniqueConstraint(
60 fields=["name"], name="plainflags_flag_unique_name"
61 ),
62 ],
63 )
64
65 def __str__(self) -> str:
66 return self.name