Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add local database support via sqlmodel #1474

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
6 changes: 6 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ verify_ssl = true
[dev-packages]

[packages]
annotated-types = {version="==0.7.0", python_version=">= '3.8'"}
apluggy = "==0.9.4"
attrs = "==23.2.0"
cattrs = {version="==23.2.3", python_version=">='3.7'"}
Expand All @@ -14,6 +15,7 @@ charset-normalizer = "==3.3.2"
click = "==8.1.7"
decorator = "==5.1.1"
deprecated = "==1.2.14"
greenlet = {version="==3.0.3", markers="python_version < '3.13' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))"}
humanize = "==4.10.0"
idna = {version="==3.7", python_version=">='3.5'"}
inquirerpy = "==0.3.4"
Expand All @@ -25,6 +27,8 @@ platformdirs = {version="==4.2.2", python_version=">='3.7'"}
plexapi = "==4.15.15"
pluggy = "==1.5.0"
prompt-toolkit = "==3.0.47"
pydantic = {version="==2.8.2", python_version=">= '3.8'"}
pydantic-core = {version="==2.20.1", python_version=">= '3.8'"}
pygments = "==2.18.0"
python-dotenv = "==1.0.1"
python-git-info = "==0.8.3"
Expand All @@ -36,6 +40,8 @@ requests-cache = "==1.2.1"
requests-oauthlib = {version="==2.0.0", python_version=">='3.4'"}
rich = "==13.7.1"
six = {version="==1.16.0", python_version=">='3.4'"}
sqlalchemy = {version="==2.0.31", python_version=">= '3.7'"}
sqlmodel = {version="==0.0.19", python_version=">= '3.7'"}
tqdm = "==4.66.4"
typing-extensions = {version="==4.12.2", python_version=">= '3.8'"}
url-normalize = {version="==1.4.3", python_version=">='3.6'"}
Expand Down
241 changes: 240 additions & 1 deletion Pipfile.lock

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions plextraktsync/db/SyncDatabase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from sqlmodel import Session, select

from plextraktsync.db.models.SyncRecord import SyncRecord

if TYPE_CHECKING:
from sqlalchemy.future import Engine

from plextraktsync.media import Media


class SyncDatabase:
def __init__(self, engine: Engine):
self.engine = engine

def find_by_id(self, media_type: str, trakt_id: int):
with Session(self.engine) as session:
statement = (
select(SyncRecord)
.where(SyncRecord.media_type == media_type)
.where(SyncRecord.trakt_id == trakt_id)
)
return session.exec(statement).first()

def insert(self, record: SyncRecord):
pass

def update(self, m: Media):
record = self.find_by_id(m.type, m.trakt_id)
if not record:
record = SyncRecord(
media_type=m.type,
trakt_id=m.trakt_id,
# plex_timestamp_watched=m.watched_on_plex,
seen_on_plex_sync=m.watched_on_plex,
# trakt_timestamp_watched=m.watched_on_trakt,
seen_on_trakt_sync=m.watched_on_trakt,
)
# if record.plex_timestamp_watched != m.watched_on_plex:
# record.plex_timestamp_watched = m.watched_on_plex
if record.seen_on_plex_sync != m.watched_on_plex:
record.seen_on_plex_sync = m.watched_on_plex
# if record.trakt_timestamp_watched != m.watched_on_trakt:
# record.trakt_timestamp_watched = m.watched_on_trakt
if record.seen_on_trakt_sync != m.watched_on_trakt:
record.seen_on_trakt_sync = m.watched_on_trakt
with Session(self.engine) as session:
session.add(record)
session.commit()
24 changes: 24 additions & 0 deletions plextraktsync/db/models/SyncRecord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from datetime import datetime
from typing import Optional

from sqlmodel import Field, SQLModel, Column, DateTime, func, text


class SyncRecord(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
media_type: str
trakt_id: int
created_at: datetime = Field(
sa_column_kwargs={
"server_default": text("CURRENT_TIMESTAMP"),
}
)
updated_at: Optional[datetime] = Field(
sa_column=Column(DateTime(), onupdate=func.now())
)
plex_timestamp_watched: Optional[datetime]
seen_on_plex_sync: bool
trakt_timestamp_watched: Optional[datetime]
seen_on_trakt_sync: bool
1 change: 1 addition & 0 deletions plextraktsync/db/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .SyncRecord import SyncRecord # noqa: F401
6 changes: 5 additions & 1 deletion plextraktsync/sync/Sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
from plextraktsync.plan.Walker import Walker
from plextraktsync.plex.PlexApi import PlexApi
from plextraktsync.trakt.TraktApi import TraktApi
from plextraktsync.db.SyncDatabase import SyncDatabase


class Sync:
logger = logging.getLogger(__name__)

def __init__(self, config: SyncConfig, plex: PlexApi, trakt: TraktApi):
def __init__(self, config: SyncConfig, plex: PlexApi, trakt: TraktApi, sync_state: SyncDatabase):
self.config = config
self.plex = plex
self.trakt = trakt
self.walker = None
self.sync_state = sync_state

@cached_property
def trakt_lists(self):
Expand All @@ -47,9 +49,11 @@ async def sync(self, walker: Walker, dry_run=False):

if self.config.need_library_walk:
async for movie in walker.find_movies():
self.sync_state.update(movie)
await pm.ahook.walk_movie(movie=movie, dry_run=dry_run)

async for episode in walker.find_episodes():
self.sync_state.update(episode)
await pm.ahook.walk_episode(episode=episode, dry_run=dry_run)

await pm.ahook.fini(walker=walker, dry_run=dry_run)
26 changes: 25 additions & 1 deletion plextraktsync/util/Factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def sync(self):
plex = self.plex_api
trakt = self.trakt_api

return Sync(self.sync_config, plex, trakt)
return Sync(self.sync_config, plex, trakt, self.sync_database)

@cached_property
def progressbar(self):
Expand Down Expand Up @@ -213,6 +213,24 @@ def watch_state_updater(self):
config=self.config,
)

@cached_property
def sync_engine(self):
from os.path import join

from sqlmodel import SQLModel, create_engine

from plextraktsync.path import cache_dir

db_path = join(cache_dir, "sync.sqlite")
engine = create_engine(f"sqlite:///{db_path}", echo=False)

# Import all models for metadata.create_all
import plextraktsync.db.models # noqa: F401

SQLModel.metadata.create_all(engine)

return engine

@cached_property
def logging(self):
import logging
Expand Down Expand Up @@ -300,6 +318,12 @@ def sync_config(self):

return SyncConfig(self.config, self.server_config)

@cached_property
def sync_database(self):
from plextraktsync.db.SyncDatabase import SyncDatabase

return SyncDatabase(self.sync_engine)

@cached_property
def queue(self):
from plextraktsync.queue.BackgroundTask import BackgroundTask
Expand Down
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-i https://pypi.org/simple
annotated-types==0.7.0; python_version >= '3.8'
apluggy==0.9.4
attrs==23.2.0; python_version >= '3.7'
cattrs==23.2.3; python_version >= '3.7' and python_version >= '3.8'
Expand All @@ -8,6 +9,7 @@ click==8.1.7
decorator==5.1.1
deprecated==1.2.14
exceptiongroup==1.2.1; python_version < '3.11'
greenlet==3.0.3; python_version < '3.13' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))
humanize==4.10.0; python_version >= '3.8'
idna==3.7; python_version >= '3.5'
inquirerpy==0.3.4
Expand All @@ -19,6 +21,8 @@ platformdirs==4.2.2; python_version >= '3.7' and python_version >= '3.8'
plexapi==4.15.15; python_version >= '3.8'
pluggy==1.5.0
prompt-toolkit==3.0.47; python_full_version >= '3.7.0'
pydantic==2.8.2; python_version >= '3.8'
pydantic-core==2.20.1; python_version >= '3.8'
pygments==2.18.0
python-dotenv==1.0.1
python-git-info==0.8.3
Expand All @@ -30,6 +34,8 @@ requests-cache==1.2.1; python_version >= '3.8'
requests-oauthlib==2.0.0; python_version >= '3.4'
rich==13.7.1
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' and python_version >= '3.4'
sqlalchemy==2.0.31; python_version >= '3.7'
sqlmodel==0.0.19; python_version >= '3.7'
tqdm==4.66.4
types-decorator==5.1.8.20240310; python_version >= '3.8'
typing-extensions==4.12.2; python_version >= '3.8'
Expand Down
Loading