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 cherrypy
import logging
import json
from datetime import datetime, timedelta
from photoapp.library import PhotoLibrary
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User, known_extensions, known_mimes, genuuid
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 datetime import datetime
from photoapp.types import Photo, PhotoSet, Tag, PhotoStatus, User, known_extensions, known_mimes, genuuid
from photoapp.utils import copysha, get_extension
from photoapp.image import special_magic_fobj
from photoapp.dbutils import db
import tempfile
from contextlib import closing
import traceback
@ -95,41 +85,6 @@ class LibraryManager(object):
assert isinstance(storage, StorageAdapter)
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):
def __init__(self, library):
@ -290,3 +245,27 @@ class PhotosApiV1(object):
else:
db.add(User(name=username, password=password_hash))
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})
def upload(self, files, metadata):
# print(">>>>>>", 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():
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("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
p_adduser = sp_action.add_parser("user", help="user manipulation functions")
p_useraction = p_adduser.add_subparsers(dest="action_user", help="action to take")
@ -83,7 +101,6 @@ def get_args():
def main():
args = get_args()
print(args)
client = PhotoApiClient(args.host, (args.user, args.password, ))
@ -107,7 +124,7 @@ def main():
raise NotImplementedError("must pass --sha-files for now")
for sha, path in hashes.items():
# hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
# http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
try:
client.byhash(sha)
# if the file is a dupe, do nothing
@ -147,6 +164,30 @@ def main():
print(f"{num} / {len(sets)}")
# 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":
if args.action_user == "create":
print(client.create_user(args.username, args.password).json())

View File

@ -263,7 +263,6 @@ class ThumbnailView(object):
best = photo
break
thumb_from = best or first
print(repr(thumb_from))
if not thumb_from:
raise cherrypy.HTTPError(404)
# 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)
def to_json(self):
def to_json(self, files=True):
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
s["lat"] = str(self.lat) if self.lat else None
s["lon"] = str(self.lon) if self.lon else None
s["date"] = self.date.isoformat()
s["files"] = {i.uuid: i.to_json() for i in self.files}
s["tags"] = [t.name for t in self.tags]
if files:
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

View File

@ -33,7 +33,7 @@ def main():
p_create.add_argument("-u", "--username", help="username", 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.add_argument("-u", "--username", help="username", required=True)