Skip to content

Commit

Permalink
feat: Change LinkId field from int to UUID and generate LinkId determ…
Browse files Browse the repository at this point in the history
…inistically from URL
  • Loading branch information
osoken committed Oct 6, 2024
1 parent 3fff12c commit 115fb1e
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 22 deletions.
68 changes: 59 additions & 9 deletions common/birdxplorer_common/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from enum import Enum
from random import Random
from typing import Any, Dict, List, Literal, Optional, Type, TypeAlias, TypeVar, Union
from uuid import UUID

from pydantic import BaseModel as PydanticBaseModel
from pydantic import ConfigDict, GetCoreSchemaHandler, HttpUrl, TypeAdapter
from pydantic import (
ConfigDict,
GetCoreSchemaHandler,
HttpUrl,
TypeAdapter,
model_validator,
)
from pydantic.alias_generators import to_camel
from pydantic.main import IncEx
from pydantic_core import core_schema
Expand Down Expand Up @@ -677,24 +685,66 @@ class XUser(BaseModel):
MediaDetails: TypeAlias = List[HttpUrl] | None


class LinkId(NonNegativeInt):
class LinkId(UUID):
"""
>>> LinkId.from_int(1)
LinkId(1)
>>> LinkId("53dc4ed6-fc9b-54ef-1afa-90f1125098c5")
LinkId('53dc4ed6-fc9b-54ef-1afa-90f1125098c5')
>>> LinkId(UUID("53dc4ed6-fc9b-54ef-1afa-90f1125098c5"))
LinkId('53dc4ed6-fc9b-54ef-1afa-90f1125098c5')
"""

pass
def __init__(
self,
hex: str | None = None,
int: int | None = None,
) -> None:
if isinstance(hex, UUID):
hex = str(hex)
super().__init__(hex, int=int)

@classmethod
def from_url(cls, url: HttpUrl) -> "LinkId":
"""
>>> LinkId.from_url("https://example.com/")
LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6')
"""
random_number_generator = Random()
random_number_generator.seed(str(url).encode("utf-8"))
return LinkId(int=random_number_generator.getrandbits(128))

@classmethod
def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
return core_schema.no_info_plain_validator_function(
cls.validate,
serialization=core_schema.plain_serializer_function_ser_schema(cls.serialize, when_used="json"),
)

@classmethod
def validate(cls, v: Any) -> "LinkId":
return cls(v)

def serialize(self) -> str:
return str(self)


class Link(BaseModel):
"""
>>> Link.model_validate_json('{"linkId": 1, "canonicalUrl": "https://example.com", "shortUrl": "https://example.com/short"}')
Link(link_id=LinkId(1), canonical_url=Url('https://example.com/'), short_url=Url('https://example.com/short'))
>>> Link.model_validate_json('{"linkId": "d5d15194-6574-0c01-8f6f-15abd72b2cf6", "url": "https://example.com"}')
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
>>> Link(url="https://example.com/")
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
>>> Link(link_id=UUID("d5d15194-6574-0c01-8f6f-15abd72b2cf6"), url="https://example.com/")
Link(link_id=LinkId('d5d15194-6574-0c01-8f6f-15abd72b2cf6'), url=Url('https://example.com/'))
""" # noqa: E501

link_id: LinkId
canonical_url: HttpUrl
short_url: HttpUrl
url: HttpUrl

@model_validator(mode="before")
def validate_link_id(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if "link_id" not in values:
values["link_id"] = LinkId.from_url(values["url"])
return values


class Post(BaseModel):
Expand Down
12 changes: 4 additions & 8 deletions common/birdxplorer_common/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from sqlalchemy import ForeignKey, create_engine, func, select
from sqlalchemy.engine import Engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship
from sqlalchemy.types import CHAR, DECIMAL, JSON, Integer, String
from sqlalchemy.types import CHAR, DECIMAL, JSON, Integer, String, Uuid

from .models import BinaryBool, LanguageIdentifier
from .models import Link as LinkModel
Expand Down Expand Up @@ -36,7 +36,7 @@ def adapt_pydantic_http_url(url: AnyUrl) -> AsIs:

class Base(DeclarativeBase):
type_annotation_map = {
LinkId: Integer,
LinkId: Uuid,
TopicId: Integer,
TopicLabel: JSON,
NoteId: String,
Expand Down Expand Up @@ -95,8 +95,7 @@ class LinkRecord(Base):
__tablename__ = "links"

link_id: Mapped[LinkId] = mapped_column(primary_key=True)
canonical_url: Mapped[HttpUrl] = mapped_column(nullable=False, index=True)
short_url: Mapped[HttpUrl] = mapped_column(nullable=False, index=True)
url: Mapped[HttpUrl] = mapped_column(nullable=False, index=True)


class PostLinkAssociation(Base):
Expand Down Expand Up @@ -216,10 +215,7 @@ def _post_record_to_model(cls, post_record: PostRecord) -> PostModel:
like_count=post_record.like_count,
repost_count=post_record.repost_count,
impression_count=post_record.impression_count,
links=[
LinkModel(link_id=link.link_id, canonical_url=link.link.canonical_url, short_url=link.link.short_url)
for link in post_record.links
],
links=[LinkModel(link_id=link.link_id, url=link.link.url) for link in post_record.links],
)

def get_user_enrollment_by_participant_id(self, participant_id: ParticipantId) -> UserEnrollment:
Expand Down
10 changes: 5 additions & 5 deletions common/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ def topic_samples(topic_factory: TopicFactory) -> Generator[List[Topic], None, N
@fixture
def link_samples(link_factory: LinkFactory) -> Generator[List[Link], None, None]:
links = [
link_factory.build(link_id=0, canonical_url="https://t.co/xxxxxxxxxxx/", short_url="https://example.com/sh0"),
link_factory.build(link_id=1, canonical_url="https://t.co/yyyyyyyyyyy/", short_url="https://example.com/sh1"),
link_factory.build(link_id=2, canonical_url="https://t.co/zzzzzzzzzzz/", short_url="https://example.com/sh2"),
link_factory.build(link_id=3, canonical_url="https://t.co/wwwwwwwwwww/", short_url="https://example.com/sh3"),
link_factory.build(link_id="9f56ee4a-6b36-b79c-d6ca-67865e54bbd5", url="https://example.com/sh0"),
link_factory.build(link_id="f5b0ac79-20fe-9718-4a40-6030bb62d156", url="https://example.com/sh1"),
link_factory.build(link_id="76a0ac4a-a20c-b1f4-1906-d00e2e8f8bf8", url="https://example.com/sh2"),
link_factory.build(link_id="6c352be8-eca3-0d96-55bf-a9bbef1c0fc2", url="https://example.com/sh3"),
]
yield links

Expand Down Expand Up @@ -416,7 +416,7 @@ def link_records_sample(
link_samples: List[Link],
engine_for_test: Engine,
) -> Generator[List[LinkRecord], None, None]:
res = [LinkRecord(link_id=d.link_id, canonical_url=d.canonical_url, short_url=d.short_url) for d in link_samples]
res = [LinkRecord(link_id=d.link_id, url=d.url) for d in link_samples]
with Session(engine_for_test) as sess:
sess.add_all(res)
sess.commit()
Expand Down

0 comments on commit 115fb1e

Please sign in to comment.