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