From 7907c755acb19af7c3b9a8942e1d208c2bf64de4 Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Sat, 8 Oct 2022 17:55:12 +0200 Subject: [PATCH] archive --- .gitignore | 13 + .pre-commit-config.yaml | 22 + Dockerfile | 12 + README.md | 13 + docker-compose.yml | 17 + memories/__init__.py | 0 memories/collection/__init__.py | 0 memories/collection/admin.py | 10 + memories/collection/apps.py | 5 + .../collection/migrations/0001_initial.py | 22 + .../migrations/0002_auto_20200510_1842.py | 24 + .../migrations/0003_picture_mimetype.py | 19 + .../migrations/0004_picture_kind.py | 18 + .../collection/migrations/0005_picture_raw.py | 18 + .../migrations/0006_auto_20200511_1111.py | 22 + .../migrations/0007_auto_20200511_1114.py | 23 + .../migrations/0008_auto_20200511_1855.py | 18 + .../migrations/0009_auto_20200531_1926.py | 17 + .../migrations/0010_auto_20200531_1927.py | 18 + .../migrations/0011_auto_20200531_1928.py | 18 + memories/collection/migrations/__init__.py | 0 memories/collection/models.py | 83 ++ memories/collection/play.png | Bin 0 -> 36881 bytes memories/collection/poc.py | 177 +++ memories/collection/templates/base.html | 133 ++ memories/collection/templates/detail.html | 59 + memories/collection/templates/list.html | 36 + memories/collection/tests.py | 3 + memories/collection/video.py | 15 + memories/collection/views.py | 173 +++ memories/core/__init__.py | 0 memories/core/db.py | 81 ++ memories/manage.py | 21 + memories/memories/__init__.py | 0 memories/memories/asgi.py | 16 + memories/memories/settings.py | 118 ++ memories/memories/urls.py | 39 + memories/memories/wsgi.py | 16 + memories/server/__init__.py | 4 + memories/server/app.py | 56 + photo.png | Bin 0 -> 592 bytes poetry.lock | 1083 +++++++++++++++++ pyproject.toml | 30 + setup.cfg | 16 + video.png | Bin 0 -> 583 bytes 45 files changed, 2468 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 memories/__init__.py create mode 100644 memories/collection/__init__.py create mode 100644 memories/collection/admin.py create mode 100644 memories/collection/apps.py create mode 100644 memories/collection/migrations/0001_initial.py create mode 100644 memories/collection/migrations/0002_auto_20200510_1842.py create mode 100644 memories/collection/migrations/0003_picture_mimetype.py create mode 100644 memories/collection/migrations/0004_picture_kind.py create mode 100644 memories/collection/migrations/0005_picture_raw.py create mode 100644 memories/collection/migrations/0006_auto_20200511_1111.py create mode 100644 memories/collection/migrations/0007_auto_20200511_1114.py create mode 100644 memories/collection/migrations/0008_auto_20200511_1855.py create mode 100644 memories/collection/migrations/0009_auto_20200531_1926.py create mode 100644 memories/collection/migrations/0010_auto_20200531_1927.py create mode 100644 memories/collection/migrations/0011_auto_20200531_1928.py create mode 100644 memories/collection/migrations/__init__.py create mode 100644 memories/collection/models.py create mode 100644 memories/collection/play.png create mode 100644 memories/collection/poc.py create mode 100644 memories/collection/templates/base.html create mode 100644 memories/collection/templates/detail.html create mode 100644 memories/collection/templates/list.html create mode 100644 memories/collection/tests.py create mode 100644 memories/collection/video.py create mode 100644 memories/collection/views.py create mode 100644 memories/core/__init__.py create mode 100644 memories/core/db.py create mode 100755 memories/manage.py create mode 100644 memories/memories/__init__.py create mode 100644 memories/memories/asgi.py create mode 100644 memories/memories/settings.py create mode 100644 memories/memories/urls.py create mode 100644 memories/memories/wsgi.py create mode 100644 memories/server/__init__.py create mode 100644 memories/server/app.py create mode 100644 photo.png create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 video.png 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 0000000000000000000000000000000000000000..0a17dbdecb247d2cc16c45dc7adc2397f417cd02 GIT binary patch literal 36881 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 yv|i_b?bLw)0001hp#7;ADTssz00000001 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

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

Details

+
    +
  • Filename: {{ picture.filename }}
  • +
  • File path: {{ picture.file_path }}
  • +
  • Type: {{ picture.kind }}
  • +
  • Mimetype: {{ picture.mimetype }}
  • +
  • RAW File: {{ picture.raw }}
  • +
  • Creation date: {{ picture.creation_date }}
  • +
  • SHA1 Checksum: {{ picture.checksum }}
  • +
+
+ +
+

Stat (parsed)

+
    + {% for key, value in picture.get_stat.as_dict.items %} +
  • {{ key }}: {{ value }}
  • + {% endfor %} +
+
+

Stat (raw)

+
    + {% for key, value in picture.get_stat.stat_result.items %} +
  • {{ key }}: {{ value }}
  • + {% endfor %} +
+
+ +
+

EXIF

+
    + {% for key, value in exif.items %} +
  • {{ key }}: {{ value }}
  • + {% endfor %} +
+
+ +{% 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 0000000000000000000000000000000000000000..7bf913212de15a5154c27311f69bb8efa7df7310 GIT binary patch literal 592 zcmeAS@N?(olHy`uVBq!ia0vp^bwC`-!3HGzJpyVO7?{L8T^vIy=DeMKu!Gt@jYb>) zQEY;&qII{o&99r_aPvZ&vGA>10cKkso@v=;Hib9W!g%3Y-c)Dlb3Pf_&gjx-w?AA{ zFuR21tZ?dODZlskH7>{mnzHGYJ=_<#Y zcMRuT%s%(aMS9=CHxKu$yIy^FrgiyY^BIobMXAPZVpDj3nq-UI wstPc3|F85_n_oyrTJtcrh`}0L=z>4l-v|{oPT^z!157Clp00i_>zopr0F>JjZvX%Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..62ceb4b54ce18ea0130a7bd6f9a11dabd779fe05 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0vp^bwC`-!3HGzJpyVO7?}7xT^vIy=DeMKI`5E!$kAv2 z>#br#JVY_iT?C;bTEk?(rwoGD({ zv$5x*pS1gKR{4wa><5|)dcwLx8*N#BXKcN6ZG|20wal%jE_TQ@g-7WYx?0R<+rMS! zCx2HjTbA5)-=q^K&11`5m*>`FE#FiYZt{BFp4*of{1rXZb@8vjmo5L;=d;~@nOHgD zCVL~#?RQE0Dmi%gpI-K7K5YK*m2<+6wmG#@7Z&SxKQ!O?T|wgCvNf_yhuJ^8%S`y; zVD*Q8zs`pb=gWh)%Nu3v-um&}zPhXL_f+5gpuWP+X5!-f+V?XTyzRT=8O?umIe*(j z{|`3zw!izNeed)K^&h|A2QGh6{qLI2g|f}|Q-80l6Z-N|>&3ea>t7Y3CFNUWYxi*N zvcA=47sq;6HrHK#ebc+XZHxQE9n0NsUp&5g!S2OxUpx-IaQ9*veYI`gFYr)Y>q!|( i1R2(7V8$9QLHov}Tii}2^P+$Wgu&C*&t;ucLK6U^3>wM+ literal 0 HcmV?d00001