11import datetime
2+ from typing import Self
3+ from sqlalchemy import select
24from sqlalchemy .orm import Mapped , mapped_column
35import sqlalchemy .ext .asyncio
46import sqlalchemy .orm
@@ -12,6 +14,71 @@ class Base(
1214 pass
1315
1416
17+ class BatchTag (Base ):
18+ __tablename__ = "batch_tags"
19+ id : Mapped [int ] = mapped_column (primary_key = True , autoincrement = True , init = False )
20+ name : Mapped [str ] = mapped_column (
21+ sqlalchemy .String (length = 256 ), unique = True , index = True
22+ )
23+
24+ batches : Mapped [list ["Batch" ]] = sqlalchemy .orm .relationship (
25+ "Batch" ,
26+ secondary = "batch_tag_batches" ,
27+ back_populates = "tags" ,
28+ init = False ,
29+ repr = False ,
30+ )
31+
32+ @classmethod
33+ async def resolve_list (cls , names : set [str ]) -> list [Self ]:
34+ from .main import async_session
35+
36+ if not names :
37+ return []
38+
39+ async with async_session () as session , session .begin ():
40+ stmt = select (cls ).where (cls .name .in_ (names ))
41+ result = (await session .scalars (stmt )).all ()
42+ seen = {r .name : r for r in result }
43+ seen_set = set (seen .keys ())
44+ missing = names - seen_set
45+ if missing :
46+ newly_created : list [BatchTag ] = [
47+ BatchTag (name = name ) for name in missing
48+ ]
49+ session .add_all (newly_created )
50+ return [* seen .values (), * newly_created ]
51+ return list (seen .values ())
52+
53+
54+ class BatchTagBatch (Base ):
55+ __tablename__ = "batch_tag_batches"
56+ id : Mapped [int ] = mapped_column (primary_key = True , autoincrement = True , init = False )
57+ batch_id : Mapped [int ] = mapped_column (
58+ sqlalchemy .ForeignKey ("batches.id" ), index = True
59+ )
60+ batch_tag_id : Mapped [int ] = mapped_column (
61+ sqlalchemy .ForeignKey (BatchTag .id ), index = True
62+ )
63+
64+ batch : Mapped ["Batch" ] = sqlalchemy .orm .relationship (
65+ "Batch" , lazy = "joined" , innerjoin = True , foreign_keys = [batch_id ], viewonly = True
66+ )
67+ batch_tag : Mapped ["BatchTag" ] = sqlalchemy .orm .relationship (
68+ "BatchTag" ,
69+ lazy = "joined" ,
70+ innerjoin = True ,
71+ foreign_keys = [batch_tag_id ],
72+ viewonly = True ,
73+ )
74+
75+ __table_args__ = (
76+ sqlalchemy .UniqueConstraint (
77+ "batch_id" , "batch_tag_id" , name = "_batch_batch_tag_uc"
78+ ),
79+ )
80+
81+
1582class Batch (Base ):
1683 __tablename__ = "batches"
1784
@@ -23,10 +90,19 @@ class Batch(Base):
2390 init = False ,
2491 index = True ,
2592 )
93+ locked : Mapped [bool ] = mapped_column (
94+ default = False
95+ ) # Indicates that a batch is locked (no more jobs can be added to it)
2696
2797 jobs : Mapped [list ["Job" ]] = sqlalchemy .orm .relationship (
2898 "Job" , secondary = "batch_jobs" , back_populates = "batches" , init = False , repr = False
2999 )
100+ tags : Mapped [list [BatchTag ]] = sqlalchemy .orm .relationship (
101+ BatchTag ,
102+ secondary = "batch_tag_batches" ,
103+ back_populates = "batches" ,
104+ default_factory = list ,
105+ )
30106
31107
32108class URL (Base ):
@@ -119,9 +195,6 @@ class Job(Base):
119195 id : Mapped [int ] = mapped_column (
120196 sqlalchemy .BigInteger , primary_key = True , autoincrement = True , init = False
121197 )
122- batches : Mapped [list [Batch ]] = sqlalchemy .orm .relationship (
123- Batch , secondary = "batch_jobs" , back_populates = "jobs"
124- )
125198 url_id : Mapped [int ] = mapped_column (
126199 sqlalchemy .ForeignKey (URL .id ),
127200 nullable = False ,
@@ -132,6 +205,9 @@ class Job(Base):
132205 url : Mapped [URL ] = sqlalchemy .orm .relationship (
133206 URL , lazy = "joined" , innerjoin = True , foreign_keys = [url_id ], back_populates = "jobs"
134207 )
208+ batches : Mapped [list [Batch ]] = sqlalchemy .orm .relationship (
209+ Batch , secondary = "batch_jobs" , back_populates = "jobs" , default_factory = list
210+ )
135211 created_at : Mapped [datetime .datetime ] = mapped_column (
136212 sqlalchemy .DateTime (timezone = True ),
137213 server_default = sqlalchemy .sql .func .now (),
0 commit comments