listing apis

This commit is contained in:
dave 2019-07-04 16:57:45 -07:00
parent 2b5ab1fcc4
commit bc64acce2a
5 changed files with 77 additions and 56 deletions

View File

@ -1,21 +1,11 @@
import os import os
import cherrypy import cherrypy
import logging
import json import json
from datetime import datetime, timedelta from datetime import datetime
from photoapp.library import PhotoLibrary from photoapp.types import Photo, PhotoSet, Tag, PhotoStatus, User, known_extensions, known_mimes, genuuid
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User, known_extensions, known_mimes, genuuid from photoapp.utils import copysha, get_extension
from jinja2 import Environment, FileSystemLoader, select_autoescape
from sqlalchemy import desc
from sqlalchemy import func, and_, or_
from sqlalchemy.exc import IntegrityError
from photoapp.common import pwhash
import math
from urllib.parse import urlparse
from photoapp.utils import mime2ext, auth, require_auth, photo_auth_filter, slugify, copysha, get_extension
from photoapp.image import special_magic_fobj from photoapp.image import special_magic_fobj
from photoapp.dbutils import db from photoapp.dbutils import db
import tempfile
from contextlib import closing from contextlib import closing
import traceback import traceback
@ -95,41 +85,6 @@ class LibraryManager(object):
assert isinstance(storage, StorageAdapter) assert isinstance(storage, StorageAdapter)
self.storage = storage self.storage = storage
# def add_photoset(self, photoset):
# """
# Commit a populated photoset object to the library. The paths in the photoset's file list entries will be updated
# as the file is moved to the library path.
# """
# # Create target directory
# path = os.path.join(self.path, self.get_datedir_path(photoset.date))
# os.makedirs(path, exist_ok=True)
# moves = [] # Track files moved. If the sql transaction files, we'll undo these
# for file in photoset.files:
# dest = os.path.join(path, os.path.basename(file.path))
# # Check if the name is already in use, rename new file if needed
# dupe_rename = 1
# while os.path.exists(dest):
# fname = os.path.basename(file.path).split(".")
# fname[-2] += "_{}".format(dupe_rename)
# dest = os.path.join(path, '.'.join(fname))
# dupe_rename += 1
# os.rename(file.path, dest)
# moves.append((file.path, dest))
# file.path = dest.lstrip(self.path)
# s = self.session()
# s.add(photoset)
# try:
# s.commit()
# except IntegrityError:
# # Commit failed, undo the moves
# for move in moves:
# os.rename(move[1], move[0])
# raise
class PhotosApi(object): class PhotosApi(object):
def __init__(self, library): def __init__(self, library):
@ -290,3 +245,27 @@ class PhotosApiV1(object):
else: else:
db.add(User(name=username, password=password_hash)) db.add(User(name=username, password=password_hash))
return "ok" return "ok"
@cherrypy.expose
@cherrypy.tools.json_out()
def stats(self):
return {"photos": db.query(PhotoSet).count(),
"files": db.query(Photo).count(),
"tags": db.query(Tag).count(),
"users": db.query(User).count(),
"public_photos": db.query(PhotoSet).filter(PhotoSet.status == PhotoStatus.public).count()}
@cherrypy.expose
@cherrypy.tools.json_out()
@cherrypy.popargs("uuid")
def photos(self, uuid=None, page=0, pagesize=50):
if uuid:
p = db.query(PhotoSet).filter(PhotoSet.uuid == uuid).first()
if not p:
cherrypy.response.status = 404
return {"error": "not found"}
return p.to_json()
else:
page, pagesize = int(page), int(pagesize)
return [p.to_json() for p in
db.query(PhotoSet).order_by(PhotoSet.id).offset(pagesize * page).limit(pagesize).all()]

View File

@ -44,9 +44,20 @@ class PhotoApiClient(object):
return self.delete("user", params={"username": username}) return self.delete("user", params={"username": username})
def upload(self, files, metadata): def upload(self, files, metadata):
# print(">>>>>>", metadata)
return self.post("upload", files=files, data={"meta": json.dumps(metadata)}) return self.post("upload", files=files, data={"meta": json.dumps(metadata)})
def stats(self):
return self.get("stats").json()
def list_photos(self, page=0, pagesize=25):
return self.get("photos", params={"page": page, "pagesize": pagesize}).json()
def maybetruncate(s, length):
if s and len(s) > length:
s = s[0:length - 3] + "..."
return s
def get_args(): def get_args():
parser = argparse.ArgumentParser(description="photo library cli") parser = argparse.ArgumentParser(description="photo library cli")
@ -65,6 +76,13 @@ def get_args():
p_ingest.add_argument("-c", "--copy-of", help="existing uuid the imported images will be placed under") p_ingest.add_argument("-c", "--copy-of", help="existing uuid the imported images will be placed under")
p_ingest.add_argument("files", nargs="+", help="files to import") p_ingest.add_argument("files", nargs="+", help="files to import")
sp_action.add_parser("stats", help="show library statistics")
p_list = sp_action.add_parser("list", help="list images in library")
p_list.add_argument("-p", "--page", default=0, help="page number")
p_list.add_argument("-z", "--page-size", default=25, help="page size")
p_list.add_argument("-l", "--show-link", action="store_true", help="print urls to each photo")
# User section # User section
p_adduser = sp_action.add_parser("user", help="user manipulation functions") p_adduser = sp_action.add_parser("user", help="user manipulation functions")
p_useraction = p_adduser.add_subparsers(dest="action_user", help="action to take") p_useraction = p_adduser.add_subparsers(dest="action_user", help="action to take")
@ -83,7 +101,6 @@ def get_args():
def main(): def main():
args = get_args() args = get_args()
print(args)
client = PhotoApiClient(args.host, (args.user, args.password, )) client = PhotoApiClient(args.host, (args.user, args.password, ))
@ -107,7 +124,7 @@ def main():
raise NotImplementedError("must pass --sha-files for now") raise NotImplementedError("must pass --sha-files for now")
for sha, path in hashes.items(): for sha, path in hashes.items():
# hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6 # http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
try: try:
client.byhash(sha) client.byhash(sha)
# if the file is a dupe, do nothing # if the file is a dupe, do nothing
@ -147,6 +164,30 @@ def main():
print(f"{num} / {len(sets)}") print(f"{num} / {len(sets)}")
# TODO be nice and close the files # TODO be nice and close the files
elif args.action == "list":
headers = ["link" if args.show_link else "uuid",
"status",
"size",
"formats",
"date",
"title"]# TODO tags
rows = []
for set_ in client.list_photos(args.page, args.page_size):
row = [set_["uuid"] if not args.show_link else args.host.rstrip("/") + "/photo/" + set_["uuid"],
PhotoStatus[set_["status"]].name,
sum([p["size"] for p in set_["files"].values()]),
" ".join(set([p["format"] for p in set_["files"].values()])),
set_["date"],
maybetruncate(set_["title"], 24),
]
rows.append(row)
print(tabulate(rows, headers=headers))
elif args.action == "stats":
print(tabulate(sorted([[k, v] for k, v in client.stats().items()],
key=lambda row: row[0], reverse=True),
headers=["item", "count"]))
elif args.action == "user": elif args.action == "user":
if args.action_user == "create": if args.action_user == "create":
print(client.create_user(args.username, args.password).json()) print(client.create_user(args.username, args.password).json())

View File

@ -263,7 +263,6 @@ class ThumbnailView(object):
best = photo best = photo
break break
thumb_from = best or first thumb_from = best or first
print(repr(thumb_from))
if not thumb_from: if not thumb_from:
raise cherrypy.HTTPError(404) raise cherrypy.HTTPError(404)
# TODO some lock around calls to this based on uuid # TODO some lock around calls to this based on uuid

View File

@ -66,13 +66,15 @@ class PhotoSet(Base):
status = Column(Enum(PhotoStatus), default=PhotoStatus.private) status = Column(Enum(PhotoStatus), default=PhotoStatus.private)
def to_json(self): def to_json(self, files=True):
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}} s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
s["lat"] = str(self.lat) if self.lat else None s["lat"] = str(self.lat) if self.lat else None
s["lon"] = str(self.lon) if self.lon else None s["lon"] = str(self.lon) if self.lon else None
s["date"] = self.date.isoformat() s["date"] = self.date.isoformat()
s["files"] = {i.uuid: i.to_json() for i in self.files} if files:
s["tags"] = [t.name for t in self.tags] s["files"] = {i.uuid: i.to_json() for i in self.files}
s["tags"] = [t.tag.name for t in self.tags]
s["status"] = self.status.name if self.status else PhotoStatus.private.name # duplicate default definition
return s return s

View File

@ -33,7 +33,7 @@ def main():
p_create.add_argument("-u", "--username", help="username", required=True) p_create.add_argument("-u", "--username", help="username", required=True)
p_create.add_argument("-p", "--password", help="password", required=True) p_create.add_argument("-p", "--password", help="password", required=True)
p_list = p_mode.add_parser('list', help='list users') p_mode.add_parser('list', help='list users')
p_delete = p_mode.add_parser('delete', help='delete users') p_delete = p_mode.add_parser('delete', help='delete users')
p_delete.add_argument("-u", "--username", help="username", required=True) p_delete.add_argument("-u", "--username", help="username", required=True)