v0.148.0
 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