move schema to separate uv project
This commit is contained in:
+1
-1
@@ -10,5 +10,5 @@ docker: clean
|
|||||||
docker build --target=production -t kontor-api -t kontor-api:0.1.0-SNAPSHOT .
|
docker build --target=production -t kontor-api -t kontor-api:0.1.0-SNAPSHOT .
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
uv run fastapi dev src/main.py --port 8008
|
DB_HOST=localhost uv run fastapi dev src/main.py --port 8008
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: kontor-api
|
||||||
|
Version: 0.1.0
|
||||||
|
Summary: Add your description here
|
||||||
|
Requires-Python: >=3.13
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: beautifulsoup4>=4.13.4
|
||||||
|
Requires-Dist: fastapi[standard]>=0.115.12
|
||||||
|
Requires-Dist: httpx==0.24.1
|
||||||
|
Requires-Dist: mariadb>=1.1.12
|
||||||
|
Requires-Dist: pathlib>=1.0.1
|
||||||
|
Requires-Dist: platformdirs>=4.3.7
|
||||||
|
Requires-Dist: pytest==7.4.0
|
||||||
|
Requires-Dist: pytest-cov>=6.1.1
|
||||||
|
Requires-Dist: pyyaml>=6.0.2
|
||||||
|
Requires-Dist: requests>=2.32.3
|
||||||
|
Requires-Dist: sqlalchemy>=2.0.40
|
||||||
|
Requires-Dist: sqlmodel>=0.0.24
|
||||||
|
Requires-Dist: kontor.schema>=0.1.0
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
src/__init__.py
|
||||||
|
src/main.py
|
||||||
|
src/kontor_api.egg-info/PKG-INFO
|
||||||
|
src/kontor_api.egg-info/SOURCES.txt
|
||||||
|
src/kontor_api.egg-info/dependency_links.txt
|
||||||
|
src/kontor_api.egg-info/requires.txt
|
||||||
|
src/kontor_api.egg-info/top_level.txt
|
||||||
|
src/models/__init__.py
|
||||||
|
src/models/comics/__init__.py
|
||||||
|
src/models/comics/artist.py
|
||||||
|
src/models/comics/comic.py
|
||||||
|
src/models/media/__init__.py
|
||||||
|
src/models/media/file.py
|
||||||
|
src/models/tysc/__init__.py
|
||||||
|
src/models/tysc/sport.py
|
||||||
|
src/routers/__init__.py
|
||||||
|
src/routers/comic.py
|
||||||
|
src/routers/media.py
|
||||||
|
src/routers/tysc.py
|
||||||
|
tests/test_main.py
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
beautifulsoup4>=4.13.4
|
||||||
|
fastapi[standard]>=0.115.12
|
||||||
|
httpx==0.24.1
|
||||||
|
mariadb>=1.1.12
|
||||||
|
pathlib>=1.0.1
|
||||||
|
platformdirs>=4.3.7
|
||||||
|
pytest==7.4.0
|
||||||
|
pytest-cov>=6.1.1
|
||||||
|
pyyaml>=6.0.2
|
||||||
|
requests>=2.32.3
|
||||||
|
sqlalchemy>=2.0.40
|
||||||
|
sqlmodel>=0.0.24
|
||||||
|
kontor.schema>=0.1.0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__init__
|
||||||
|
main
|
||||||
|
models
|
||||||
|
routers
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from kontor_schema import Artist
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.schema import Artist
|
|
||||||
|
|
||||||
|
|
||||||
class ArtistCreation(BaseModel):
|
class ArtistCreation(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from kontor_schema import Comic
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.schema import Comic
|
|
||||||
|
|
||||||
|
|
||||||
class ComicResponse(BaseModel):
|
class ComicResponse(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from src.schema.media import MediaFile
|
from kontor_schema import MediaFile
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from kontor_schema import Base
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
|
||||||
|
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
||||||
|
os.environ.get('DB_USER', 'kontor'),
|
||||||
|
os.environ.get('DB_PASSWORD', 'kontor'),
|
||||||
|
os.environ.get('DB_HOST', 'mariadb'),
|
||||||
|
os.environ.get('DB_PORT', 3306),
|
||||||
|
os.environ.get('DB_NAME', 'kontor')
|
||||||
|
))
|
||||||
|
engine = create_engine(connect_string)
|
||||||
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
|
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
logging.info("get_db")
|
||||||
|
with SessionLocal() as db:
|
||||||
|
yield db
|
||||||
|
|
||||||
|
SessionDep = Annotated[Session, Depends(get_db)]
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import List
|
from typing import List
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
from kontor_schema import Comic, Artist
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.models.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details
|
from src.models.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details
|
||||||
from src.models.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
|
from src.models.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
|
||||||
from src.schema import Comic, SessionDep, Artist
|
from src.routers import SessionDep
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/comic",
|
prefix="/comic",
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ from typing import List
|
|||||||
from uuid import uuid4, UUID
|
from uuid import uuid4, UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, status, HTTPException
|
from fastapi import APIRouter, status, HTTPException
|
||||||
|
from kontor_schema import MediaFile
|
||||||
from sqlalchemy import select, Sequence
|
from sqlalchemy import select, Sequence
|
||||||
|
|
||||||
from src.models.media.file import MediaFileResponse, Link, get_file_details
|
from src.models.media.file import MediaFileResponse, Link, get_file_details
|
||||||
from src.schema import MediaFile, SessionDep
|
from src.routers import SessionDep
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/media",
|
prefix="/media",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from kontor_schema import Sport
|
||||||
|
|
||||||
from src.models.tysc.sport import SportResponse
|
from src.models.tysc.sport import SportResponse
|
||||||
from src.schema import Sport, SessionDep
|
from src.routers import SessionDep
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/tysc",
|
prefix="/tysc",
|
||||||
@@ -17,4 +18,3 @@ def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
|||||||
for sport in sports:
|
for sport in sports:
|
||||||
results.append(SportResponse(id=sport.id, name=sport.name))
|
results.append(SportResponse(id=sport.id, name=sport.name))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
|
|
||||||
from fastapi import Depends
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy.orm import sessionmaker, Session
|
|
||||||
|
|
||||||
from .admin import User, Token, Role, AuthorizationMatrix, ModuleData, MailAccount, Mail
|
|
||||||
from .bookshelf import Article, Book, Author, BookshelfPublisher, ArticleAuthor, BookAuthor
|
|
||||||
from .comic import Comic, Artist, Publisher, Issue, StoryArc, TradePaperback, Volume, ComicWork, WorkType
|
|
||||||
from .metadata import MetaDataTable, MetaDataColumn
|
|
||||||
from .tysc import Card, CardSet, Sport, Team, FieldPosition, Rooster, Player, Vendor
|
|
||||||
from .media import MediaFile, MediaArticle, MediaVideo
|
|
||||||
from .base import Base
|
|
||||||
from .database import KontorDB, ColumnEntry
|
|
||||||
|
|
||||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
|
||||||
os.environ.get('DB_USER', 'kontor'),
|
|
||||||
os.environ.get('DB_PASSWORD', 'kontor'),
|
|
||||||
os.environ.get('DB_HOST', 'mariadb'),
|
|
||||||
os.environ.get('DB_PORT', 3306),
|
|
||||||
os.environ.get('DB_NAME', 'kontor')
|
|
||||||
))
|
|
||||||
engine = create_engine(connect_string)
|
|
||||||
SessionLocal = sessionmaker(bind=engine)
|
|
||||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
|
||||||
|
|
||||||
def get_db():
|
|
||||||
logging.info("get_db")
|
|
||||||
with SessionLocal() as db:
|
|
||||||
yield db
|
|
||||||
|
|
||||||
SessionDep = Annotated[Session, Depends(get_db)]
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
|
||||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class User(Base, BaseMixin):
|
|
||||||
__tablename__ = 'user'
|
|
||||||
first_name = Column(String(255))
|
|
||||||
last_name = Column(String(255))
|
|
||||||
user_name = Column(String(255), nullable=False)
|
|
||||||
email = Column(String(255))
|
|
||||||
password = Column(String(255))
|
|
||||||
enabled = Column(Boolean)
|
|
||||||
matrix = relationship("AuthorizationMatrix")
|
|
||||||
tokens = relationship("Token")
|
|
||||||
|
|
||||||
def get_full_name(self) -> str:
|
|
||||||
full_name = ""
|
|
||||||
if self.first_name is not None:
|
|
||||||
full_name += self.first_name
|
|
||||||
if self.last_name is not None:
|
|
||||||
if len(full_name) > 0:
|
|
||||||
full_name += " "
|
|
||||||
full_name += self.last_name
|
|
||||||
return full_name
|
|
||||||
|
|
||||||
|
|
||||||
class Token(Base, BaseMixin):
|
|
||||||
__tablename__ = "token"
|
|
||||||
token = Column(String(255), nullable=False, unique=True)
|
|
||||||
name = Column(String(255))
|
|
||||||
last_used_date: Mapped[datetime] = mapped_column()
|
|
||||||
enabled = Column(Boolean)
|
|
||||||
user_id = Column(String(255), ForeignKey("user.id"), nullable=False)
|
|
||||||
user = relationship("User", back_populates="tokens")
|
|
||||||
|
|
||||||
|
|
||||||
class Role(Base, BaseMixin):
|
|
||||||
__tablename__ = "role"
|
|
||||||
name = Column(String(255), nullable=False)
|
|
||||||
matrix = relationship("AuthorizationMatrix")
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationMatrix(Base, BaseMixin):
|
|
||||||
__tablename__ = "authorization_matrix"
|
|
||||||
user_id = Column(String, ForeignKey("user.id"), nullable=False)
|
|
||||||
user = relationship("User", back_populates="matrix")
|
|
||||||
role_id = Column(String, ForeignKey("role.id"), nullable=False)
|
|
||||||
role = relationship("Role", back_populates="matrix")
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleData(Base, BaseMixin):
|
|
||||||
__tablename__ = "module_data"
|
|
||||||
module_name = Column(String(255), nullable=False)
|
|
||||||
import_data = Column(Boolean)
|
|
||||||
|
|
||||||
|
|
||||||
class MailAccount(Base, BaseMixin):
|
|
||||||
__tablename__ = "mail_account"
|
|
||||||
host = Column(String(255))
|
|
||||||
port = Column(Integer)
|
|
||||||
protocol = Column(String(255))
|
|
||||||
user_name = Column(String(255))
|
|
||||||
password = Column(String(255))
|
|
||||||
start_tls = Column(Boolean)
|
|
||||||
|
|
||||||
|
|
||||||
class Mail(Base, BaseMixin):
|
|
||||||
__tablename__ = "mail"
|
|
||||||
folder: Mapped[str] = mapped_column()
|
|
||||||
subject: Mapped[str] = mapped_column()
|
|
||||||
body: Mapped[str] = mapped_column()
|
|
||||||
sent_date: Mapped[datetime] = mapped_column()
|
|
||||||
received_date: Mapped[datetime] = mapped_column()
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Boolean, func, Column, String
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMixin:
|
|
||||||
id = Column(String(255), primary_key=True, default=uuid.uuid4())
|
|
||||||
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
|
|
||||||
# created_date = Column(DateTime)
|
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
# last_modified_date = Column(DateTime)
|
|
||||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
# version = Column(Integer)
|
|
||||||
version: Mapped[int] = mapped_column(default=0)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
|
||||||
cloud_link = Column(String(255))
|
|
||||||
file_name = Column(String(255))
|
|
||||||
path = Column(String(255))
|
|
||||||
review = Column(Boolean)
|
|
||||||
title = Column(String(255))
|
|
||||||
url = Column(String(255), unique=True)
|
|
||||||
should_download = Column(Boolean)
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class Publisher(Base, BaseMixin):
|
|
||||||
__tablename__ = "publisher"
|
|
||||||
name = Column(String(length=255), unique=True)
|
|
||||||
comics = relationship("Comic")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'Publisher({self.id} {self.name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.__repr__()
|
|
||||||
|
|
||||||
|
|
||||||
class Comic(Base, BaseMixin):
|
|
||||||
__tablename__ = 'comic'
|
|
||||||
title = Column(String(length=255), unique=True)
|
|
||||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
|
||||||
publisher = relationship("Publisher", back_populates="comics")
|
|
||||||
current_order = Column(Boolean)
|
|
||||||
completed = Column(Boolean)
|
|
||||||
issues = relationship("Issue")
|
|
||||||
story_arcs = relationship("StoryArc")
|
|
||||||
trade_paperbacks = relationship("TradePaperback")
|
|
||||||
volumes = relationship("Volume")
|
|
||||||
comic_works = relationship("ComicWork")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.title}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
class Volume(Base, BaseMixin):
|
|
||||||
__tablename__ = "volume"
|
|
||||||
name = Column(String(length=255), nullable=False)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
|
||||||
comic = relationship("Comic", back_populates="volumes")
|
|
||||||
issues = relationship("Issue")
|
|
||||||
|
|
||||||
|
|
||||||
class TradePaperback(Base, BaseMixin):
|
|
||||||
__tablename__ = "trade_paperback"
|
|
||||||
name = Column(String(length=255), nullable=False)
|
|
||||||
issue_start = Column(Integer)
|
|
||||||
issue_end = Column(Integer)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
|
||||||
comic = relationship("Comic", back_populates="trade_paperbacks")
|
|
||||||
|
|
||||||
|
|
||||||
class StoryArc(Base, BaseMixin):
|
|
||||||
__tablename__ = "story_arc"
|
|
||||||
name = Column(String(length=255), nullable=False)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
|
||||||
comic = relationship("Comic", back_populates="story_arcs")
|
|
||||||
|
|
||||||
|
|
||||||
class Issue(Base, BaseMixin):
|
|
||||||
__tablename__ = "issue"
|
|
||||||
issue_number = Column(String(255))
|
|
||||||
in_stock = Column(Boolean)
|
|
||||||
is_read = Column(Boolean)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
|
||||||
comic = relationship("Comic", back_populates="issues")
|
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
|
||||||
volume = relationship("Volume", back_populates="issues")
|
|
||||||
|
|
||||||
|
|
||||||
class Artist(Base, BaseMixin):
|
|
||||||
__tablename__ = "artist"
|
|
||||||
name = Column(String(length=255), nullable=False)
|
|
||||||
comic_works = relationship("ComicWork")
|
|
||||||
|
|
||||||
|
|
||||||
class WorkType(Base, BaseMixin):
|
|
||||||
__tablename__ = "worktype"
|
|
||||||
name = Column(String(length=255), nullable=False, unique=True)
|
|
||||||
comic_works = relationship("ComicWork")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.name}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
class ComicWork(Base, BaseMixin):
|
|
||||||
__tablename__ = "comic_work"
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
|
||||||
comic = relationship("Comic", back_populates="comic_works")
|
|
||||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
|
||||||
artist = relationship("Artist", back_populates="comic_works")
|
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
|
||||||
work_type = relationship("WorkType", back_populates="comic_works")
|
|
||||||
@@ -1,391 +0,0 @@
|
|||||||
import json
|
|
||||||
import logging
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from enum import Enum, auto
|
|
||||||
from logging import Logger
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from .tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
|
||||||
from .comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
|
||||||
from .bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
|
||||||
from .admin import Mail, MailAccount, ModuleData, Role, User, Token, AuthorizationMatrix
|
|
||||||
from .metadata import MetaDataTable, MetaDataColumn
|
|
||||||
from .media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
|
||||||
|
|
||||||
|
|
||||||
class ColumnEntry(Enum):
|
|
||||||
COLUMN_NAME = 'column'
|
|
||||||
COLUMN_LABEL = 'label'
|
|
||||||
COLUMN_ORDER = 'order'
|
|
||||||
COLUMN_REF_COLUMN = 'ref_column'
|
|
||||||
COLUMN_TYPE = 'type'
|
|
||||||
COLUMN_WIDGET = 'widget'
|
|
||||||
|
|
||||||
|
|
||||||
class StatusType(Enum):
|
|
||||||
UNKNOWN = auto()
|
|
||||||
FILE_NAME = auto()
|
|
||||||
FILE_ID = auto()
|
|
||||||
DUPLICATE = auto()
|
|
||||||
CLOUD_LINK = auto()
|
|
||||||
CLOUD_LINK_ID = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class KontorDB:
|
|
||||||
|
|
||||||
def __init__(self, db_engine: Any):
|
|
||||||
self.engine = db_engine
|
|
||||||
self.registry = {}
|
|
||||||
self.init_registry()
|
|
||||||
|
|
||||||
def init_registry(self):
|
|
||||||
self.registry[Card.__tablename__] = Card
|
|
||||||
self.registry[CardSet.__tablename__] = CardSet
|
|
||||||
self.registry[Rooster.__tablename__] = Rooster
|
|
||||||
self.registry[Team.__tablename__] = Team
|
|
||||||
self.registry[FieldPosition.__tablename__] = FieldPosition
|
|
||||||
self.registry[Player.__tablename__] = Player
|
|
||||||
self.registry[Vendor.__tablename__] = Vendor
|
|
||||||
self.registry[Sport.__tablename__] = Sport
|
|
||||||
self.registry[Issue.__tablename__] = Issue
|
|
||||||
self.registry[TradePaperback.__tablename__] = TradePaperback
|
|
||||||
self.registry[StoryArc.__tablename__] = StoryArc
|
|
||||||
self.registry[Volume.__tablename__] = Volume
|
|
||||||
self.registry[ComicWork.__tablename__] = ComicWork
|
|
||||||
self.registry[Artist.__tablename__] = Artist
|
|
||||||
self.registry[Comic.__tablename__] = Comic
|
|
||||||
self.registry[Publisher.__tablename__] = Publisher
|
|
||||||
self.registry[WorkType.__tablename__] = WorkType
|
|
||||||
self.registry[ArticleAuthor.__tablename__] = ArticleAuthor
|
|
||||||
self.registry[BookAuthor.__tablename__] = BookAuthor
|
|
||||||
self.registry[BookshelfPublisher.__tablename__] = BookshelfPublisher
|
|
||||||
self.registry[Article.__tablename__] = Article
|
|
||||||
self.registry[Book.__tablename__] = Book
|
|
||||||
self.registry[Author.__tablename__] = Author
|
|
||||||
self.registry[MediaFile.__tablename__] = MediaFile
|
|
||||||
self.registry[MediaActor.__tablename__] = MediaActor
|
|
||||||
self.registry[MediaActorFile.__tablename__] = MediaActorFile
|
|
||||||
self.registry[MediaArticle.__tablename__] = MediaArticle
|
|
||||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
|
||||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
|
||||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
|
||||||
self.registry[AuthorizationMatrix.__tablename__] = AuthorizationMatrix
|
|
||||||
self.registry[Token.__tablename__] = Token
|
|
||||||
self.registry[User.__tablename__] = User
|
|
||||||
self.registry[Role.__tablename__] = Role
|
|
||||||
self.registry[ModuleData.__tablename__] = ModuleData
|
|
||||||
self.registry[MailAccount.__tablename__] = MailAccount
|
|
||||||
self.registry[Mail.__tablename__] = Mail
|
|
||||||
|
|
||||||
def get_table_names(self) -> list:
|
|
||||||
result = []
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
tables = session.scalars(select(MetaDataTable)).all()
|
|
||||||
result = [table.table_name for table in tables]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_table_by_name(self, table_name: str) -> dict:
|
|
||||||
result = {}
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
_filter = {'table_name': table_name}
|
|
||||||
with __session__() as session:
|
|
||||||
table = session.query(MetaDataTable).filter_by(**_filter).one()
|
|
||||||
result['id'] = table.id
|
|
||||||
result['table_name'] = table.table_name
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_column_meta_data(self, table_name: str, view_only=True) -> dict:
|
|
||||||
meta_data = {}
|
|
||||||
order = 0
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
columns = list()
|
|
||||||
table_info = self.get_table_by_name(table_name)
|
|
||||||
_filters = {'table_id': table_info['id']}
|
|
||||||
if view_only:
|
|
||||||
_filters['is_shown'] = True
|
|
||||||
with __session__() as session:
|
|
||||||
columns = session.query(MetaDataColumn).filter_by(**_filters).all()
|
|
||||||
for column in columns:
|
|
||||||
# self.log.info("get_column_meta_data: %s %s %d", column.column_name, column.column_label, column.column_order)
|
|
||||||
meta_data[order] = {
|
|
||||||
ColumnEntry.COLUMN_NAME: column.column_name,
|
|
||||||
ColumnEntry.COLUMN_LABEL: column.column_label,
|
|
||||||
ColumnEntry.COLUMN_ORDER: column.column_order,
|
|
||||||
ColumnEntry.COLUMN_REF_COLUMN: column.ref_column,
|
|
||||||
ColumnEntry.COLUMN_TYPE: column.column_type
|
|
||||||
}
|
|
||||||
order += 1
|
|
||||||
return meta_data
|
|
||||||
|
|
||||||
def get_columns(self, table_name: str) -> dict:
|
|
||||||
columns = {}
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
table_info = self.get_table_by_name(table_name)
|
|
||||||
_filters = {'table_id': table_info['id']}
|
|
||||||
with __session__() as session:
|
|
||||||
for column in session.query(MetaDataColumn).filter_by(**_filters).all():
|
|
||||||
columns[column.column_name] = {
|
|
||||||
ColumnEntry.COLUMN_ORDER: column.column_order,
|
|
||||||
ColumnEntry.COLUMN_TYPE: column.column_type
|
|
||||||
}
|
|
||||||
return columns
|
|
||||||
|
|
||||||
def get_filters(self, table_name: str) -> dict:
|
|
||||||
_filter_map = {}
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
table_info = self.get_table_by_name(table_name)
|
|
||||||
_filters = {'table_id': table_info['id'], 'show_filter': True}
|
|
||||||
with __session__() as session:
|
|
||||||
for column in session.query(MetaDataColumn).filter_by(**_filters).all():
|
|
||||||
_filter_map[column.column_name] = {
|
|
||||||
ColumnEntry.COLUMN_LABEL: column.filter_label,
|
|
||||||
ColumnEntry.COLUMN_WIDGET: None
|
|
||||||
}
|
|
||||||
return _filter_map
|
|
||||||
|
|
||||||
def data(self, table_name: str, columns: dict, filters: dict) -> list:
|
|
||||||
data = []
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
table = self.registry[table_name]
|
|
||||||
with __session__() as session:
|
|
||||||
entries = []
|
|
||||||
if len(filters) == 0:
|
|
||||||
entries = session.scalars(select(table)).all()
|
|
||||||
else:
|
|
||||||
entries = session.scalars(select(table).filter_by(**filters)).all()
|
|
||||||
for entry in entries:
|
|
||||||
# self.log.info("data: %s", entry)
|
|
||||||
row = []
|
|
||||||
for order in columns.keys():
|
|
||||||
column_name = columns[order][ColumnEntry.COLUMN_NAME]
|
|
||||||
ref_column = columns[order][ColumnEntry.COLUMN_REF_COLUMN]
|
|
||||||
if str(column_name).endswith("_id"):
|
|
||||||
ref_table = column_name[:-3]
|
|
||||||
ref = getattr(entry, ref_table)
|
|
||||||
value = getattr(ref, ref_column)
|
|
||||||
row.append(value)
|
|
||||||
else:
|
|
||||||
row.append(getattr(entry, column_name))
|
|
||||||
data.append(row)
|
|
||||||
# self.log.info("data: %s", data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def export_db(self, export_type: str, export_file_name: str) -> dict:
|
|
||||||
results = {}
|
|
||||||
db = {}
|
|
||||||
export_table_list = self.get_table_names()
|
|
||||||
for table in export_table_list:
|
|
||||||
columns = self.get_column_meta_data(table, view_only=False)
|
|
||||||
if table in self.registry:
|
|
||||||
model = self.registry[table]
|
|
||||||
else:
|
|
||||||
logging.info(f"table {table} is not registered")
|
|
||||||
continue
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
rows = session.query(model).all()
|
|
||||||
entries = []
|
|
||||||
for row in rows:
|
|
||||||
# print(row)
|
|
||||||
entry = {}
|
|
||||||
for order in columns:
|
|
||||||
# print(columns[order])
|
|
||||||
column_name = columns[order][ColumnEntry.COLUMN_NAME]
|
|
||||||
# print(f"get value {column_name} from {row} of table {table}")
|
|
||||||
try:
|
|
||||||
value = getattr(row, column_name)
|
|
||||||
if isinstance(value, datetime):
|
|
||||||
entry[column_name] = str(value)
|
|
||||||
else:
|
|
||||||
entry[column_name] = value
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
entries.append(entry)
|
|
||||||
db[table] = entries
|
|
||||||
results[table] = len(entries)
|
|
||||||
match export_type:
|
|
||||||
case "JSON":
|
|
||||||
json_dump = json.dumps(db, indent=4)
|
|
||||||
with open(export_file_name, "w") as dump_file:
|
|
||||||
dump_file.write(json_dump)
|
|
||||||
case "YAML":
|
|
||||||
pass
|
|
||||||
case "SQLite":
|
|
||||||
pass
|
|
||||||
logging.info(f"{len(results)} tables exported")
|
|
||||||
return results
|
|
||||||
|
|
||||||
def import_db(self, import_file_name: str) -> dict:
|
|
||||||
result = {}
|
|
||||||
import_file = Path(import_file_name)
|
|
||||||
if not import_file.exists():
|
|
||||||
logging.info(f"File {import_file_name} does not exist. Do nothing.")
|
|
||||||
return result
|
|
||||||
match import_file.suffix:
|
|
||||||
case '.json':
|
|
||||||
print("read json file")
|
|
||||||
with open(import_file_name, 'r') as json_file:
|
|
||||||
json_load = json.load(json_file)
|
|
||||||
for table in json_load:
|
|
||||||
logging.info(f"{table}: {len(json_load[table])}")
|
|
||||||
result[table] = self.import_table(table, json_load[table])
|
|
||||||
case '.yml':
|
|
||||||
print("read yaml file")
|
|
||||||
case '.yaml':
|
|
||||||
print("read yaml file")
|
|
||||||
case '.db':
|
|
||||||
print("read sqlite file")
|
|
||||||
return result
|
|
||||||
|
|
||||||
def import_table(self, table_name: str, items:list) -> dict:
|
|
||||||
result = {}
|
|
||||||
updated = []
|
|
||||||
added = []
|
|
||||||
remaining = []
|
|
||||||
existing_ids = self.get_ids(table_name)
|
|
||||||
logging.info(f"found {len(existing_ids)} existing ids for table {table_name}")
|
|
||||||
for item in items:
|
|
||||||
current_id = item['id']
|
|
||||||
# print(f"import item: {item}")
|
|
||||||
found_item = None
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
found_item = session.get(self.registry[table_name], current_id)
|
|
||||||
# print(f"found item: {found_item}")
|
|
||||||
if found_item is not None:
|
|
||||||
changed = self.update_entry(table_name, current_id, item)
|
|
||||||
updated.append(item)
|
|
||||||
if changed:
|
|
||||||
logging.info(f"{current_id} has changed")
|
|
||||||
updated.append(item)
|
|
||||||
existing_ids.remove(current_id)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.add_entry(table_name, item)
|
|
||||||
added.append(item)
|
|
||||||
except IntegrityError as error:
|
|
||||||
logging.info(f"Could not add item, due to: {error.detail}")
|
|
||||||
if len(existing_ids) > 0:
|
|
||||||
print(f"remaining items for {table_name}: {existing_ids}")
|
|
||||||
remaining.extend(existing_ids)
|
|
||||||
result['updated'] = updated
|
|
||||||
result['added'] = added
|
|
||||||
result['remaining'] = remaining
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_ids(self, table_name: str) -> list:
|
|
||||||
existing_ids = []
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
items = session.query(self.registry[table_name]).all()
|
|
||||||
for item in items:
|
|
||||||
existing_ids.append(getattr(item, 'id'))
|
|
||||||
return existing_ids
|
|
||||||
|
|
||||||
def add_entry(self, table_name: str, update_item: dict):
|
|
||||||
logging.debug(f"add entry to table {table_name} with {update_item}")
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
add_item = self.registry[table_name]()
|
|
||||||
for key in update_item.keys():
|
|
||||||
update_value = update_item[key]
|
|
||||||
setattr(add_item, key, update_value)
|
|
||||||
session.add(add_item)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
def update_entry(self, table_name, current_id, update_item: dict) -> bool:
|
|
||||||
# self.log.info("update entry to table %s", table_name)
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
existing_item = session.query(self.registry[table_name]).get(current_id)
|
|
||||||
changed = False
|
|
||||||
for key in update_item.keys():
|
|
||||||
update_value = update_item[key]
|
|
||||||
existing_value = getattr(existing_item, key)
|
|
||||||
if type(existing_value) is not type(update_value):
|
|
||||||
existing_value = str(existing_value)
|
|
||||||
if existing_value != update_value:
|
|
||||||
logging.info(f"{key} has changed: {existing_value} != {update_value}")
|
|
||||||
setattr(existing_item, key, update_value)
|
|
||||||
session.commit()
|
|
||||||
changed = True
|
|
||||||
logging.info(f"update {key} with {update_value}")
|
|
||||||
return changed
|
|
||||||
|
|
||||||
def add_link(self, link: str) -> dict:
|
|
||||||
result = {}
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
media_file = MediaFile()
|
|
||||||
media_file.id = str(uuid.uuid4())
|
|
||||||
media_file.created_date = datetime.now()
|
|
||||||
media_file.last_modified_date = datetime.now()
|
|
||||||
media_file.version = 0
|
|
||||||
media_file.url = link
|
|
||||||
media_file.review = True
|
|
||||||
media_file.should_download = True
|
|
||||||
try:
|
|
||||||
session.add(media_file)
|
|
||||||
session.commit()
|
|
||||||
result['added'] = {'url': media_file.url, 'title': media_file.title, 'review': media_file.review, 'download': media_file.should_download}
|
|
||||||
except IntegrityError as error:
|
|
||||||
session.rollback()
|
|
||||||
result['error'] = error.orig
|
|
||||||
return result
|
|
||||||
|
|
||||||
def update_titles(self) -> dict:
|
|
||||||
update_list = {}
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
_filter = { 'review': True}
|
|
||||||
with __session__() as session:
|
|
||||||
links = session.query(MediaFile).filter_by(**_filter).all()
|
|
||||||
for link in links:
|
|
||||||
url = link.url
|
|
||||||
if url is None:
|
|
||||||
continue
|
|
||||||
link.update_title()
|
|
||||||
session.commit()
|
|
||||||
update_list[link.id] = link.title
|
|
||||||
return update_list
|
|
||||||
|
|
||||||
def get_download_list(self) -> list:
|
|
||||||
download_list = []
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
_filter = { 'should_download': True}
|
|
||||||
with __session__() as session:
|
|
||||||
links = session.query(MediaFile).filter_by(**_filter).all()
|
|
||||||
for link in links:
|
|
||||||
url = link.url
|
|
||||||
if url is None:
|
|
||||||
continue
|
|
||||||
download_list.append(link.id)
|
|
||||||
return download_list
|
|
||||||
|
|
||||||
def download_file(self, entry_id: str, download_dir = "/data/media", dl_tool = "yt-dlp") -> str:
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
link = session.query(MediaFile).get(entry_id)
|
|
||||||
link.download_file(download_dir, dl_tool)
|
|
||||||
session.commit()
|
|
||||||
file_name = link.file_name
|
|
||||||
return file_name
|
|
||||||
|
|
||||||
def delete_entries(self):
|
|
||||||
for (table_name, table) in self.registry.items():
|
|
||||||
# self.log.info("delete entries from table %s", table_name)
|
|
||||||
__session__ = sessionmaker(self.engine)
|
|
||||||
with __session__() as session:
|
|
||||||
items = session.query(table).all()
|
|
||||||
for item in items:
|
|
||||||
session.delete(item)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
def check_files(self):
|
|
||||||
pass
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import logging
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from sqlalchemy import Boolean, Column, String, ForeignKey
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin, BaseVideoMixin
|
|
||||||
|
|
||||||
|
|
||||||
class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|
||||||
__tablename__ = 'media_file'
|
|
||||||
media_actor_files = relationship("MediaActorFile")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'MediaFile({self.id} {self.title} {self.title})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.title}({self.id})'
|
|
||||||
|
|
||||||
def update_title(self) -> None:
|
|
||||||
logging.info(f"update title for {self.url}")
|
|
||||||
try:
|
|
||||||
r = requests.get(self.url)
|
|
||||||
soup = BeautifulSoup(r.content, "html.parser")
|
|
||||||
title = soup.title.string
|
|
||||||
self.title = title
|
|
||||||
self.review = False
|
|
||||||
except:
|
|
||||||
self.title = None
|
|
||||||
self.review = True
|
|
||||||
self.last_modified_date = datetime.now()
|
|
||||||
|
|
||||||
def download_file(self, download_dir: str, dl_tool: str):
|
|
||||||
logging.info(f"download file for {self.url} to {download_dir}")
|
|
||||||
result = subprocess.run([dl_tool, self.url], cwd=download_dir, capture_output=True, text=True)
|
|
||||||
if result.returncode == 0:
|
|
||||||
output = result.stdout
|
|
||||||
output = re.sub(' +', ' ', output)
|
|
||||||
lines_list = output.splitlines()
|
|
||||||
file_name = self.__parse_output__(lines_list)
|
|
||||||
if file_name is None:
|
|
||||||
self.review = True
|
|
||||||
self.should_download = True
|
|
||||||
self.file_name = None
|
|
||||||
else:
|
|
||||||
download_file = Path(file_name)
|
|
||||||
self.should_download = False
|
|
||||||
self.file_name = download_file.name
|
|
||||||
self.cloud_link = str(download_file.absolute())
|
|
||||||
self.last_modified_date = datetime.now()
|
|
||||||
|
|
||||||
def __parse_output__(self, lines_list):
|
|
||||||
self.file_name = None
|
|
||||||
for line in lines_list:
|
|
||||||
if 'has already been downloaded' in line:
|
|
||||||
end_len = len(' has already been downloaded')
|
|
||||||
self.file_name = line[11:-end_len]
|
|
||||||
if 'Destination' in line:
|
|
||||||
line_len = len(line)
|
|
||||||
start_len = len('[download] Destination: ')
|
|
||||||
file_len = line_len - start_len
|
|
||||||
self.file_name = line[-file_len:]
|
|
||||||
return self.file_name
|
|
||||||
|
|
||||||
|
|
||||||
class MediaActor(Base, BaseMixin):
|
|
||||||
__tablename__ = 'media_actor'
|
|
||||||
name = Column(String(255))
|
|
||||||
media_actor_files = relationship("MediaActorFile")
|
|
||||||
|
|
||||||
|
|
||||||
class MediaActorFile(Base, BaseMixin):
|
|
||||||
__tablename__ = 'media_actor_file'
|
|
||||||
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
|
|
||||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
|
||||||
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
|
|
||||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
|
||||||
|
|
||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
|
||||||
__tablename__ = 'media_article'
|
|
||||||
review = Column(Boolean)
|
|
||||||
title = Column(String(255))
|
|
||||||
url = Column(String(255), unique=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MediaVideo(Base, BaseMixin):
|
|
||||||
__tablename__ = 'media_video'
|
|
||||||
cloud_link = Column(String(255))
|
|
||||||
file_name = Column(String(255))
|
|
||||||
path = Column(String(255))
|
|
||||||
review = Column(Boolean)
|
|
||||||
title = Column(String(255))
|
|
||||||
url = Column(String(255), unique=True)
|
|
||||||
should_download = Column(Boolean)
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
from sqlalchemy import Column, String, ForeignKey, Integer, Boolean
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class MetaDataTable(Base, BaseMixin):
|
|
||||||
__tablename__ = 'meta_data_table'
|
|
||||||
table_name = Column(String(255), unique=True)
|
|
||||||
table_columns = relationship("MetaDataColumn")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'MetaDataTable({self.id} {self.table_name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.table_name}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
class MetaDataColumn(Base, BaseMixin):
|
|
||||||
__tablename__ = 'meta_data_column'
|
|
||||||
column_name = Column(String(255), nullable=False)
|
|
||||||
column_sync_name = Column(String(255))
|
|
||||||
column_type = Column(String(255))
|
|
||||||
column_modifier = Column(String(255), nullable=True)
|
|
||||||
column_order = Column(Integer)
|
|
||||||
table_id = Column(String, ForeignKey('meta_data_table.id'))
|
|
||||||
table = relationship("MetaDataTable", back_populates="table_columns")
|
|
||||||
column_label = Column(String(255))
|
|
||||||
filter_label = Column(String(255))
|
|
||||||
is_shown = Column(Boolean)
|
|
||||||
show_filter = Column(Boolean)
|
|
||||||
ref_column = Column(String, nullable=True)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.column_name is None:
|
|
||||||
return f'MetaDataColumn({self.id} {self.table.table_name}.__)'
|
|
||||||
else:
|
|
||||||
return f'MetaDataColumn({self.id} {self.table.table_name}.{self.column_name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.column_name}({self.id})'
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String, ForeignKey, UniqueConstraint
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class Sport(Base, BaseMixin):
|
|
||||||
__tablename__ = "sport"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("name"),
|
|
||||||
)
|
|
||||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
|
||||||
teams = relationship("Team")
|
|
||||||
positions = relationship("FieldPosition")
|
|
||||||
|
|
||||||
|
|
||||||
class Team(Base, BaseMixin):
|
|
||||||
__tablename__ = "team"
|
|
||||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
|
||||||
short_name = Column(String(255), nullable=False, )
|
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
|
||||||
sport = relationship("Sport", back_populates="teams")
|
|
||||||
roosters = relationship("Rooster")
|
|
||||||
|
|
||||||
|
|
||||||
class FieldPosition(Base, BaseMixin):
|
|
||||||
__tablename__ = "field_position"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("name", "sport_id"),
|
|
||||||
UniqueConstraint("short_name", "sport_id"),
|
|
||||||
)
|
|
||||||
name = Column(String(255), nullable=False, index=True)
|
|
||||||
short_name = Column(String(255), nullable=False)
|
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
|
||||||
sport = relationship("Sport", back_populates="positions")
|
|
||||||
roosters = relationship("Rooster")
|
|
||||||
|
|
||||||
|
|
||||||
class Player(Base, BaseMixin):
|
|
||||||
__tablename__ = "player"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("first_name", "last_name"),
|
|
||||||
)
|
|
||||||
first_name = Column(String(255), nullable=False, index=True)
|
|
||||||
last_name = Column(String(255), nullable=False, index=True)
|
|
||||||
roosters = relationship("Rooster")
|
|
||||||
|
|
||||||
def get_full_name(self) -> str:
|
|
||||||
return f"{self.last_name}, {self.first_name}"
|
|
||||||
|
|
||||||
|
|
||||||
class Rooster(Base, BaseMixin):
|
|
||||||
__tablename__ = "rooster"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("year", "team_id", "player_id", "position_id"),
|
|
||||||
)
|
|
||||||
year = Column(Integer)
|
|
||||||
team_id = Column(String, ForeignKey("team.id"), nullable=False, index=True)
|
|
||||||
team = relationship("Team", back_populates="roosters")
|
|
||||||
player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
|
|
||||||
player = relationship("Player", back_populates="roosters")
|
|
||||||
position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
|
|
||||||
position = relationship("FieldPosition", back_populates="roosters")
|
|
||||||
cards = relationship("Card")
|
|
||||||
|
|
||||||
|
|
||||||
class Vendor(Base, BaseMixin):
|
|
||||||
__tablename__ = "vendor"
|
|
||||||
name = Column(String(255), nullable=False, unique=True, index=True)
|
|
||||||
card_sets = relationship("CardSet")
|
|
||||||
cards = relationship("Card")
|
|
||||||
|
|
||||||
|
|
||||||
class CardSet(Base, BaseMixin):
|
|
||||||
__tablename__ = "card_set"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("name", "vendor_id"),
|
|
||||||
)
|
|
||||||
name = Column(String(255), index=True)
|
|
||||||
parallel_set = Column(Boolean)
|
|
||||||
insert_set = Column(Boolean)
|
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
|
||||||
vendor = relationship("Vendor", back_populates="card_sets")
|
|
||||||
cards = relationship("Card")
|
|
||||||
|
|
||||||
|
|
||||||
class Card(Base, BaseMixin):
|
|
||||||
__tablename__ = "card"
|
|
||||||
__table_args__ = (
|
|
||||||
UniqueConstraint("card_number", "year", "vendor_id", "card_set_id"),
|
|
||||||
)
|
|
||||||
card_number = Column(Integer, index=True)
|
|
||||||
year = Column(Integer, index=True)
|
|
||||||
card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
|
|
||||||
card_set = relationship("CardSet", back_populates="cards")
|
|
||||||
rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
|
|
||||||
rooster = relationship("Rooster", back_populates="cards")
|
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
|
|
||||||
vendor = relationship("Vendor", back_populates="cards")
|
|
||||||
Generated
+19
@@ -300,6 +300,7 @@ dependencies = [
|
|||||||
{ name = "beautifulsoup4" },
|
{ name = "beautifulsoup4" },
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
{ name = "kontor-schema" },
|
||||||
{ name = "mariadb" },
|
{ name = "mariadb" },
|
||||||
{ name = "pathlib" },
|
{ name = "pathlib" },
|
||||||
{ name = "platformdirs" },
|
{ name = "platformdirs" },
|
||||||
@@ -316,6 +317,7 @@ requires-dist = [
|
|||||||
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
||||||
{ name = "httpx", specifier = "==0.24.1" },
|
{ name = "httpx", specifier = "==0.24.1" },
|
||||||
|
{ name = "kontor-schema", directory = "../kontor-schema" },
|
||||||
{ name = "mariadb", specifier = ">=1.1.12" },
|
{ name = "mariadb", specifier = ">=1.1.12" },
|
||||||
{ name = "pathlib", specifier = ">=1.0.1" },
|
{ name = "pathlib", specifier = ">=1.0.1" },
|
||||||
{ name = "platformdirs", specifier = ">=4.3.7" },
|
{ name = "platformdirs", specifier = ">=4.3.7" },
|
||||||
@@ -327,6 +329,23 @@ requires-dist = [
|
|||||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kontor-schema"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { directory = "../kontor-schema" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beautifulsoup4" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.40" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mariadb"
|
name = "mariadb"
|
||||||
version = "1.1.12"
|
version = "1.1.12"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
||||||
@@ -7,7 +7,11 @@ authors = [
|
|||||||
{ name = "Thomas Peetz", email = "thomas.peetz@ingenieurbuero-peetz.de" }
|
{ name = "Thomas Peetz", email = "thomas.peetz@ingenieurbuero-peetz.de" }
|
||||||
]
|
]
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
"beautifulsoup4>=4.13.4",
|
||||||
|
"requests>=2.32.3",
|
||||||
|
"sqlalchemy>=2.0.40",
|
||||||
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
from .base import Base, BaseMixin
|
||||||
@@ -14,7 +13,7 @@ class User(Base, BaseMixin):
|
|||||||
user_name = Column(String(255), nullable=False)
|
user_name = Column(String(255), nullable=False)
|
||||||
email = Column(String(255))
|
email = Column(String(255))
|
||||||
password = Column(String(255))
|
password = Column(String(255))
|
||||||
enabled = Column(BIT(1))
|
enabled = Column(Boolean)
|
||||||
matrix = relationship("AuthorizationMatrix")
|
matrix = relationship("AuthorizationMatrix")
|
||||||
tokens = relationship("Token")
|
tokens = relationship("Token")
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ class Token(Base, BaseMixin):
|
|||||||
token = Column(String(255), nullable=False, unique=True)
|
token = Column(String(255), nullable=False, unique=True)
|
||||||
name = Column(String(255))
|
name = Column(String(255))
|
||||||
last_used_date: Mapped[datetime] = mapped_column()
|
last_used_date: Mapped[datetime] = mapped_column()
|
||||||
enabled = Column(BIT(1))
|
enabled = Column(Boolean)
|
||||||
user_id = Column(String(255), ForeignKey("user.id"), nullable=False)
|
user_id = Column(String(255), ForeignKey("user.id"), nullable=False)
|
||||||
user = relationship("User", back_populates="tokens")
|
user = relationship("User", back_populates="tokens")
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ class AuthorizationMatrix(Base, BaseMixin):
|
|||||||
class ModuleData(Base, BaseMixin):
|
class ModuleData(Base, BaseMixin):
|
||||||
__tablename__ = "module_data"
|
__tablename__ = "module_data"
|
||||||
module_name = Column(String(255), nullable=False)
|
module_name = Column(String(255), nullable=False)
|
||||||
import_data = Column(BIT(1))
|
import_data = Column(Boolean)
|
||||||
|
|
||||||
|
|
||||||
class MailAccount(Base, BaseMixin):
|
class MailAccount(Base, BaseMixin):
|
||||||
@@ -66,7 +65,7 @@ class MailAccount(Base, BaseMixin):
|
|||||||
protocol = Column(String(255))
|
protocol = Column(String(255))
|
||||||
user_name = Column(String(255))
|
user_name = Column(String(255))
|
||||||
password = Column(String(255))
|
password = Column(String(255))
|
||||||
start_tls = Column(BIT(1))
|
start_tls = Column(Boolean)
|
||||||
|
|
||||||
|
|
||||||
class Mail(Base, BaseMixin):
|
class Mail(Base, BaseMixin):
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import func, Column, String
|
from sqlalchemy import func, Column, String, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ class BaseVideoMixin:
|
|||||||
cloud_link = Column(String(255))
|
cloud_link = Column(String(255))
|
||||||
file_name = Column(String(255))
|
file_name = Column(String(255))
|
||||||
path = Column(String(255))
|
path = Column(String(255))
|
||||||
review = Column(BIT(1))
|
review = Column(Boolean)
|
||||||
title = Column(String(255))
|
title = Column(String(255))
|
||||||
url = Column(String(255), unique=True)
|
url = Column(String(255), unique=True)
|
||||||
should_download = Column(BIT(1))
|
should_download = Column(Boolean)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
from .base import Base, BaseMixin
|
||||||
@@ -22,8 +21,8 @@ class Comic(Base, BaseMixin):
|
|||||||
title = Column(String(length=255), unique=True)
|
title = Column(String(length=255), unique=True)
|
||||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||||
publisher = relationship("Publisher", back_populates="comics")
|
publisher = relationship("Publisher", back_populates="comics")
|
||||||
current_order = Column(BIT(1))
|
current_order = Column(Boolean)
|
||||||
completed = Column(BIT(1))
|
completed = Column(Boolean)
|
||||||
issues = relationship("Issue")
|
issues = relationship("Issue")
|
||||||
story_arcs = relationship("StoryArc")
|
story_arcs = relationship("StoryArc")
|
||||||
trade_paperbacks = relationship("TradePaperback")
|
trade_paperbacks = relationship("TradePaperback")
|
||||||
@@ -64,8 +63,8 @@ class StoryArc(Base, BaseMixin):
|
|||||||
class Issue(Base, BaseMixin):
|
class Issue(Base, BaseMixin):
|
||||||
__tablename__ = "issue"
|
__tablename__ = "issue"
|
||||||
issue_number = Column(String(255))
|
issue_number = Column(String(255))
|
||||||
in_stock = Column(BIT(1))
|
in_stock = Column(Boolean)
|
||||||
is_read = Column(BIT(1))
|
is_read = Column(Boolean)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="issues")
|
comic = relationship("Comic", back_populates="issues")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ class StatusType(Enum):
|
|||||||
CLOUD_LINK_ID = auto()
|
CLOUD_LINK_ID = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ExportType(Enum):
|
||||||
|
JSON = "JSON"
|
||||||
|
YAML = "YAML"
|
||||||
|
SQLITE = "SQLite"
|
||||||
|
|
||||||
|
|
||||||
class KontorDB:
|
class KontorDB:
|
||||||
|
|
||||||
def __init__(self, db_engine: Engine, log: Logger):
|
def __init__(self, db_engine: Engine, log: Logger):
|
||||||
@@ -125,7 +131,6 @@ class KontorDB:
|
|||||||
|
|
||||||
def get_columns(self, table_name: str) -> dict:
|
def get_columns(self, table_name: str) -> dict:
|
||||||
columns = {}
|
columns = {}
|
||||||
order = 0
|
|
||||||
__session__ = sessionmaker(self.engine)
|
__session__ = sessionmaker(self.engine)
|
||||||
table_info = self.get_table_by_name(table_name)
|
table_info = self.get_table_by_name(table_name)
|
||||||
_filters = {'table_id': table_info['id']}
|
_filters = {'table_id': table_info['id']}
|
||||||
@@ -177,7 +182,7 @@ class KontorDB:
|
|||||||
# self.log.info("data: %s", data)
|
# self.log.info("data: %s", data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def export_db(self, export_type: str, export_file_name: str) -> dict:
|
def export_db(self, export_type: ExportType, export_file_name: str) -> dict:
|
||||||
results = {}
|
results = {}
|
||||||
db = {}
|
db = {}
|
||||||
export_table_list = self.get_table_names()
|
export_table_list = self.get_table_names()
|
||||||
@@ -211,14 +216,14 @@ class KontorDB:
|
|||||||
db[table] = entries
|
db[table] = entries
|
||||||
results[table] = len(entries)
|
results[table] = len(entries)
|
||||||
match export_type:
|
match export_type:
|
||||||
case "JSON":
|
case ExportType.JSON:
|
||||||
json_dump = json.dumps(db, indent=4)
|
json_dump = json.dumps(db, indent=4)
|
||||||
with open(export_file_name, "w") as dump_file:
|
with open(export_file_name, "w") as dump_file:
|
||||||
dump_file.write(json_dump)
|
dump_file.write(json_dump)
|
||||||
case "YAML":
|
case ExportType.YAML:
|
||||||
export_file = Path(export_file_name)
|
pass
|
||||||
case "SQLite":
|
case ExportType.SQLITE:
|
||||||
export_file = Path(export_file_name)
|
pass
|
||||||
self.log.info(f"{len(results)} tables exported")
|
self.log.info(f"{len(results)} tables exported")
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from sqlalchemy import Column, String, ForeignKey
|
from sqlalchemy import Column, String, ForeignKey, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from .base import Base, BaseMixin, BaseVideoMixin
|
from .base import Base, BaseMixin, BaseVideoMixin
|
||||||
@@ -44,12 +43,12 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
lines_list = output.splitlines()
|
lines_list = output.splitlines()
|
||||||
file_name = self.__parse_output__(lines_list)
|
file_name = self.__parse_output__(lines_list)
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
self.review = 1
|
self.review = True
|
||||||
self.should_download = 1
|
self.should_download = True
|
||||||
self.file_name = None
|
self.file_name = None
|
||||||
else:
|
else:
|
||||||
download_file = Path(file_name)
|
download_file = Path(file_name)
|
||||||
self.should_download = 0
|
self.should_download = False
|
||||||
self.file_name = download_file.name
|
self.file_name = download_file.name
|
||||||
self.cloud_link = str(download_file.absolute())
|
self.cloud_link = str(download_file.absolute())
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
@@ -84,17 +83,10 @@ class MediaActorFile(Base, BaseMixin):
|
|||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
class MediaArticle(Base, BaseMixin):
|
||||||
__tablename__ = 'media_article'
|
__tablename__ = 'media_article'
|
||||||
review = Column(BIT(1))
|
review = Column(Boolean)
|
||||||
title = Column(String(255))
|
title = Column(String(255))
|
||||||
url = Column(String(255), unique=True)
|
url = Column(String(255), unique=True)
|
||||||
|
|
||||||
|
|
||||||
class MediaVideo(Base, BaseMixin):
|
class MediaVideo(Base, BaseMixin, BaseVideoMixin):
|
||||||
__tablename__ = 'media_video'
|
__tablename__ = 'media_video'
|
||||||
cloud_link = Column(String(255))
|
|
||||||
file_name = Column(String(255))
|
|
||||||
path = Column(String(255))
|
|
||||||
review = Column(BIT(1))
|
|
||||||
title = Column(String(255))
|
|
||||||
url = Column(String(255), unique=True)
|
|
||||||
should_download = Column(BIT(1))
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from sqlalchemy import Column, String, ForeignKey, Integer
|
from sqlalchemy import Column, String, ForeignKey, Integer, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
from .base import Base, BaseMixin
|
||||||
@@ -28,8 +27,8 @@ class MetaDataColumn(Base, BaseMixin):
|
|||||||
table = relationship("MetaDataTable", back_populates="table_columns")
|
table = relationship("MetaDataTable", back_populates="table_columns")
|
||||||
column_label = Column(String(255))
|
column_label = Column(String(255))
|
||||||
filter_label = Column(String(255))
|
filter_label = Column(String(255))
|
||||||
is_shown = Column(BIT(1))
|
is_shown = Column(Boolean)
|
||||||
show_filter = Column(BIT(1))
|
show_filter = Column(Boolean)
|
||||||
ref_column = Column(String, nullable=True)
|
ref_column = Column(String, nullable=True)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
from .base import Base, BaseMixin
|
||||||
@@ -78,8 +77,8 @@ class CardSet(Base, BaseMixin):
|
|||||||
UniqueConstraint("name", "vendor_id"),
|
UniqueConstraint("name", "vendor_id"),
|
||||||
)
|
)
|
||||||
name = Column(String(255), index=True)
|
name = Column(String(255), index=True)
|
||||||
parallel_set = Column(BIT(1))
|
parallel_set = Column(Boolean)
|
||||||
insert_set = Column(BIT(1))
|
insert_set = Column(Boolean)
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||||
vendor = relationship("Vendor", back_populates="card_sets")
|
vendor = relationship("Vendor", back_populates="card_sets")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|||||||
Generated
+153
@@ -2,7 +2,160 @@ version = 1
|
|||||||
revision = 1
|
revision = 1
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beautifulsoup4"
|
||||||
|
version = "4.13.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "soupsieve" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2025.1.31"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/9c/666d8c71b18d0189cf801c0e0b31c4bfc609ac823883286045b1f3ae8994/greenlet-3.2.0.tar.gz", hash = "sha256:1d2d43bd711a43db8d9b9187500e6432ddb4fafe112d082ffabca8660a9e01a7", size = 183685 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/43/c0b655d4d7eae19282b028bcec449e5c80626ad0d8d0ca3703f9b1c29258/greenlet-3.2.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b86a3ccc865ae601f446af042707b749eebc297928ea7bd0c5f60c56525850be", size = 269131 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/7d/c8f51c373c7f7ac0f73d04a6fd77ab34f6f643cb41a0d186d05ba96708e7/greenlet-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144283ad88ed77f3ebd74710dd419b55dd15d18704b0ae05935766a93f5671c5", size = 637323 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/65/c3ee41b2e56586737d6e124b250583695628ffa6b324855b3a1267a8d1d9/greenlet-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5be69cd50994b8465c3ad1467f9e63001f76e53a89440ad4440d1b6d52591280", size = 651430 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/07/33bd7a3dcde1db7259371d026ce76be1eb653d2d892334fc79a500b3c5ee/greenlet-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47aeadd1e8fbdef8fdceb8fb4edc0cbb398a57568d56fd68f2bc00d0d809e6b6", size = 645798 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/5b/33c221a6a867030b0b770513a1b78f6c30e04294131dafdc8da78906bbe6/greenlet-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18adc14ab154ca6e53eecc9dc50ff17aeb7ba70b7e14779b26e16d71efa90038", size = 648271 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/dd/d6452248fa6093504e3b7525dc2bdc4e55a4296ec6ee74ba241a51d852e2/greenlet-3.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8622b33d8694ec373ad55050c3d4e49818132b44852158442e1931bb02af336", size = 606779 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/24/160f04d2589bcb15b8661dcd1763437b22e01643626899a4139bf98f02af/greenlet-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8ac9a2c20fbff3d0b853e9ef705cdedb70d9276af977d1ec1cde86a87a4c821", size = 1117968 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/ff/c6e3f3a5168fef5209cfd9498b2b5dd77a0bf29dfc686a03dcc614cf4432/greenlet-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:cd37273dc7ca1d5da149b58c8b3ce0711181672ba1b09969663905a765affe21", size = 1145510 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/62/5215e374819052e542b5bde06bd7d4a171454b6938c96a2384f21cb94279/greenlet-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8a8940a8d301828acd8b9f3f85db23069a692ff2933358861b19936e29946b95", size = 296004 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/6d/dc9c909cba5cbf4b0833fce69912927a8ca74791c23c47b9fd4f28092108/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee59db626760f1ca8da697a086454210d36a19f7abecc9922a2374c04b47735b", size = 629900 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/a9/f3f304fbbbd604858ff3df303d7fa1d8f7f9e45a6ef74481aaf03aaac021/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7154b13ef87a8b62fc05419f12d75532d7783586ad016c57b5de8a1c6feeb517", size = 635270 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/92/4b7b4e2e23ecc723cceef9fe3898e78c8e14e106cc7ba2f276a66161da3e/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:199453d64b02d0c9d139e36d29681efd0e407ed8e2c0bf89d88878d6a787c28f", size = 632534 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/7f/91f0ecbe72c9d789fb7f400b39da9d1e87fcc2cf8746a9636479ba79ab01/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0010e928e1901d36625f21d008618273f9dda26b516dbdecf873937d39c9dff0", size = 628826 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/59/e449a44ce52b13751f55376d85adc155dd311608f6d2aa5b6bd2c8d15486/greenlet-3.2.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6005f7a86de836a1dc4b8d824a2339cdd5a1ca7cb1af55ea92575401f9952f4c", size = 593697 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/09/cca3392927c5c990b7a8ede64ccd0712808438d6490d63ce6b8704d6df5f/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:17fd241c0d50bacb7ce8ff77a30f94a2d0ca69434ba2e0187cf95a5414aeb7e1", size = 1105762 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/b9/3d201f819afc3b7a8cd7ebe645f1a17799603e2d62c968154518f79f4881/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:7b17a26abc6a1890bf77d5d6b71c0999705386b00060d15c10b8182679ff2790", size = 1125173 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/7b/773a30602234597fc2882091f8e1d1a38ea0b4419d99ca7ed82c827e2c3a/greenlet-3.2.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:397b6bbda06f8fe895893d96218cd6f6d855a6701dc45012ebe12262423cec8b", size = 269908 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kontor-schema"
|
name = "kontor-schema"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beautifulsoup4" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.40" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.32.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "soupsieve"
|
||||||
|
version = "2.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlalchemy"
|
||||||
|
version = "2.0.40"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.13.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
|
||||||
|
]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
find . -name '*.py[co]' -delete
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
python -m pytest \
|
||||||
|
-v \
|
||||||
|
--cov=kontor \
|
||||||
|
--cov-report=term \
|
||||||
|
--cov-report=html:coverage-report \
|
||||||
|
tests/
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
uv sync
|
||||||
|
uv build
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# kontor-scripts
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class FileStatus:
|
|||||||
self.id = response['id']
|
self.id = response['id']
|
||||||
|
|
||||||
|
|
||||||
def get_status_of_file(found_file: Path, cursor, log) -> FileStatus:
|
def get_status_of_file(found_file: Path, cursor, logger) -> FileStatus:
|
||||||
status = FileStatus()
|
status = FileStatus()
|
||||||
try:
|
try:
|
||||||
cursor.execute(f'SELECT id, cloud_link FROM media_file WHERE file_name="{found_file.name}"')
|
cursor.execute(f'SELECT id, cloud_link FROM media_file WHERE file_name="{found_file.name}"')
|
||||||
@@ -45,7 +45,7 @@ def get_status_of_file(found_file: Path, cursor, log) -> FileStatus:
|
|||||||
status.status_type = StatusType.FILE_NAME
|
status.status_type = StatusType.FILE_NAME
|
||||||
status.id = rows[0][0]
|
status.id = rows[0][0]
|
||||||
except mariadb.Error as error:
|
except mariadb.Error as error:
|
||||||
log.debug(f'select failed with {error}')
|
logger.debug(f'select failed with {error}')
|
||||||
try:
|
try:
|
||||||
cursor.execute(f'SELECT id FROM media_file WHERE id="{found_file.stem}"')
|
cursor.execute(f'SELECT id FROM media_file WHERE id="{found_file.stem}"')
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
@@ -55,9 +55,9 @@ def get_status_of_file(found_file: Path, cursor, log) -> FileStatus:
|
|||||||
if len(rows) > 1:
|
if len(rows) > 1:
|
||||||
status.status_type = StatusType.DUPLICATE
|
status.status_type = StatusType.DUPLICATE
|
||||||
for row in rows:
|
for row in rows:
|
||||||
log.info(f"found {row[0]} with {found_file}")
|
logger.info(f"found {row[0]} with {found_file}")
|
||||||
except mariadb.Error as error:
|
except mariadb.Error as error:
|
||||||
log.debug(f'select failed with {error}')
|
logger.debug(f'select failed with {error}')
|
||||||
try:
|
try:
|
||||||
cursor.execute(f'SELECT id FROM media_file WHERE cloud_link LIKE "%{found_file.stem}%"')
|
cursor.execute(f'SELECT id FROM media_file WHERE cloud_link LIKE "%{found_file.stem}%"')
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
@@ -68,75 +68,75 @@ def get_status_of_file(found_file: Path, cursor, log) -> FileStatus:
|
|||||||
else:
|
else:
|
||||||
status.status_type = StatusType.CLOUD_LINK
|
status.status_type = StatusType.CLOUD_LINK
|
||||||
except mariadb.Error as error:
|
except mariadb.Error as error:
|
||||||
log.debug(f'select failed with {error}')
|
logger.debug(f'select failed with {error}')
|
||||||
response = requests.get(f"http://127.0.0.1:8800/media/files/{found_file.stem}")
|
response = requests.get(f"http://127.0.0.1:8800/media/files/{found_file.stem}")
|
||||||
log.debug(f"Status: {response.status_code}")
|
logger.debug(f"Status: {response.status_code}")
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
status.status_type = StatusType.FILE_ID
|
status.status_type = StatusType.FILE_ID
|
||||||
status.id = response.json()['id']
|
status.id = response.json()['id']
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def rename_files_to_id(media_dir, dry_run, conn, log):
|
def rename_files_to_id(media_dir, dry_run, conn, logger):
|
||||||
media_path = Path(media_dir)
|
media_path = Path(media_dir)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
for file in media_path.iterdir():
|
for file in media_path.iterdir():
|
||||||
log.debug('found file: {}'.format(file.name))
|
logger.debug('found file: {}'.format(file.name))
|
||||||
status: FileStatus = get_status_of_file(file, cursor, log)
|
status: FileStatus = get_status_of_file(file, cursor, logger)
|
||||||
file_id = status.id
|
file_id = status.id
|
||||||
if not file_id:
|
if not file_id:
|
||||||
log.info(f"ID of file {file.name} is unknown")
|
logger.info(f"ID of file {file.name} is unknown")
|
||||||
continue
|
continue
|
||||||
new_file_path = file.with_name(f"{file_id}{file.suffix}")
|
new_file_path = file.with_name(f"{file_id}{file.suffix}")
|
||||||
match status.status_type:
|
match status.status_type:
|
||||||
case StatusType.FILE_NAME:
|
case StatusType.FILE_NAME:
|
||||||
log.info(f'status of {file.name} is file_name')
|
logger.info(f'status of {file.name} is file_name')
|
||||||
rename_file(file, new_file_path, dry_run, log)
|
rename_file(file, new_file_path, dry_run, logger)
|
||||||
update_cloud_link(file_id, new_file_path, conn, dry_run, log)
|
update_cloud_link(file_id, new_file_path, conn, dry_run, logger)
|
||||||
case StatusType.FILE_ID:
|
case StatusType.FILE_ID:
|
||||||
log.info(f'status of {file.name} is file_id')
|
logger.info(f'status of {file.name} is file_id')
|
||||||
update_cloud_link(file_id, new_file_path, conn, dry_run, log)
|
update_cloud_link(file_id, new_file_path, conn, dry_run, logger)
|
||||||
case StatusType.CLOUD_LINK:
|
case StatusType.CLOUD_LINK:
|
||||||
log.info(f'status of {file.name} is cloud_link')
|
logger.info(f'status of {file.name} is cloud_link')
|
||||||
rename_file(file, new_file_path, dry_run, log)
|
rename_file(file, new_file_path, dry_run, logger)
|
||||||
update_cloud_link(file_id, new_file_path, conn, dry_run, log)
|
update_cloud_link(file_id, new_file_path, conn, dry_run, logger)
|
||||||
case StatusType.CLOUD_LINK_ID:
|
case StatusType.CLOUD_LINK_ID:
|
||||||
log.debug(f'status of {file.name} is cloud_link_id')
|
logger.debug(f'status of {file.name} is cloud_link_id')
|
||||||
update_cloud_link(file_id, new_file_path, conn, dry_run, log)
|
update_cloud_link(file_id, new_file_path, conn, dry_run, logger)
|
||||||
case StatusType.DUPLICATE:
|
case StatusType.DUPLICATE:
|
||||||
log.info(f'status of {file.name} is duplicate')
|
logger.info(f'status of {file.name} is duplicate')
|
||||||
case StatusType.UNKNOWN:
|
case StatusType.UNKNOWN:
|
||||||
log.info(f'status of {file.name} is unknown')
|
logger.info(f'status of {file.name} is unknown')
|
||||||
|
|
||||||
def rename_file(current_file, new_file_path, dry_run, log):
|
def rename_file(current_file, new_file_path, dry_run, logger):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
log.info('rename file {} to {}'.format(current_file.name, new_file_path.name))
|
logger.info('rename file {} to {}'.format(current_file.name, new_file_path.name))
|
||||||
else:
|
else:
|
||||||
current_file.rename(Path(new_file_path))
|
current_file.rename(Path(new_file_path))
|
||||||
|
|
||||||
def update_cloud_link(file_id, file_path, conn, dry_run, log):
|
def update_cloud_link(file_id, file_path, conn, dry_run, logger):
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
log.debug(f'update entry {file_id} with {file_path.absolute()}')
|
logger.debug(f'update entry {file_id} with {file_path.absolute()}')
|
||||||
if dry_run:
|
if dry_run:
|
||||||
log.debug(f'UPDATE media_file: cloud_link={file_path.absolute()}')
|
logger.debug(f'UPDATE media_file: cloud_link={file_path.absolute()}')
|
||||||
else:
|
else:
|
||||||
cursor.execute('UPDATE media_file SET cloud_link="{}" WHERE id="{}"'.format(file_path.absolute(), file_id))
|
cursor.execute('UPDATE media_file SET cloud_link="{}" WHERE id="{}"'.format(file_path.absolute(), file_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def reset_cloud_link(conn, dry_run, log):
|
def reset_cloud_link(conn, dry_run, logger):
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
if dry_run:
|
if dry_run:
|
||||||
log.info('UPDATE media_file SET cloud_link=""')
|
logger.info('UPDATE media_file SET cloud_link=""')
|
||||||
else:
|
else:
|
||||||
cursor.execute('UPDATE media_file SET cloud_link="" WHERE id is NOT NULL')
|
cursor.execute('UPDATE media_file SET cloud_link="" WHERE id is NOT NULL')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
def check_file_with_db(data_file: Path, m_conn, log):
|
def check_file_with_db(json_file: Path, conn, logger):
|
||||||
log.info(f"read json file: {data_file}")
|
logger.info(f"read json file: {json_file}")
|
||||||
cursor = m_conn.cursor()
|
cursor = conn.cursor()
|
||||||
with open(data_file, 'r') as json_file:
|
with open(json_file, 'r') as json_file:
|
||||||
json_load = json.load(json_file)
|
json_load = json.load(json_file)
|
||||||
for table in json_load:
|
for table in json_load:
|
||||||
log.info(f"{table}: {len(json_load[table])}")
|
logger.info(f"{table}: {len(json_load[table])}")
|
||||||
items = json_load[table]
|
items = json_load[table]
|
||||||
for item in items:
|
for item in items:
|
||||||
item_id = item['id']
|
item_id = item['id']
|
||||||
@@ -144,11 +144,11 @@ def check_file_with_db(data_file: Path, m_conn, log):
|
|||||||
cursor.execute(select_statement)
|
cursor.execute(select_statement)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
count = len(rows)
|
count = len(rows)
|
||||||
log.info(f"{count} entries found for {item_id}")
|
logger.info(f"{count} entries found for {item_id}")
|
||||||
if count == 0:
|
if count == 0:
|
||||||
log.info(f"entry for {item_id} not found")
|
logger.info(f"entry for {item_id} not found")
|
||||||
if count == 1:
|
if count == 1:
|
||||||
log.info(f"check entry {item_id}")
|
logger.info(f"check entry {item_id}")
|
||||||
#log.info(f"entry {rows[0]}")
|
#log.info(f"entry {rows[0]}")
|
||||||
columns = []
|
columns = []
|
||||||
values = []
|
values = []
|
||||||
@@ -156,7 +156,7 @@ def check_file_with_db(data_file: Path, m_conn, log):
|
|||||||
columns.append(key)
|
columns.append(key)
|
||||||
values.append(value)
|
values.append(value)
|
||||||
for index, _ in enumerate(columns):
|
for index, _ in enumerate(columns):
|
||||||
log.info(f"compare {values[index]} with {rows[0][index]}")
|
logger.info(f"compare {values[index]} with {rows[0][index]}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -21,6 +22,8 @@ parser.add_argument('--tool', '-t', default='yt-dlp')
|
|||||||
parser.add_argument('--dry-run', '-m', action='store_true')
|
parser.add_argument('--dry-run', '-m', action='store_true')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
type FileInfo = Dict[str, Union[str, bool]]
|
||||||
|
|
||||||
class FileStatus(Enum):
|
class FileStatus(Enum):
|
||||||
DOWNLOADED = auto()
|
DOWNLOADED = auto()
|
||||||
RENAMED = auto()
|
RENAMED = auto()
|
||||||
@@ -61,37 +64,41 @@ def __parse_output__(lines_list: list[str]) -> str | None:
|
|||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
|
|
||||||
def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
def is_file_downloaded(media_file: FileInfo, media_dir: Path) -> FileStatus:
|
||||||
file_name_as_title = f"{media_file['file_name']}"
|
file_name_as_title = f"{media_file['file_name']}"
|
||||||
file_title = Path(dir, f"{file_name_as_title}.mp4")
|
file_title = Path(media_dir, f"{file_name_as_title}.mp4")
|
||||||
if file_title.exists():
|
if file_title.exists():
|
||||||
log.info(f"{file_name_as_title} has been downloaded")
|
log.info(f"{file_name_as_title} has been downloaded")
|
||||||
|
media_file['review'] = False
|
||||||
media_file['should_download'] = False
|
media_file['should_download'] = False
|
||||||
return FileStatus.DOWNLOADED
|
return FileStatus.DOWNLOADED
|
||||||
file_name_as_id = f"{media_file['id']}"
|
file_name_as_id = f"{media_file['id']}"
|
||||||
file_with_id_as_name = Path(dir, f"{file_name_as_id}.mp4")
|
file_with_id_as_name = Path(media_dir, f"{file_name_as_id}.mp4")
|
||||||
if file_with_id_as_name.exists():
|
if file_with_id_as_name.exists():
|
||||||
log.info(f"{file_with_id_as_name} has been downloaded and renamed")
|
log.info(f"{file_with_id_as_name} has been downloaded and renamed")
|
||||||
media_file['cloud_link'] = file_with_id_as_name
|
media_file['cloud_link'] = file_with_id_as_name.as_posix()
|
||||||
|
media_file['review'] = False
|
||||||
media_file['should_download'] = False
|
media_file['should_download'] = False
|
||||||
return FileStatus.RENAMED
|
return FileStatus.RENAMED
|
||||||
log.info("could not find file - start download")
|
log.info("could not find file - start download")
|
||||||
return FileStatus.UNKNOWN
|
return FileStatus.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def update_status(item_id: UUID, file_info: dict):
|
def update_status(item_id: UUID, file_info: FileInfo):
|
||||||
update = requests.put(f"http://127.0.0.1:8800/media/files/{item_id}", json=file_info)
|
update = requests.put(f"http://127.0.0.1:8800/media/files/{item_id}", json=file_info)
|
||||||
log.info(f"update status: {update.status_code}")
|
status = update.status_code
|
||||||
log.info(f"update result: {update.json()}")
|
log.info(f"update status: {status}")
|
||||||
|
if status < 300:
|
||||||
|
log.info(f"update result: {update.json()}")
|
||||||
|
|
||||||
|
|
||||||
def rename_file(file_info: dict):
|
def rename_file(file_info: FileInfo):
|
||||||
item_id = file_info['id']
|
item_id = file_info['id']
|
||||||
file = Path(args.dir, file_info['file_name'])
|
file = Path(args.dir, file_info['file_name'])
|
||||||
new_file_path = file.with_name(f"{item_id}{file.suffix}")
|
new_file_path = file.with_name(f"{item_id}{file.suffix}")
|
||||||
log.info(f"rename {file} to {new_file_path}")
|
log.info(f"rename {file} to {new_file_path}")
|
||||||
file.rename(Path(new_file_path))
|
file.rename(Path(new_file_path))
|
||||||
file_info['cloud_link'] = str(new_file_path)
|
file_info['cloud_link'] = new_file_path.as_posix()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -105,6 +112,9 @@ if __name__ == '__main__':
|
|||||||
link = item['url']
|
link = item['url']
|
||||||
file_id = item['id']
|
file_id = item['id']
|
||||||
log.info(f"{file_id} - {link}")
|
log.info(f"{file_id} - {link}")
|
||||||
|
if link is None:
|
||||||
|
item['url'] = ""
|
||||||
|
log.info(f"set url for {file_id} to empty string")
|
||||||
download_status: FileStatus = is_file_downloaded(item, args.dir)
|
download_status: FileStatus = is_file_downloaded(item, args.dir)
|
||||||
match download_status:
|
match download_status:
|
||||||
case FileStatus.DOWNLOADED:
|
case FileStatus.DOWNLOADED:
|
||||||
@@ -119,4 +129,3 @@ if __name__ == '__main__':
|
|||||||
log.info(f'{item}')
|
log.info(f'{item}')
|
||||||
update_status(file_id, item)
|
update_status(file_id, item)
|
||||||
log.info('kontor.download finished')
|
log.info('kontor.download finished')
|
||||||
|
|
||||||
@@ -4,14 +4,14 @@ import data from json file to MariaDB
|
|||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
from kontor_schema import Base, KontorDB
|
||||||
|
from kontor_schema.database import ExportType
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from platformdirs import PlatformDirs
|
from platformdirs import PlatformDirs
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from schema import Base, KontorDB
|
|
||||||
from config import get_logger
|
from config import get_logger
|
||||||
from schema.database import ExportType
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[project]
|
||||||
|
name = "kontor-scripts"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Scripts to execute Kontor actions from commandline"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"beautifulsoup4>=4.13.4",
|
||||||
|
"mariadb>=1.1.12",
|
||||||
|
"pathlib>=1.0.1",
|
||||||
|
"platformdirs>=4.3.7",
|
||||||
|
"pytest>=8.3.5",
|
||||||
|
"pytest-cov>=6.1.1",
|
||||||
|
"pyyaml>=6.0.2",
|
||||||
|
"requests>=2.32.3",
|
||||||
|
"kontor.schema>=0.1.0",
|
||||||
|
"sqlalchemy>=2.0.40",
|
||||||
|
]
|
||||||
|
[tool.uv.sources]
|
||||||
|
kontor-schema = { path = "../kontor-schema"}
|
||||||
Generated
+333
@@ -0,0 +1,333 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 1
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beautifulsoup4"
|
||||||
|
version = "4.13.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "soupsieve" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2025.1.31"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.8.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/9c/666d8c71b18d0189cf801c0e0b31c4bfc609ac823883286045b1f3ae8994/greenlet-3.2.0.tar.gz", hash = "sha256:1d2d43bd711a43db8d9b9187500e6432ddb4fafe112d082ffabca8660a9e01a7", size = 183685 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/43/c0b655d4d7eae19282b028bcec449e5c80626ad0d8d0ca3703f9b1c29258/greenlet-3.2.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b86a3ccc865ae601f446af042707b749eebc297928ea7bd0c5f60c56525850be", size = 269131 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/7d/c8f51c373c7f7ac0f73d04a6fd77ab34f6f643cb41a0d186d05ba96708e7/greenlet-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144283ad88ed77f3ebd74710dd419b55dd15d18704b0ae05935766a93f5671c5", size = 637323 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/65/c3ee41b2e56586737d6e124b250583695628ffa6b324855b3a1267a8d1d9/greenlet-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5be69cd50994b8465c3ad1467f9e63001f76e53a89440ad4440d1b6d52591280", size = 651430 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/07/33bd7a3dcde1db7259371d026ce76be1eb653d2d892334fc79a500b3c5ee/greenlet-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47aeadd1e8fbdef8fdceb8fb4edc0cbb398a57568d56fd68f2bc00d0d809e6b6", size = 645798 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/5b/33c221a6a867030b0b770513a1b78f6c30e04294131dafdc8da78906bbe6/greenlet-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18adc14ab154ca6e53eecc9dc50ff17aeb7ba70b7e14779b26e16d71efa90038", size = 648271 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/dd/d6452248fa6093504e3b7525dc2bdc4e55a4296ec6ee74ba241a51d852e2/greenlet-3.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8622b33d8694ec373ad55050c3d4e49818132b44852158442e1931bb02af336", size = 606779 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/24/160f04d2589bcb15b8661dcd1763437b22e01643626899a4139bf98f02af/greenlet-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8ac9a2c20fbff3d0b853e9ef705cdedb70d9276af977d1ec1cde86a87a4c821", size = 1117968 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/ff/c6e3f3a5168fef5209cfd9498b2b5dd77a0bf29dfc686a03dcc614cf4432/greenlet-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:cd37273dc7ca1d5da149b58c8b3ce0711181672ba1b09969663905a765affe21", size = 1145510 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/62/5215e374819052e542b5bde06bd7d4a171454b6938c96a2384f21cb94279/greenlet-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8a8940a8d301828acd8b9f3f85db23069a692ff2933358861b19936e29946b95", size = 296004 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/6d/dc9c909cba5cbf4b0833fce69912927a8ca74791c23c47b9fd4f28092108/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee59db626760f1ca8da697a086454210d36a19f7abecc9922a2374c04b47735b", size = 629900 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/a9/f3f304fbbbd604858ff3df303d7fa1d8f7f9e45a6ef74481aaf03aaac021/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7154b13ef87a8b62fc05419f12d75532d7783586ad016c57b5de8a1c6feeb517", size = 635270 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/92/4b7b4e2e23ecc723cceef9fe3898e78c8e14e106cc7ba2f276a66161da3e/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:199453d64b02d0c9d139e36d29681efd0e407ed8e2c0bf89d88878d6a787c28f", size = 632534 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/7f/91f0ecbe72c9d789fb7f400b39da9d1e87fcc2cf8746a9636479ba79ab01/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0010e928e1901d36625f21d008618273f9dda26b516dbdecf873937d39c9dff0", size = 628826 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/59/e449a44ce52b13751f55376d85adc155dd311608f6d2aa5b6bd2c8d15486/greenlet-3.2.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6005f7a86de836a1dc4b8d824a2339cdd5a1ca7cb1af55ea92575401f9952f4c", size = 593697 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/09/cca3392927c5c990b7a8ede64ccd0712808438d6490d63ce6b8704d6df5f/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:17fd241c0d50bacb7ce8ff77a30f94a2d0ca69434ba2e0187cf95a5414aeb7e1", size = 1105762 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/b9/3d201f819afc3b7a8cd7ebe645f1a17799603e2d62c968154518f79f4881/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:7b17a26abc6a1890bf77d5d6b71c0999705386b00060d15c10b8182679ff2790", size = 1125173 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/7b/773a30602234597fc2882091f8e1d1a38ea0b4419d99ca7ed82c827e2c3a/greenlet-3.2.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:397b6bbda06f8fe895893d96218cd6f6d855a6701dc45012ebe12262423cec8b", size = 269908 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kontor-schema"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { directory = "../kontor-schema" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beautifulsoup4" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.40" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kontor-scripts"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beautifulsoup4" },
|
||||||
|
{ name = "kontor-schema" },
|
||||||
|
{ name = "mariadb" },
|
||||||
|
{ name = "pathlib" },
|
||||||
|
{ name = "platformdirs" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-cov" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "beautifulsoup4", specifier = ">=4.13.4" },
|
||||||
|
{ name = "kontor-schema", directory = "../kontor-schema" },
|
||||||
|
{ name = "mariadb", specifier = ">=1.1.12" },
|
||||||
|
{ name = "pathlib", specifier = ">=1.0.1" },
|
||||||
|
{ name = "platformdirs", specifier = ">=4.3.7" },
|
||||||
|
{ name = "pytest", specifier = ">=8.3.5" },
|
||||||
|
{ name = "pytest-cov", specifier = ">=6.1.1" },
|
||||||
|
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.40" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mariadb"
|
||||||
|
version = "1.1.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "packaging" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/17/bb/4bbc803fbdafedbfba015f7cc1ab1e87a6d1de36725ba058c53e2f8a45ad/mariadb-1.1.12.tar.gz", hash = "sha256:50b02ff2c78b1b4f4628a054e3c8c7dd92972137727a5cc309a64c9ed20c878c", size = 85934 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/1b/b6eca3870ac1b5577a10d3b49ba42ac263c2e5718c9224cc1c8463940422/mariadb-1.1.12-cp313-cp313-win32.whl", hash = "sha256:ba43c42130d41352f32a5786c339cc931d05472ef7640fa3764d428dc294b88e", size = 184338 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/ff/c29a543ee1f9009755bc304138f61cd9b0ee1f14533e446513f84ccf143a/mariadb-1.1.12-cp313-cp313-win_amd64.whl", hash = "sha256:b69bc18418e72fcf359d17736cdc3f601a271203aff13ef7c57a415c8fd52ab0", size = 201272 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathlib"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ac/aa/9b065a76b9af472437a0059f77e8f962fe350438b927cb80184c32f075eb/pathlib-1.0.1.tar.gz", hash = "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f", size = 49298 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/f9/690a8600b93c332de3ab4a344a4ac34f00c8f104917061f779db6a918ed6/pathlib-1.0.1-py3-none-any.whl", hash = "sha256:f35f95ab8b0f59e6d354090350b44a80a80635d22efdedfa84c7ad1cf0a74147", size = 14363 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.3.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.3.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "6.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.32.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "soupsieve"
|
||||||
|
version = "2.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlalchemy"
|
||||||
|
version = "2.0.40"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.13.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
|
||||||
|
]
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
.PHONY: clean virtualenv test docker dist dist-upload
|
|
||||||
|
|
||||||
clean:
|
|
||||||
find . -name '*.py[co]' -delete
|
|
||||||
|
|
||||||
virtualenv:
|
|
||||||
virtualenv --prompt '|> kontor <| ' env
|
|
||||||
env/bin/pip install -r requirements-dev.txt
|
|
||||||
env/bin/python setup.py develop
|
|
||||||
@echo
|
|
||||||
@echo "VirtualENV Setup Complete. Now run: source env/bin/activate"
|
|
||||||
@echo
|
|
||||||
|
|
||||||
test:
|
|
||||||
python -m pytest \
|
|
||||||
-v \
|
|
||||||
--cov=kontor \
|
|
||||||
--cov-report=term \
|
|
||||||
--cov-report=html:coverage-report \
|
|
||||||
tests/
|
|
||||||
|
|
||||||
docker: clean
|
|
||||||
docker build -t kontor:latest .
|
|
||||||
|
|
||||||
dist: clean
|
|
||||||
rm -rf dist/*
|
|
||||||
python setup.py sdist
|
|
||||||
python setup.py bdist_wheel
|
|
||||||
|
|
||||||
dist-upload:
|
|
||||||
twine upload dist/*
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from .base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class Article(Base, BaseMixin):
|
|
||||||
__tablename__ = 'article'
|
|
||||||
title = Column(String(length=255), unique=True)
|
|
||||||
article_authors = relationship("ArticleAuthor")
|
|
||||||
|
|
||||||
|
|
||||||
class Author(Base, BaseMixin):
|
|
||||||
__tablename__ = 'author'
|
|
||||||
first_name = Column(String(255))
|
|
||||||
last_name = Column(String(255))
|
|
||||||
article_authors = relationship("ArticleAuthor")
|
|
||||||
book_authors = relationship("BookAuthor")
|
|
||||||
|
|
||||||
|
|
||||||
class BookshelfPublisher(Base, BaseMixin):
|
|
||||||
__tablename__ = 'bookshelf_publisher'
|
|
||||||
name = Column(String(length=255), unique=True)
|
|
||||||
books = relationship("Book")
|
|
||||||
|
|
||||||
|
|
||||||
class Book(Base, BaseMixin):
|
|
||||||
__tablename__ = 'book'
|
|
||||||
isbn = Column(String(255), unique=True)
|
|
||||||
title = Column(String(255))
|
|
||||||
year = Column(Integer, nullable=False)
|
|
||||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
|
||||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
|
||||||
book_authors = relationship("BookAuthor")
|
|
||||||
|
|
||||||
|
|
||||||
class ArticleAuthor(Base, BaseMixin):
|
|
||||||
__tablename__ = 'article_author'
|
|
||||||
article_id = Column(String, ForeignKey('article.id'), nullable=False)
|
|
||||||
article = relationship('Article', back_populates="article_authors")
|
|
||||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
|
||||||
author = relationship('Author', back_populates="article_authors")
|
|
||||||
|
|
||||||
|
|
||||||
class BookAuthor(Base, BaseMixin):
|
|
||||||
__tablename__ = 'book_author'
|
|
||||||
author_id = Column(String, ForeignKey('author.id'), nullable=False)
|
|
||||||
author = relationship('Author', back_populates="book_authors")
|
|
||||||
book_id = Column(String, ForeignKey('book.id'), nullable=False)
|
|
||||||
book = relationship('Book', back_populates="book_authors")
|
|
||||||
Reference in New Issue
Block a user