commit 7907c755acb19af7c3b9a8942e1d208c2bf64de4 Author: Felipe Martin Date: Sat Oct 8 17:55:12 2022 +0200 archive diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61a1c20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Editor +.vscode + +# Python +*.py[o|c] + +# Test files +testphotos/ + +.DS_Store + +db.sqlite3 +*.egg-* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5586db7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: flake8 + +- repo: https://github.com/asottile/seed-isort-config + rev: v1.9.2 + hooks: + - id: seed-isort-config +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.20 + hooks: + - id: isort + +- repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + language_version: python3.7 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4904fca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:latest + +ENV PYTHON_VERSION=3.8.2-r0 +ENV BUILD_DIR "/tmp/build" + +WORKDIR ${BUILD_DIR} +COPY . ${BUILD_DIR} +RUN apk --update add python3-dev==${PYTHON_VERSION} gcc musl-dev libffi-dev openssl-dev && \ + pip3 install poetry && \ + poetry install + +CMD ["poetry", "run", "python", "memories/manage.py", "runserver"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9adb92c --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Memories + +Proof of concept for a photo gallery manager. + +## Sumary + +- **Python** 3.7+ +- Tests: [![builds.sr.ht status](https://builds.sr.ht/~fmartingr/memories/test.yml.svg)](https://builds.sr.ht/~fmartingr/memories/test.yml?) +- Documentation: TODO + +## References + +- 360 video: https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..80c6d1e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.3' + +services: + # web: + # build: . + # ports: + # - "8000:8000" + + db: + image: fmartingr/postgres + ports: + - "5432:5432" + environment: + DB_USER: memories + DB_PASS: memories + DB_NAME: memories + DB_EXTENSION: hstore diff --git a/memories/__init__.py b/memories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memories/collection/__init__.py b/memories/collection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memories/collection/admin.py b/memories/collection/admin.py new file mode 100644 index 0000000..b5fd6ae --- /dev/null +++ b/memories/collection/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import Picture + + +class PictureAdmin(admin.ModelAdmin): + list_display = ("file_path", "creation_date", "checksum", "metadata", ) + + +admin.site.register(Picture, PictureAdmin) diff --git a/memories/collection/apps.py b/memories/collection/apps.py new file mode 100644 index 0000000..1454371 --- /dev/null +++ b/memories/collection/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CollectionConfig(AppConfig): + name = 'collection' diff --git a/memories/collection/migrations/0001_initial.py b/memories/collection/migrations/0001_initial.py new file mode 100644 index 0000000..fb3b77f --- /dev/null +++ b/memories/collection/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.6 on 2020-05-10 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Picture', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file_path', models.FilePathField()), + ('creation_date', models.DateTimeField()), + ], + ), + ] diff --git a/memories/collection/migrations/0002_auto_20200510_1842.py b/memories/collection/migrations/0002_auto_20200510_1842.py new file mode 100644 index 0000000..d80e79c --- /dev/null +++ b/memories/collection/migrations/0002_auto_20200510_1842.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.6 on 2020-05-10 18:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='picture', + name='metadata', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='picture', + name='checksum', + field=models.CharField(default=123, max_length=40), + preserve_default=False, + ), + ] diff --git a/memories/collection/migrations/0003_picture_mimetype.py b/memories/collection/migrations/0003_picture_mimetype.py new file mode 100644 index 0000000..6608d99 --- /dev/null +++ b/memories/collection/migrations/0003_picture_mimetype.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.6 on 2020-05-10 19:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0002_auto_20200510_1842'), + ] + + operations = [ + migrations.AddField( + model_name='picture', + name='mimetype', + field=models.CharField(default=123, max_length=64), + preserve_default=False, + ), + ] diff --git a/memories/collection/migrations/0004_picture_kind.py b/memories/collection/migrations/0004_picture_kind.py new file mode 100644 index 0000000..e9babda --- /dev/null +++ b/memories/collection/migrations/0004_picture_kind.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-10 21:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0003_picture_mimetype'), + ] + + operations = [ + migrations.AddField( + model_name='picture', + name='kind', + field=models.CharField(choices=[('picture', 'Picture'), ('video', 'Video')], default='picture', max_length=24), + ), + ] diff --git a/memories/collection/migrations/0005_picture_raw.py b/memories/collection/migrations/0005_picture_raw.py new file mode 100644 index 0000000..a75efe1 --- /dev/null +++ b/memories/collection/migrations/0005_picture_raw.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-10 22:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0004_picture_kind'), + ] + + operations = [ + migrations.AddField( + model_name='picture', + name='raw', + field=models.BooleanField(default=False), + ), + ] diff --git a/memories/collection/migrations/0006_auto_20200511_1111.py b/memories/collection/migrations/0006_auto_20200511_1111.py new file mode 100644 index 0000000..0281b59 --- /dev/null +++ b/memories/collection/migrations/0006_auto_20200511_1111.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.6 on 2020-05-11 11:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0005_picture_raw'), + ] + + operations = [ + migrations.AlterModelOptions( + name='picture', + options={'ordering': ('creation_date',)}, + ), + migrations.AlterField( + model_name='picture', + name='creation_date', + field=models.DateTimeField(db_index=True), + ), + ] diff --git a/memories/collection/migrations/0007_auto_20200511_1114.py b/memories/collection/migrations/0007_auto_20200511_1114.py new file mode 100644 index 0000000..c595bd4 --- /dev/null +++ b/memories/collection/migrations/0007_auto_20200511_1114.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.6 on 2020-05-11 11:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0006_auto_20200511_1111'), + ] + + operations = [ + migrations.AddField( + model_name='picture', + name='exif', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='picture', + name='stat', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/memories/collection/migrations/0008_auto_20200511_1855.py b/memories/collection/migrations/0008_auto_20200511_1855.py new file mode 100644 index 0000000..8df14cd --- /dev/null +++ b/memories/collection/migrations/0008_auto_20200511_1855.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-11 18:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0007_auto_20200511_1114'), + ] + + operations = [ + migrations.AlterField( + model_name='picture', + name='file_path', + field=models.FilePathField(max_length=255), + ), + ] diff --git a/memories/collection/migrations/0009_auto_20200531_1926.py b/memories/collection/migrations/0009_auto_20200531_1926.py new file mode 100644 index 0000000..d09a567 --- /dev/null +++ b/memories/collection/migrations/0009_auto_20200531_1926.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.6 on 2020-05-31 19:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0008_auto_20200511_1855'), + ] + + operations = [ + migrations.AlterModelOptions( + name='picture', + options={}, + ), + ] diff --git a/memories/collection/migrations/0010_auto_20200531_1927.py b/memories/collection/migrations/0010_auto_20200531_1927.py new file mode 100644 index 0000000..812d91b --- /dev/null +++ b/memories/collection/migrations/0010_auto_20200531_1927.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-31 19:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0009_auto_20200531_1926'), + ] + + operations = [ + migrations.RenameField( + model_name='picture', + old_name='checksum', + new_name='checksum', + ), + ] diff --git a/memories/collection/migrations/0011_auto_20200531_1928.py b/memories/collection/migrations/0011_auto_20200531_1928.py new file mode 100644 index 0000000..d3c8eb4 --- /dev/null +++ b/memories/collection/migrations/0011_auto_20200531_1928.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-31 19:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0010_auto_20200531_1927'), + ] + + operations = [ + migrations.AlterField( + model_name='picture', + name='checksum', + field=models.CharField(max_length=64), + ), + ] diff --git a/memories/collection/migrations/__init__.py b/memories/collection/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memories/collection/models.py b/memories/collection/models.py new file mode 100644 index 0000000..af0fe85 --- /dev/null +++ b/memories/collection/models.py @@ -0,0 +1,83 @@ +import os +import os.path +import stat +import json +from datetime import datetime + +from django.db import models + + +class Stat: + stat_result: os.stat_result + + def __init__(self, stat_result): + self.stat_result = stat_result + + def __dict__(self): + return { + "mode": self.mode, + "access_time": self.access_time, + "modified_time": self.modified_time, + "creation_time": self.creation_time, + "birth_time": self.birth_time, + } + + def as_dict(self): + return self.__dict__() + + @property + def mode(self): + return self.stat_result["st_mode"] + + @property + def access_time(self): + return datetime.fromtimestamp(self.stat_result["st_atime"]) + + @property + def modified_time(self): + return datetime.fromtimestamp(self.stat_result["st_mtime"]) + + @property + def creation_time(self): + return datetime.fromtimestamp(self.stat_result["st_ctime"]) + + @property + def birth_time(self): + if "st_birthtime" in self.stat_result: + return datetime.fromtimestamp(self.stat_result["st_birthtime"]) + return None + + +class Picture(models.Model): + PICTURE = "picture" + # PICTURE_360 = 'picture-360' + # PANORAMA = 'picture-panorama' + VIDEO = "video" + # VIDEO_360 = 'video-360' + KIND_CHOICES = ( + (PICTURE, PICTURE.capitalize()), + # (PICTURE_360, "Picture 360"), + # (PANORAMA, PANORAMA.capitalize()), + (VIDEO, VIDEO.capitalize()), + # (VIDEO_360, "Video 360"), + ) + + file_path = models.FilePathField(max_length=255) + creation_date = models.DateTimeField(db_index=True) + checksum = models.CharField(max_length=64) + metadata = models.TextField(blank=True, null=True) + exif = models.TextField(blank=True, null=True) + stat = models.TextField(blank=True, null=True) + mimetype = models.CharField(max_length=64) + raw = models.BooleanField(default=False) + kind = models.CharField(choices=KIND_CHOICES, default=PICTURE, max_length=24) + + @property + def filename(self): + return os.path.basename(self.file_path) + + def get_stat(self): + return Stat(json.loads(self.stat)) + + # class Meta: + # ordering = ("creation_date", ) diff --git a/memories/collection/play.png b/memories/collection/play.png new file mode 100644 index 0000000..0a17dbd Binary files /dev/null and b/memories/collection/play.png differ diff --git a/memories/collection/poc.py b/memories/collection/poc.py new file mode 100644 index 0000000..299302d --- /dev/null +++ b/memories/collection/poc.py @@ -0,0 +1,177 @@ +import os +from datetime import datetime +import mimetypes +from typing import Text +from dataclasses import dataclass +import hashlib +import subprocess + +import mutagen + +LIBRARY_PATH = "/home/fmartingr/Code/memories/testphotos" +RAW_MIMETYPES = { + # RAW pictures + "ARW": "image/x-sony-arw", + "CR2": "image/x-canon-cr2", + "CR3": "image/x-canon-cr3", + "CRW": "image/x-canon-crw", + "DCR": "image/x-kodak-dcr", + "DNG": "image/x-adobe-dng", + "ERF": "image/x-epson-erf", + "K25": "image/x-kodak-k25", + "KDC": "image/x-kodak-kdc", + "MRW": "image/x-minolta-mrw", + "NEF": "image/x-nikon-nef", + "ORF": "image/x-olympus-orf", + "PEF": "image/x-pentax-pef", + "RAF": "image/x-fuji-raf", + "RAW": "image/x-panasonic-raw", + "SR2": "image/x-sony-sr2", + "SRF": "image/x-sony-srf", + "X3F": "image/x-sigma-x3f", +} +# High Efficiency Image/Video (apple) +APPLE_MIMETYPES = {"HEIC": "image/heic", "HEIF": "image/heif", "HEVC": "video/hevc"} + +CUSTOM_MIMETYPES = {} +CUSTOM_MIMETYPES.update(RAW_MIMETYPES) +CUSTOM_MIMETYPES.update(APPLE_MIMETYPES) + +for extension, mimetype in CUSTOM_MIMETYPES.items(): + mimetypes.add_type(mimetype, f".{extension}") + mimetypes.add_type(mimetype, f".{extension.lower()}") + + +def read_exif(path): + output = {} + with subprocess.Popen(["exiftool", path], stdout=subprocess.PIPE) as proc: + for line in proc.stdout.readlines(): + key, value = line.decode("utf-8").strip().split(":", maxsplit=1) + output[key.strip()] = value.strip() + return output + + +@dataclass +class File: + __mro__ = {"path", "_type"} + + path: str + + @property + def mimetype(self) -> Text: + """Retrieves the file mimetype by extension""" + if not getattr(self, "_mimetype", False): + self._mimetype, _ = mimetypes.guess_type(self.path) + if not self._mimetype: + print(f"Can't guess type of file {self.path}") + return self._mimetype + + @property + def is_raw(self) -> bool: + return self.mimetype in RAW_MIMETYPES.values() + + @property + def is_image(self) -> bool: + return "image" in self.mimetype + + @property + def is_video(self) -> bool: + return "video" in self.mimetype + + @property + def stat(self): + stat = os.stat(self.path) + return {k: getattr(stat, k) for k in dir(stat) if k.startswith("st_")} + + @property + def exif(self) -> dict: + """ + Retrieve EXIF data from the file and merge it with wathever mutagen finds in there for video files. + """ + if not getattr(self, "_exif", False): + self._exif = read_exif(self.path) + if self.is_video: + self._exif.update(mutagen.File(self.path)) + return self._exif + + def get_datetime(self) -> datetime: + """ + Tries to guess the original datetime for the provided file. + This is done extracting several EXIF values and the file birthdate/modification date. + The oldest one is the winner. + """ + + CREATION_DATE_EXIF_KEYS = ( + "Content Create Date", + "Date/Time Original", + "Create Date", + "Date Created", + "File Modification Date/Time", + ) + + datetimes = [] + + for key in CREATION_DATE_EXIF_KEYS: + try: + dt = datetime.strptime(self.exif[key], "%Y:%m:%d %H:%M:%S%z") + datetimes.append(dt.replace(tzinfo=None)) + except KeyError: + pass + except ValueError: + try: + cleaned = self.exif[key].rsplit(".", maxsplit=1) + datetimes.append(datetime.strptime(cleaned[0], "%Y:%m:%d %H:%M:%S")) + except ValueError: + pass + + # Last resort, use file creation/modification date + stat = os.stat(self.path) + try: + datetimes.append(datetime.fromtimestamp(stat.st_birthtime)) + except AttributeError: + # Linux: No easy way to get creation dates here, + # so we'll settle for when its content was last modified. + datetimes.append(datetime.fromtimestamp(stat.st_mtime)) + + sorted_datetimes = sorted(datetimes) + return sorted_datetimes[0] + + @property + def datetime(self): + if not getattr(self, "_datetime", False): + self._datetime = self.get_datetime() + return self._datetime + + @property + def filename(self): + return os.path.splitext(os.path.basename(self.path))[0] + + @property + def extension(self): + return os.path.splitext(self.path)[1][1:].lower() + + @property + def checksum(self) -> Text: + if not getattr(self, "_checksum", False): + digest = hashlib.sha224() + with open(self.path, "rb") as handler: + digest.update(handler.read()) + + self._checksum = f"sha224-{digest.hexdigest()}" + return self._checksum + + def as_dict(self): + return { + "path": self.path, + "filename": self.filename, + "extension": self.extension, + "checksum": self.checksum, + "datetime": self.datetime, + "exif": self.exif, + } + + +def get_files(): + for root, dirs, files in os.walk(LIBRARY_PATH): + for filename in files: + yield File(path=os.path.join(root, filename)) diff --git a/memories/collection/templates/base.html b/memories/collection/templates/base.html new file mode 100644 index 0000000..a11d708 --- /dev/null +++ b/memories/collection/templates/base.html @@ -0,0 +1,133 @@ + + + + + + + + + + Memories + + + + + + {% block content %}{% endblock %} + + + diff --git a/memories/collection/templates/detail.html b/memories/collection/templates/detail.html new file mode 100644 index 0000000..d362432 --- /dev/null +++ b/memories/collection/templates/detail.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% block content %} +

Picture detail

+ +
+ Preview | Details | Stat | EXIF +
+ +
+

Preview

+ {% if picture.raw %} + RAW display in browser not available + {% elif "video" in picture.mimetype %} + + {% else %} + + {% endif %} +
+ +
+

Details

+ +
+ +
+

Stat (parsed)

+ +
+

Stat (raw)

+ +
+ +
+

EXIF

+ +
+ +{% endblock %} diff --git a/memories/collection/templates/list.html b/memories/collection/templates/list.html new file mode 100644 index 0000000..3109717 --- /dev/null +++ b/memories/collection/templates/list.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} +

Gallery

+ +
+ Sort by {% for sort_by_key, sort_by_name in sort_by_options.items %} | {% if sort_by != sort_by_key %}{% else %}{% endif %}{{ sort_by_name }}{% if sort_by != sort_by_key %}{% else %}{% endif %}{% endfor %} +
+
+ Year | {% for year in years %}{% if selected_year != year %}{% endif %}{{ year }}{% if selected_year != year %}{% endif %}{% if not forloop.last %} + | {% endif %}{% endfor %} +
+{% if months %} +
+ Month | {% for month in months %}{% if selected_month != month %}{% endif %}{{ month }}{% if selected_month != month %}{% endif %}{% if not forloop.last %} + | {% endif %}{% endfor %} +
+{% endif %} + +
+ {% for picture in pictures %} +
+
+
{{ picture.mimetype }}
+ +
{{ picture.creation_date}}
+ +
+
+
+ {% endfor %} +
+{% endblock %} diff --git a/memories/collection/tests.py b/memories/collection/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/memories/collection/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/memories/collection/video.py b/memories/collection/video.py new file mode 100644 index 0000000..aa328ea --- /dev/null +++ b/memories/collection/video.py @@ -0,0 +1,15 @@ +import subprocess +from PIL import Image + + +def get_thumbnail_from_video(path, checksum): + cmd = ["ffmpeg", "-y", f"-i", f"{path}", "-vframes", "1", "-vf", "scale=480:-2", "-q:v", "3", f"/tmp/{checksum}-thumb-320.jpg"] + subprocess.call(cmd) + image = Image.open(f"/tmp/{checksum}-thumb-320.jpg") + play = Image.open("/home/fmartingr/Code/memories/memories/collection/play.png") + play = play.resize((round(play.size[0] * 0.5), round(play.size[1] * 0.5))) + image.paste( + play, (int(image.size[0] / 2 - play.size[0] / 2), int(image.size[1] / 2 - play.size[1] / 2)), play + ) + image.save(f"/tmp/{checksum}-thumb-320.jpg") + return open(f"/tmp/{checksum}-thumb-320.jpg", "rb").read() diff --git a/memories/collection/views.py b/memories/collection/views.py new file mode 100644 index 0000000..49b5c6a --- /dev/null +++ b/memories/collection/views.py @@ -0,0 +1,173 @@ +import json +import os + +import pyheif +import rawpy +from django.shortcuts import render, get_object_or_404, reverse +from django.http.response import HttpResponse, HttpResponseRedirect +from django.views.decorators.cache import cache_page +from PIL import Image + +from collection.poc import get_files +from collection.models import Picture +from collection.video import get_thumbnail_from_video + + +def gather_view(request): + Picture.objects.all().delete() + for file in get_files(): + try: + Picture.objects.get(file_path=file.path, checksum=file.checksum) + except Picture.DoesNotExist: + Picture.objects.create( + file_path=file.path, + kind=Picture.VIDEO if file.is_video else Picture.PICTURE, + raw=file.is_raw, + creation_date=file.datetime, + checksum=file.checksum, + exif=json.dumps(file.exif), + stat=json.dumps(file.stat), + mimetype=file.mimetype, + ) + + for file in Picture.objects.all(): + if not os.path.exists(file.file_path): + file.delete() + + return HttpResponse("ok") + + +DEFAULT_SORT_BY = "-creation_date" +SORT_BY_OPTIONS = { + "creation_date": "Creation date ASC", + "-creation_date": "Creation date DESC", +} + + +def list_view(request): + sort_by = request.GET.get("sort_by") + if sort_by not in SORT_BY_OPTIONS.keys(): + return HttpResponseRedirect(reverse("list") + f"?sort_by={DEFAULT_SORT_BY}") + pictures = Picture.objects.all().order_by(DEFAULT_SORT_BY) + years = tuple( + Picture.objects.all() + .distinct("creation_date__year") + .values_list("creation_date__year", flat=True) + ) + + return render( + template_name="list.html", + request=request, + context=dict( + pictures=pictures, + years=years, + sort_by=sort_by, + sort_by_options=SORT_BY_OPTIONS, + ), + ) + + +def list_year_month(request, year: int, month: int = None): + sort_by = request.GET.get("sort_by") + pictures = Picture.objects.filter(creation_date__year=year) + + years = tuple( + Picture.objects.all() + .distinct("creation_date__year") + .values_list("creation_date__year", flat=True) + ) + months = tuple( + pictures.distinct("creation_date__month").values_list( + "creation_date__month", flat=True + ) + ) + + if month: + pictures = pictures.filter(creation_date__month=month) + + return render( + template_name="list.html", + request=request, + context=dict( + pictures=pictures.order_by(DEFAULT_SORT_BY), + years=years, + months=months, + selected_year=year, + selected_month=month, + ), + ) + + +def detail_view(request, picture_id): + picture = get_object_or_404(Picture, id=picture_id) + exif = { + k: v + for k, v in sorted(json.loads(picture.exif).items(), key=lambda item: item[0]) + } + file_stat = { + k: v + for k, v in sorted(json.loads(picture.stat).items(), key=lambda item: item[0]) + } + + return render( + template_name="detail.html", + request=request, + context=dict(picture=picture, exif=exif, file_stat=file_stat), + ) + + +def image_view(request, picture_id): + picture = get_object_or_404(Picture, id=picture_id) + + return HttpResponse(open(picture.file_path, "rb").read()) + + +def thumbnail_view(request, picture_id): + picture = get_object_or_404(Picture, id=picture_id) + + if picture.kind == picture.VIDEO: + thumbnail = get_thumbnail_from_video(picture.file_path, picture.checksum) + return HttpResponse(thumbnail, content_type="image/jpg") + + if picture.raw: + try: + with rawpy.imread(picture.file_path) as raw: + # raises rawpy.LibRawNoThumbnailError if thumbnail missing + # raises rawpy.LibRawUnsupportedThumbnailError if unsupported format + thumb = raw.extract_thumb() + + if thumb.format == rawpy.ThumbFormat.JPEG: + # thumb.data is already in JPEG format, save as-is + return HttpResponse(thumb.data, content_type="image/jpeg") + elif thumb.format == rawpy.ThumbFormat.BITMAP: + # thumb.data is an RGB numpy array, convert with imageio + return HttpResponse(thumb.data, content_type="image/bitmap") + except rawpy.LibRawFileUnsupportedError: + return HttpResponse(open("photo.png", "rb"), content_type="image/png") + + if picture.mimetype == "image/heic": + heif_file = pyheif.read_heif(picture.file_path) + image = Image.frombytes( + mode=heif_file.mode, size=heif_file.size, data=heif_file.data + ) + image.thumbnail((480, 480)) + image.save(f"/tmp/thumb-480-{picture.checksum}.jpg") + return HttpResponse( + open(f"/tmp/thumb-480-{picture.checksum}.jpg", "rb").read(), + content_type="image/jpg", + ) + + if picture.kind == picture.VIDEO: + return HttpResponse(open("video.png", "rb"), content_type="image/png") + + if picture.kind == picture.PICTURE: + image = Image.open(picture.file_path) + image.thumbnail((480, 480)) + image.save(f"/tmp/thumb-480-{picture.checksum}.jpg") + return HttpResponse( + open(f"/tmp/thumb-480-{picture.checksum}.jpg", "rb").read(), + content_type="image/jpg", + ) + + +video_view = image_view diff --git a/memories/core/__init__.py b/memories/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memories/core/db.py b/memories/core/db.py new file mode 100644 index 0000000..879bdf2 --- /dev/null +++ b/memories/core/db.py @@ -0,0 +1,81 @@ +import os +from datetime import datetime + +from playhouse.postgres_ext import PostgresqlExtDatabase, HStoreField, Model +from peewee import CharField, DateTimeField + + +database = PostgresqlExtDatabase( + "memories", + user="memories", + password="memories", + host="127.0.0.1", + port=5432, + register_hstore=True, +) + + +class Item(Model): + PICTURE = "picture" + VIDEO = "video" + KIND_CHOICES = ((PICTURE, "Picture"), (VIDEO, "Video")) + + file_path = CharField() + kind = CharField(choices=KIND_CHOICES, default=PICTURE, index=True) + creation_date = DateTimeField() + checksum = CharField(max_length=63) + metadata = HStoreField() + exif = HStoreField() + stat = HStoreField() + mimetype = CharField() + + class Meta: + database = database + + @property + def filename(self): + return os.path.basename(self.file_path) + + def get_stat(self): + return Stat(self.stat) + + +class Stat: + stat_result: os.stat_result + + def __init__(self, stat_result): + self.stat_result = stat_result + + def __dict__(self): + return { + "mode": self.mode, + "access_time": self.access_time, + "modified_time": self.modified_time, + "creation_time": self.creation_time, + "birth_time": self.birth_time, + } + + def as_dict(self): + return self.__dict__() + + @property + def mode(self): + return self.stat_result["st_mode"] + + @property + def access_time(self): + return datetime.fromtimestamp(self.stat_result["st_atime"]) + + @property + def modified_time(self): + return datetime.fromtimestamp(self.stat_result["st_mtime"]) + + @property + def creation_time(self): + return datetime.fromtimestamp(self.stat_result["st_ctime"]) + + @property + def birth_time(self): + if "st_birthtime" in self.stat_result: + return datetime.fromtimestamp(self.stat_result["st_birthtime"]) + return None diff --git a/memories/manage.py b/memories/manage.py new file mode 100755 index 0000000..276970d --- /dev/null +++ b/memories/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'memories.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/memories/memories/__init__.py b/memories/memories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/memories/memories/asgi.py b/memories/memories/asgi.py new file mode 100644 index 0000000..0efd0b2 --- /dev/null +++ b/memories/memories/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for memories project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'memories.settings') + +application = get_asgi_application() diff --git a/memories/memories/settings.py b/memories/memories/settings.py new file mode 100644 index 0000000..5560d62 --- /dev/null +++ b/memories/memories/settings.py @@ -0,0 +1,118 @@ +""" +Django settings for memories project. + +Generated by 'django-admin startproject' using Django 3.0.6. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "^-jw5=h-ockcmwgj!vj2q+23!&q$sf#+h!134pg()_x3w#d+6o" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "collection", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "memories.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] + }, + } +] + +WSGI_APPLICATION = "memories.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "memories", + "USER": "memories", + "PASSWORD": "memories", + "HOST": "localhost", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + +STATIC_URL = "/static/" diff --git a/memories/memories/urls.py b/memories/memories/urls.py new file mode 100644 index 0000000..f4947c7 --- /dev/null +++ b/memories/memories/urls.py @@ -0,0 +1,39 @@ +"""memories URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path +from collection.views import ( + gather_view, + list_view, + detail_view, + image_view, + thumbnail_view, + video_view, + list_year_month, +) + + +urlpatterns = [ + path("admin/", admin.site.urls), + path("gather/", gather_view), + path("", list_view, name="list"), + path("year/", list_year_month, name="list-year"), + path("year//", list_year_month, name="list-year-month"), + path("", detail_view, name="detail"), + path("img/", image_view, name="image"), + path("thumb/", thumbnail_view, name="thumbnail"), + path("video/", video_view, name="video"), +] diff --git a/memories/memories/wsgi.py b/memories/memories/wsgi.py new file mode 100644 index 0000000..2e51680 --- /dev/null +++ b/memories/memories/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for memories project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'memories.settings') + +application = get_wsgi_application() diff --git a/memories/server/__init__.py b/memories/server/__init__.py new file mode 100644 index 0000000..599ccea --- /dev/null +++ b/memories/server/__init__.py @@ -0,0 +1,4 @@ +from memories.server.app import app + + +__all__ = ("app", ) diff --git a/memories/server/app.py b/memories/server/app.py new file mode 100644 index 0000000..9f24322 --- /dev/null +++ b/memories/server/app.py @@ -0,0 +1,56 @@ +import os.path + +from fastapi import FastAPI, APIRouter +from pydantic.types import UUID4 + +from memories.collection.poc import get_files +from memories.core.db import Item, database + +app = FastAPI(title="Memories") +# database.connect() +# database.create_tables([Item]) +# database.close() + +router_items = APIRouter() + + +@app.get("/healthz") +async def healthz(): + return {"status": "ok"} + + +@router_items.get("/") +async def items_list(): + return [] + + +@router_items.post("/") +async def item_create(): + return {} + + +@router_items.get("/{item_id}") +async def item_detail(item_id: UUID4): + return {"item_id": item_id} + +app.include_router(router_items, prefix="/items", tags=["items"]) + +@app.get("/test/sync") +async def test_sync_view(): + with database.atomic(): + for item in Item.select(): + if not os.path.exists(item.file_path): + item.delete() + + for file in get_files(): + if not Item.select().where(Item.file_path == file.path).exists(): + Item.create( + file_path=file.path, + kind=Item.VIDEO if file.is_video else Item.PICTURE, + metadata={"raw": file.is_raw, "panorama": False}, + creation_date=file.datetime, + checksum=file.checksum, + exif=file.exif, + stat=file.stat, + mimetype=file.mimetype, + ) diff --git a/photo.png b/photo.png new file mode 100644 index 0000000..7bf9132 Binary files /dev/null and b/photo.png differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ee649aa --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1083 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "dev" +description = "Disable App Nap on OS X 10.9" +marker = "python_version >= \"3.4\" and sys_platform == \"darwin\"" +name = "appnope" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "main" +description = "ASGI specs, helper code, and adapters" +name = "asgiref" +optional = false +python-versions = ">=3.5" +version = "3.2.7" + +[package.extras] +tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"] + +[[package]] +category = "dev" +description = "A few extensions to pyyaml." +name = "aspy.yaml" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.3.0" + +[package.dependencies] +pyyaml = "*" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "19.3.0" + +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + +[[package]] +category = "dev" +description = "Specifications for callback functions passed in to an API" +marker = "python_version >= \"3.4\"" +name = "backcall" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "18.9b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=17.4.0" +click = ">=6.5" +toml = ">=0.9.4" + +[package.extras] +d = ["aiohttp (>=3.3.2)"] + +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" + +[[package]] +category = "dev" +description = "Validate configuration and produce human readable error messages." +name = "cfgv" +optional = false +python-versions = ">=3.6.1" +version = "3.1.0" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "python_version >= \"3.4\" and sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "dev" +description = "Decorators for Humans" +marker = "python_version >= \"3.4\"" +name = "decorator" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" + +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.0" + +[[package]] +category = "main" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" +optional = false +python-versions = ">=3.6" +version = "3.0.6" + +[package.dependencies] +asgiref = ">=3.2,<4.0" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +category = "dev" +description = "Discover and load entry points from installed packages." +name = "entrypoints" +optional = false +python-versions = ">=2.7" +version = "0.3" + +[[package]] +category = "main" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +name = "fastapi" +optional = false +python-versions = ">=3.6" +version = "0.54.2" + +[package.dependencies] +pydantic = ">=0.32.2,<2.0.0" +starlette = "0.13.2" + +[package.extras] +all = ["requests", "aiofiles", "jinja2", "python-multipart", "itsdangerous", "pyyaml", "graphene", "ujson", "orjson", "email-validator", "uvicorn", "async-exit-stack", "async-generator"] +dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"] +doc = ["mkdocs", "mkdocs-material", "markdown-include", "typer", "typer-cli", "pyyaml"] +test = ["pytest (>=4.0.0)", "pytest-cov", "mypy", "black", "isort", "requests", "email-validator", "sqlalchemy", "peewee", "databases", "orjson", "async-exit-stack", "async-generator", "python-multipart", "aiofiles", "flask"] + +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "dev" +description = "the modular source code checker: pep8, pyflakes and co" +name = "flake8" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.7.9" + +[package.dependencies] +entrypoints = ">=0.3.0,<0.4.0" +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.5.0,<2.6.0" +pyflakes = ">=2.1.0,<2.2.0" + +[[package]] +category = "main" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +name = "h11" +optional = false +python-versions = "*" +version = "0.9.0" + +[[package]] +category = "main" +description = "A collection of framework independent HTTP protocol utils." +marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" +name = "httptools" +optional = false +python-versions = "*" +version = "0.1.1" + +[package.extras] +test = ["Cython (0.29.14)"] + +[[package]] +category = "dev" +description = "File identification library for Python" +name = "identify" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "1.4.15" + +[package.extras] +license = ["editdistance"] + +[[package]] +category = "dev" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.6.0" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "dev" +description = "IPython-enabled pdb" +name = "ipdb" +optional = false +python-versions = ">=2.7" +version = "0.12.3" + +[package.dependencies] +setuptools = "*" + +[package.dependencies.ipython] +python = ">=3.4" +version = ">=5.1.0" + +[[package]] +category = "dev" +description = "IPython: Productive Interactive Computing" +marker = "python_version >= \"3.4\"" +name = "ipython" +optional = false +python-versions = ">=3.6" +version = "7.14.0" + +[package.dependencies] +appnope = "*" +backcall = "*" +colorama = "*" +decorator = "*" +jedi = ">=0.10" +pexpect = "*" +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["nose (>=0.10.1)", "Sphinx (>=1.3)", "testpath", "nbformat", "ipywidgets", "qtconsole", "numpy (>=1.14)", "notebook", "ipyparallel", "ipykernel", "pygments", "requests", "nbconvert"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] + +[[package]] +category = "dev" +description = "Vestigial utilities from IPython" +marker = "python_version >= \"3.4\"" +name = "ipython-genutils" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "4.3.21" + +[package.extras] +pipfile = ["pipreqs", "requirementslib"] +pyproject = ["toml"] +requirements = ["pipreqs", "pip-api"] +xdg_home = ["appdirs (>=1.4.0)"] + +[[package]] +category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +marker = "python_version >= \"3.4\"" +name = "jedi" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.17.0" + +[package.dependencies] +parso = ">=0.7.0" + +[package.extras] +qa = ["flake8 (3.7.9)"] +testing = ["colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] + +[[package]] +category = "dev" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + +[[package]] +category = "main" +description = "read and write audio tags for many formats" +name = "mutagen" +optional = false +python-versions = ">=3.5, <4" +version = "1.44.0" + +[[package]] +category = "dev" +description = "Node.js virtual environment builder" +name = "nodeenv" +optional = false +python-versions = "*" +version = "1.3.5" + +[[package]] +category = "main" +description = "NumPy is the fundamental package for array computing with Python." +name = "numpy" +optional = false +python-versions = ">=3.5" +version = "1.18.4" + +[[package]] +category = "dev" +description = "A Python Parser" +marker = "python_version >= \"3.4\"" +name = "parso" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.7.0" + +[package.extras] +testing = ["docopt", "pytest (>=3.0.7)"] + +[[package]] +category = "main" +description = "a little orm" +name = "peewee" +optional = false +python-versions = "*" +version = "3.13.3" + +[[package]] +category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" +name = "pexpect" +optional = false +python-versions = "*" +version = "4.8.0" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +marker = "python_version >= \"3.4\"" +name = "pickleshare" +optional = false +python-versions = "*" +version = "0.7.5" + +[[package]] +category = "main" +description = "Python Imaging Library (Fork)" +name = "pillow" +optional = false +python-versions = ">=3.5" +version = "7.1.2" + +[[package]] +category = "dev" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "pre-commit" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.21.0" + +[package.dependencies] +"aspy.yaml" = "*" +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = "*" +six = "*" +toml = "*" +virtualenv = ">=15.2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + +[[package]] +category = "dev" +description = "Library for building powerful interactive command lines in Python" +marker = "python_version >= \"3.4\"" +name = "prompt-toolkit" +optional = false +python-versions = ">=3.6.1" +version = "3.0.5" + +[package.dependencies] +wcwidth = "*" + +[[package]] +category = "main" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg2" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "2.8.5" + +[[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "python_version >= \"3.4\" and sys_platform != \"win32\"" +name = "ptyprocess" +optional = false +python-versions = "*" +version = "0.6.0" + +[[package]] +category = "dev" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.5.0" + +[[package]] +category = "main" +description = "C parser in Python" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + +[[package]] +category = "main" +description = "Data validation and settings management using python 3.6 type hinting" +name = "pydantic" +optional = false +python-versions = ">=3.6" +version = "1.5.1" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] +typing_extensions = ["typing-extensions (>=3.7.2)"] + +[[package]] +category = "dev" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.1.1" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +marker = "python_version >= \"3.4\"" +name = "pygments" +optional = false +python-versions = ">=3.5" +version = "2.6.1" + +[[package]] +category = "main" +description = "Python 3.6+ interface to libheif library" +name = "pyheif" +optional = false +python-versions = ">= 3.6" +version = "0.4" + +[package.dependencies] +cffi = ">=1.0.0" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2020.1" + +[[package]] +category = "dev" +description = "YAML parser and emitter for Python" +name = "pyyaml" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.3.1" + +[[package]] +category = "main" +description = "RAW image processing for Python, a wrapper for libraw" +name = "rawpy" +optional = false +python-versions = "*" +version = "0.14.0" + +[package.dependencies] +numpy = "*" + +[[package]] +category = "dev" +description = "a python refactoring library..." +name = "rope" +optional = false +python-versions = "*" +version = "0.14.0" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.14.0" + +[[package]] +category = "main" +description = "Non-validating SQL parser" +name = "sqlparse" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.3.1" + +[[package]] +category = "main" +description = "The little ASGI library that shines." +name = "starlette" +optional = false +python-versions = ">=3.6" +version = "0.13.2" + +[package.extras] +full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.0" + +[[package]] +category = "dev" +description = "Traitlets Python config system" +marker = "python_version >= \"3.4\"" +name = "traitlets" +optional = false +python-versions = "*" +version = "4.3.3" + +[package.dependencies] +decorator = "*" +ipython-genutils = "*" +six = "*" + +[package.extras] +test = ["pytest", "mock"] + +[[package]] +category = "main" +description = "The lightning-fast ASGI server." +name = "uvicorn" +optional = false +python-versions = "*" +version = "0.11.5" + +[package.dependencies] +click = ">=7.0.0,<8.0.0" +h11 = ">=0.8,<0.10" +httptools = ">=0.1.0,<0.2.0" +uvloop = ">=0.14.0" +websockets = ">=8.0.0,<9.0.0" + +[package.extras] +watchgodreload = ["watchgod (>=0.6,<0.7)"] + +[[package]] +category = "main" +description = "Fast implementation of asyncio event loop on top of libuv" +marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" +name = "uvloop" +optional = false +python-versions = "*" +version = "0.14.0" + +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.20" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12,<2" + +[package.extras] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] + +[[package]] +category = "dev" +description = "Measures number of Terminal column cells of wide-character codes" +marker = "python_version >= \"3.4\"" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.9" + +[[package]] +category = "main" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +name = "websockets" +optional = false +python-versions = ">=3.6.1" +version = "8.1" + +[[package]] +category = "dev" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" +optional = false +python-versions = ">=3.6" +version = "3.1.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["jaraco.itertools", "func-timeout"] + +[metadata] +content-hash = "5aeeed9bbbaead54da5f08b61ce9074d74d4fe53e8071f52f21de4c13fd81630" +python-versions = "^3.7" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +appnope = [ + {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, + {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, +] +asgiref = [ + {file = "asgiref-3.2.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"}, + {file = "asgiref-3.2.7.tar.gz", hash = "sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5"}, +] +"aspy.yaml" = [ + {file = "aspy.yaml-1.3.0-py2.py3-none-any.whl", hash = "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"}, + {file = "aspy.yaml-1.3.0.tar.gz", hash = "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +backcall = [ + {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, + {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, +] +black = [ + {file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"}, + {file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"}, +] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] +cfgv = [ + {file = "cfgv-3.1.0-py2.py3-none-any.whl", hash = "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53"}, + {file = "cfgv-3.1.0.tar.gz", hash = "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] +django = [ + {file = "Django-3.0.6-py3-none-any.whl", hash = "sha256:051ba55d42daa3eeda3944a8e4df2bc96d4c62f94316dea217248a22563c3621"}, + {file = "Django-3.0.6.tar.gz", hash = "sha256:9aaa6a09678e1b8f0d98a948c56482eac3e3dd2ddbfb8de70a868135ef3b5e01"}, +] +entrypoints = [ + {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, + {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, +] +fastapi = [ + {file = "fastapi-0.54.2-py3-none-any.whl", hash = "sha256:c8651f8316956240c2ffe5bc05c334c8359a3887e642720a9b23319c51e82907"}, + {file = "fastapi-0.54.2.tar.gz", hash = "sha256:fff1b4a7fdf4812abb4507fb7aa30ef4206a0435839626ebe3b2871ec9aa367f"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +flake8 = [ + {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, + {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, +] +h11 = [ + {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, + {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, +] +httptools = [ + {file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"}, + {file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"}, + {file = "httptools-0.1.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6"}, + {file = "httptools-0.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c"}, + {file = "httptools-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a"}, + {file = "httptools-0.1.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f"}, + {file = "httptools-0.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2"}, + {file = "httptools-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009"}, + {file = "httptools-0.1.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"}, + {file = "httptools-0.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d"}, + {file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"}, + {file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"}, +] +identify = [ + {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, + {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, + {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, +] +ipdb = [ + {file = "ipdb-0.12.3.tar.gz", hash = "sha256:5d9a4a0e3b7027a158fc6f2929934341045b9c3b0b86ed5d7e84e409653f72fd"}, +] +ipython = [ + {file = "ipython-7.14.0-py3-none-any.whl", hash = "sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb"}, + {file = "ipython-7.14.0.tar.gz", hash = "sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +isort = [ + {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, + {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, +] +jedi = [ + {file = "jedi-0.17.0-py2.py3-none-any.whl", hash = "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798"}, + {file = "jedi-0.17.0.tar.gz", hash = "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mutagen = [ + {file = "mutagen-1.44.0-py3-none-any.whl", hash = "sha256:1cfc9f40cc0c89f051e3f3dbd5d9057a193c98433cf6c95e02d7f5a395615c01"}, + {file = "mutagen-1.44.0.tar.gz", hash = "sha256:56065d8a9ca0bc64610a4d0f37e2bd4453381dde3226b8835ee656faa3287be4"}, +] +nodeenv = [ + {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, +] +numpy = [ + {file = "numpy-1.18.4-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720"}, + {file = "numpy-1.18.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26"}, + {file = "numpy-1.18.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a"}, + {file = "numpy-1.18.4-cp35-cp35m-win32.whl", hash = "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170"}, + {file = "numpy-1.18.4-cp35-cp35m-win_amd64.whl", hash = "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897"}, + {file = "numpy-1.18.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032"}, + {file = "numpy-1.18.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae"}, + {file = "numpy-1.18.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7"}, + {file = "numpy-1.18.4-cp36-cp36m-win32.whl", hash = "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d"}, + {file = "numpy-1.18.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2"}, + {file = "numpy-1.18.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c"}, + {file = "numpy-1.18.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88"}, + {file = "numpy-1.18.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085"}, + {file = "numpy-1.18.4-cp37-cp37m-win32.whl", hash = "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba"}, + {file = "numpy-1.18.4-cp37-cp37m-win_amd64.whl", hash = "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961"}, + {file = "numpy-1.18.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d"}, + {file = "numpy-1.18.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d"}, + {file = "numpy-1.18.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5"}, + {file = "numpy-1.18.4-cp38-cp38-win32.whl", hash = "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec"}, + {file = "numpy-1.18.4-cp38-cp38-win_amd64.whl", hash = "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6"}, + {file = "numpy-1.18.4.zip", hash = "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509"}, +] +parso = [ + {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, + {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, +] +peewee = [ + {file = "peewee-3.13.3.tar.gz", hash = "sha256:1269a9736865512bd4056298003aab190957afe07d2616cf22eaf56cb6398369"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pillow = [ + {file = "Pillow-7.1.2-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d"}, + {file = "Pillow-7.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f"}, + {file = "Pillow-7.1.2-cp35-cp35m-win32.whl", hash = "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523"}, + {file = "Pillow-7.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705"}, + {file = "Pillow-7.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3"}, + {file = "Pillow-7.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d"}, + {file = "Pillow-7.1.2-cp36-cp36m-win32.whl", hash = "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891"}, + {file = "Pillow-7.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088"}, + {file = "Pillow-7.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457"}, + {file = "Pillow-7.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3"}, + {file = "Pillow-7.1.2-cp37-cp37m-win32.whl", hash = "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7"}, + {file = "Pillow-7.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac"}, + {file = "Pillow-7.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2"}, + {file = "Pillow-7.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344"}, + {file = "Pillow-7.1.2-cp38-cp38-win32.whl", hash = "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd"}, + {file = "Pillow-7.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079"}, + {file = "Pillow-7.1.2-pp373-pypy36_pp73-win32.whl", hash = "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9"}, + {file = "Pillow-7.1.2-py3.8-macosx-10.9-x86_64.egg", hash = "sha256:70e3e0d99a0dcda66283a185f80697a9b08806963c6149c8e6c5f452b2aa59c0"}, + {file = "Pillow-7.1.2.tar.gz", hash = "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd"}, +] +pre-commit = [ + {file = "pre_commit-1.21.0-py2.py3-none-any.whl", hash = "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"}, + {file = "pre_commit-1.21.0.tar.gz", hash = "sha256:8f48d8637bdae6fa70cc97db9c1dd5aa7c5c8bf71968932a380628c25978b850"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, + {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, +] +psycopg2 = [ + {file = "psycopg2-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf"}, + {file = "psycopg2-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb"}, + {file = "psycopg2-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72"}, + {file = "psycopg2-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e"}, + {file = "psycopg2-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055"}, + {file = "psycopg2-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52"}, + {file = "psycopg2-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c"}, + {file = "psycopg2-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81"}, + {file = "psycopg2-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9"}, + {file = "psycopg2-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080"}, + {file = "psycopg2-2.8.5-cp38-cp38-win32.whl", hash = "sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7"}, + {file = "psycopg2-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535"}, + {file = "psycopg2-2.8.5.tar.gz", hash = "sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"}, +] +ptyprocess = [ + {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, + {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, +] +pycodestyle = [ + {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, + {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pydantic = [ + {file = "pydantic-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2a6904e9f18dea58f76f16b95cba6a2f20b72d787abd84ecd67ebc526e61dce6"}, + {file = "pydantic-1.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da8099fca5ee339d5572cfa8af12cf0856ae993406f0b1eb9bb38c8a660e7416"}, + {file = "pydantic-1.5.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:68dece67bff2b3a5cc188258e46b49f676a722304f1c6148ae08e9291e284d98"}, + {file = "pydantic-1.5.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ab863853cb502480b118187d670f753be65ec144e1654924bec33d63bc8b3ce2"}, + {file = "pydantic-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:2007eb062ed0e57875ce8ead12760a6e44bf5836e6a1a7ea81d71eeecf3ede0f"}, + {file = "pydantic-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:20a15a303ce1e4d831b4e79c17a4a29cb6740b12524f5bba3ea363bff65732bc"}, + {file = "pydantic-1.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:473101121b1bd454c8effc9fe66d54812fdc128184d9015c5aaa0d4e58a6d338"}, + {file = "pydantic-1.5.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:9be755919258d5d168aeffbe913ed6e8bd562e018df7724b68cabdee3371e331"}, + {file = "pydantic-1.5.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b96ce81c4b5ca62ab81181212edfd057beaa41411cd9700fbcb48a6ba6564b4e"}, + {file = "pydantic-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:93b9f265329d9827f39f0fca68f5d72cc8321881cdc519a1304fa73b9f8a75bd"}, + {file = "pydantic-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2c753d355126ddd1eefeb167fa61c7037ecd30b98e7ebecdc0d1da463b4ea09"}, + {file = "pydantic-1.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8433dbb87246c0f562af75d00fa80155b74e4f6924b0db6a2078a3cd2f11c6c4"}, + {file = "pydantic-1.5.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0a1cdf24e567d42dc762d3fed399bd211a13db2e8462af9dfa93b34c41648efb"}, + {file = "pydantic-1.5.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:8be325fc9da897029ee48d1b5e40df817d97fe969f3ac3fd2434ba7e198c55d5"}, + {file = "pydantic-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:3714a4056f5bdbecf3a41e0706ec9b228c9513eee2ad884dc2c568c4dfa540e9"}, + {file = "pydantic-1.5.1-py36.py37.py38-none-any.whl", hash = "sha256:70f27d2f0268f490fe3de0a9b6fca7b7492b8fd6623f9fecd25b221ebee385e3"}, + {file = "pydantic-1.5.1.tar.gz", hash = "sha256:f0018613c7a0d19df3240c2a913849786f21b6539b9f23d85ce4067489dfacfa"}, +] +pyflakes = [ + {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, + {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, +] +pygments = [ + {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, + {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, +] +pyheif = [ + {file = "pyheif-0.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ed0a86136651964ae11262af56acb98e308bb0be427b708dd01bf18229da53dd"}, + {file = "pyheif-0.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:292729f84f2e78366902596c169ad14ee22bc69c77fe7978b3fe4d675837bc4a"}, + {file = "pyheif-0.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1fc992662a35c77d5cca2c2034fdb6a09d21eb0174959513fa34f3e6286284b9"}, + {file = "pyheif-0.4.tar.gz", hash = "sha256:c522affce3a496060c2889b9a9ede7edac53a682b1139086d9b7f9e0e49a7cdd"}, +] +pytz = [ + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, +] +pyyaml = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] +rawpy = [ + {file = "rawpy-0.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:e47e46553832f64eeaec6a5e862a09280ec9e4fc517012eb122445ae0088e36d"}, + {file = "rawpy-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:7c375d572f10bfe3a79cbe46d951701f232018edd510f90824fba6aff020a196"}, + {file = "rawpy-0.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:6ffa1c58bbaaf1dd4f6fc0fa085875e707a129104b2ccacf338dc821fedfb744"}, + {file = "rawpy-0.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74b78f66a6045ad457a97f8a80249d83810fa4728b48cbdade603f882432638e"}, + {file = "rawpy-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:da8b426bf87d61ed9514235a83fa762db54072959526727ceaf282f505855ca6"}, + {file = "rawpy-0.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a642bdab61bae16aa722395bd00992d03f4e8bf4e685bbc46956735546a58613"}, + {file = "rawpy-0.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b115494e2045da37fcaec4f499670da83e62ddb68c785e06b3bb38a0b160daf"}, + {file = "rawpy-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a7675e2888323e3ae303e0b520cd9105dfb0b96e6fff000b49e98242a0308341"}, + {file = "rawpy-0.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a96ce12f11299f4e985b58acd2fac57b328c379880366c9362beb3fe294fd3d7"}, + {file = "rawpy-0.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7ad875acc7c58b98b5e051d2e2360180ea2d28c8ab2e86585e2fd6edc85151d"}, + {file = "rawpy-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b65b382b3ab0e11965660bf5fe000b9340ca44748409066a3070c9297b95769c"}, + {file = "rawpy-0.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:0fe87f10cad688d834130103405359aaf64ceb2087d0a7feea2c7f57e9466cba"}, +] +rope = [ + {file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"}, + {file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"}, + {file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +sqlparse = [ + {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"}, + {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"}, +] +starlette = [ + {file = "starlette-0.13.2-py3-none-any.whl", hash = "sha256:6169ee78ded501095d1dda7b141a1dc9f9934d37ad23196e180150ace2c6449b"}, + {file = "starlette-0.13.2.tar.gz", hash = "sha256:a9bb130fa7aa736eda8a814b6ceb85ccf7a209ed53843d0d61e246b380afa10f"}, +] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +traitlets = [ + {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, + {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, +] +uvicorn = [ + {file = "uvicorn-0.11.5-py3-none-any.whl", hash = "sha256:50577d599775dac2301bac8bd5b540d19a9560144143c5bdab13cba92783b6e7"}, + {file = "uvicorn-0.11.5.tar.gz", hash = "sha256:596eaa8645b6dbc24d6610e335f8ddf5f925b4c4b86fdc7146abb0bf0da65d17"}, +] +uvloop = [ + {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"}, + {file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"}, + {file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"}, + {file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"}, + {file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"}, + {file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"}, + {file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"}, + {file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"}, + {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, +] +virtualenv = [ + {file = "virtualenv-20.0.20-py2.py3-none-any.whl", hash = "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74"}, + {file = "virtualenv-20.0.20.tar.gz", hash = "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"}, +] +wcwidth = [ + {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, + {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, +] +websockets = [ + {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, + {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, + {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, + {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, + {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, + {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, + {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, + {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, + {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, + {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, + {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, + {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, + {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, + {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, + {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, + {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, +] +zipp = [ + {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, + {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..20f00d9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[tool.poetry] +name = "memories" +version = "0.1.0a1" +description = "" +authors = ["Felipe Martin "] + +[tool.poetry.dependencies] +python = "^3.7" +mutagen = "^1.44.0" +django = "^3.0.6" +rawpy = "^0.14.0" +psycopg2 = "^2.8.5" +numpy = "^1.18.4" +Pillow = "^7.1.2" +pyheif = "^0.4" +peewee = "^3.13.3" +fastapi = "^0.54.2" +uvicorn = "^0.11.5" + +[tool.poetry.dev-dependencies] +black = {version = "^18.3-alpha.0", allow-prereleases = true} +flake8 = "^3.7" +isort = "^4.3" +pre-commit = "^1.18" +rope = "^0.14.0" +ipdb = "^0.12.2" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..159245f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,16 @@ +[flake8] +ignore = E203, E266, E501, W503, F403 +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 + +[isort] +use_parentheses = True +multi_line_output = 3 +include_trailing_comma = True +length_sort = 1 +lines_between_types = 0 +line_length = 88 +known_third_party = click,django,docker,factory,pydantic,pytest,requests,toml +sections = FUTURE, STDLIB, THIRDPARTY, FIRSTPARTY, LOCALFOLDER +no_lines_before = LOCALFOLDER diff --git a/video.png b/video.png new file mode 100644 index 0000000..62ceb4b Binary files /dev/null and b/video.png differ