aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuppie2026-09-04 16:35:33 -0600
committerstuppie2026-09-04 16:35:33 -0600
commit7690eb3cc1f575e6bc16e8f7022d613601ebb14d (patch)
treeb0d89804ec8edac073edef39e82511448a53fdfe
parent48687643a8e11ce8f7f01cf5e721d4bb7202cd82 (diff)
downloadgeneralresearch-7690eb3cc1f575e6bc16e8f7022d613601ebb14d.tar.gz
generalresearch-7690eb3cc1f575e6bc16e8f7022d613601ebb14d.zip
experimenting with a UserRef model to avoid this import hell
-rw-r--r--generalresearch/models/thl/__init__.py12
-rw-r--r--generalresearch/models/thl/user.py26
-rw-r--r--generalresearch/models/thl/user_identifiers.py33
-rw-r--r--generalresearch/models/thl/user_ref.py17
-rw-r--r--generalresearch/models/thl/wallet/cashout_method.py30
-rw-r--r--generalresearch/models/thl/wallet/user_wallet.py10
6 files changed, 69 insertions, 59 deletions
diff --git a/generalresearch/models/thl/__init__.py b/generalresearch/models/thl/__init__.py
index d0791e4..4a29349 100644
--- a/generalresearch/models/thl/__init__.py
+++ b/generalresearch/models/thl/__init__.py
@@ -9,15 +9,9 @@ from generalresearch.models.thl.payout import (
)
from generalresearch.models.thl.product import Product
-_ = (
- Product,
- PayoutEvent,
- BrokerageProductPayoutEvent,
- ProductBalances,
- POPFinancial,
-)
-
Product.model_rebuild()
-LedgerAccount.model_rebuild()
PayoutEvent.model_rebuild()
BrokerageProductPayoutEvent.model_rebuild()
+ProductBalances.model_rebuild()
+POPFinancial.model_rebuild()
+LedgerAccount.model_rebuild()
diff --git a/generalresearch/models/thl/user.py b/generalresearch/models/thl/user.py
index 9944944..77f9069 100644
--- a/generalresearch/models/thl/user.py
+++ b/generalresearch/models/thl/user.py
@@ -28,6 +28,7 @@ from generalresearch.models.definitions import MAX_INT32
from generalresearch.models.thl.ipinfo import GeoIPInformation
from generalresearch.models.thl.ledger import LedgerTransaction
from generalresearch.models.thl.product import Product
+from generalresearch.models.thl.user_identifiers import BPUIDStr
from generalresearch.models.thl.userhealth import AuditLog
if TYPE_CHECKING:
@@ -40,8 +41,6 @@ if TYPE_CHECKING:
logger = logging.getLogger()
-BPUID_ALLOWED = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-.:;<=>?@[\]^_{|}~"
-
class User(BaseModel):
model_config = ConfigDict(extra="forbid", validate_assignment=True)
@@ -105,21 +104,6 @@ class User(BaseModel):
)
# --- Validation ---
- @field_validator("product_user_id")
- def check_product_user_id(cls, v: str | None) -> str:
- if v is not None:
- if " " in v:
- raise ValueError("String cannot contain spaces")
- if "\\" in v:
- raise ValueError("String cannot contain backslash")
- if "/" in v:
- raise ValueError("String cannot contain slash")
- # I think the * on the regex messes up value matches that are
- # the same length as the
- rex = re.fullmatch("[" + BPUID_ALLOWED + "]*", v)
- if not bool(rex):
- raise ValueError("String is not valid regex")
- return v
# noinspection PyNestedDecoratorsk
@field_validator("created", "last_seen")
@@ -312,12 +296,6 @@ class User(BaseModel):
)
-# Used in other places where the bpuid is part of a model that's used in
-# the API (separate from a User)
-BPUIDStr = Annotated[
- str,
- StringConstraints(min_length=3, max_length=128),
- AfterValidator(User.check_product_user_id),
-]
+
User.model_rebuild()
diff --git a/generalresearch/models/thl/user_identifiers.py b/generalresearch/models/thl/user_identifiers.py
new file mode 100644
index 0000000..57a2970
--- /dev/null
+++ b/generalresearch/models/thl/user_identifiers.py
@@ -0,0 +1,33 @@
+import re
+from typing import Annotated
+
+from pydantic import (
+ AfterValidator,
+ StringConstraints,
+)
+
+BPUID_ALLOWED = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-.:;<=>?@[\]^_{|}~"
+
+
+def validate_product_user_id(v: str) -> str:
+ if " " in v:
+ raise ValueError("String cannot contain spaces")
+ if "\\" in v:
+ raise ValueError("String cannot contain backslash")
+ if "/" in v:
+ raise ValueError("String cannot contain slash")
+ # I think the * on the regex messes up value matches that are
+ # the same length as the
+ rex = re.fullmatch("[" + BPUID_ALLOWED + "]*", v)
+ if not bool(rex):
+ raise ValueError("String is not valid regex")
+ return v
+
+
+# Used in other places where the bpuid is part of a model that's used in
+# the API (separate from a User)
+BPUIDStr = Annotated[
+ str,
+ StringConstraints(min_length=3, max_length=128),
+ AfterValidator(validate_product_user_id),
+]
diff --git a/generalresearch/models/thl/user_ref.py b/generalresearch/models/thl/user_ref.py
new file mode 100644
index 0000000..9622abe
--- /dev/null
+++ b/generalresearch/models/thl/user_ref.py
@@ -0,0 +1,17 @@
+from pydantic import BaseModel, PositiveInt
+
+from generalresearch.models.custom_types import UUIDStr
+from generalresearch.models.thl.user_identifiers import BPUIDStr
+
+
+class UserRef(BaseModel):
+ """
+ Use in place of the full User model in places where we want to
+ associate something with a User, but can't use the full User
+ model due to cyclic import issues.
+ As a side-effect, this also avoids the type|None ruff issues.
+ """
+
+ user_id: PositiveInt
+ product_id: UUIDStr
+ product_user_id: BPUIDStr
diff --git a/generalresearch/models/thl/wallet/cashout_method.py b/generalresearch/models/thl/wallet/cashout_method.py
index 286a126..6ad0bf2 100644
--- a/generalresearch/models/thl/wallet/cashout_method.py
+++ b/generalresearch/models/thl/wallet/cashout_method.py
@@ -4,7 +4,7 @@ import hashlib
import logging
from datetime import UTC, datetime
from enum import StrEnum
-from typing import TYPE_CHECKING, Any, Literal, Self
+from typing import Any, Literal, Self
from pydantic import (
BaseModel,
@@ -25,15 +25,12 @@ from generalresearch.models.custom_types import (
)
from generalresearch.models.legacy.api_status import StatusResponse
from generalresearch.models.thl.definitions import PayoutStatus
-from generalresearch.models.thl.wallet.definitions import PayoutType
+from generalresearch.models.thl.locales import CountryISO
+from generalresearch.models.thl.user_identifiers import BPUIDStr
+from generalresearch.models.thl.user_ref import UserRef
+from generalresearch.models.thl.wallet.definitions import Currency, PayoutType
from generalresearch.utils.enum import ReprEnumMeta
-if TYPE_CHECKING:
-
- from generalresearch.models.thl.locales import CountryISO
- from generalresearch.models.thl.user import BPUIDStr, User
- from generalresearch.models.thl.wallet.definitions import Currency
-
logger = logging.getLogger()
example_cashout_method = {
@@ -140,7 +137,7 @@ class CashoutMethodBase(BaseModel):
class CashoutMethod(CashoutMethodBase):
- user: User | None = Field(
+ user: UserRef | None = Field(
default=None,
description="If set, this cashout method is custom for this user. For example"
"a user may have a paypal cashout method with their paypal"
@@ -152,13 +149,13 @@ class CashoutMethod(CashoutMethodBase):
@model_validator(mode="after")
def validate_user(self) -> Self:
if self.type in {PayoutType.PAYPAL, PayoutType.CASH_IN_MAIL}:
- assert (
- self.user is not None
- ), "user_id must be set for this cashout method type"
+ assert self.user is not None, (
+ "user_id must be set for this cashout method type"
+ )
else:
- assert (
- self.user is None
- ), "user_id must NOT be set for this cashout method type"
+ assert self.user is None, (
+ "user_id must NOT be set for this cashout method type"
+ )
return self
@@ -306,8 +303,7 @@ class CashMailOrderData(BaseModel):
default=None,
min_length=1,
max_length=50,
- description="Current status of delivery, e.g., pending, in "
- "transit, delivered",
+ description="Current status of delivery, e.g., pending, in transit, delivered",
)
last_updated: AwareDatetimeISO | None = Field(
default=None,
diff --git a/generalresearch/models/thl/wallet/user_wallet.py b/generalresearch/models/thl/wallet/user_wallet.py
index 625fd52..cbbc6d1 100644
--- a/generalresearch/models/thl/wallet/user_wallet.py
+++ b/generalresearch/models/thl/wallet/user_wallet.py
@@ -1,19 +1,11 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING
from pydantic import BaseModel, ConfigDict, Field, NonNegativeInt
from generalresearch.models.legacy.api_status import StatusResponse
-from generalresearch.models.thl.payout_format import (
- PayoutFormatField,
-)
-
-if TYPE_CHECKING:
- from generalresearch.models.thl.payout_format import (
- PayoutFormatType,
- )
+from generalresearch.models.thl.payout_format import PayoutFormatField, PayoutFormatType
logger = logging.getLogger()