Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,142 @@
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID

from src.util.alembic_helpers import created_at_column

# revision identifiers, used by Alembic.
revision: str = '1d3398f9cd8a'
down_revision: Union[str, None] = '5d6412540aba'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

def _alter_anonymous_annotation_agency():
# Add new column
op.add_column(
"anonymous_annotation_agency",
sa.Column(
"session_id",
UUID,
sa.ForeignKey("anonymous_sessions.id"),
nullable=False
)
)

# Drop prior unique constraint/primary key
op.drop_constraint(
"anonymous_annotation_agency_pkey",
"anonymous_annotation_agency"
)

# Add new unique constraint/primary key
op.create_primary_key(
"anonymous_annotation_agency_pkey",
"anonymous_annotation_agency",
["session_id", "url_id", "agency_id"]
)

def _alter_anonymous_annotation_location():
# Add new column
op.add_column(
"anonymous_annotation_location",
sa.Column(
"session_id",
UUID,
sa.ForeignKey("anonymous_sessions.id"),
nullable=False
)
)

# Drop prior unique constraint/primary key
op.drop_constraint(
"anonymous_annotation_location_pkey",
"anonymous_annotation_location"
)

# Add new unique constraint/primary key
op.create_primary_key(
"anonymous_annotation_location_pkey",
"anonymous_annotation_location",
["session_id", "url_id", "location_id"]
)

def _alter_anonymous_annotation_record_type():
# Add new column
op.add_column(
"anonymous_annotation_record_type",
sa.Column(
"session_id",
UUID,
sa.ForeignKey("anonymous_sessions.id"),
nullable=False
)
)

# Drop prior unique constraint/primary key
op.drop_constraint(
"anonymous_annotation_record_type_pkey",
"anonymous_annotation_record_type"
)

# Add new unique constraint/primary key
op.create_primary_key(
"anonymous_annotation_record_type_pkey",
"anonymous_annotation_record_type",
["session_id", "url_id", "record_type"]
)

def _alter_anonymous_annotation_url_type():
# Add new column
op.add_column(
"anonymous_annotation_url_type",
sa.Column(
"session_id",
UUID,
sa.ForeignKey("anonymous_sessions.id"),
nullable=False
)
)

# Drop prior unique constraint/primary key
op.drop_constraint(
"anonymous_annotation_url_type_pkey",
"anonymous_annotation_url_type"
)

# Add new unique constraint/primary key
op.create_primary_key(
"anonymous_annotation_url_type_pkey",
"anonymous_annotation_url_type",
["session_id", "url_id", "url_type"]
)

def upgrade() -> None:
# Create anonymous_sessions table
_create_anonymous_sessions_table()

# Remove all prior anonymous annotations
_remove_prior_sessions()

_alter_anonymous_annotation_agency()
_alter_anonymous_annotation_location()
_alter_anonymous_annotation_record_type()
_alter_anonymous_annotation_url_type()


def _remove_prior_sessions():
for table in [
"anonymous_annotation_agency",
"anonymous_annotation_location",
"anonymous_annotation_record_type",
"anonymous_annotation_url_type"
]:
op.execute(
f"""
DELETE FROM {table}
"""
)

Check failure on line 146 in alembic/versions/2025_12_01_1632-1d3398f9cd8a_create_anonymous_session_users.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] alembic/versions/2025_12_01_1632-1d3398f9cd8a_create_anonymous_session_users.py#L146 <123>

closing bracket does not match indentation of opening bracket's line
Raw output
./alembic/versions/2025_12_01_1632-1d3398f9cd8a_create_anonymous_session_users.py:146:13: E123 closing bracket does not match indentation of opening bracket's line


def _create_anonymous_sessions_table():
op.create_table(
"anonymous_sessions",
sa.Column(
Expand All @@ -28,12 +155,9 @@
server_default=sa.text("gen_random_uuid()"),
primary_key=True
),
created_at_column()
)

# TODO: Update anonymous tables to link to anonymous sessions table

## TODO: Drop any unique IDs forbidding more than a single ID for these columns


def downgrade() -> None:
pass
2 changes: 1 addition & 1 deletion src/api/endpoints/annotate/_shared/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def extract_and_format_get_annotation_result(
session: AsyncSession,
url: URL,
batch_id: int | None = None
):
) -> GetNextURLForAllAnnotationResponse:
html_response_info = DTOConverter.html_content_list_to_html_response_info(
url.html_content
)
Expand Down
27 changes: 27 additions & 0 deletions src/api/endpoints/annotate/anonymous/get/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Protocol, TypeVar

Check warning on line 1 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L1 <100>

Missing docstring in public module
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:1:1: D100 Missing docstring in public module
from uuid import UUID

from marshmallow.fields import Bool

Check warning on line 4 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L4 <401>

'marshmallow.fields.Bool' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:4:1: F401 'marshmallow.fields.Bool' imported but unused
from sqlalchemy import Exists, select, exists, ColumnElement, Boolean

Check warning on line 5 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L5 <401>

'sqlalchemy.Exists' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:5:1: F401 'sqlalchemy.Exists' imported but unused

Check warning on line 5 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L5 <401>

'sqlalchemy.Boolean' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:5:1: F401 'sqlalchemy.Boolean' imported but unused

from src.db.models.impl.url.core.sqlalchemy import URL
from src.db.models.mixins import AnonymousSessionMixin, URLDependentMixin

Check warning on line 8 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L8 <401>

'src.db.models.mixins.AnonymousSessionMixin' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:8:1: F401 'src.db.models.mixins.AnonymousSessionMixin' imported but unused

Check warning on line 8 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L8 <401>

'src.db.models.mixins.URLDependentMixin' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:8:1: F401 'src.db.models.mixins.URLDependentMixin' imported but unused
from src.db.models.templates_.base import Base

Check warning on line 9 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L9 <401>

'src.db.models.templates_.base.Base' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:9:1: F401 'src.db.models.templates_.base.Base' imported but unused


class AnonymousURLModelProtocol(

Check warning on line 12 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L12 <101>

Missing docstring in public class
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:12:1: D101 Missing docstring in public class
Protocol,
):
session_id: ColumnElement[UUID]
url_id: ColumnElement[int]

AnonModel = TypeVar("AnonModel", bound=AnonymousURLModelProtocol)

Check failure on line 18 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L18 <305>

expected 2 blank lines after class or function definition, found 1
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:18:1: E305 expected 2 blank lines after class or function definition, found 1

def not_exists_anon_annotation(session_id: UUID, anon_model: AnonModel) -> ColumnElement[bool]:

Check warning on line 20 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L20 <103>

Missing docstring in public function
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:20:1: D103 Missing docstring in public function
return ~exists(
select(anon_model.url_id)
.where(
anon_model.url_id == URL.id,
anon_model.session_id == session_id,
)
)

Check warning on line 27 in src/api/endpoints/annotate/anonymous/get/helpers.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/helpers.py#L27 <292>

no newline at end of file
Raw output
./src/api/endpoints/annotate/anonymous/get/helpers.py:27:6: W292 no newline at end of file
58 changes: 51 additions & 7 deletions src/api/endpoints/annotate/anonymous/get/query.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from typing import Any
from uuid import UUID

from sqlalchemy import Select, func
from sqlalchemy import Select, func, exists, select

Check warning on line 4 in src/api/endpoints/annotate/anonymous/get/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/query.py#L4 <401>

'sqlalchemy.func' imported but unused
Raw output
./src/api/endpoints/annotate/anonymous/get/query.py:4:1: F401 'sqlalchemy.func' imported but unused
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload

from src.api.endpoints.annotate._shared.extract import extract_and_format_get_annotation_result
from src.api.endpoints.annotate.all.get.models.response import GetNextURLForAllAnnotationResponse
from src.api.endpoints.annotate.anonymous.get.helpers import not_exists_anon_annotation
from src.api.endpoints.annotate.anonymous.get.response import GetNextURLForAnonymousAnnotationResponse
from src.collectors.enums import URLStatus
from src.db.helpers.query import not_exists_url
from src.db.models.impl.flag.url_suspended.sqlalchemy import FlagURLSuspended
from src.db.models.impl.url.core.sqlalchemy import URL
from src.db.models.impl.url.suggestion.anonymous.agency.sqlalchemy import AnonymousAnnotationAgency
from src.db.models.impl.url.suggestion.anonymous.location.sqlalchemy import AnonymousAnnotationLocation
from src.db.models.impl.url.suggestion.anonymous.record_type.sqlalchemy import AnonymousAnnotationRecordType
from src.db.models.impl.url.suggestion.anonymous.url_type.sqlalchemy import AnonymousAnnotationURLType
from src.db.models.views.unvalidated_url import UnvalidatedURL
from src.db.models.views.url_anno_count import URLAnnotationCount
Expand All @@ -18,7 +25,14 @@

class GetNextURLForAnonymousAnnotationQueryBuilder(QueryBuilderBase):

async def run(self, session: AsyncSession) -> GetNextURLForAllAnnotationResponse:
def __init__(

Check warning on line 28 in src/api/endpoints/annotate/anonymous/get/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/query.py#L28 <107>

Missing docstring in __init__
Raw output
./src/api/endpoints/annotate/anonymous/get/query.py:28:1: D107 Missing docstring in __init__
self,
session_id: UUID
):
super().__init__()
self.session_id = session_id

async def run(self, session: AsyncSession) -> GetNextURLForAnonymousAnnotationResponse:

Check warning on line 35 in src/api/endpoints/annotate/anonymous/get/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/query.py#L35 <102>

Missing docstring in public method
Raw output
./src/api/endpoints/annotate/anonymous/get/query.py:35:1: D102 Missing docstring in public method

query = (
Select(URL)
Expand All @@ -37,7 +51,31 @@
)
.where(
URL.status == URLStatus.OK.value,
not_exists_url(AnonymousAnnotationURLType)
# Must not have been previously annotated by user
not_exists_anon_annotation(
session_id=self.session_id,
anon_model=AnonymousAnnotationURLType
),
not_exists_anon_annotation(
session_id=self.session_id,
anon_model=AnonymousAnnotationRecordType
),
not_exists_anon_annotation(
session_id=self.session_id,
anon_model=AnonymousAnnotationLocation
),
not_exists_anon_annotation(
session_id=self.session_id,
anon_model=AnonymousAnnotationAgency
),
~exists(
select(
FlagURLSuspended.url_id
)
.where(
FlagURLSuspended.url_id == URL.id,
)
)
)
.options(
joinedload(URL.html_content),
Expand All @@ -46,16 +84,22 @@
joinedload(URL.name_suggestions),
)
.order_by(
func.random()
URLAnnotationCount.total_anno_count.desc(),
URL.id.asc()
)
.limit(1)
)

raw_results = (await session.execute(query)).unique()
url: URL | None = raw_results.scalars().one_or_none()
if url is None:
return GetNextURLForAllAnnotationResponse(
next_annotation=None
return GetNextURLForAnonymousAnnotationResponse(
next_annotation=None,
session_id=self.session_id
)

return await extract_and_format_get_annotation_result(session, url=url)
response: GetNextURLForAllAnnotationResponse = await extract_and_format_get_annotation_result(session, url=url)
return GetNextURLForAnonymousAnnotationResponse(
session_id=self.session_id,
next_annotation=response.next_annotation
)
10 changes: 10 additions & 0 deletions src/api/endpoints/annotate/anonymous/get/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from uuid import UUID

Check warning on line 1 in src/api/endpoints/annotate/anonymous/get/response.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/response.py#L1 <100>

Missing docstring in public module
Raw output
./src/api/endpoints/annotate/anonymous/get/response.py:1:1: D100 Missing docstring in public module

from pydantic import BaseModel

from src.api.endpoints.annotate.all.get.models.response import GetNextURLForAllAnnotationInnerResponse


class GetNextURLForAnonymousAnnotationResponse(BaseModel):

Check warning on line 8 in src/api/endpoints/annotate/anonymous/get/response.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/response.py#L8 <101>

Missing docstring in public class
Raw output
./src/api/endpoints/annotate/anonymous/get/response.py:8:1: D101 Missing docstring in public class
next_annotation: GetNextURLForAllAnnotationInnerResponse | None
session_id: UUID

Check warning on line 10 in src/api/endpoints/annotate/anonymous/get/response.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/get/response.py#L10 <292>

no newline at end of file
Raw output
./src/api/endpoints/annotate/anonymous/get/response.py:10:21: W292 no newline at end of file
16 changes: 12 additions & 4 deletions src/api/endpoints/annotate/anonymous/post/query.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from uuid import UUID

Check warning on line 1 in src/api/endpoints/annotate/anonymous/post/query.py

View workflow job for this annotation

GitHub Actions / flake8

[flake8] src/api/endpoints/annotate/anonymous/post/query.py#L1 <100>

Missing docstring in public module
Raw output
./src/api/endpoints/annotate/anonymous/post/query.py:1:1: D100 Missing docstring in public module

from sqlalchemy.ext.asyncio import AsyncSession

from src.api.endpoints.annotate.all.post.models.request import AllAnnotationPostInfo
Expand All @@ -11,33 +13,38 @@
class AddAnonymousAnnotationsToURLQueryBuilder(QueryBuilderBase):
def __init__(
self,
session_id: UUID,
url_id: int,
post_info: AllAnnotationPostInfo
):
super().__init__()
self.session_id = session_id
self.url_id = url_id
self.post_info = post_info

async def run(self, session: AsyncSession) -> None:

url_type_suggestion = AnonymousAnnotationURLType(
url_id=self.url_id,
url_type=self.post_info.suggested_status
url_type=self.post_info.suggested_status,
session_id=self.session_id
)
session.add(url_type_suggestion)

if self.post_info.record_type is not None:
record_type_suggestion = AnonymousAnnotationRecordType(
url_id=self.url_id,
record_type=self.post_info.record_type
record_type=self.post_info.record_type,
session_id=self.session_id
)
session.add(record_type_suggestion)

if len(self.post_info.location_info.location_ids) != 0:
location_suggestions = [
AnonymousAnnotationLocation(
url_id=self.url_id,
location_id=location_id
location_id=location_id,
session_id=self.session_id
)
for location_id in self.post_info.location_info.location_ids
]
Expand All @@ -47,7 +54,8 @@
agency_suggestions = [
AnonymousAnnotationAgency(
url_id=self.url_id,
agency_id=agency_id
agency_id=agency_id,
session_id=self.session_id
)
for agency_id in self.post_info.agency_info.agency_ids
]
Expand Down
Loading