From f36df81dccfaf0a6d07963b7d4a97d9388dca9aa Mon Sep 17 00:00:00 2001 From: Praful932 Date: Mon, 17 Oct 2022 19:40:42 +0530 Subject: [PATCH] Release 0.1.0 (#270) * Add poetry and update deps * lint code, setup github actions * Add default auto field key * Remove unnecessary file * lint code and remove deps file * Add gunicorn to env * Add url to path * remove django heroku * Add whitenoise * Update env vars * Update csrf trusted origins * add https * enable dev via http and prod via https auth * Add collectstatic * Add new setup instructions --- .github/workflows/tests.yml | 24 +- .gitignore | 3 + .pre-commit-config.yaml | 12 + BookRecSystem/asgi.py | 2 +- BookRecSystem/settings.py | 168 ++-- BookRecSystem/urls.py | 12 +- BookRecSystem/wsgi.py | 2 +- CONTRIBUTING.md | 37 +- Procfile | 1 - README.md | 52 +- mainapp/admin.py | 1 + mainapp/apps.py | 2 +- mainapp/helpers.py | 124 ++- mainapp/migrations/0001_initial.py | 25 +- mainapp/migrations/0002_saveforlater.py | 24 +- mainapp/models.py | 14 +- mainapp/tests.py | 362 ++++---- mainapp/urls.py | 28 +- mainapp/views.py | 125 ++- mainapp/views_ajax.py | 121 +-- manage.py | 4 +- poetry.lock | 1095 +++++++++++++++++++++++ pyproject.toml | 25 + requirements.txt | 41 - runtime.txt | 1 - 25 files changed, 1778 insertions(+), 527 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 Procfile create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 runtime.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 65a1c286..0df9dfd4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions name: Tests -# Controls when the action will run. +# Controls when the action will run. on: # Triggers the workflow on all branches push: @@ -19,26 +19,30 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8'] - + python-version: ['3.8.6'] + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - - - - name: Set up Python ๐Ÿ ${{ matrix.python-version }} + + + - name: Set up Python ๐Ÿ ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - + + - name: Install dependencies - run: pip install -r requirements.txt + run: | + curl -sSL https://install.python-poetry.org | python3 - + poetry export -f requirements.txt --output requirements.txt --without-hashes + pip install -r requirements.txt - name: Run Lint and Unit Tests โš’ env: KITABE_SECRET_KEY: "RANDOM_KEY" run: | - flake8 + pre-commit run --all-files + python manage.py collectstatic --no-input python manage.py test diff --git a/.gitignore b/.gitignore index 24d89cee..946a9123 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ bookenv/ .vscode +# colllect static output +staticfiles/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7f2a69e1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +exclude: 'wip' +repos: +- repo: https://github.com/ambv/black + rev: 22.3.0 + hooks: + - id: black + name: black-py +- repo: https://github.com/asottile/pyupgrade + rev: v2.32.0 + hooks: + - id: pyupgrade + name: pyupgrade-py \ No newline at end of file diff --git a/BookRecSystem/asgi.py b/BookRecSystem/asgi.py index a11baf98..1285b4b2 100644 --- a/BookRecSystem/asgi.py +++ b/BookRecSystem/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'BookRecSystem.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BookRecSystem.settings") application = get_asgi_application() diff --git a/BookRecSystem/settings.py b/BookRecSystem/settings.py index c992d5e8..c0f0145a 100644 --- a/BookRecSystem/settings.py +++ b/BookRecSystem/settings.py @@ -22,75 +22,87 @@ # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get('KITABE_SECRET_KEY') +SECRET_KEY = os.environ.get("SECRET_KEY", "RANDOM_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = str(os.environ.get("DEBUG", True)) == "True" -ALLOWED_HOSTS = ['kitabe-app.herokuapp.com'] +ALLOWED_HOSTS = [ + "kitabe-app.herokuapp.com", + "kitabe.up.railway.app", + "127.0.0.1", + "localhost", +] + +CSRF_TRUSTED_ORIGINS = [ + "https://kitabe-app.herokuapp.com", + "https://kitabe.up.railway.app", +] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - - 'allauth', - 'allauth.account', - 'allauth.socialaccount', - 'allauth.socialaccount.providers.google', - 'mainapp', - 'storages', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + # disable django static file handling during development as well + "whitenoise.runserver_nostatic", + "django.contrib.staticfiles", + "django.contrib.sites", + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google", + "mainapp", + "storages", ] 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', + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "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 = 'BookRecSystem.urls' +ROOT_URLCONF = "BookRecSystem.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, 'templates'), + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "templates"), ], - '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', + "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", # allauth - 'django.template.context_processors.request' + "django.template.context_processors.request", ], }, }, ] -WSGI_APPLICATION = 'BookRecSystem.wsgi.application' +WSGI_APPLICATION = "BookRecSystem.wsgi.application" # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -100,16 +112,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -117,9 +129,9 @@ # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'Asia/Calcutta' +TIME_ZONE = "Asia/Calcutta" USE_I18N = True @@ -131,45 +143,51 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') -STATIC_URL = '/static/' -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, 'static') -] +# url/placeholder path at which static files are served +STATIC_URL = "/static/" +# other folders to look for static files +STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +# Deployment +# Directory where collectstatic will collect the static files for deployment +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") + AUTHENICATION_BACKENDS = [ - 'django.contrib.auth.backends.ModelBackend', + "django.contrib.auth.backends.ModelBackend", # allauth - 'allauth.account.auth_backends.AuthenicationBackend' + "allauth.account.auth_backends.AuthenicationBackend", ] SITE_ID = 1 SOCIALACCOUNT_PROVIDERS = { - 'google': { - 'APP': { - 'client_id': os.environ.get('KITABE_AUTH_ID'), - 'secret': os.environ.get('KITABE_AUTH_SECRET'), - 'key': '' + "google": { + "APP": { + "client_id": os.environ.get("KITABE_AUTH_ID"), + "secret": os.environ.get("KITABE_AUTH_SECRET"), + "key": "", } } } +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # media files -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +MEDIA_URL = "/media/" -LOGIN_REDIRECT_URL = 'index' -LOGOUT_REDIRECT_URL = 'index' +LOGIN_REDIRECT_URL = "index" +LOGOUT_REDIRECT_URL = "index" # simple mail transfer protocal # EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" -EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST = "smtp.gmail.com" EMAIL_PORT = 587 EMAIL_USE_TLS = True -EMAIL_HOST_USER = os.environ.get('KITABE_EMAIL') -EMAIL_HOST_PASSWORD = os.environ.get('KITABE_PASS') +EMAIL_HOST_USER = os.environ.get("KITABE_EMAIL") +EMAIL_HOST_PASSWORD = os.environ.get("KITABE_PASS") # Alluth Settings ACCOUNT_AUTHENTICATION_METHOD = "username" @@ -177,16 +195,14 @@ ACCOUNT_EMAIL_VERIFICATION = "none" ACCOUNT_LOGOUT_ON_GET = True +if not DEBUG: + # access via https in production + ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" + MESSAGE_TAGS = { - messages.DEBUG: 'alert-info', - messages.INFO: 'alert-info', - messages.SUCCESS: 'alert-success', - messages.WARNING: 'alert-warning', - messages.ERROR: 'alert-danger', + messages.DEBUG: "alert-info", + messages.INFO: "alert-info", + messages.SUCCESS: "alert-success", + messages.WARNING: "alert-warning", + messages.ERROR: "alert-danger", } - -# Django Heroku Settings -if 'CHECK_HEROKU' in os.environ: - DEBUG = eval(os.environ.get('KITABE_DEBUG')) - import django_heroku - django_heroku.settings(locals()) diff --git a/BookRecSystem/urls.py b/BookRecSystem/urls.py index f7b2315f..eafb4241 100644 --- a/BookRecSystem/urls.py +++ b/BookRecSystem/urls.py @@ -19,10 +19,10 @@ from django.conf.urls.static import static urlpatterns = [ - path('', include('mainapp.urls')), - path('admin/', admin.site.urls), - path('accounts/', include('allauth.urls')), -]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path("", include("mainapp.urls")), + path("admin/", admin.site.urls), + path("accounts/", include("allauth.urls")), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # error handler -handler404 = 'mainapp.views.handler404' -handler500 = 'mainapp.views.handler500' +handler404 = "mainapp.views.handler404" +handler500 = "mainapp.views.handler500" diff --git a/BookRecSystem/wsgi.py b/BookRecSystem/wsgi.py index 69791666..40e98491 100644 --- a/BookRecSystem/wsgi.py +++ b/BookRecSystem/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'BookRecSystem.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BookRecSystem.settings") application = get_wsgi_application() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aac8f141..1691d330 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,43 +10,30 @@ For Contributions we strictly follow [Github Flow](https://guides.github.com/int ### Setting Up the Project -- The Project works seamlessly on Python version `3.8.6` +1. The Project works seamlessly on Python version `3.8.6` -- [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) the Repository +2. (Optional) [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) the Repository -- Clone Your Forked copy - - `git clone https://github.com/[YOUR-USERNAME]/Kitabe.git` +3. Clone Your Forked copy/the original repo - `git clone https://github.com/[YOUR-USERNAME]/Kitabe.git` -- Navigate to the directory of project - - `cd Kitabe/` +4. Navigate to the directory of project - `cd Kitabe/` -- Create a new branch - - `git checkout -b [branch_name]` -- If you don't have virtualenv already installed - - `pip install virtualenv` +5. (Optional) If you're intending to raise an MR else you can skip this step, Create a new branch - `git checkout -b [branch_name]` -- Create a new environment - - `virtualenv bookenv` +6. Install requirements from [poetry](https://python-poetry.org/docs/#installation) - `poetry install` + - If you prefer the vanilla route `poetry export -f requirements.txt --output requirements.txt --without-hashes` skip to step (8) -- Activate the environment - - - For Linux/Unix OS : `source bookenv/bin/activate` - - For Windows OS: `bookenv\Scripts\activate` -- Install requirements - - `pip install -r requirements.txt` +7. Activate the environment - `poetry shell` -- Open `BookRecSystem/settings.py` +8. Open `BookRecSystem/settings.py` -- Set `SECRET_KEY = "RANDOM_KEY"` +9. Make Migrations - `python manage.py migrate` -- Set `ALLOWED_HOSTS = ['kitabe-app.herokuapp.com', '127.0.0.1', 'localhost']` - -- Make Migrations - - `python manage.py migrate` - -- `python manage.py runserver` - You're good to Go!! +10. `python manage.py runserver` - You're good to Go!! +๐Ÿ“ Raise an issue/start a discussion if you face difficulties while setting up the repo, we'll try to resolve it asap #### Optional - [Setting up Google Auth](https://django-allauth.readthedocs.io/en/latest/installation.html) diff --git a/Procfile b/Procfile deleted file mode 100644 index ff304151..00000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: gunicorn BookRecSystem.wsgi \ No newline at end of file diff --git a/README.md b/README.md index ffa14dc1..c23df869 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Simply Rate โญ some books and get immediate recommendations tailored for you ๐Ÿคฉ.
See [Demo](#user-content-demo-) ๐ŸŽฅ -[![Website shields.io](https://img.shields.io/website-up-down-green-red/http/shields.io.svg)](https://kitabe-app.herokuapp.com/) -[![Build Status](https://travis-ci.com/Praful932/Kitabe.svg?token=XKcoN48yFyATXWUZ6d8j&branch=master)](https://travis-ci.com/Praful932/Kitabe) +[![Website shields.io](https://img.shields.io/website-up-down-green-red/http/shields.io.svg)](https://kitabe.up.railway.app/) +[![Build Status](https://travis-ci.com/Praful932/Kitabe.svg?token=XKcoN48yFyATXWUZ6d8j&branch=master)](https://travis-ci.com/Praful932/Kitabe) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/Praful932/Kitabe/blob/master/CONTRIBUTING.md) [![GitHub license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FPraful932%2FKitabe&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) @@ -35,23 +35,23 @@ For Contributing ๐Ÿ’œ and setting Up head [here](https://github.com/Praful932/Ki ### Demo ๐ŸŽฅ ![kitabe](https://user-images.githubusercontent.com/45713796/98460071-f6a23980-21c6-11eb-881f-ba0f75896751.gif)
-[Live Application](https://kitabe-app.herokuapp.com/) ๐ŸŒ +[Live Application](https://kitabe.up.railway.app/) ๐ŸŒ ### Objective โœ -Our objective is to build an application for all Book Lovers โ™ฅ like us out there where all you have to +Our objective is to build an application for all Book Lovers โ™ฅ like us out there where all you have to do is rate some of your favorite books and the application will do it's **voodoo magic** ๐Ÿง™โ€โ™‚๏ธ and give you some more books that you may **love๐Ÿ˜ to read**. ### Dataset ๐Ÿงพ The Dataset that we used for this task is the [goodbooks-10k](https://github.com/zygmuntz/goodbooks-10k) dataset. It consists of 10k books with a total of 6 million ratings. That's huge right! ๐Ÿ˜ฎ. There are some more huge datasets such as [Book-Crossings](http://www2.informatik.uni-freiburg.de/~cziegler/BX/) but they are kinda old ๐Ÿ˜ฌ. -**Dataset Structure** +**Dataset Structure** ``` -GoodBooks10k - โ”œโ”€โ”€ books.csv # Contains book info with book-id - โ”œโ”€โ”€ ratings.csv # Maps user-id to book-id and rating +GoodBooks10k + โ”œโ”€โ”€ books.csv # Contains book info with book-id + โ”œโ”€โ”€ ratings.csv # Maps user-id to book-id and rating โ”œโ”€โ”€ book_tags.csv # Contains tag-id associated with book-ids โ”œโ”€โ”€ tags.csv # Contains tag-name associated with tag-id - โ”œโ”€โ”€ to_read.csv # Contains book-ids marked as to-read by user + โ”œโ”€โ”€ to_read.csv # Contains book-ids marked as to-read by user ``` ### PreProcessing ๐Ÿ›  @@ -69,7 +69,7 @@ For Recommendation Problems there are multiple approaches that are possible: We experimented with several methods and chose Embedding Matrix & Term Frequency. -- **Embedding Matrix** - This method is often called [FunkSVD](https://www.coursera.org/lecture/matrix-factorization/deriving-funksvd-lyTpD) which won the Netflix Prize back in 2004. Since it is a gradient based function minimization approach we like to call it as Embedding Matrix. Calling it SVD [confuses](https://www.quora.com/What-is-the-difference-between-SVD-and-matrix-factorization-in-context-of-recommendation-engine/answer/Luis-Argerich) it with the one in Linear Algebra. This Embedding Matrix constructs a vector for each user and each book, such that when the product is applied with additional constraints it gives us the rating. For more elaborate info on FunkSVD refer [this](http://sifter.org/~simon/journal/20061211.html). +- **Embedding Matrix** - This method is often called [FunkSVD](https://www.coursera.org/lecture/matrix-factorization/deriving-funksvd-lyTpD) which won the Netflix Prize back in 2004. Since it is a gradient based function minimization approach we like to call it as Embedding Matrix. Calling it SVD [confuses](https://www.quora.com/What-is-the-difference-between-SVD-and-matrix-factorization-in-context-of-recommendation-engine/answer/Luis-Argerich) it with the one in Linear Algebra. This Embedding Matrix constructs a vector for each user and each book, such that when the product is applied with additional constraints it gives us the rating. For more elaborate info on FunkSVD refer [this](http://sifter.org/~simon/journal/20061211.html). We used the book embedding as a representation of the books to infer underlying patterns. This led to the embedding able to detect books from the same authors and also infer genres such as Fiction, Autobiography and more. - **Term Frequency** - This method is like a helper function to above, it shines where embedding fails. Term Frequency takes into account the tokens in a book title be it the book title itself, the name of authors and also rating. Taking into consideration it finds books which match closely with the tokens in the rated book. @@ -85,37 +85,37 @@ The [Image](https://coggle.it/diagram/X6TOUxlMvSl8FBM4/t/dataset/7083ac4f2de3951 ### Project Structure ๐Ÿ’โ€โ™€๏ธ ``` Kitabe -โ”‚ +โ”‚ โ”œโ”€โ”€โ”€BookRecSystem # Main Project Directory -โ”‚ +โ”‚ โ”œโ”€โ”€โ”€mainapp # Project Main App Directory -โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€migrations # Migrations -โ”‚ -โ”œโ”€โ”€โ”€static +โ”‚ +โ”œโ”€โ”€โ”€static | | # Static Directory โ”‚ โ””โ”€โ”€โ”€mainapp -โ”‚ โ”œโ”€โ”€โ”€css # CSS Files -| | +โ”‚ โ”œโ”€โ”€โ”€css # CSS Files +| | โ”‚ โ”œโ”€โ”€โ”€dataset # Dataset Files -โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€gif # GIF Media -โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€model_files # Model Files -| | | +| | | โ”‚ โ”‚ โ”œโ”€โ”€โ”€surprise # FunkSVD Files -โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€cv # CV Files -โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€png # PNG Media FIles -| +| โ””โ”€โ”€โ”€templates # Root Template DIrectory | โ”œโ”€โ”€โ”€account # Account App Templates - โ”‚ + โ”‚ โ””โ”€โ”€โ”€mainapp # Project Main App Templates - -``` + +``` ### To Do ๐ŸŽฏ - [X] Display Popular Books Among Users diff --git a/mainapp/admin.py b/mainapp/admin.py index d902ac02..bff2d434 100644 --- a/mainapp/admin.py +++ b/mainapp/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin from mainapp.models import UserRating, SaveForLater + # Register your models here. admin.site.register(UserRating) diff --git a/mainapp/apps.py b/mainapp/apps.py index a240b2d1..72cbda26 100644 --- a/mainapp/apps.py +++ b/mainapp/apps.py @@ -2,4 +2,4 @@ class MainappConfig(AppConfig): - name = 'mainapp' + name = "mainapp" diff --git a/mainapp/helpers.py b/mainapp/helpers.py index a5a0661f..1ce8c48b 100644 --- a/mainapp/helpers.py +++ b/mainapp/helpers.py @@ -9,29 +9,43 @@ import BookRecSystem.settings as settings import mainapp.models -book_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/dataset/books.csv') +book_path = os.path.join(settings.STATICFILES_DIRS[0] + "/mainapp/dataset/books.csv") # For Count Vectorizer -cosine_sim_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/tf-idf/cosine_rating_sim.npz') -book_indices_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/tf-idf/indices.pkl') +cosine_sim_path = os.path.join( + settings.STATICFILES_DIRS[0] + "/mainapp/model_files/tf-idf/cosine_rating_sim.npz" +) +book_indices_path = os.path.join( + settings.STATICFILES_DIRS[0] + "/mainapp/model_files/tf-idf/indices.pkl" +) # For Embedding -book_id_map_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/surprise/book_raw_to_inner_id.pickle') -book_raw_map_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/surprise/book_inner_id_to_raw.pickle') -book_embed_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/surprise/book_embedding.npy') -sim_books_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/model_files/surprise/sim_books.pickle') - -with open(book_id_map_path, 'rb') as handle: +book_id_map_path = os.path.join( + settings.STATICFILES_DIRS[0] + + "/mainapp/model_files/surprise/book_raw_to_inner_id.pickle" +) +book_raw_map_path = os.path.join( + settings.STATICFILES_DIRS[0] + + "/mainapp/model_files/surprise/book_inner_id_to_raw.pickle" +) +book_embed_path = os.path.join( + settings.STATICFILES_DIRS[0] + "/mainapp/model_files/surprise/book_embedding.npy" +) +sim_books_path = os.path.join( + settings.STATICFILES_DIRS[0] + "/mainapp/model_files/surprise/sim_books.pickle" +) + +with open(book_id_map_path, "rb") as handle: book_raw_to_inner_id = pickle.load(handle) -with open(book_raw_map_path, 'rb') as handle: +with open(book_raw_map_path, "rb") as handle: book_inner_id_to_raw = pickle.load(handle) book_embedding = np.load(book_embed_path) -with open(sim_books_path, 'rb') as handle: +with open(sim_books_path, "rb") as handle: sim_books_dict = pickle.load(handle) -cols = ['original_title', 'authors', 'average_rating', 'image_url', 'book_id'] +cols = ["original_title", "authors", "average_rating", "image_url", "book_id"] df_book = pd.read_csv(book_path) total_books = df_book.shape[0] @@ -78,7 +92,7 @@ def is_bookid_invalid(bookid): """ if not bookid or not bookid.isdigit(): return True - elif sum(df_book['book_id'] == int(bookid)) == 0: + elif sum(df_book["book_id"] == int(bookid)) == 0: # If bookid does not exist return True return False @@ -98,7 +112,7 @@ def get_book_title(bookid): Title of the book corresponding the given book id. """ - return df_book[df_book['book_id'] == bookid]['original_title'].values[0] + return df_book[df_book["book_id"] == bookid]["original_title"].values[0] def get_book_ids(index_list): @@ -154,7 +168,7 @@ def get_raw_id(book_id): Corresponding raw_id of the book_id. """ - raw_id = df_book[df_book.book_id == book_id]['r_index'].values[0] + raw_id = df_book[df_book.book_id == book_id]["r_index"].values[0] return raw_id @@ -172,7 +186,7 @@ def get_bookid(raw_id_list): List of bookids corresponding to raw ids. """ - bookid_list = list(df_book[df_book.r_index.isin(raw_id_list)]['book_id'].values) + bookid_list = list(df_book[df_book.r_index.isin(raw_id_list)]["book_id"].values) return bookid_list @@ -198,13 +212,13 @@ def genre_wise(genre, percentile=0.85): qualified = df_book[df_book.genre.str.contains(genre.lower())] # Imdb Formula - v = qualified['ratings_count'] - m = qualified['ratings_count'].quantile(percentile) - R = qualified['average_rating'] - C = qualified['average_rating'].mean() - W = (R*v + C*m) / (v + m) + v = qualified["ratings_count"] + m = qualified["ratings_count"].quantile(percentile) + R = qualified["average_rating"] + C = qualified["average_rating"].mean() + W = (R * v + C * m) / (v + m) qualified = qualified.assign(weighted_rating=W) - qualified.sort_values('weighted_rating', ascending=False, inplace=True) + qualified.sort_values("weighted_rating", ascending=False, inplace=True) return qualified[cols].head(min_genre_book_count).sample(n_books) @@ -224,9 +238,9 @@ def tfidf_recommendations(bookid): """ indices = pd.read_pickle(book_indices_path) - cosine_sim = np.load(cosine_sim_path)['array1'] + cosine_sim = np.load(cosine_sim_path)["array1"] book_title = get_book_title(bookid) - book_title = book_title.replace(' ', '').lower() + book_title = book_title.replace(" ", "").lower() idx = indices[book_title] # Get this books similarity with all other books, enum to keep track of book index @@ -268,7 +282,9 @@ def embedding_recommendations(sorted_user_ratings): for book in best_user_books: raw_id = get_raw_id(book) - top_sim_books = [book for book, similiarity in sim_books_dict[raw_id][:top_similiar]] + top_sim_books = [ + book for book, similiarity in sim_books_dict[raw_id][:top_similiar] + ] similar_bookid_list.extend(top_sim_books) similar_bookid_list = get_bookid(similar_bookid_list) @@ -290,7 +306,9 @@ def get_book_dict(bookid_list): Dictionary of book details based on provided list of bookids. """ - rec_books_dict = df_book[df_book['book_id'].isin(bookid_list)][cols].to_dict('records') + rec_books_dict = df_book[df_book["book_id"].isin(bookid_list)][cols].to_dict( + "records" + ) return rec_books_dict @@ -330,16 +348,20 @@ def combine_ids(tfidf_bookids, embedding_bookids, already_rated, recommendations # If not enough recommendations if len(best_bookids) < recommendations: - two_n = (recommendations - len(best_bookids)) + two_n = recommendations - len(best_bookids) # Divide remaining recommendations into two parts - n1, n2 = math.ceil(two_n/2), math.floor(two_n/2) + n1, n2 = math.ceil(two_n / 2), math.floor(two_n / 2) # n1 number of books from remaining tf_idf books - best_bookids_tfidf = tfidf_bookids[3: (3*2)+n1] - best_bookids_tfidf = list(set(best_bookids_tfidf).difference(set(best_bookids)))[:n1] + best_bookids_tfidf = tfidf_bookids[3 : (3 * 2) + n1] + best_bookids_tfidf = list( + set(best_bookids_tfidf).difference(set(best_bookids)) + )[:n1] # n2 number of books from list of top rated books of the most common genre among the books yet recommended - genre_recomm_bookids = most_common_genre_recommendations(best_bookids + already_rated + best_bookids_tfidf, n2) + genre_recomm_bookids = most_common_genre_recommendations( + best_bookids + already_rated + best_bookids_tfidf, n2 + ) # number of recommendations = len(best_bookids) + n1 + n2 = len(best_bookids) + two_n best_bookids = best_bookids + best_bookids_tfidf + genre_recomm_bookids @@ -347,7 +369,7 @@ def combine_ids(tfidf_bookids, embedding_bookids, already_rated, recommendations def most_common_genre_recommendations(books, n): - '''Returns n top rated of the most_common_genre among all lists taken as input + """Returns n top rated of the most_common_genre among all lists taken as input Parameters ---------- @@ -359,17 +381,19 @@ def most_common_genre_recommendations(books, n): ------- genre_recommendations : list List containing n number of books of the most common genre among all the input books. - ''' + """ # Accumulation of all the genres listed from books genre_frequency = [] for book in books: - genre_frequency.append(df_book[df_book['book_id'] == book]['genre'].values[0].split(", ")[0]) + genre_frequency.append( + df_book[df_book["book_id"] == book]["genre"].values[0].split(", ")[0] + ) most_common_genre = sorted(Counter(genre_frequency).most_common())[0][0] # Recommendations list, listing 2n bookids - genre_recommendations = genre_wise(most_common_genre).book_id.to_list()[:2*n] + genre_recommendations = genre_wise(most_common_genre).book_id.to_list()[: 2 * n] # Removing common bookids from `books` and Slicing out the first n bookids genre_recommendations = list(set(genre_recommendations).difference(books))[:n] @@ -391,14 +415,15 @@ def get_top_n(top_n=400): """ df_books_copy = df_book.copy() - v = df_books_copy['ratings_count'] - m = df_books_copy['ratings_count'].quantile(0.95) - R = df_books_copy['average_rating'] - C = df_books_copy['average_rating'].mean() - W = (R*v + C*m) / (v + m) + v = df_books_copy["ratings_count"] + m = df_books_copy["ratings_count"].quantile(0.95) + R = df_books_copy["average_rating"] + C = df_books_copy["average_rating"].mean() + W = (R * v + C * m) / (v + m) df_books_copy = df_books_copy.assign(weighted_rating=W) - qualified = df_books_copy.sort_values( - 'weighted_rating', ascending=False)[cols].head(top_n) + qualified = df_books_copy.sort_values("weighted_rating", ascending=False)[ + cols + ].head(top_n) return qualified.sample(top_n) @@ -419,20 +444,25 @@ def popular_among_users(N=15): Dictionary of book details. """ - all_ratings = list(mainapp.models.UserRating.objects.all().order_by('-bookrating')) + all_ratings = list(mainapp.models.UserRating.objects.all().order_by("-bookrating")) random.shuffle(all_ratings) - best_user_ratings = sorted(all_ratings, key=operator.attrgetter('bookrating'), reverse=True) + best_user_ratings = sorted( + all_ratings, key=operator.attrgetter("bookrating"), reverse=True + ) filtered_books = set() for i, rating in enumerate(best_user_ratings): if rating.bookrating >= 4: - filtered_books.add((rating.bookid)) + filtered_books.add(rating.bookid) elif rating.bookrating < 4 or len(filtered_books) == N: break remaining_books_nos = N - len(filtered_books) if remaining_books_nos >= 0: - rem_books = get_top_n(2*N)['book_id'].tolist() - filtered_books = list(filtered_books) + list((set(rem_books) - filtered_books))[:remaining_books_nos] + rem_books = get_top_n(2 * N)["book_id"].tolist() + filtered_books = ( + list(filtered_books) + + list(set(rem_books) - filtered_books)[:remaining_books_nos] + ) return get_book_dict(filtered_books) diff --git a/mainapp/migrations/0001_initial.py b/mainapp/migrations/0001_initial.py index e3db0ec9..3885006a 100644 --- a/mainapp/migrations/0001_initial.py +++ b/mainapp/migrations/0001_initial.py @@ -15,12 +15,27 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='UserRating', + name="UserRating", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('bookid', models.IntegerField()), - ('bookrating', models.IntegerField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_rating', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("bookid", models.IntegerField()), + ("bookrating", models.IntegerField()), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="user_rating", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/mainapp/migrations/0002_saveforlater.py b/mainapp/migrations/0002_saveforlater.py index c06c2c9c..f00f59fe 100644 --- a/mainapp/migrations/0002_saveforlater.py +++ b/mainapp/migrations/0002_saveforlater.py @@ -9,16 +9,30 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('mainapp', '0001_initial'), + ("mainapp", "0001_initial"), ] operations = [ migrations.CreateModel( - name='SaveForLater', + name="SaveForLater", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('bookid', models.IntegerField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("bookid", models.IntegerField()), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/mainapp/models.py b/mainapp/models.py index bbf644fb..93c99e2e 100644 --- a/mainapp/models.py +++ b/mainapp/models.py @@ -6,17 +6,23 @@ import pandas as pd import os -book_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/dataset/books.csv') +book_path = os.path.join(settings.STATICFILES_DIRS[0] + "/mainapp/dataset/books.csv") df_book = pd.read_csv(book_path) class UserRating(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_rating') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_rating") bookid = models.IntegerField() bookrating = models.IntegerField() def __str__(self): - return self.user.username.capitalize() + '- ' + get_book_title(self.bookid) + ' - ' + str(self.bookrating) + return ( + self.user.username.capitalize() + + "- " + + get_book_title(self.bookid) + + " - " + + str(self.bookrating) + ) class SaveForLater(models.Model): @@ -24,4 +30,4 @@ class SaveForLater(models.Model): bookid = models.IntegerField() def __str__(self): - return self.user.username.capitalize() + '- ' + get_book_title(self.bookid) + return self.user.username.capitalize() + "- " + get_book_title(self.bookid) diff --git a/mainapp/tests.py b/mainapp/tests.py index 2f369311..efcd92c5 100644 --- a/mainapp/tests.py +++ b/mainapp/tests.py @@ -12,221 +12,259 @@ class HomeTests(TestCase): - ''' - Index View Test Case - ''' + """ + Index View Test Case + """ + def setUp(self): - self.url = reverse('index') + self.url = reverse("index") def test_home_view_status_code(self): - ''' - Index View Status Code - ''' + """ + Index View Status Code + """ response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) def test_home_url_resolves_home_view(self): - ''' - Root URL Status Code - ''' - view = resolve('/') - self.assertEquals(view.func, views.index) + """ + Root URL Status Code + """ + view = resolve("/") + self.assertEqual(view.func, views.index) class GenreTestCase(TestCase): - ''' - Genre View Test Case - ''' + """ + Genre View Test Case + """ def setUp(self): - self.genres = ['art', 'biography', 'business', 'Christian', 'Comics', 'Contemporary', 'Cookbooks', 'Crime', - 'Fantasy', 'Fiction', 'History', 'Horror', 'Manga', 'Memoir', 'Mystery', 'Nonfiction', - 'Paranormal', 'Philosophy', 'Poetry', 'Psychology', 'Religion', 'Science', 'Suspense', - 'Spirituality', 'Sports', 'Thriller', 'Travel', 'Classics'] + self.genres = [ + "art", + "biography", + "business", + "Christian", + "Comics", + "Contemporary", + "Cookbooks", + "Crime", + "Fantasy", + "Fiction", + "History", + "Horror", + "Manga", + "Memoir", + "Mystery", + "Nonfiction", + "Paranormal", + "Philosophy", + "Poetry", + "Psychology", + "Religion", + "Science", + "Suspense", + "Spirituality", + "Sports", + "Thriller", + "Travel", + "Classics", + ] def test_genre_status_code(self): - ''' - All Genre Tests - ''' + """ + All Genre Tests + """ for genre in self.genres: - url = reverse('genre_books', kwargs={'genre': genre}) + url = reverse("genre_books", kwargs={"genre": genre}) response = self.client.get(url) - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) class ExploreTestCase(TestCase): - ''' - Explore View Test Case - ''' + """ + Explore View Test Case + """ def setUp(self): - self.url = reverse('explore_books') + self.url = reverse("explore_books") def test_explore_status_code(self): - ''' - Explore View Status Code - ''' + """ + Explore View Status Code + """ response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) class SearchAjaxTestCase(TestCase): - ''' - AJAX Search View Test Case - ''' + """ + AJAX Search View Test Case + """ def setUp(self): - self.url = reverse('search_ajax') + self.url = reverse("search_ajax") def test_search_ajax_view_status_code(self): - ''' - AJAX Test request with valid and invalid Book Name - ''' + """ + AJAX Test request with valid and invalid Book Name + """ response = self.client.post( - self.url, - data={'bookName': 'Text'}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + self.url, data={"bookName": "Text"}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" ) - self.assertEquals(response.status_code, 200) - self.assertIn('true', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("true", response.content.decode("utf-8")) response = self.client.post( - self.url, - data={}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + self.url, data={}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" ) - self.assertEquals(response.status_code, 200) - self.assertIn('false', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("false", response.content.decode("utf-8")) class BookSummaryTestCase(TestCase): - ''' - Book Summary View Test Case - ''' + """ + Book Summary View Test Case + """ def setUp(self): - self.url = reverse('summary_ajax') - self.inputs = ['random_text', 1e10, ""] + self.url = reverse("summary_ajax") + self.inputs = ["random_text", 1e10, ""] def test_book_summary_view_status_code(self): - ''' - AJAX Test request with valid and invalid Book Id - ''' + """ + AJAX Test request with valid and invalid Book Id + """ for ele in self.inputs: response = self.client.post( - self.url, - data={'bookid': ele}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + self.url, data={"bookid": ele}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" ) - self.assertEquals(response.status_code, 200) - self.assertIn('false', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("false", response.content.decode("utf-8")) class BookDetailsTestCase(TestCase): - ''' - AJAX Book Details View Test Case - ''' + """ + AJAX Book Details View Test Case + """ def setUp(self): - self.url = reverse('book_details') - self.inputs = ['random_text', 1e10, ""] + self.url = reverse("book_details") + self.inputs = ["random_text", 1e10, ""] def test_book_details_view_status_code(self): - ''' - AJAX Test request with valid and invalid Book Id - ''' + """ + AJAX Test request with valid and invalid Book Id + """ for ele in self.inputs: response = self.client.post( - self.url, - data={'bookid': ele}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + self.url, data={"bookid": ele}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" ) - self.assertEquals(response.status_code, 200) - self.assertIn('false', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("false", response.content.decode("utf-8")) class UserRateBookTestCase(TestCase): - ''' - AJAX User Rate Book Test Case - ''' + """ + AJAX User Rate Book Test Case + """ def setUp(self): self.client = Client() - self.user = User.objects.create_user(username='test_user', email='qwe@gmail.com') - self.user.set_password('foopassword') + self.user = User.objects.create_user( + username="test_user", email="qwe@gmail.com" + ) + self.user.set_password("foopassword") self.user.save() - self.url = reverse('user_rate_book') - self.inputs = [('random_text', 7), (1e10, 5), ("", 1.0)] + self.url = reverse("user_rate_book") + self.inputs = [("random_text", 7), (1e10, 5), ("", 1.0)] def test_user_rated_book_invalid(self): - ''' - Test User Rates Book with invalid bookid - with/out login - ''' + """ + Test User Rates Book with invalid bookid + with/out login + """ for bookid, bookrating in self.inputs: response = self.client.post( self.url, - data={'bookid': bookid, - 'bookrating': bookrating}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + data={"bookid": bookid, "bookrating": bookrating}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 302) + self.assertEqual(response.status_code, 302) - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") for bookid, bookrating in self.inputs: response = self.client.post( self.url, - data={'bookid': bookid, - 'bookrating': bookrating}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + data={"bookid": bookid, "bookrating": bookrating}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 200) - self.assertIn('false', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("false", response.content.decode("utf-8")) self.client.logout() def test_user_rated_book_valid(self): - ''' - Test User Rates Book with valid bookid - with/out login - ''' + """ + Test User Rates Book with valid bookid + with/out login + """ valid_book_id = 2 valid_bookrating = 4 response = self.client.post( self.url, - data={'bookid': valid_book_id, - 'bookrating': valid_bookrating}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + data={"bookid": valid_book_id, "bookrating": valid_bookrating}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 302) + self.assertEqual(response.status_code, 302) - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") response = self.client.post( self.url, - data={'bookid': valid_book_id, - 'bookrating': valid_bookrating}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + data={"bookid": valid_book_id, "bookrating": valid_bookrating}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 200) - self.assertIn('true', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("true", response.content.decode("utf-8")) rating = UserRating.objects.get(bookid=valid_book_id) - self.assertEquals(rating.bookrating, valid_bookrating) - self.assertEquals(rating.user, self.user) + self.assertEqual(rating.bookrating, valid_bookrating) + self.assertEqual(rating.user, self.user) self.client.logout() class MostCommonGenreTestCase(TestCase): - ''' + """ Test most common genre books when recommendations are short - ''' + """ + def setUp(self): self.SEED = 42 - self.df_book = pd.read_csv(os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/dataset/books.csv')) + self.df_book = pd.read_csv( + os.path.join(settings.STATICFILES_DIRS[0] + "/mainapp/dataset/books.csv") + ) def test_genre_driver(self): - test_cases = [(10, 5, 1), (10, 5, 2), (10, 5, 3), (10, 5, 4), (10, 5, 5), (10, 6, 1), (10, 6, 1), (10, 6, 2), (10, 6, 3), (10, 6, 4), (10, 7, 1), (10, 7, 2), (10, 7, 3), (10, 8, 1), (10, 8, 2), (10, 9, 1), (10, 10, 0)] + test_cases = [ + (10, 5, 1), + (10, 5, 2), + (10, 5, 3), + (10, 5, 4), + (10, 5, 5), + (10, 6, 1), + (10, 6, 1), + (10, 6, 2), + (10, 6, 3), + (10, 6, 4), + (10, 7, 1), + (10, 7, 2), + (10, 7, 3), + (10, 8, 1), + (10, 8, 2), + (10, 9, 1), + (10, 10, 0), + ] for tnum, already_slice, bestbookids_slice in test_cases: all_books, n2 = self.template(tnum, already_slice, bestbookids_slice) genre_recomm_bookids = most_common_genre_recommendations(all_books, n2) @@ -236,77 +274,87 @@ def test_genre_driver(self): def template(self, tnum, already_slice, bestbookids_slice): """ - Generates `tnum` random bookids, divides the bookids into 3 input variables of the function `most_common_genre_recommendations` - The variables store, - already_rated - books rated by user - best_bookids - books recommended consisting of top 6 bookids from embedding_bookids and top 3 from tfidf recommendations - best_bookids_tfidf - `n1` books taken from remaining tfidf recommendations + Generates `tnum` random bookids, divides the bookids into 3 input variables of the function `most_common_genre_recommendations` + The variables store, + already_rated - books rated by user + best_bookids - books recommended consisting of top 6 bookids from embedding_bookids and top 3 from tfidf recommendations + best_bookids_tfidf - `n1` books taken from remaining tfidf recommendations """ random.seed(self.SEED) books = random.sample(self.df_book.book_id.to_list(), tnum) already_rated = books[:already_slice] - best_bookids = books[already_slice:already_slice+bestbookids_slice] - n1 = math.ceil((9-len(best_bookids))/2) - n2 = math.floor((9-len(best_bookids))/2) - best_bookids_tfidf = books[tnum-n1+1:] + best_bookids = books[already_slice : already_slice + bestbookids_slice] + n1 = math.ceil((9 - len(best_bookids)) / 2) + n2 = math.floor((9 - len(best_bookids)) / 2) + best_bookids_tfidf = books[tnum - n1 + 1 :] all_books = best_bookids + already_rated + best_bookids_tfidf return all_books, n2 class RatedBooksTestCase(TestCase): """Already Read Books View Test Case""" + def setUp(self): self.client = Client() - self.user = User.objects.create_user(username='test_user', email='qwe@gmail.com') - self.user.set_password('foopassword') + self.user = User.objects.create_user( + username="test_user", email="qwe@gmail.com" + ) + self.user.set_password("foopassword") self.user.save() - self.url = reverse('read_books') + self.url = reverse("read_books") def test_redirect_if_not_rated(self): """Test If The read_books Redirects Accordingly When No Book Is Rated """ - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") response = self.client.get(self.url) - self.assertRedirects(response, reverse('index')) + self.assertRedirects(response, reverse("index")) self.client.logout() def test_read_book_status_code(self): """Test The Status Code Of read_books When Book Has Been Rated """ - self.userRating = UserRating.objects.create(user=self.user, bookid='2', bookrating='4') + self.userRating = UserRating.objects.create( + user=self.user, bookid="2", bookrating="4" + ) self.userRating.save() - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") response = self.client.get(self.url) - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) self.client.logout() class AddBooksTestCase(TestCase): """Saved For Later Books View Test Case""" + def setUp(self): self.client = Client() - self.user = User.objects.create_user(username='test_user', email='qwe@gmail.com') - self.user.set_password('foopassword') + self.user = User.objects.create_user( + username="test_user", email="qwe@gmail.com" + ) + self.user.set_password("foopassword") self.user.save() - self.book = pd.read_csv(os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/dataset/books.csv')) - self.bookid = self.book.iloc[0]['book_id'] + self.book = pd.read_csv( + os.path.join(settings.STATICFILES_DIRS[0] + "/mainapp/dataset/books.csv") + ) + self.bookid = self.book.iloc[0]["book_id"] def test_save_book_status(self): """Test the status code of save_book When a book is Saved """ book_id = self.bookid - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") response = self.client.post( - reverse('save_book'), - data={'bookid': book_id}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + reverse("save_book"), + data={"bookid": book_id}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 200) - self.assertIn('true', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("true", response.content.decode("utf-8")) self.client.logout() def test_after_remove(self): @@ -314,33 +362,33 @@ def test_after_remove(self): remove_saved_book When a book is removed """ book_id = self.bookid - self.client.login(username='test_user', password='foopassword') + self.client.login(username="test_user", password="foopassword") response = self.client.post( - reverse('remove_saved_book'), - data={'bookid': book_id}, - HTTP_X_REQUESTED_WITH='XMLHttpRequest' + reverse("remove_saved_book"), + data={"bookid": book_id}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", ) - self.assertEquals(response.status_code, 200) - self.assertIn('true', response.content.decode("utf-8")) + self.assertEqual(response.status_code, 200) + self.assertIn("true", response.content.decode("utf-8")) self.client.logout() def test_redirect_if_not_saved(self): """Test If The to_read Redirects Accordingly When No Book Is Saved """ - self.client.login(username='test_user', password='foopassword') - response = self.client.get(reverse('to_read')) - self.assertRedirects(response, reverse('index')) + self.client.login(username="test_user", password="foopassword") + response = self.client.get(reverse("to_read")) + self.assertRedirects(response, reverse("index")) self.client.logout() def test_to_read_status_if_saved(self): """Test the status code of to_read When a book is Saved """ - self.client.login(username='test_user', password='foopassword') - self.saveLater = SaveForLater.objects.create(user=self.user, bookid='2') + self.client.login(username="test_user", password="foopassword") + self.saveLater = SaveForLater.objects.create(user=self.user, bookid="2") self.saveLater.save() - response = self.client.get(reverse('to_read')) - self.assertEquals(response.status_code, 200) + response = self.client.get(reverse("to_read")) + self.assertEqual(response.status_code, 200) self.client.logout() diff --git a/mainapp/urls.py b/mainapp/urls.py index 3127a378..5b803f25 100644 --- a/mainapp/urls.py +++ b/mainapp/urls.py @@ -1,21 +1,23 @@ from django.urls import path from mainapp import views, views_ajax -urlpatterns = [ - path('', views.index, name='index'), - path('genre_books/', views.genre_books, name='genre_books'), - path('explore_books/', views.explore_books, name='explore_books'), - path('book_recommendations/', views.book_recommendations, name='book_recommendations'), - path('library/rated_books', views.read_books, name='read_books'), - path('library/saved_books', views.SaveList, name='to_read') +urlpatterns = [ + path("", views.index, name="index"), + path("genre_books/", views.genre_books, name="genre_books"), + path("explore_books/", views.explore_books, name="explore_books"), + path( + "book_recommendations/", views.book_recommendations, name="book_recommendations" + ), + path("library/rated_books", views.read_books, name="read_books"), + path("library/saved_books", views.SaveList, name="to_read"), ] # Ajax Views urlpatterns += [ - path('search_ajax/', views_ajax.search, name='search_ajax'), - path('book_summary_ajax/', views_ajax.book_summary, name='summary_ajax'), - path('book_details_ajax/', views_ajax.get_book_details, name='book_details'), - path('user_rate_book/', views_ajax.user_rate_book, name='user_rate_book'), - path('save_book/', views_ajax.save_book, name='save_book'), - path('remove_saved_book/', views_ajax.remove_saved_book, name='remove_saved_book') + path("search_ajax/", views_ajax.search, name="search_ajax"), + path("book_summary_ajax/", views_ajax.book_summary, name="summary_ajax"), + path("book_details_ajax/", views_ajax.get_book_details, name="book_details"), + path("user_rate_book/", views_ajax.user_rate_book, name="user_rate_book"), + path("save_book/", views_ajax.save_book, name="save_book"), + path("remove_saved_book/", views_ajax.remove_saved_book, name="remove_saved_book"), ] diff --git a/mainapp/views.py b/mainapp/views.py index 052ac588..f32b43cf 100644 --- a/mainapp/views.py +++ b/mainapp/views.py @@ -1,7 +1,16 @@ from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import ensure_csrf_cookie -from mainapp.helpers import genre_wise, tfidf_recommendations, get_book_dict, get_rated_bookids, combine_ids, embedding_recommendations, get_top_n, popular_among_users +from mainapp.helpers import ( + genre_wise, + tfidf_recommendations, + get_book_dict, + get_rated_bookids, + combine_ids, + embedding_recommendations, + get_top_n, + popular_among_users, +) from mainapp.models import UserRating, SaveForLater from django.contrib import messages from django.core.paginator import Paginator @@ -13,56 +22,60 @@ @ensure_csrf_cookie def index(request): - ''' - View to render Homepage - ''' + """ + View to render Homepage + """ books = popular_among_users() - return render(request, 'mainapp/index.html', {'books': books}) + return render(request, "mainapp/index.html", {"books": books}) @ensure_csrf_cookie def genre_books(request, genre): - ''' - View to render Books in a particular genre - ''' + """ + View to render Books in a particular genre + """ genre_topbooks = genre_wise(genre) - genre_topbooks = genre_topbooks.to_dict('records') + genre_topbooks = genre_topbooks.to_dict("records") context = { - 'genre': genre.capitalize(), - 'genre_topbook': genre_topbooks, + "genre": genre.capitalize(), + "genre_topbook": genre_topbooks, } - return render(request, 'mainapp/genre.html', context) + return render(request, "mainapp/genre.html", context) @ensure_csrf_cookie def explore_books(request): - ''' - View to Render Explore Page - Renders Top N Books - ''' + """ + View to Render Explore Page + Renders Top N Books + """ N = 152 - sample = get_top_n().sample(N).to_dict('records') - return render(request, 'mainapp/explore.html', {'book': sample}) + sample = get_top_n().sample(N).to_dict("records") + return render(request, "mainapp/explore.html", {"book": sample}) @login_required @ensure_csrf_cookie def book_recommendations(request): - ''' - View to render book recommendations - - Count Vectorizer Approach: - 1. Get Ratings of User - 2. Shuffle by Top Ratings(For Randomness each time) - 3. Recommend according to Top Rated Book - ''' - user_ratings = list(UserRating.objects.filter(user=request.user).order_by('-bookrating')) + """ + View to render book recommendations + + Count Vectorizer Approach: + 1. Get Ratings of User + 2. Shuffle by Top Ratings(For Randomness each time) + 3. Recommend according to Top Rated Book + """ + user_ratings = list( + UserRating.objects.filter(user=request.user).order_by("-bookrating") + ) random.shuffle(user_ratings) - best_user_ratings = sorted(user_ratings, key=operator.attrgetter('bookrating'), reverse=True) + best_user_ratings = sorted( + user_ratings, key=operator.attrgetter("bookrating"), reverse=True + ) if len(best_user_ratings) < 4: - messages.info(request, 'Please rate atleast 5 books') - return redirect('index') + messages.info(request, "Please rate atleast 5 books") + return redirect("index") if best_user_ratings: # If one or more book is rated bookid = best_user_ratings[0].bookid @@ -72,67 +85,81 @@ def book_recommendations(request): # Shuffle again for randomness for second approach random.shuffle(user_ratings) - best_user_ratings = sorted(user_ratings, key=operator.attrgetter('bookrating'), reverse=True) + best_user_ratings = sorted( + user_ratings, key=operator.attrgetter("bookrating"), reverse=True + ) # Get Top 10 bookids based on embedding embedding_bookids = set(embedding_recommendations(best_user_ratings)) - best_bookids = combine_ids(tfidf_bookids, embedding_bookids, already_rated_books) + best_bookids = combine_ids( + tfidf_bookids, embedding_bookids, already_rated_books + ) all_books_dict = get_book_dict(best_bookids) else: - return redirect('index') - return render(request, 'mainapp/recommendation.html', {'books': all_books_dict}) + return redirect("index") + return render(request, "mainapp/recommendation.html", {"books": all_books_dict}) @login_required @ensure_csrf_cookie def read_books(request): """View To Render Library Page""" - user_ratings = list(UserRating.objects.filter(user=request.user).order_by('-bookrating')) + user_ratings = list( + UserRating.objects.filter(user=request.user).order_by("-bookrating") + ) if len(user_ratings) == 0: - messages.info(request, 'Please rate some books') - return redirect('index') + messages.info(request, "Please rate some books") + return redirect("index") if user_ratings: rated_books = set(get_rated_bookids(user_ratings)) books = get_book_dict(rated_books) num = len(books) # Add pagination to the page showing 10 books paginator = Paginator(books, 10) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) else: - return redirect('index') - return render(request, 'mainapp/read.html', {'page_obj': page_obj, 'num': num}) + return redirect("index") + return render(request, "mainapp/read.html", {"page_obj": page_obj, "num": num}) def handler404(request, *args, **argv): - response = render(request, 'mainapp/error_handler.html') + response = render(request, "mainapp/error_handler.html") response.status_code = 404 return response def handler500(request, *args, **argv): - response = render(request, 'mainapp/error_handler.html') + response = render(request, "mainapp/error_handler.html") response.status_code = 500 return response def SaveList(request): """View to render Saved books page""" - user_ratings = list(UserRating.objects.filter(user=request.user).order_by('-bookrating')) + user_ratings = list( + UserRating.objects.filter(user=request.user).order_by("-bookrating") + ) rated_books = set(get_rated_bookids(user_ratings)) - book = set(SaveForLater.objects.filter(user=request.user).values_list('bookid', flat=True)) + book = set( + SaveForLater.objects.filter(user=request.user).values_list("bookid", flat=True) + ) book_id = list(book) for i in range(len(book_id)): if book_id[i] in rated_books: - saved_book = SaveForLater.objects.filter(user=request.user, bookid=book_id[i]) + saved_book = SaveForLater.objects.filter( + user=request.user, bookid=book_id[i] + ) saved_book.delete() book_id.remove(book_id[i]) if len(book_id) == 0: - messages.info(request, 'Please Add Some Books') - return redirect('index') + messages.info(request, "Please Add Some Books") + return redirect("index") books = get_book_dict(book_id) total_books = len(books) paginator = Paginator(books, 10) - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) - return render(request, 'mainapp/saved_book.html', {'page_obj': page_obj, 'num': total_books}) + return render( + request, "mainapp/saved_book.html", {"page_obj": page_obj, "num": total_books} + ) diff --git a/mainapp/views_ajax.py b/mainapp/views_ajax.py index b513fa25..310cd694 100644 --- a/mainapp/views_ajax.py +++ b/mainapp/views_ajax.py @@ -9,118 +9,127 @@ import json import requests -''' +""" Production File Path : staticfiles_storage.url(file) Developement File Path : settings.STATICFILES_DIRS[0] + 'app/.../file' -''' -book_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/dataset/books.csv') -user_ratings_path = os.path.join(settings.STATICFILES_DIRS[0] + '/mainapp/csv/userratings.csv') +""" +book_path = os.path.join(settings.STATICFILES_DIRS[0] + "/mainapp/dataset/books.csv") +user_ratings_path = os.path.join( + settings.STATICFILES_DIRS[0] + "/mainapp/csv/userratings.csv" +) + + +def is_ajax(request): + return request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" def search(request): - ''' - AJAX request for search bar functionality - ''' - if request.method == "POST" and request.is_ajax(): - query = request.POST.get('bookName', None) + """ + AJAX request for search bar functionality + """ + if request.method == "POST" and is_ajax(request=request): + query = request.POST.get("bookName", None) if not query: - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) df_book = pd.read_csv(book_path) - top5_result = df_book[df_book['original_title'].str.contains( - query, regex=False, case=False)][:5] - top5_result = json.dumps(top5_result.to_dict('records')) + top5_result = df_book[ + df_book["original_title"].str.contains(query, regex=False, case=False) + ][:5] + top5_result = json.dumps(top5_result.to_dict("records")) - return JsonResponse({'success': True, 'top5_result': top5_result}, status=200) + return JsonResponse({"success": True, "top5_result": top5_result}, status=200) def book_summary(request): - ''' - AJAX request for book summary - ''' - if request.method == 'POST' and request.is_ajax(): - bookid = request.POST.get('bookid', None) + """ + AJAX request for book summary + """ + if request.method == "POST" and is_ajax(request=request): + bookid = request.POST.get("bookid", None) if is_bookid_invalid(bookid): - return JsonResponse({'success': False}, status=200) - URL = 'https://www.goodreads.com/book/show/' + bookid + return JsonResponse({"success": False}, status=200) + URL = "https://www.goodreads.com/book/show/" + bookid page = requests.get(URL) - soup = BeautifulSoup(page.content, 'html.parser') - div_container = soup.find(id='description') + soup = BeautifulSoup(page.content, "html.parser") + div_container = soup.find(id="description") full_book_summary = "" if not div_container: - return JsonResponse({'success': False}, status=200) - for spantag in div_container.find_all('span'): + return JsonResponse({"success": False}, status=200) + for spantag in div_container.find_all("span"): try: # When text is too long, consider till last complete sentence - full_book_summary += spantag.text[:spantag.text.rindex('.')] + '. ' + full_book_summary += spantag.text[: spantag.text.rindex(".")] + ". " except ValueError: - full_book_summary += spantag.text + ' ' + full_book_summary += spantag.text + " " break - part_summary = ' '.join(full_book_summary.split()[:65]) + ' . . .' - return JsonResponse({'success': True, 'booksummary': part_summary}, status=200) + part_summary = " ".join(full_book_summary.split()[:65]) + " . . ." + return JsonResponse({"success": True, "booksummary": part_summary}, status=200) def get_book_details(request): - ''' - AJAX request for book details - ''' - if request.method == 'POST' and request.is_ajax(): - bookid = request.POST.get('bookid', None) + """ + AJAX request for book details + """ + if request.method == "POST" and is_ajax(request=request): + bookid = request.POST.get("bookid", None) if is_bookid_invalid(bookid): - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) df_book = pd.read_csv(book_path) - book_details = df_book[df_book['book_id'] == int(bookid)] + book_details = df_book[df_book["book_id"] == int(bookid)] if not len(book_details): - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) - book_details = json.dumps(book_details.to_dict('records')) - return JsonResponse({'success': True, 'book_details': book_details}, status=200) + book_details = json.dumps(book_details.to_dict("records")) + return JsonResponse({"success": True, "book_details": book_details}, status=200) @login_required def user_rate_book(request): - ''' - AJAX request when user rates book - ''' - if request.method == 'POST' and request.is_ajax(): - bookid = request.POST.get('bookid', None) - bookrating = request.POST.get('bookrating', None) + """ + AJAX request when user rates book + """ + if request.method == "POST" and is_ajax(request=request): + bookid = request.POST.get("bookid", None) + bookrating = request.POST.get("bookrating", None) if is_bookid_invalid(bookid) or is_rating_invalid(bookrating): - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) # Using Inbuilt Model query = UserRating.objects.filter(user=request.user).filter(bookid=bookid) if not query: # Create Rating - UserRating.objects.create(user=request.user, bookid=bookid, bookrating=bookrating) + UserRating.objects.create( + user=request.user, bookid=bookid, bookrating=bookrating + ) else: # Update Rating rating_object = query[0] rating_object.bookrating = bookrating rating_object.save() - return JsonResponse({'success': True}, status=200) + return JsonResponse({"success": True}, status=200) def save_book(request): """AJAX request when user saves book""" - if request.method == 'POST' and request.is_ajax(): - bookid = request.POST.get('bookid', None) + if request.method == "POST" and is_ajax(request=request): + bookid = request.POST.get("bookid", None) user_ratings = list(UserRating.objects.filter(user=request.user)) rated_books = set(get_rated_bookids(user_ratings)) if is_bookid_invalid(bookid) or bookid in rated_books: - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) SaveForLater.objects.create(user=request.user, bookid=bookid) - return JsonResponse({'success': True}, status=200) + return JsonResponse({"success": True}, status=200) def remove_saved_book(request): """AJAX request when user removes book""" - if request.method == 'POST' and request.is_ajax(): - bookid = request.POST.get('bookid', None) + if request.method == "POST" and is_ajax(request=request): + bookid = request.POST.get("bookid", None) if is_bookid_invalid(bookid): - return JsonResponse({'success': False}, status=200) + return JsonResponse({"success": False}, status=200) saved_book = SaveForLater.objects.filter(user=request.user, bookid=bookid) saved_book.delete() - return JsonResponse({'success': True}, status=200) + return JsonResponse({"success": True}, status=200) diff --git a/manage.py b/manage.py index 389d2e71..73758413 100644 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'BookRecSystem.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BookRecSystem.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..9950e38b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1095 @@ +[[package]] +name = "asgiref" +version = "3.5.2" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "astroid" +version = "2.12.11" +description = "An abstract syntax tree for Python with inference support." +category = "main" +optional = false +python-versions = ">=3.7.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + +[[package]] +name = "backports.zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +tzdata = ["tzdata"] + +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bs4" +version = "0.0.1" +description = "Dummy package for Beautiful Soup" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +beautifulsoup4 = "*" + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "main" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cryptography" +version = "38.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools-rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "dill" +version = "0.3.5.1" +description = "serialize all of python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "django" +version = "4.1.2" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +asgiref = ">=3.5.2,<4" +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +sqlparse = ">=0.2.2" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-allauth" +version = "0.51.0" +description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Django = ">=2.0" +pyjwt = {version = ">=1.7", extras = ["crypto"]} +python3-openid = ">=3.0.8" +requests = "*" +requests-oauthlib = ">=0.3.0" + +[[package]] +name = "django-storages" +version = "1.13.1" +description = "Support for many storage backends in Django" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Django = ">=3.2" + +[package.extras] +azure = ["azure-storage-blob (>=12.0.0)"] +boto3 = ["boto3 (>=1.4.4)"] +dropbox = ["dropbox (>=7.2.1)"] +google = ["google-cloud-storage (>=1.27.0)"] +libcloud = ["apache-libcloud"] +sftp = ["paramiko"] + +[[package]] +name = "filelock" +version = "3.8.0" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "identify" +version = "2.5.6" +description = "File identification library for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "main" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "lazy-object-proxy" +version = "1.7.1" +description = "A fast and thorough lazy object proxy." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[[package]] +name = "numpy" +version = "1.23.3" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "oauthlib" +version = "3.2.1" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "pandas" +version = "1.5.0" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = [ + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, + {version = ">=1.20.3", markers = "python_version < \"3.10\""}, +] +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["pytest-xdist (>=1.31)", "pytest (>=6.0)", "hypothesis (>=5.5.3)"] + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyjwt" +version = "2.5.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cryptography = {version = ">=3.3.1", optional = true, markers = "extra == \"crypto\""} +types-cryptography = {version = ">=3.3.21", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.3.1)", "types-cryptography (>=3.3.21)"] +dev = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1)", "types-cryptography (>=3.3.21)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "pre-commit"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"] + +[[package]] +name = "pylint" +version = "2.15.4" +description = "python code static checker" +category = "main" +optional = false +python-versions = ">=3.7.2" + +[package.dependencies] +astroid = ">=2.12.11,<=2.14.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = ">=0.2" +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python3-openid" +version = "3.2.0" +description = "OpenID support for modern servers and consumers." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +defusedxml = "*" + +[package.extras] +mysql = ["mysql-connector-python"] +postgresql = ["psycopg2"] + +[[package]] +name = "pytz" +version = "2022.4" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sqlparse" +version = "0.4.3" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tomlkit" +version = "0.11.5" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "types-cryptography" +version = "3.3.23" +description = "Typing stubs for cryptography" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tzdata" +version = "2022.4" +description = "Provider of IANA time zone data" +category = "main" +optional = false +python-versions = ">=2" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.16.5" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.5,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "whitenoise" +version = "6.2.0" +description = "Radically simplified static file serving for WSGI applications" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +brotli = ["brotli"] + +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "9ee1df70bdcb170b5a374c73a96e6d90237b9306b8ebaf5fdd7f212d841375ca" + +[metadata.files] +asgiref = [ + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, +] +astroid = [ + {file = "astroid-2.12.11-py3-none-any.whl", hash = "sha256:867a756bbf35b7bc07b35bfa6522acd01f91ad9919df675e8428072869792dce"}, + {file = "astroid-2.12.11.tar.gz", hash = "sha256:2df4f9980c4511474687895cbfdb8558293c1a826d9118bb09233d7c2bff1c83"}, +] +"backports.zoneinfo" = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] +bs4 = [ + {file = "bs4-0.0.1.tar.gz", hash = "sha256:36ecea1fd7cc5c0c6e4a1ff075df26d50da647b75376626cc186e2212886dd3a"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +cryptography = [ + {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"}, + {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"}, + {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"}, + {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"}, + {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"}, + {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"}, + {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"}, + {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"}, + {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"}, + {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"}, + {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"}, + {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"}, + {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"}, + {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"}, + {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"}, + {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"}, + {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"}, + {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"}, + {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"}, + {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"}, + {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"}, +] +defusedxml = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] +django = [ + {file = "Django-4.1.2-py3-none-any.whl", hash = "sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793"}, + {file = "Django-4.1.2.tar.gz", hash = "sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"}, +] +django-allauth = [ + {file = "django-allauth-0.51.0.tar.gz", hash = "sha256:ca1622733b6faa591580ccd3984042f12d8c79ade93438212de249b7ffb6f91f"}, +] +django-storages = [ + {file = "django-storages-1.13.1.tar.gz", hash = "sha256:b3d98ecc09f1b1627c2b2cf430964322ce4e08617dbf9b4236c16a32875a1e0b"}, + {file = "django_storages-1.13.1-py3-none-any.whl", hash = "sha256:3540b45618b04be2c867c0982e8d2bd8e34f84dae922267fcebe4691fb93daf0"}, +] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] +identify = [ + {file = "identify-2.5.6-py2.py3-none-any.whl", hash = "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"}, + {file = "identify-2.5.6.tar.gz", hash = "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, +] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] +numpy = [ + {file = "numpy-1.23.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee"}, + {file = "numpy-1.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54"}, + {file = "numpy-1.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440"}, + {file = "numpy-1.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089"}, + {file = "numpy-1.23.3-cp310-cp310-win32.whl", hash = "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a"}, + {file = "numpy-1.23.3-cp310-cp310-win_amd64.whl", hash = "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036"}, + {file = "numpy-1.23.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c"}, + {file = "numpy-1.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c"}, + {file = "numpy-1.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411"}, + {file = "numpy-1.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd"}, + {file = "numpy-1.23.3-cp311-cp311-win32.whl", hash = "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4"}, + {file = "numpy-1.23.3-cp311-cp311-win_amd64.whl", hash = "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f"}, + {file = "numpy-1.23.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6"}, + {file = "numpy-1.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d"}, + {file = "numpy-1.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460"}, + {file = "numpy-1.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e"}, + {file = "numpy-1.23.3-cp38-cp38-win32.whl", hash = "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85"}, + {file = "numpy-1.23.3-cp38-cp38-win_amd64.whl", hash = "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6"}, + {file = "numpy-1.23.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164"}, + {file = "numpy-1.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d"}, + {file = "numpy-1.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14"}, + {file = "numpy-1.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7"}, + {file = "numpy-1.23.3-cp39-cp39-win32.whl", hash = "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1"}, + {file = "numpy-1.23.3-cp39-cp39-win_amd64.whl", hash = "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c"}, + {file = "numpy-1.23.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18"}, + {file = "numpy-1.23.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584"}, + {file = "numpy-1.23.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8"}, + {file = "numpy-1.23.3.tar.gz", hash = "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd"}, +] +oauthlib = [ + {file = "oauthlib-3.2.1-py3-none-any.whl", hash = "sha256:88e912ca1ad915e1dcc1c06fc9259d19de8deacd6fd17cc2df266decc2e49066"}, + {file = "oauthlib-3.2.1.tar.gz", hash = "sha256:1565237372795bf6ee3e5aba5e2a85bd5a65d0e2aa5c628b9a97b7d7a0da3721"}, +] +pandas = [ + {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484"}, + {file = "pandas-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c"}, + {file = "pandas-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3"}, + {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f"}, + {file = "pandas-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be"}, + {file = "pandas-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc"}, + {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8"}, + {file = "pandas-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b"}, + {file = "pandas-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a"}, + {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8"}, + {file = "pandas-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060"}, + {file = "pandas-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9"}, + {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989"}, + {file = "pandas-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b"}, + {file = "pandas-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761"}, + {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38"}, + {file = "pandas-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9"}, + {file = "pandas-1.5.0-cp38-cp38-win32.whl", hash = "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8"}, + {file = "pandas-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f"}, + {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515"}, + {file = "pandas-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009"}, + {file = "pandas-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a"}, + {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac"}, + {file = "pandas-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d"}, + {file = "pandas-1.5.0-cp39-cp39-win32.whl", hash = "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488"}, + {file = "pandas-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec"}, + {file = "pandas-1.5.0.tar.gz", hash = "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +pre-commit = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pyjwt = [ + {file = "PyJWT-2.5.0-py3-none-any.whl", hash = "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80"}, + {file = "PyJWT-2.5.0.tar.gz", hash = "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b"}, +] +pylint = [ + {file = "pylint-2.15.4-py3-none-any.whl", hash = "sha256:629cf1dbdfb6609d7e7a45815a8bb59300e34aa35783b5ac563acaca2c4022e9"}, + {file = "pylint-2.15.4.tar.gz", hash = "sha256:5441e9294335d354b7bad57c1044e5bd7cce25c433475d76b440e53452fa5cb8"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python3-openid = [ + {file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"}, + {file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"}, +] +pytz = [ + {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, + {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +requests-oauthlib = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +sqlparse = [ + {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, + {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tomlkit = [ + {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"}, + {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"}, +] +types-cryptography = [ + {file = "types-cryptography-3.3.23.tar.gz", hash = "sha256:b85c45fd4d3d92e8b18e9a5ee2da84517e8fff658e3ef5755c885b1c2a27c1fe"}, + {file = "types_cryptography-3.3.23-py3-none-any.whl", hash = "sha256:913b3e66a502edbf4bfc3bb45e33ab476040c56942164a7ff37bd1f0ef8ef783"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +tzdata = [ + {file = "tzdata-2022.4-py2.py3-none-any.whl", hash = "sha256:74da81ecf2b3887c94e53fc1d466d4362aaf8b26fc87cda18f22004544694583"}, + {file = "tzdata-2022.4.tar.gz", hash = "sha256:ada9133fbd561e6ec3d1674d3fba50251636e918aa97bd59d63735bef5a513bb"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +virtualenv = [ + {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, + {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, +] +whitenoise = [ + {file = "whitenoise-6.2.0-py3-none-any.whl", hash = "sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2"}, + {file = "whitenoise-6.2.0.tar.gz", hash = "sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..8daf08b9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[tool.poetry] +name = "kitabe" +version = "0.1.0" +description = "Book Recommendation System" +authors = ["maneprajakta ", "Praful932 "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +Django = "^4.1.2" +django-allauth = "^0.51.0" +django-storages = "^1.13.1" +pandas = "^1.5.0" +numpy = "^1.23.3" +bs4 = "^0.0.1" +pre-commit = "^2.20.0" +pylint = "^2.15.4" +gunicorn = "^20.1.0" +whitenoise = "^6.2.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c539a8da..00000000 --- a/requirements.txt +++ /dev/null @@ -1,41 +0,0 @@ -asgiref==3.2.10 -autopep8==1.5.4 -beautifulsoup4==4.9.1 -boto3==1.16.13 -botocore==1.19.13 -certifi==2020.6.20 -chardet==3.0.4 -click==7.1.2 -coverage==5.5 -defusedxml==0.6.0 -dj-database-url==0.5.0 -Django==3.1.13 -django-allauth==0.42.0 -django-heroku==0.3.1 -django-storages==1.10.1 -docstr-coverage==2.0.1 -flake8==3.8.4 -gunicorn==20.0.4 -idna==2.10 -jmespath==0.10.0 -joblib==0.17.0 -mccabe==0.6.1 -numpy==1.21.0 -oauthlib==3.1.0 -pandas==1.2.3 -psycopg2==2.8.6 -pycodestyle==2.6.0 -pyflakes==2.2.0 -python-dateutil==2.8.1 -python3-openid==3.2.0 -pytz==2020.4 -PyYAML==5.4.1 -requests==2.24.0 -requests-oauthlib==1.3.0 -s3transfer==0.3.3 -six==1.15.0 -soupsieve==2.0.1 -sqlparse==0.4.1 -toml==0.10.2 -urllib3==1.25.11 -whitenoise==5.2.0 diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index 0fd6938d..00000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.8.6