diff options
| author | stuppie | 2026-09-04 16:35:33 -0600 |
|---|---|---|
| committer | stuppie | 2026-09-04 16:35:33 -0600 |
| commit | 7690eb3cc1f575e6bc16e8f7022d613601ebb14d (patch) | |
| tree | b0d89804ec8edac073edef39e82511448a53fdfe | |
| parent | 48687643a8e11ce8f7f01cf5e721d4bb7202cd82 (diff) | |
| download | generalresearch-7690eb3cc1f575e6bc16e8f7022d613601ebb14d.tar.gz generalresearch-7690eb3cc1f575e6bc16e8f7022d613601ebb14d.zip | |
experimenting with a UserRef model to avoid this import hell
| -rw-r--r-- | generalresearch/models/thl/__init__.py | 12 | ||||
| -rw-r--r-- | generalresearch/models/thl/user.py | 26 | ||||
| -rw-r--r-- | generalresearch/models/thl/user_identifiers.py | 33 | ||||
| -rw-r--r-- | generalresearch/models/thl/user_ref.py | 17 | ||||
| -rw-r--r-- | generalresearch/models/thl/wallet/cashout_method.py | 30 | ||||
| -rw-r--r-- | generalresearch/models/thl/wallet/user_wallet.py | 10 |
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() |
