diff options
| author | Max Nanis | 2026-10-11 19:17:02 -0700 |
|---|---|---|
| committer | Max Nanis | 2026-10-11 19:17:02 -0700 |
| commit | 590b57d0a495f2cfbbb2e4187b59c9ac25b8896b (patch) | |
| tree | 906a33bcef2515b484264b36fbb186a282cf0c8f | |
| parent | 64a93f0874381d19212e1af4c0beacaeb36323d9 (diff) | |
| download | generalresearch-3.7.7.tar.gz generalresearch-3.7.7.zip | |
| -rw-r--r-- | generalresearch/managers/gr/business.py | 49 | ||||
| -rw-r--r-- | generalresearch/managers/gr/team.py | 29 | ||||
| -rw-r--r-- | generalresearch/managers/thl/product.py | 49 | ||||
| -rw-r--r-- | generalresearch/models/gr/authentication.py | 175 | ||||
| -rw-r--r-- | pyproject.toml | 2 |
5 files changed, 164 insertions, 140 deletions
diff --git a/generalresearch/managers/gr/business.py b/generalresearch/managers/gr/business.py index 9338f03..bb7944d 100644 --- a/generalresearch/managers/gr/business.py +++ b/generalresearch/managers/gr/business.py @@ -314,48 +314,33 @@ class BusinessManager(PostgresManagerWithRedis): return response - def get_ids_by_user_id(self, user_id: PositiveInt) -> list[PositiveInt]: + def get_uuids(self, user_id: PositiveInt | None) -> list[UUIDStr]: """ - :return: Every Business UUIDStr that this GRUser has permission to view + :param user_id: If given, restrict to Businesses this GRUser has permission to view. + If None, return every Business. + :return: Business UUIDStrs """ - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" - SELECT b.id + if user_id is None: + query = sql.SQL(""" + SELECT b.uuid FROM common_business AS b - INNER JOIN common_team_businesses AS tb - ON tb.business_id = b.id - INNER JOIN common_membership AS cm - ON tb.team_id = cm.team_id - WHERE cm.user_id = %s - """), - params=(user_id,), - ) - - res = c.fetchall() - - return [i["id"] for i in res] - - def get_uuids_by_user_id(self, user_id: PositiveInt) -> list[UUIDStr]: - """ - :return: Every Business UUIDStr that this GRUser has permission to view - """ - - with self.pg_config.make_connection() as conn, conn.cursor() as c: - c.execute( - query=sql.SQL(""" + """) + params: tuple[int, ...] = () + else: + query = sql.SQL(""" SELECT b.uuid FROM common_business AS b - INNER JOIN common_team_businesses AS tb + INNER JOIN common_team_businesses AS tb ON tb.business_id = b.id - INNER JOIN common_membership AS cm + INNER JOIN common_membership AS cm ON tb.team_id = cm.team_id WHERE cm.user_id = %s - """), - params=(user_id,), - ) + """) + params = (user_id,) + with self.pg_config.make_connection() as conn, conn.cursor() as c: + c.execute(query=query, params=params) res = c.fetchall() return [i["uuid"] for i in res] diff --git a/generalresearch/managers/gr/team.py b/generalresearch/managers/gr/team.py index 41af709..968f3b5 100644 --- a/generalresearch/managers/gr/team.py +++ b/generalresearch/managers/gr/team.py @@ -278,3 +278,32 @@ class TeamManager(PostgresManagerWithRedis): res = c.fetchall() return [Team.model_validate(item) for item in res] + + def get_uuids(self, user_id: PositiveInt | None) -> list[UUIDStr]: + """ + :param user_id: If given, restrict to Teams this GRUser has permission to view. + If None, return every Team. + :return: Team UUIDStrs + """ + + if user_id is None: + query = sql.SQL(""" + SELECT team.uuid + FROM common_team AS team + """) + params: tuple[int, ...] = () + else: + query = sql.SQL(""" + SELECT team.uuid + FROM common_team AS team + INNER JOIN common_membership AS mem + ON mem.team_id = team.id + WHERE mem.user_id = %s + """) + params = (user_id,) + + with self.pg_config.make_connection() as conn, conn.cursor() as c: + c.execute(query=query, params=params) + res = c.fetchall() + + return [i["uuid"] for i in res] diff --git a/generalresearch/managers/thl/product.py b/generalresearch/managers/thl/product.py index aadb30b..78b0b7a 100644 --- a/generalresearch/managers/thl/product.py +++ b/generalresearch/managers/thl/product.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any from cachetools import TTLCache, cachedmethod, keys from psycopg import Connection, sql -from pydantic import NonNegativeInt +from pydantic import NonNegativeInt, PositiveInt from sentry_sdk import capture_exception from generalresearch.managers.base import ( @@ -128,7 +128,7 @@ class ProductManager(PostgresManager): product_uuids = self.get_all_uuids(rand_limit=rand_limit) return self.filter_paginated(product_uuids=product_uuids) - def get_all_uuids(self, rand_limit: int | None) -> list[UUIDStr]: + def get_all_uuids(self, rand_limit: PositiveInt | None) -> list[UUIDStr]: if rand_limit: res = self.pg_config.execute_sql_query( @@ -142,12 +142,10 @@ class ProductManager(PostgresManager): ) else: - res = self.pg_config.execute_sql_query( - query=""" + res = self.pg_config.execute_sql_query(query=""" SELECT p.id::uuid FROM userprofile_brokerageproduct AS p - """ - ) + """) return [i["id"] for i in res] def filter_paginated( @@ -163,7 +161,7 @@ class ProductManager(PostgresManager): order_field: str = "created", descending: bool = False, conn: Connection | None = None, - ): + ) -> list[Product]: """ Automatically paginate the results of a filter query. """ @@ -390,6 +388,27 @@ class ProductManager(PostgresManager): res = c.fetchone() return int(res["cnt"]) + def get_uuids( + self, + business_uuids: list[UUIDStr] | None = None, + team_uuids: list[UUIDStr] | None = None, + conn: Connection | None = None, + ) -> list[UUIDStr]: + filter_str, params = self.make_filter_str( + business_uuids=business_uuids, + team_uuids=team_uuids, + ) + query = f""" + SELECT bp.uuid + FROM userprofile_brokerageproduct AS bp + {filter_str} + """ + with self.connection(conn) as conn, conn.cursor() as c: + c.execute(query=query, params=params) + res = c.fetchall() + + return [i["uuid"] for i in res] + @staticmethod def make_filter_str( product_uuids: list[UUIDStr] | None = None, @@ -404,9 +423,9 @@ class ProductManager(PostgresManager): params = {} filters = [] identifiers = [product_uuids, business_uuids, team_uuids] - assert sum([bool(x) for x in identifiers]) == 1, ( - "Must provide exactly one set of identifiers" - ) + assert ( + sum([bool(x) for x in identifiers]) == 1 + ), "Must provide exactly one set of identifiers" identifier = next(x for x in identifiers if x) assert all(is_valid_uuid(v) for v in identifier), "invalid uuid" @@ -557,16 +576,10 @@ class ProductManager(PostgresManager): insert_data["payments_enabled"] = instance.payments_enabled try: - insert_data["id_int"] = next( - iter( - self.pg_config.execute_sql_query( - query=""" + insert_data["id_int"] = next(iter(self.pg_config.execute_sql_query(query=""" SELECT COALESCE(MAX(id_int), 0) + 1 as id_int FROM userprofile_brokerageproduct - """ - ) - ) - )["id_int"] + """)))["id_int"] instance.id_int = insert_data["id_int"] query = """ diff --git a/generalresearch/models/gr/authentication.py b/generalresearch/models/gr/authentication.py index 142f842..fa0bcae 100644 --- a/generalresearch/models/gr/authentication.py +++ b/generalresearch/models/gr/authentication.py @@ -16,10 +16,12 @@ from pydantic import ( field_validator, ) -from generalresearch.decorators import LOG from generalresearch.models.custom_types import AwareDatetimeISO, UUIDStr if TYPE_CHECKING: + from generalresearch.managers.gr.business import BusinessManager + from generalresearch.managers.gr.team import TeamManager + from generalresearch.managers.thl.product import ProductManager from generalresearch.models.gr.business import Business from generalresearch.models.gr.team import Team from generalresearch.models.thl.product import Product @@ -164,8 +166,8 @@ class GRUser(BaseModel): self.prefetch_businesses(pg_config=pg_config, redis_config=redis_config) self.prefetch_teams(pg_config=pg_config, redis_config=redis_config) - business_uuids = self.business_uuids or [] - team_uuids = self.team_uuids or [] + business_uuids = [b.uuid for b in self.businesses or []] + team_uuids = [t.uuid for t in self.teams or []] if len(business_uuids + team_uuids) == 0: self.products = [] @@ -205,83 +207,78 @@ class GRUser(BaseModel): def cache_key(self) -> str: return f"gr_user:{self.id}" - @property - def business_uuids(self) -> list[UUIDStr] | None: - if self.businesses is None: - LOG.warning("prefetch not run") - return None - - return [b.uuid for b in self.businesses] - - @property - def business_ids(self) -> list[PositiveInt] | None: - if self.businesses is None: - LOG.warning("prefetch not run") - return None - - return [b.id for b in self.businesses if b.id is not None] - - @property - def team_uuids(self) -> list[UUIDStr] | None: - if self.teams is None: - LOG.warning("prefetch not run") - return None - - return [t.uuid for t in self.teams] - - @property - def team_ids(self) -> list[PositiveInt] | None: - if self.teams is None: - LOG.warning("prefetch not run") - return None - - return [t.id for t in self.teams if t.id is not None] - - @property - def product_uuids(self) -> list[UUIDStr] | None: - if self.products is None: - LOG.warning("prefetch not run") - return None + # --- Methods --- - return [p.uuid for p in self.products] + def business_uuids(self, business_manager: BusinessManager) -> list[UUIDStr]: + if self.is_superuser: + return business_manager.get_uuids(user_id=None) + else: + return business_manager.get_uuids(user_id=self.id) - # --- Methods --- + def team_uuids(self, team_manager: TeamManager) -> list[UUIDStr]: + if self.is_superuser: + return team_manager.get_uuids(user_id=None) + else: + return team_manager.get_uuids(user_id=self.id) - def set_cache( + def product_uuids( self, - pg_config: PostgresConfig, - thl_web_rr: PostgresConfig, - redis_config: RedisConfig, - ) -> None: - ex_secs = 60 * 60 * 24 * 3 # 3 days - - self.prefetch_teams(pg_config=pg_config, redis_config=redis_config) - self.prefetch_businesses(pg_config=pg_config, redis_config=redis_config) - self.prefetch_products( - pg_config=pg_config, - thl_pg_config=thl_web_rr, - redis_config=redis_config, + business_manager: BusinessManager | None, + team_manager: TeamManager | None, + product_manager: ProductManager, + ) -> list[UUIDStr]: + assert ( + business_manager is not None or team_manager is not None + ), "At least one of business_manager or team_manager is required" + + return product_manager.get_uuids( + business_uuids=( + self.business_uuids(business_manager=business_manager) + if business_manager is not None + else None + ), + team_uuids=( + self.team_uuids(team_manager=team_manager) + if team_manager is not None + else None + ), ) - self.prefetch_token(pg_config=pg_config) - rc = redis_config.create_redis_client() - - rc.set(name=self.cache_key, value=self.to_redis(), ex=ex_secs) - rc.set( - name=f"{self.cache_key}:team_uuids", - value=json.dumps(self.team_uuids), - ex=ex_secs, - ) - rc.set( - name=f"{self.cache_key}:business_uuids", - value=json.dumps(self.business_uuids), - ex=ex_secs, - ) - rc.set( - name=f"{self.cache_key}:product_uuids", - value=json.dumps(self.product_uuids), - ex=ex_secs, - ) + # def set_cache( + # self, + # pg_config: PostgresConfig, + # thl_web_rr: PostgresConfig, + # redis_config: RedisConfig, + # ) -> None: + # ex_secs = 60 * 60 * 24 * 3 # 3 days + + # self.prefetch_teams(pg_config=pg_config, redis_config=redis_config) + # self.prefetch_businesses(pg_config=pg_config, redis_config=redis_config) + # self.prefetch_products( + # pg_config=pg_config, + # thl_pg_config=thl_web_rr, + # redis_config=redis_config, + # ) + # self.prefetch_token(pg_config=pg_config) + + # rc = redis_config.create_redis_client() + + # rc.set(name=self.cache_key, value=self.to_redis(), ex=ex_secs) + # rc.set( + # name=f"{self.cache_key}:team_uuids", + # value=json.dumps(self.team_uuids), + # ex=ex_secs, + # ) + # rc.set( + # name=f"{self.cache_key}:business_uuids", + # value=json.dumps(self.business_uuids), + # ex=ex_secs, + # ) + # rc.set( + # name=f"{self.cache_key}:product_uuids", + # value=json.dumps(self.product_uuids), + # ex=ex_secs, + # ) # --- ORM --- @@ -290,26 +287,26 @@ class GRUser(BaseModel): d["date_joined"] = d["date_joined"].replace(tzinfo=UTC) return GRUser.model_validate(d) - @classmethod - def from_redis(cls, d: str | dict[str, Any]) -> GRUser: - if isinstance(d, str): - d = json.loads(d) - assert isinstance(d, dict) + # @classmethod + # def from_redis(cls, d: str | dict[str, Any]) -> GRUser: + # if isinstance(d, str): + # d = json.loads(d) + # assert isinstance(d, dict) - d["date_joined"] = datetime.fromisoformat(d["date_joined"]) + # d["date_joined"] = datetime.fromisoformat(d["date_joined"]) - if d.get("token"): - d["token"] = GRToken.from_redis(d["token"]) + # if d.get("token"): + # d["token"] = GRToken.from_redis(d["token"]) - return GRUser.model_validate(d) + # return GRUser.model_validate(d) - def to_redis(self) -> str: - d = self.model_dump(mode="json", exclude={"businesses", "teams", "products"}) - d["business_uuids"] = self.business_uuids - d["team_uuids"] = self.team_uuids - d["product_uuids"] = self.product_uuids + # def to_redis(self) -> str: + # d = self.model_dump(mode="json", exclude={"businesses", "teams", "products"}) + # d["business_uuids"] = self.business_uuids + # d["team_uuids"] = self.team_uuids + # d["product_uuids"] = self.product_uuids - return json.dumps(d) + # return json.dumps(d) class GRToken(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index 5839d26..bf52137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "generalresearch" -version = "3.7.6" +version = "3.7.7" description = "Python Utilities for General Research" readme = "README.md" requires-python = ">=3.14" |
