vague jobs client / api architecture
This commit is contained in:
parent
7b487e562b
commit
6958040ad5
@ -2,8 +2,8 @@ import os
|
||||
import cherrypy
|
||||
import json
|
||||
from datetime import datetime
|
||||
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User, known_extensions, known_mimes, \
|
||||
genuuid, generate_storage_path
|
||||
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User, JobTargetType, known_extensions, \
|
||||
known_mimes, genuuid, generate_storage_path
|
||||
from photoapp.utils import copysha, get_extension, slugify
|
||||
from photoapp.image import special_magic_fobj
|
||||
from photoapp.storage import StorageAdapter
|
||||
@ -287,44 +287,74 @@ class PhotosApiV1PhotoTags(object):
|
||||
return {}
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@cherrypy.popargs("uuid")
|
||||
class JobsApiV1(object):
|
||||
def __init__(self, library):
|
||||
self.library = library
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.allow(methods=["POST", "GET"])
|
||||
@cherrypy.tools.json_out()
|
||||
def index(
|
||||
self,
|
||||
uuid=None, # when fetching an existing job, the job's UUID
|
||||
|
||||
):
|
||||
def GET(self, uuid=None):
|
||||
"""
|
||||
show the list of jobs or the specified job
|
||||
|
||||
or, if we're POST/PUTing, create a new job
|
||||
POSTing without the job_args field will tell you the list of job args you need to submit
|
||||
POSTing with job_args will actually create the job
|
||||
"""
|
||||
# if cherrypy.request.method == "POST": # creating job
|
||||
# print()
|
||||
# print('body', cherrypy.request.body.read())
|
||||
# return 'handle post'
|
||||
return "jobs get " + str(uuid)
|
||||
|
||||
# return 'job: ' + str(uuid)
|
||||
def POST(self):
|
||||
"""
|
||||
create a new job or update an existing one
|
||||
|
||||
# return cherrypy.dispatch.Dispatcher.call(self.other)
|
||||
JSON body should look like:
|
||||
|
||||
request, response = cherrypy.request, cherrypy.response
|
||||
request.dispatch = cherrypy.dispatch.Dispatcher()
|
||||
cherrypy._cptools.HandlerTool.callable_method_handler(self.other)
|
||||
{
|
||||
"name": "blah",
|
||||
"targets": [
|
||||
{
|
||||
"type": "photo",
|
||||
"ids": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
print("response:", response)
|
||||
POSTing without the job_args field will tell you the list of job args you need to submit
|
||||
POSTing with job_args will actually create the job
|
||||
"""
|
||||
|
||||
return "lol"
|
||||
body = json.loads(cherrypy.request.body.read().decode()) # TODO max size
|
||||
|
||||
@cherrypy.expose
|
||||
def other(self):
|
||||
print("in other")
|
||||
yield "other result"
|
||||
# translate target UUIDs to IDs
|
||||
target_ids = []
|
||||
for target_number, target in enumerate(body["targets"]):
|
||||
typ = JobTargetType[target["type"]]
|
||||
|
||||
if typ == JobTargetType.photo:
|
||||
query = db.query(Photo.id).filter(Photo.uuid.in_(target["uuids"]))
|
||||
elif typ == JobTargetType.photoset:
|
||||
query = db.query(PhotoSet.id).filter(PhotoSet.uuid.in_(target["uuids"]))
|
||||
elif typ == JobTargetType.tag:
|
||||
query = db.query(Tag.id).filter(Tag.name.in_(target["uuids"]))
|
||||
else:
|
||||
raise Exception()
|
||||
|
||||
ids = [r[0] for r in query.all()]
|
||||
|
||||
if len(target["uuids"]) != len(ids): # TODO would be nice if we would say exactly which
|
||||
raise cherrypy.HTTPError(400, "missing or duplicate UUIDs in target {}".format(target_number))
|
||||
|
||||
target_ids.append(dict(
|
||||
type=JobTargetType[target["type"]],
|
||||
targets=[r[0] for r in query.all()]
|
||||
))
|
||||
|
||||
# output the job's UUID
|
||||
return cherrypy.engine.publish("job-create", body["name"], target_ids)[0]
|
||||
|
||||
def DELETE(self, uuid):
|
||||
"""
|
||||
delete an existing job
|
||||
"""
|
||||
return "jobs delete"
|
||||
|
@ -18,6 +18,7 @@ from photoapp.dbutils import SAEnginePlugin, SATool, db, get_db_engine, date_for
|
||||
from photoapp.utils import auth, require_auth, photoset_auth_filter, slugify, cherryparam, number_format
|
||||
from photoapp.storage import uri_to_storage
|
||||
from photoapp.webutils import validate_password, serve_thumbnail_placeholder
|
||||
from photoapp.jobs import JobsClient, JobSubscriber
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from sqlalchemy import desc, asc, func, and_, or_
|
||||
|
||||
@ -632,6 +633,8 @@ def setup_webapp(database_url, library_url, cache_url, thumb_service_url, debug=
|
||||
library_storage = uri_to_storage(library_url)
|
||||
library_manager = LibraryManager(library_storage)
|
||||
thumbnail_tool = ThumbGenerator(library_manager, uri_to_storage(cache_url), thumb_service_url)
|
||||
jobs_client = JobsClient(engine)
|
||||
JobSubscriber(jobs_client)
|
||||
|
||||
# Setup and mount web ui
|
||||
tpl_dir = os.path.join(APPROOT, "templates") if not debug else "templates"
|
||||
|
@ -1,11 +1,14 @@
|
||||
from contextlib import closing
|
||||
import sqlalchemy
|
||||
import cherrypy
|
||||
from cherrypy.process import plugins
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.pool import NullPool
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@ -115,3 +118,20 @@ class SATool(cherrypy.Tool):
|
||||
raise
|
||||
finally:
|
||||
self.session.remove()
|
||||
|
||||
|
||||
def cursorwrap(func):
|
||||
"""
|
||||
Provides a cursor to the wrapped method as the first arg. This assumes that the wrapped function belongs to an
|
||||
object because the cursor is sourced from the object's session attribute which is assumed to be a
|
||||
sessionmaker callable.
|
||||
"""
|
||||
def wrapped(*args, **kwargs):
|
||||
self = args[0]
|
||||
# passthru if someone already passed a session
|
||||
if len(args) >= 2 and isinstance(args[1], (Session, sqlalchemy.orm.scoping.scoped_session, DbAlias)):
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
with closing(self.session()) as c:
|
||||
return func(self, c, *args[1:], **kwargs)
|
||||
return wrapped
|
||||
|
@ -1,4 +1,45 @@
|
||||
import cherrypy
|
||||
from photoapp.dbutils import create_db_sessionmaker, cursorwrap
|
||||
from photoapp.types import Job, JobTargetType, JobTarget
|
||||
|
||||
class JobsDAO(object):
|
||||
|
||||
pass
|
||||
class JobSubscriber(object):
|
||||
"""
|
||||
adapter between cherrypy bus and JobsClient
|
||||
"""
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
cherrypy.engine.subscribe("job-create", self.client.create_job) # TODO make "job-create" a const somewhere?
|
||||
|
||||
|
||||
class JobsClient(object):
|
||||
def __init__(self, dbengine):
|
||||
self.engine = dbengine
|
||||
self.session = create_db_sessionmaker(self.engine)
|
||||
|
||||
@cursorwrap
|
||||
def create_job(self, db, name, targets):
|
||||
"""
|
||||
targets: list of dict(
|
||||
type=JobTargetType.TYPE,
|
||||
targets=[1, 2, 3]
|
||||
)
|
||||
"""
|
||||
job_targets = []
|
||||
|
||||
for target in targets:
|
||||
for target_id in target["targets"]:
|
||||
job_targets.append(
|
||||
JobTarget(target_type=target["type"],
|
||||
target=target_id)
|
||||
)
|
||||
|
||||
j = Job(
|
||||
job_name=name,
|
||||
targets=job_targets,
|
||||
)
|
||||
|
||||
db.add(j)
|
||||
db.commit()
|
||||
|
||||
return j.uuid
|
||||
|
@ -237,7 +237,7 @@ class Job(Base):
|
||||
uuid = Column(Unicode(length=36), unique=True, nullable=False, default=lambda: str(uuid.uuid4()))
|
||||
created = Column(DateTime, nullable=False, default=lambda: datetime.now())
|
||||
|
||||
job_name = Column(String(length=64), nullable=False)
|
||||
job_name = Column(String(length=64), unique=True, nullable=False)
|
||||
status = Column(Enum(JobStatus), nullable=False, default=JobStatus.paused)
|
||||
|
||||
targets = relationship("JobTarget", back_populates="job")
|
||||
|
Loading…
x
Reference in New Issue
Block a user