-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f70878f
commit 65dc3e0
Showing
10 changed files
with
302 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import logging | ||
from typing import List, Type, Union | ||
|
||
import sqlalchemy as sa | ||
from pydantic import UUID4 | ||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError | ||
from sqlalchemy.ext.asyncio import AsyncSession | ||
from tornado.web import HTTPError | ||
|
||
from .model import DockerImageSQL | ||
from .schemas import (DockerImageCreateSchema, DockerImageOutSchema, | ||
DockerImageUpdateSchema) | ||
|
||
|
||
class ImagesDatabaseManager: | ||
|
||
@property | ||
def _table(self) -> Type[DockerImageSQL]: | ||
return DockerImageSQL | ||
|
||
@property | ||
def _schema_out(self) -> Type[DockerImageOutSchema]: | ||
return DockerImageOutSchema | ||
|
||
async def create( | ||
self, db: AsyncSession, obj_in: DockerImageCreateSchema | ||
) -> DockerImageOutSchema: | ||
""" | ||
Create one resource. | ||
```sql | ||
INSERT INTO resource | ||
VALUES (...) | ||
``` | ||
Args: | ||
db: An asyncio version of SQLAlchemy session. | ||
obj_in: An object containing the resource instance to create | ||
Returns: | ||
The created resource instance on success. | ||
Raises: | ||
DatabaseError: If `db.commit()` failed. | ||
""" | ||
entry = self._table(**obj_in.model_dump()) | ||
|
||
db.add(entry) | ||
|
||
try: | ||
await db.commit() | ||
# db.refresh(entry) | ||
except IntegrityError as e: | ||
logging.error(f"create: {e}") | ||
raise HTTPError(409, "That resource already exists.") | ||
except SQLAlchemyError as e: | ||
logging.error(f"create: {e}") | ||
raise e | ||
|
||
return self._schema_out.model_validate(entry) | ||
|
||
async def read( | ||
self, db: AsyncSession, uid: UUID4 | ||
) -> Union[DockerImageOutSchema, None]: | ||
""" | ||
Get one resource by uid. | ||
```sql | ||
SELECT * | ||
FROM %tablename | ||
WHERE uid=%s | ||
``` | ||
Args: | ||
db: An asyncio version of SQLAlchemy session. | ||
uid: The primary key of the resource to retrieve. | ||
Returns: | ||
The first resource instance found, `None` if no instance retrieved. | ||
""" | ||
if entry := await db.get(self._table, uid): | ||
return self._schema_out.model_validate(entry) | ||
return None | ||
|
||
async def read_many( | ||
self, db: AsyncSession, uids: List[UUID4] | ||
) -> List[DockerImageOutSchema]: | ||
""" | ||
Get multiple resources. | ||
```sql | ||
SELECT * | ||
FROM %tablename | ||
WHERE uid=_in(%s...) | ||
``` | ||
Args: | ||
db: An asyncio version of SQLAlchemy session. | ||
uids: The primary keys of the resources to retrieve. | ||
Returns: | ||
The list of resources retrieved. | ||
""" | ||
resources = ( | ||
await db.execute(sa.select(self._table).where(self._table.uid.in_(uids))) | ||
).scalars() | ||
return [self._schema_out.model_validate(r) for r in resources] | ||
|
||
async def update( | ||
self, db: AsyncSession, obj_in: DockerImageUpdateSchema, optimistic: bool = True | ||
) -> Union[DockerImageOutSchema, None]: | ||
""" | ||
Update one object. | ||
```sql | ||
UPDATE %tablename | ||
SET (...), updated_at=now() | ||
WHERE uid=%s | ||
``` | ||
Args: | ||
db: An asyncio version of SQLAlchemy session. | ||
obj_in: A model containing values to update | ||
optimistic: If `True`, assert the new model instance to be | ||
`**{**obj_db.dict(), **obj_in.dict()}` | ||
Returns: | ||
The updated model instance on success, `None` if it does not exist | ||
yet in database. | ||
Raises: | ||
DatabaseError: If `db.commit()` failed. | ||
""" | ||
if not (obj_db := await self.read(db=db, uid=obj_in.uid)): | ||
await self.create(db, obj_in) | ||
|
||
update_data = obj_in.model_dump() | ||
|
||
await db.execute( | ||
sa.update(self._table) | ||
.where(self._table.uid == obj_in.uid) | ||
.values(**update_data) | ||
) | ||
|
||
try: | ||
await db.commit() | ||
except SQLAlchemyError as e: | ||
logging.error(f"update: {e}") | ||
raise e | ||
|
||
if optimistic: | ||
for field in update_data: | ||
setattr(obj_db, field, update_data[field]) | ||
return self._schema_out.model_validate(obj_db) | ||
|
||
return await self.read(db=db, uid=obj_in.uid) | ||
|
||
async def delete(self, db: AsyncSession, uid: UUID4) -> bool: | ||
""" | ||
Delete one object. | ||
```sql | ||
DELETE | ||
FROM %tablename | ||
WHERE uid=%s | ||
``` | ||
Args: | ||
db: An asyncio version of SQLAlchemy session. | ||
uid: The primary key of the resource to delete. | ||
Returns: | ||
bool: `True` if the object has been deleted, `False` otherwise. | ||
Raises: | ||
DatabaseError: If `db.commit()` failed. | ||
""" | ||
results = await db.execute(sa.delete(self._table).where(self._table.uid == uid)) | ||
|
||
try: | ||
await db.commit() | ||
except SQLAlchemyError as e: | ||
logging.error(f"delete: {e}") | ||
raise e | ||
|
||
return results.rowcount == 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import uuid | ||
|
||
from jupyterhub.orm import JSONDict | ||
from sqlalchemy import Column, String, Text | ||
from sqlalchemy.dialects.postgresql import ENUM, UUID | ||
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base | ||
|
||
from .schemas import BuildStatusType | ||
|
||
BaseSQL: DeclarativeMeta = declarative_base() | ||
|
||
|
||
class DockerImageSQL(BaseSQL): | ||
""" | ||
SQLAlchemy image table definition. | ||
""" | ||
|
||
__tablename__ = "images" | ||
|
||
uid = Column( | ||
UUID(as_uuid=True), | ||
primary_key=True, | ||
default=uuid.uuid4, | ||
) | ||
|
||
name = Column(String(length=4096), unique=False, nullable=False) | ||
|
||
status = Column( | ||
ENUM( | ||
BuildStatusType, | ||
name="build_status_enum", | ||
create_type=False, | ||
values_callable=lambda enum: [e.value for e in enum], | ||
), | ||
nullable=False, | ||
) | ||
|
||
log = Column(Text) | ||
|
||
metadata = Column(JSONDict, default={}) | ||
|
||
__mapper_args__ = {"eager_defaults": True} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from enum import Enum | ||
|
||
from pydantic import UUID4, BaseModel | ||
|
||
|
||
class BuildStatusType(str, Enum): | ||
SUCCESS = "success" | ||
BUILDING = "building" | ||
FAILED = "failed" | ||
|
||
|
||
class ImageMetadataType(BaseModel): | ||
label: str | ||
repo: str | ||
ref: str | ||
cpu: str | ||
memory: str | ||
|
||
|
||
class DockerImageCreateSchema(BaseModel): | ||
uid: UUID4 | ||
name: str | ||
status: BuildStatusType | ||
log: str | ||
metadata: ImageMetadataType | ||
|
||
class Config: | ||
use_enum_values = True | ||
|
||
|
||
class DockerImageUpdateSchema(DockerImageCreateSchema): | ||
pass | ||
|
||
|
||
class DockerImageOutSchema(DockerImageCreateSchema): | ||
|
||
class Config: | ||
use_enum_values = True | ||
from_attributes = True | ||
orm_mode = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.