1import re
2from datetime import datetime
3
4from plain.exceptions import ValidationError
5from plain.postgres import Field, types
6
7from plain import postgres
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 Flag(postgres.Model):
19 created_at: Field[datetime] = types.DateTimeField(create_now=True)
20 updated_at: Field[datetime] = types.DateTimeField(create_now=True, update_now=True)
21 name: Field[str] = types.TextField(max_length=255, validators=[validate_flag_name])
22
23 # Optional description that can be filled in after the flag is used/created
24 description: Field[str] = types.TextField(required=False, default="")
25
26 # To manually disable a flag before completing deleting
27 # (good to disable first to make sure the code doesn't use the flag anymore)
28 enabled: Field[bool] = types.BooleanField(default=True)
29
30 # To provide an easier way to see if a flag is still being used
31 used_at: Field[datetime | None] = types.DateTimeField(
32 required=False, allow_null=True, default=None
33 )
34
35 model_options = postgres.Options(
36 constraints=[
37 postgres.UniqueConstraint(
38 fields=["name"], name="plainflags_flag_unique_name"
39 ),
40 ],
41 )
42
43 def __str__(self) -> str:
44 return self.name
45
46
47@postgres.register_model
48class FlagResult(postgres.Model):
49 created_at: Field[datetime] = types.DateTimeField(create_now=True)
50 updated_at: Field[datetime] = types.DateTimeField(create_now=True, update_now=True)
51 flag: Field[Flag] = types.ForeignKeyField(Flag, on_delete=postgres.CASCADE)
52 key: Field[str] = types.TextField(max_length=255)
53 value: Field[dict] = types.JSONField()
54
55 model_options = postgres.Options(
56 constraints=[
57 postgres.UniqueConstraint(
58 fields=["flag", "key"], name="plainflags_flagresult_unique_key"
59 ),
60 ],
61 )
62
63 def __str__(self) -> str:
64 return self.key