Skip to content

Commit

Permalink
Abstract File class to make associable not break
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrillkuettel committed Jul 4, 2024
1 parent b13f041 commit 14993d5
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 149 deletions.
2 changes: 1 addition & 1 deletion src/privatim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,6 @@ def upgrade(context: 'UpgradeContext'): # type: ignore[no-untyped-def]
context.operations.add_column(
'consultations',
Column('searchable_text_de_CH', TSVECTOR())
)
)

context.commit()
9 changes: 7 additions & 2 deletions src/privatim/cli/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def reindex(config_uri: str) -> None:
results = db.execute(stmt).scalars().fetchall()
for instance in results:
assert isinstance(instance, cls)
click.echo(f"\nReindexing model: {cls.__name__} with "
f"title: {instance.title[:30]}")
name = getattr(instance, 'title', None)
if name is not None:
click.echo(f"\nReindexing model: {cls.__name__} with "
f"title: {name[:30]}")
else:
click.echo(f"\nReindexing model: {cls.__name__} with")

instance.reindex_files()
5 changes: 3 additions & 2 deletions src/privatim/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@


from typing import TYPE_CHECKING # noqa: E402
from typing import Any as Incomplete # noqa: E402
if TYPE_CHECKING:
from pyramid.config import Configurator
from sqlalchemy.orm import Mapper
Expand Down Expand Up @@ -75,7 +74,9 @@ def includeme(config: 'Configurator') -> None:


def update_fulltext_search_text(
mapper: 'Mapper[SearchableAssociatedFiles]', connection: 'Connection', target: SearchableAssociatedFiles
mapper: 'Mapper[SearchableAssociatedFiles]',
connection: 'Connection',
target: SearchableAssociatedFiles,
) -> None:
"""
Event listener for the 'files' relationship. Triggers a full reindex
Expand Down
7 changes: 4 additions & 3 deletions src/privatim/models/associated_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
class AssociatedFiles:
""" Use this mixin if uploaded files belong to a specific instance """

# one-to-many
files = associated(GeneralFile, 'files')
files = associated(
GeneralFile, 'files', 'one-to-many'
)


class SearchableAssociatedFiles:
Expand All @@ -31,7 +32,7 @@ class SearchableAssociatedFiles:
__name__: ClassVar[str]

files: Mapped[list[SearchableFile]] = associated(
SearchableFile, 'files'
SearchableFile, 'files', 'one-to-many'
)

@declared_attr
Expand Down
4 changes: 3 additions & 1 deletion src/privatim/models/commentable.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
class Commentable:
""" Use this in your model to attach a list[Comment] """

comments = associated(Comment, 'comments')
comments = associated(
Comment, 'comments', 'one-to-many'
)
63 changes: 13 additions & 50 deletions src/privatim/models/file.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,35 @@
import uuid
from pyramid.authorization import Allow
from pyramid.authorization import Authenticated
from sqlalchemy.orm import Mapped, mapped_column, deferred
from sqlalchemy_file import File

from privatim.orm.associable import Associable
from privatim.orm.meta import UUIDStrPK, AttachedFile
from privatim.orm.abstract import AbstractFile
from privatim.orm.meta import UUIDStrPK
from sqlalchemy import Text, ForeignKey, Integer
from privatim.orm import Base

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from privatim.types import ACL


class GeneralFile(Base, Associable):
""" A general file (image, document, pdf, etc), referenced in the database.
class GeneralFile(AbstractFile):
"""A general file (image, document, pdf, etc), referenced in the database.
A thin wrapper around the `File` from sqlalchemy-file so that we can easily
route to the file via the usual pathway i.e. create_uuid_factory.
"""
__tablename__ = 'general_files'

def __init__(self, filename: str, content: bytes) -> None:
self.id = str(uuid.uuid4())
self.filename = filename
self.file = File(content=content, filename=filename)

id: Mapped[UUIDStrPK]

file: Mapped[AttachedFile] = mapped_column(nullable=False)

filename: Mapped[str] = mapped_column(nullable=False)

@property
def content(self) -> bytes:
return self.file.file.read()

@property
def content_type(self) -> str:
return self.file.content_type
__tablename__ = 'general_files'

def __acl__(self) -> list['ACL']:
return [
(Allow, Authenticated, ['view']),
]
__mapper_args__ = {
'polymorphic_identity': 'general_file',
}


class SearchableFile(GeneralFile):
class SearchableFile(AbstractFile):
"""
A file with the intention of being searchable. Should to be used with
SearchableAssociatedFiles.
"""
__tablename__ = 'searchable_files'

def __init__(self, filename: str, content: bytes) -> None:
super().__init__(filename, content)
__tablename__ = 'searchable_files'

__mapper_args__ = {
'polymorphic_identity': 'searchable_file',
}
id: Mapped[UUIDStrPK] = mapped_column(
ForeignKey('general_files.id'), primary_key=True
)
Expand All @@ -70,12 +42,3 @@ def __init__(self, filename: str, content: bytes) -> None:
pages_count: Mapped[int] = mapped_column(Integer, nullable=True)

word_count: Mapped[int] = mapped_column(Integer, nullable=True)

__mapper_args__ = {
'polymorphic_identity': 'searchable_file',
}

def __acl__(self) -> list['ACL']:
return [
(Allow, Authenticated, ['view']),
]
40 changes: 40 additions & 0 deletions src/privatim/orm/abstract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import uuid
from pyramid.authorization import Allow
from pyramid.authorization import Authenticated
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy_file import File

from privatim.orm.associable import Associable
from privatim.orm.meta import UUIDStrPK, AttachedFile
from privatim.orm import Base


from typing import TYPE_CHECKING
if TYPE_CHECKING:
from privatim.types import ACL


class AbstractFile(Base, Associable):
__abstract__ = True

id: Mapped[UUIDStrPK] = mapped_column(primary_key=True)
file: Mapped[AttachedFile] = mapped_column(nullable=False)
filename: Mapped[str] = mapped_column(nullable=False)

def __init__(self, filename: str, content: bytes) -> None:
self.id = str(uuid.uuid4())
self.filename = filename
self.file = File(content=content, filename=filename)

@property
def content(self) -> bytes:
return self.file.file.read()

@property
def content_type(self) -> str:
return self.file.content_type

def __acl__(self) -> list['ACL']:
return [
(Allow, Authenticated, ['view']),
]
Loading

0 comments on commit 14993d5

Please sign in to comment.