Browse Source

api beginnings

api
dave 2 years ago
parent
commit
16271aa54a
  1. 41
      photoapp/api.py
  2. 55
      photoapp/daemon.py
  3. 55
      photoapp/dbutils.py
  4. 2
      photoapp/ingest.py
  5. 5
      photoapp/types.py
  6. 47
      photoapp/utils.py
  7. 6
      requirements.txt

41
photoapp/api.py

@ -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

55
photoapp/daemon.py

@ -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

@ -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()

2
photoapp/ingest.py

@ -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)

5
photoapp/types.py

@ -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

@ -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 == '-')

6
requirements.txt

@ -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
Loading…
Cancel
Save