api beginnings

This commit is contained in:
dave 2019-06-17 22:43:57 -07:00
parent 3e6aae09db
commit 16271aa54a
7 changed files with 158 additions and 53 deletions

41
photoapp/api.py Normal file
View File

@ -0,0 +1,41 @@
import os
import cherrypy
import logging
from datetime import datetime, timedelta
from photoapp.library import PhotoLibrary
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User
from jinja2 import Environment, FileSystemLoader, select_autoescape
from sqlalchemy import desc
from sqlalchemy import func, and_, or_
from photoapp.common import pwhash
import math
from urllib.parse import urlparse
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify
from photoapp.dbutils import db
class PhotosApi(object):
def __init__(self):
self.v1 = PhotosApiV1()
class PhotosApiV1(object):
def __init__(self):
# self.tpl.filters.update(mime2ext=mime2ext,
# basename=os.path.basename,
# ceil=math.ceil,
# statusstr=lambda x: str(x).split(".")[-1])
pass
@cherrypy.expose
def index(self):
yield f"<plaintext>hello, this is the api. my database is: {db()}\n"
@cherrypy.expose
def upload(self, files, meta):
pass
@cherrypy.expose
@cherrypy.tools.json_out()
def findbysha(self, sha):
pass

View File

@ -8,58 +8,16 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from sqlalchemy import desc
from sqlalchemy import func, and_, or_
from photoapp.common import pwhash
from photoapp.api import PhotosApi
from photoapp.dbutils import SAEnginePlugin, SATool
import math
from urllib.parse import urlparse
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify
APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
def auth():
"""
Return the currently authorized username (per request) or None
"""
return cherrypy.session.get('authed', None)
def mime2ext(mime):
"""
Given a mime type return the canonical file extension
"""
return {"image/png": "png",
"image/jpeg": "jpg",
"image/gif": "gif",
"application/octet-stream-xmp": "xmp",
"image/x-canon-cr2": "cr2",
"video/mp4": "mp4",
"video/quicktime": "mov"}[mime]
def require_auth(func):
"""
Decorator: raise 403 unless session is authed
"""
def wrapped(*args, **kwargs):
if not auth():
raise cherrypy.HTTPError(403)
return func(*args, **kwargs)
return wrapped
def photo_auth_filter(query):
"""
Sqlalchemy helper: filter the given PhotoSet query to items that match the authorized user's PhotoStatus access
level. Currently, authed users can access ALL photos, and unauthed users can access only PhotoStatus.public
status items.
"""
return query.filter(PhotoSet.status == PhotoStatus.public) if not auth() else query
def slugify(words):
return ''.join(letter for letter in '-'.join(words.lower().split())
if ('a' <= letter <= 'z') or ('0' <= letter <= '9') or letter == '-')
class PhotosWeb(object):
def __init__(self, library, template_dir):
self.library = library
@ -511,6 +469,13 @@ def main():
'tools.auth_basic.realm': 'photolib',
'tools.auth_basic.checkpassword': validate_password}})
cherrypy.tools.db = SATool()
SAEnginePlugin(cherrypy.engine, library.engine).subscribe()
api = PhotosApi()
cherrypy.tree.mount(api, '/api', {'/': {'tools.trailing_slash.on': False,
'tools.auth_basic.checkpassword': validate_password,
'tools.db.on': True}})
cherrypy.config.update({
'tools.sessions.on': True,
'tools.sessions.locking': 'explicit',

55
photoapp/dbutils.py Normal file
View File

@ -0,0 +1,55 @@
import sqlalchemy
import cherrypy
from cherrypy.process import plugins
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
def db():
return cherrypy.request.db
class SAEnginePlugin(plugins.SimplePlugin):
def __init__(self, bus, dbcon):
plugins.SimplePlugin.__init__(self, bus)
self.sa_engine = dbcon
self.bus.subscribe("bind", self.bind)
def start(self):
Base.metadata.create_all(self.sa_engine)
def bind(self, session):
session.configure(bind=self.sa_engine)
class SATool(cherrypy.Tool):
def __init__(self):
cherrypy.Tool.__init__(self, 'before_request_body',
self.bind_session,
priority=100)
self.session = sqlalchemy.orm.scoped_session(
sqlalchemy.orm.sessionmaker(autoflush=True, autocommit=False))
def _setup(self):
cherrypy.Tool._setup(self)
cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80)
def bind_session(self):
cherrypy.engine.publish('bind', self.session)
cherrypy.request.db = self.session
con = cherrypy.request.db.connection().connection.connection
if hasattr(con, "ping"): # not available on sqlite
con.ping()
def commit_transaction(self):
cherrypy.request.db = None
try:
self.session.commit()
except Exception:
self.session.rollback()
raise
finally:
self.session.remove()

View File

@ -122,7 +122,7 @@ def main():
parser.add_argument("files", nargs="+")
args = parser.parse_args()
library = PhotoLibrary("photos.db", "./library/")
library = PhotoLibrary("photos.db", "./library", "./cache")
batch_ingest(library, args.files)

View File

@ -1,15 +1,12 @@
from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
from sqlalchemy.orm import relationship
from sqlalchemy.schema import UniqueConstraint
from sqlalchemy.ext.declarative import declarative_base
from photoapp.dbutils import Base
from datetime import datetime
import uuid
import enum
Base = declarative_base()
class PhotoStatus(enum.Enum):
private = 0
public = 1

47
photoapp/utils.py Normal file
View File

@ -0,0 +1,47 @@
import cherrypy
from photoapp.types import PhotoSet, PhotoStatus
def mime2ext(mime):
"""
Given a mime type return the canonical file extension
"""
return {"image/png": "png",
"image/jpeg": "jpg",
"image/gif": "gif",
"application/octet-stream-xmp": "xmp",
"image/x-canon-cr2": "cr2",
"video/mp4": "mp4",
"video/quicktime": "mov"}[mime]
def auth():
"""
Return the currently authorized username (per request) or None
"""
return cherrypy.session.get('authed', None)
def require_auth(func):
"""
Decorator: raise 403 unless session is authed
"""
def wrapped(*args, **kwargs):
if not auth():
raise cherrypy.HTTPError(403)
return func(*args, **kwargs)
return wrapped
def photo_auth_filter(query):
"""
Sqlalchemy helper: filter the given PhotoSet query to items that match the authorized user's PhotoStatus access
level. Currently, authed users can access ALL photos, and unauthed users can access only PhotoStatus.public
status items.
"""
return query.filter(PhotoSet.status == PhotoStatus.public) if not auth() else query
def slugify(words):
return ''.join(letter for letter in '-'.join(words.lower().split())
if ('a' <= letter <= 'z') or ('0' <= letter <= '9') or letter == '-')

View File

@ -1,9 +1,9 @@
backports.functools-lru-cache==1.5
cheroot==6.5.2
CherryPy==18.0.0
CherryPy==18.1.1
contextlib2==0.5.5
jaraco.functools==1.20
Jinja2==2.10
Jinja2==2.10.1
MarkupSafe==1.0
more-itertools==4.3.0
Pillow==5.2.0
@ -11,6 +11,6 @@ portend==2.3
python-magic==0.4.15
pytz==2018.5
six==1.11.0
SQLAlchemy==1.2.11
SQLAlchemy==1.3.5
tempora==1.13
zc.lockfile==1.3.0