api beginnings
This commit is contained in:
parent
3e6aae09db
commit
16271aa54a
|
@ -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
|
|
@ -8,58 +8,16 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
from sqlalchemy import func, and_, or_
|
from sqlalchemy import func, and_, or_
|
||||||
from photoapp.common import pwhash
|
from photoapp.common import pwhash
|
||||||
|
from photoapp.api import PhotosApi
|
||||||
|
from photoapp.dbutils import SAEnginePlugin, SATool
|
||||||
import math
|
import math
|
||||||
from urllib.parse import urlparse
|
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__), "../"))
|
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):
|
class PhotosWeb(object):
|
||||||
def __init__(self, library, template_dir):
|
def __init__(self, library, template_dir):
|
||||||
self.library = library
|
self.library = library
|
||||||
|
@ -511,6 +469,13 @@ def main():
|
||||||
'tools.auth_basic.realm': 'photolib',
|
'tools.auth_basic.realm': 'photolib',
|
||||||
'tools.auth_basic.checkpassword': validate_password}})
|
'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({
|
cherrypy.config.update({
|
||||||
'tools.sessions.on': True,
|
'tools.sessions.on': True,
|
||||||
'tools.sessions.locking': 'explicit',
|
'tools.sessions.locking': 'explicit',
|
||||||
|
|
|
@ -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()
|
|
@ -122,7 +122,7 @@ def main():
|
||||||
parser.add_argument("files", nargs="+")
|
parser.add_argument("files", nargs="+")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
library = PhotoLibrary("photos.db", "./library/")
|
library = PhotoLibrary("photos.db", "./library", "./cache")
|
||||||
|
|
||||||
batch_ingest(library, args.files)
|
batch_ingest(library, args.files)
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
|
from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.schema import UniqueConstraint
|
from sqlalchemy.schema import UniqueConstraint
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from photoapp.dbutils import Base
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
|
|
||||||
class PhotoStatus(enum.Enum):
|
class PhotoStatus(enum.Enum):
|
||||||
private = 0
|
private = 0
|
||||||
public = 1
|
public = 1
|
||||||
|
|
|
@ -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 == '-')
|
|
@ -1,9 +1,9 @@
|
||||||
backports.functools-lru-cache==1.5
|
backports.functools-lru-cache==1.5
|
||||||
cheroot==6.5.2
|
cheroot==6.5.2
|
||||||
CherryPy==18.0.0
|
CherryPy==18.1.1
|
||||||
contextlib2==0.5.5
|
contextlib2==0.5.5
|
||||||
jaraco.functools==1.20
|
jaraco.functools==1.20
|
||||||
Jinja2==2.10
|
Jinja2==2.10.1
|
||||||
MarkupSafe==1.0
|
MarkupSafe==1.0
|
||||||
more-itertools==4.3.0
|
more-itertools==4.3.0
|
||||||
Pillow==5.2.0
|
Pillow==5.2.0
|
||||||
|
@ -11,6 +11,6 @@ portend==2.3
|
||||||
python-magic==0.4.15
|
python-magic==0.4.15
|
||||||
pytz==2018.5
|
pytz==2018.5
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
SQLAlchemy==1.2.11
|
SQLAlchemy==1.3.5
|
||||||
tempora==1.13
|
tempora==1.13
|
||||||
zc.lockfile==1.3.0
|
zc.lockfile==1.3.0
|
||||||
|
|
Loading…
Reference in New Issue