├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── djangoflutterwave ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20201222_0800.py │ └── __init__.py ├── models.py ├── serializers.py ├── settings.py ├── static │ └── djangoflutterwave │ │ └── js │ │ └── payment.js ├── templates │ └── djangoflutterwave │ │ ├── pay_button.html │ │ └── transaction.html ├── templatetags │ ├── __init__.py │ └── djangoflutterwave_tags.py ├── tests │ ├── __init__.py │ ├── factories.py │ ├── test_serializers.py │ ├── test_template_tags.py │ ├── test_utils.py │ └── test_views.py ├── urls.py ├── utils.py └── views.py ├── docker-compose.yml ├── example ├── example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── jupyter_notebooks │ └── ipython_config.py ├── manage.py ├── paymanager │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── management │ │ └── commands │ │ │ └── import.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── paymanager │ │ │ └── signup.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── poetry.lock └── pyproject.toml ├── poetry.lock └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | # Edit at https://www.gitignore.io/?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | pip-wheel-metadata/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | *.ipynb 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # celery beat schedule file 99 | celerybeat-schedule 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # End of https://www.gitignore.io/api/python 132 | 133 | .DS_Store 134 | 135 | # vscode 136 | .vscode/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Pull base image 2 | FROM python:3.7.3 3 | 4 | # Set environment varibles 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | # Upgrade pip and install poetry 9 | RUN pip install --upgrade pip 10 | RUN pip install poetry 11 | 12 | # Set work directory 13 | WORKDIR /code 14 | 15 | # Copy project into container 16 | COPY . . 17 | 18 | WORKDIR /code/example 19 | 20 | RUN poetry install -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Brendon Delate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dup: 2 | @docker-compose up -d 3 | 4 | build: 5 | @docker-compose build 6 | 7 | migrations: 8 | @docker-compose run django poetry run ./manage.py makemigrations 9 | 10 | migrate: 11 | @docker-compose run django poetry run ./manage.py migrate 12 | 13 | bash: 14 | @docker-compose run django bash 15 | 16 | down: 17 | @docker-compose down 18 | 19 | shell: 20 | @docker-compose run django poetry run ./manage.py shell 21 | 22 | test: 23 | @docker-compose run django poetry run ./manage.py test djangoflutterwave 24 | 25 | test_cov: 26 | @docker-compose run django poetry run coverage run --source=djangoflutterwave ./manage.py test djangoflutterwave 27 | @docker-compose run django poetry run coverage html 28 | @open example/htmlcov/index.html 29 | 30 | logs: 31 | @docker-compose logs -tf django 32 | 33 | notebook: 34 | @docker-compose run -p 8888:8888 django poetry run ./manage.py shell_plus --notebook 35 | 36 | import: 37 | @docker-compose run django poetry run ./manage.py import -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Flutterwave 2 | 3 | ## Project Description 4 | 5 | This project provides Django integration for [Flutterwave](https://flutterwave.com/) payments and subscriptions. 6 | 7 | Current functionality: 8 | 9 | - Allow users to make payments (once off and subscription) 10 | - Create payment buttons which launch inline payment modals 11 | - Maintain a payment transaction history linked to users 12 | 13 | # Requirements 14 | 15 | - Python >= 3.6 16 | - Django >= 2.0 17 | 18 | # Installation 19 | 20 | ```bash 21 | pip install djangoflutterwave 22 | ``` 23 | 24 | # Setup 25 | 26 | Add `"djangoflutterwave"` to your `INSTALLED_APPS` 27 | 28 | Run Django migrations: 29 | 30 | ```python 31 | manage.py migrate 32 | ``` 33 | 34 | Add the following to your `settings.py`: 35 | 36 | ```python 37 | FLW_PRODUCTION_PUBLIC_KEY = "your key" 38 | FLW_PRODUCTION_SECRET_KEY = "your key" 39 | FLW_SANDBOX_PUBLIC_KEY = "your key" 40 | FLW_SANDBOX_SECRET_KEY = "your key" 41 | FLW_SANDBOX = True 42 | ``` 43 | 44 | The above config will ensure `djangoflutterwave` uses your sandbox. Once you're ready to 45 | go live, set `FLW_SANDBOX = False` 46 | 47 | Add `djangoflutterwave` to your `urls.py`: 48 | 49 | ```python 50 | path("djangoflutterwave/", include("djangoflutterwave.urls", namespace="djangoflutterwave")) 51 | ``` 52 | 53 | Add the following url as a webhook in your Flutterwave dashboard. This will be used by 54 | Flutterwave to `POST` payment transactions to your site: 55 | 56 | ```bash 57 | http://yoursite.com/djangoflutterwave/transaction/ 58 | ``` 59 | 60 | `Note:` while in development, a tool like ngrok (or similar) may prove useful to ensure 61 | your localhost is accessible to Flutterwave for the above webhook calls. 62 | 63 | # Usage 64 | 65 | `djangoflutterwave` provides two models, namely: 66 | 67 | - The `FlwPlanModel` allows you to create `once off` or `subscription` plans. When creating a `subscription` plan, you will need to create the plan in Flutterwave first and then enter the corresonding information as a `FlwPlanModel` instance (ie: `flw_plan_id` field corresponds to the Flutterwave `Plan ID`). 68 | - The `FlwTransactionModel` creates transactions when Flutterwave POSTS to the above mentioned webhook url. This provides a history of all transactions (once off or recurring), linked to the relevant `FlwPlanModel` and `user`. 69 | 70 | A payment button can be created as follows: 71 | 72 | 1. Create a new plan (ie: `FlwPlanModel`) using the django admin. 73 | 2. In the view where you wish the button to appear, add the above created `FlwPlanModel` instance to your context, eg: 74 | 75 | ```python 76 | from djangoflutterwave.models import FlwPlanModel 77 | 78 | class SignUpView(TemplateView): 79 | """Sign Up view""" 80 | 81 | template_name = "my_payment_template.html" 82 | 83 | def get_context_data(self, **kwargs): 84 | """Add payment type to context data""" 85 | kwargs = super().get_context_data(**kwargs) 86 | kwargs["pro_plan"] = FlwPlanModel.objects.filter( 87 | name="Pro Plan" 88 | ).first() 89 | return kwargs 90 | ``` 91 | 92 | 3. In your template, add the button wherever you wish for it to appear as follows: 93 | 94 | ```python 95 | {% include 'djangoflutterwave/pay_button.html' with plan=pro_plan %} 96 | ``` 97 | 98 | `Note:` You can add multiple buttons to a single template by simply adding multiple 99 | plans to your context data and then including each of them with their own `include` 100 | tag as above. 101 | 102 | 4. Add the following to your django base template (or anywhere in your template heirarchy that ensures it is loaded before your payment buttons): 103 | 104 | ```html 105 | 109 | 110 | ``` 111 | 112 | # Button Styling 113 | 114 | Use the `pay_button_css_classes` field on the `FlwPlanModel` model to add css classes to 115 | buttons which will be rendered in your template. 116 | 117 | # Transaction Detail Page 118 | 119 | Following a user payment, they will be redirected to the transaction detail page 120 | located at `/djangoflutterwave//`. 121 | 122 | A default transaction detail template is already available, however if you want 123 | to override it, you may do so by creating a new template in your root 124 | templates directory, ie: `/templates/djangoflutterwave/transaction.html` 125 | 126 | You will have access to `{{ transaction }}` within that template. 127 | 128 | # API's and single page apps 129 | 130 | As an alternative to rendering the above mentioned buttons in a django template, an API 131 | end point is provided for retrieving payment params which can then be used in an API based app (eg: Vue, React). 132 | 133 | End point: `djangoflutterwave/payment-params/?plan=nameofplanhere` 134 | 135 | The above end point requires an authenticated user and will return the following: 136 | 137 | ```json 138 | { 139 | "public_key": "flutterwave public key", 140 | "tx_ref": "transaction ref", 141 | "amount": 123.45, 142 | "currency": "USD", 143 | "payment_plan": 3453, 144 | "customer": { 145 | "email": "foo@bar.com", 146 | "name": "John Smith" 147 | }, 148 | "customizations": { 149 | "title": "Pro Plan", 150 | "logo": "http://example.com/image.png" 151 | } 152 | } 153 | ``` 154 | 155 | Below is a basic example of implementing Django Flutterwave into a Vue app: 156 | 157 | ```js 158 | 163 | 164 | 199 | ``` 200 | 201 | The important points to note are: 202 | 203 | - All initial setup is the same as per the above documentation. 204 | - To call the end point, the user must be authenticated (ie: add their token). 205 | - `https://checkout.flutterwave.com/v3.js` must be loaded as a script. 206 | - A plan name is used to call the API end point which returns the payment params. Those payment params are then used in the `makePayment` method. 207 | - There is a call back function which should be used to specify what to do after a 208 | payment has been completed. 209 | 210 | # Development and contribution 211 | 212 | If you wish to contribute to the project, there is an example app that demonstrates 213 | general usage. 214 | 215 | ### Running the example: 216 | 217 | ```bash 218 | git clone https://github.com/bdelate/django-flutterwave.git 219 | cd django-flutterwave 220 | ``` 221 | 222 | Create file `example/env/dev.env` and populate it with the following: 223 | 224 | ```bash 225 | FLW_SANDBOX_PUBLIC_KEY=your_sandbox_public_key 226 | FLW_SANDBOX_SECRET_KEY=your_sandbox_secret_key 227 | FLW_PRODUCTION_PUBLIC_KEY=test 228 | FLW_PRODUCTION_SECRET_KEY=test 229 | ``` 230 | 231 | Run the following commands: 232 | 233 | ```bash 234 | make build 235 | make migrate 236 | make import 237 | make dup 238 | ``` 239 | 240 | Flutterwave requires payments to be associated with users who have an email address. 241 | Therefore, create and login with a new django user or use the existing user which will 242 | have been created by the above import command: 243 | 244 | ``` 245 | username: admin 246 | password: adminadmin 247 | ``` 248 | 249 | Navigate to http://localhost:8000/ 250 | -------------------------------------------------------------------------------- /djangoflutterwave/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/djangoflutterwave/__init__.py -------------------------------------------------------------------------------- /djangoflutterwave/admin.py: -------------------------------------------------------------------------------- 1 | # stdlib import 2 | 3 | # django imports 4 | from django.contrib import admin 5 | 6 | # project imports 7 | from djangoflutterwave.models import FlwPlanModel, FlwTransactionModel 8 | 9 | # project imports 10 | 11 | 12 | class PlanAdmin(admin.ModelAdmin): 13 | list_display = ("name", "amount", "flw_plan_id", "interval") 14 | search_fields = ("name",) 15 | readonly_fields = ("created_datetime",) 16 | 17 | 18 | class TransactionAdmin(admin.ModelAdmin): 19 | list_display = ("user", "plan", "status", "tx_ref", "amount", "created_at") 20 | search_fields = ("user__username", "plan__name", "tx_ref") 21 | readonly_fields = ("created_datetime",) 22 | 23 | 24 | admin.site.register(FlwPlanModel, PlanAdmin) 25 | admin.site.register(FlwTransactionModel, TransactionAdmin) 26 | -------------------------------------------------------------------------------- /djangoflutterwave/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoflutterwaveConfig(AppConfig): 5 | name = 'djangoflutterwave' 6 | -------------------------------------------------------------------------------- /djangoflutterwave/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.4 on 2020-12-18 12:51 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='FlwPlanModel', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=50, unique=True)), 22 | ('amount', models.DecimalField(decimal_places=2, max_digits=9)), 23 | ('flw_plan_id', models.PositiveIntegerField(blank=True, help_text='Flutterwave plan id. Only required if this is a subscription plan.', null=True, unique=True)), 24 | ('currency', models.CharField(default='USD', max_length=3)), 25 | ('modal_logo_url', models.URLField(blank=True, help_text='URL to logo image to be displayed on payment modal.', max_length=500, null=True)), 26 | ('modal_title', models.CharField(blank=True, help_text='Title to be displayed on payment modal.', max_length=200, null=True)), 27 | ('pay_button_text', models.CharField(default='Sign Up', help_text='Text used for button when displayed in a template.', max_length=100)), 28 | ('pay_button_css_classes', models.CharField(blank=True, help_text='css classes to be applied to pay button in template.', max_length=200, null=True)), 29 | ('created_datetime', models.DateTimeField(auto_now_add=True)), 30 | ], 31 | options={ 32 | 'verbose_name': 'Plan', 33 | 'verbose_name_plural': 'Plans', 34 | }, 35 | ), 36 | migrations.CreateModel( 37 | name='FlwTransactionModel', 38 | fields=[ 39 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 40 | ('created_datetime', models.DateTimeField(auto_now_add=True)), 41 | ('tx_ref', models.CharField(max_length=100)), 42 | ('flw_ref', models.CharField(max_length=100)), 43 | ('device_fingerprint', models.CharField(max_length=100)), 44 | ('amount', models.DecimalField(decimal_places=2, max_digits=9)), 45 | ('currency', models.CharField(max_length=3)), 46 | ('charged_amount', models.DecimalField(decimal_places=2, max_digits=9)), 47 | ('app_fee', models.DecimalField(decimal_places=2, max_digits=9)), 48 | ('merchant_fee', models.DecimalField(decimal_places=2, max_digits=9)), 49 | ('processor_response', models.CharField(max_length=100)), 50 | ('auth_model', models.CharField(max_length=100)), 51 | ('ip', models.CharField(max_length=100)), 52 | ('narration', models.CharField(max_length=100)), 53 | ('status', models.CharField(max_length=50)), 54 | ('payment_type', models.CharField(max_length=50)), 55 | ('created_at', models.CharField(max_length=100)), 56 | ('account_id', models.PositiveIntegerField()), 57 | ('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flw_transactions', to='djangoflutterwave.flwplanmodel')), 58 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flw_transactions', to=settings.AUTH_USER_MODEL)), 59 | ], 60 | options={ 61 | 'verbose_name': 'Transaction', 62 | 'verbose_name_plural': 'Transactions', 63 | }, 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /djangoflutterwave/migrations/0002_auto_20201222_0800.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.4 on 2020-12-22 08:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('djangoflutterwave', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='flwplanmodel', 15 | name='interval', 16 | field=models.CharField(blank=True, choices=[('hourly', 'Hourly'), ('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('quarterly', 'Quarterly'), ('bi_annually', 'Bi Annually'), ('yearly', 'Yearly')], help_text='Payment frequency. Only required if this is a subscription plan.', max_length=11, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='flwtransactionmodel', 20 | name='created_at', 21 | field=models.DateTimeField(help_text='Created datetime received from Flutterwave'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /djangoflutterwave/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/djangoflutterwave/migrations/__init__.py -------------------------------------------------------------------------------- /djangoflutterwave/models.py: -------------------------------------------------------------------------------- 1 | # stdlib import 2 | 3 | # django imports 4 | from django.contrib.auth import get_user_model 5 | from django.db import models 6 | 7 | # 3rd party imports 8 | 9 | # project imports 10 | 11 | UserModel = get_user_model() 12 | 13 | 14 | class FlwPlanModel(models.Model): 15 | """Represents either a Plan or OnceOff payment type""" 16 | 17 | HOURLY = "hourly" 18 | DAILY = "daily" 19 | WEEKLY = "weekly" 20 | MONTHLY = "monthly" 21 | QUARTERLY = "quarterly" 22 | BI_ANNUALLY = "bi_annually" 23 | YEARLY = "yearly" 24 | INTERVAL_CHOICES = ( 25 | (HOURLY, "Hourly"), 26 | (DAILY, "Daily"), 27 | (WEEKLY, "Weekly"), 28 | (MONTHLY, "Monthly"), 29 | (QUARTERLY, "Quarterly"), 30 | (BI_ANNUALLY, "Bi Annually"), 31 | (YEARLY, "Yearly"), 32 | ) 33 | name = models.CharField(max_length=50, unique=True) 34 | amount = models.DecimalField(decimal_places=2, max_digits=9) 35 | flw_plan_id = models.PositiveIntegerField( 36 | unique=True, 37 | blank=True, 38 | null=True, 39 | help_text="Flutterwave plan id. Only required if this is a subscription plan.", 40 | ) 41 | interval = models.CharField( 42 | max_length=11, 43 | blank=True, 44 | null=True, 45 | choices=INTERVAL_CHOICES, 46 | help_text="Payment frequency. Only required if this is a subscription plan.", 47 | ) 48 | currency = models.CharField(max_length=3, default="USD") 49 | modal_logo_url = models.URLField( 50 | max_length=500, 51 | blank=True, 52 | null=True, 53 | help_text="URL to logo image to be displayed on payment modal.", 54 | ) 55 | modal_title = models.CharField( 56 | max_length=200, 57 | blank=True, 58 | null=True, 59 | help_text="Title to be displayed on payment modal.", 60 | ) 61 | pay_button_text = models.CharField( 62 | max_length=100, 63 | default="Sign Up", 64 | help_text="Text used for button when displayed in a template.", 65 | ) 66 | pay_button_css_classes = models.CharField( 67 | max_length=200, 68 | blank=True, 69 | null=True, 70 | help_text="css classes to be applied to pay button in template.", 71 | ) 72 | created_datetime = models.DateTimeField(auto_now_add=True) 73 | 74 | class Meta: 75 | verbose_name = "Plan" 76 | verbose_name_plural = "Plans" 77 | 78 | def __str__(self): 79 | return self.name 80 | 81 | 82 | class FlwTransactionModel(models.Model): 83 | """Represents a transaction for a specific payment type and user""" 84 | 85 | plan = models.ForeignKey( 86 | to=FlwPlanModel, related_name="flw_transactions", on_delete=models.CASCADE 87 | ) 88 | user = models.ForeignKey( 89 | to=UserModel, related_name="flw_transactions", on_delete=models.CASCADE 90 | ) 91 | created_datetime = models.DateTimeField(auto_now_add=True) 92 | tx_ref = models.CharField(max_length=100) 93 | flw_ref = models.CharField(max_length=100) 94 | device_fingerprint = models.CharField(max_length=100) 95 | amount = models.DecimalField(decimal_places=2, max_digits=9) 96 | currency = models.CharField(max_length=3) 97 | charged_amount = models.DecimalField(decimal_places=2, max_digits=9) 98 | app_fee = models.DecimalField(decimal_places=2, max_digits=9) 99 | merchant_fee = models.DecimalField(decimal_places=2, max_digits=9) 100 | processor_response = models.CharField(max_length=100) 101 | auth_model = models.CharField(max_length=100) 102 | ip = models.CharField(max_length=100) 103 | narration = models.CharField(max_length=100) 104 | status = models.CharField(max_length=50) 105 | payment_type = models.CharField(max_length=50) 106 | created_at = models.DateTimeField( 107 | help_text="Created datetime received from Flutterwave" 108 | ) 109 | account_id = models.PositiveIntegerField() 110 | 111 | class Meta: 112 | verbose_name = "Transaction" 113 | verbose_name_plural = "Transactions" 114 | 115 | def __str__(self): 116 | return self.tx_ref 117 | -------------------------------------------------------------------------------- /djangoflutterwave/serializers.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.contrib.auth import get_user_model 5 | 6 | # 3rd party imports 7 | from rest_framework import serializers 8 | 9 | # project imports 10 | from djangoflutterwave.models import FlwTransactionModel, FlwPlanModel 11 | 12 | 13 | UserModel = get_user_model() 14 | 15 | 16 | class DRTransactionSerializer(serializers.ModelSerializer): 17 | """Serializer for the FlwTransactionModel Model""" 18 | 19 | class Meta: 20 | model = FlwTransactionModel 21 | fields = ( 22 | "tx_ref", 23 | "flw_ref", 24 | "device_fingerprint", 25 | "amount", 26 | "currency", 27 | "charged_amount", 28 | "app_fee", 29 | "merchant_fee", 30 | "processor_response", 31 | "auth_model", 32 | "ip", 33 | "narration", 34 | "status", 35 | "payment_type", 36 | "created_at", 37 | "account_id", 38 | ) 39 | 40 | def validate_reference(self, value: str) -> str: 41 | """Ensure the received reference contains a valid plan_id and 42 | user_id""" 43 | try: 44 | plan_id = value.split("__")[0] 45 | FlwPlanModel.objects.get(id=plan_id) 46 | except FlwPlanModel.DoesNotExist: 47 | raise serializers.ValidationError("Payment type does not exist") 48 | 49 | try: 50 | user_id = value.split("__")[2] 51 | UserModel.objects.get(id=user_id) 52 | except UserModel.DoesNotExist: 53 | raise serializers.ValidationError("User does not exist") 54 | 55 | return value 56 | -------------------------------------------------------------------------------- /djangoflutterwave/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | djangoflutterwave default settings are defined here and determined in combination 3 | with what has been defined in django.conf.settings. 4 | """ 5 | 6 | # stdlib import 7 | 8 | # django imports 9 | from django.conf import settings 10 | 11 | # 3rd party imports 12 | 13 | # project imports 14 | 15 | FLW_SANDBOX = getattr(settings, "FLW_SANDBOX", True) 16 | 17 | if FLW_SANDBOX: 18 | FLW_PUBLIC_KEY = getattr(settings, "FLW_SANDBOX_PUBLIC_KEY", "not set") 19 | FLW_SECRET_KEY = getattr(settings, "FLW_SANDBOX_SECRET_KEY", "not set") 20 | else: 21 | FLW_PUBLIC_KEY = getattr(settings, "FLW_PRODUCTION_PUBLIC_KEY", "not set") 22 | FLW_SECRET_KEY = getattr(settings, "FLW_PRODUCTION_SECRET_KEY", "not set") 23 | -------------------------------------------------------------------------------- /djangoflutterwave/static/djangoflutterwave/js/payment.js: -------------------------------------------------------------------------------- 1 | const paymentParams = {}; // paymentParams for each pay button on page 2 | 3 | 4 | function makePayment(paymentParams) { 5 | FlutterwaveCheckout({ 6 | ...paymentParams, 7 | callback: function (data) { 8 | // not currently used 9 | }, 10 | onclose: function () { // redirect to redirect_url when modal is closed 11 | window.location.href = paymentParams.redirect_url; 12 | }, 13 | }); 14 | } -------------------------------------------------------------------------------- /djangoflutterwave/templates/djangoflutterwave/pay_button.html: -------------------------------------------------------------------------------- 1 | {% load djangoflutterwave_tags %} 2 | 6 | 33 | 34 | {% if user.is_authenticated %} 35 | 41 | {% else %} 42 |

User must be signed in

43 | {% endif %} -------------------------------------------------------------------------------- /djangoflutterwave/templates/djangoflutterwave/transaction.html: -------------------------------------------------------------------------------- 1 |

Transaction Details

2 | {% if transaction %} 3 |

4 | Reference: {{ transaction.tx_ref }} 5 |

6 |

7 | Created Date / Time: {{ transaction.created_datetime }} 8 |

9 |

10 | Amount: {{ transaction.amount }} 11 |

12 | {% else %} 13 |
14 |

15 | Transaction does not exist. 16 |

17 |

18 | Confirmation of payment might not have been received yet. Please check back again later. 19 |

20 |
21 | {% endif %} -------------------------------------------------------------------------------- /djangoflutterwave/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/djangoflutterwave/templatetags/__init__.py -------------------------------------------------------------------------------- /djangoflutterwave/templatetags/djangoflutterwave_tags.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | import json 3 | 4 | # django imports 5 | from django.shortcuts import reverse 6 | from django import template 7 | 8 | # 3rd party imports 9 | 10 | # project imports 11 | from djangoflutterwave import settings 12 | from djangoflutterwave.utils import create_transaction_ref 13 | 14 | register = template.Library() 15 | 16 | 17 | @register.simple_tag() 18 | def pay_button_params(user_pk: str, plan_pk: str) -> str: 19 | """Returns params required when submitting a payment request to flutterwave. 20 | 21 | Returns: 22 | tx_ref: created by combining plan_pk, timestamp and user_pk 23 | redirect_url: transaction detail page to redirect to 24 | public_key: public key from settings 25 | """ 26 | tx_ref = create_transaction_ref(plan_pk=plan_pk, user_pk=user_pk) 27 | redirect_url = reverse( 28 | "djangoflutterwave:transaction_detail", kwargs={"tx_ref": tx_ref} 29 | ) 30 | return json.dumps( 31 | { 32 | "tx_ref": tx_ref, 33 | "redirect_url": redirect_url, 34 | "public_key": settings.FLW_PUBLIC_KEY, 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /djangoflutterwave/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/djangoflutterwave/tests/__init__.py -------------------------------------------------------------------------------- /djangoflutterwave/tests/factories.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | from datetime import datetime 3 | import pytz 4 | 5 | # django imports 6 | from django.contrib.auth import get_user_model 7 | 8 | # 3rd party imports 9 | from factory import fuzzy 10 | from factory.django import DjangoModelFactory 11 | import factory 12 | 13 | # project imports 14 | from djangoflutterwave.models import FlwPlanModel, FlwTransactionModel 15 | 16 | 17 | class FlwPlanModelFactory(DjangoModelFactory): 18 | """Factory for the FlwPlanModel""" 19 | 20 | name = factory.Faker("word") 21 | amount = fuzzy.FuzzyDecimal(low=20, high=100, precision=2) 22 | currency = fuzzy.FuzzyChoice(choices=["USD", "ZAR", "EUR"]) 23 | modal_title = factory.Faker("word") 24 | 25 | class Meta: 26 | model = FlwPlanModel 27 | 28 | 29 | class UserFactory(DjangoModelFactory): 30 | """Factory for the User model""" 31 | 32 | username = factory.Faker("word") 33 | first_name = factory.Faker("first_name") 34 | last_name = factory.Faker("last_name") 35 | email = factory.Faker("email") 36 | password = factory.PostGenerationMethodCall("set_password", "adminadmin") 37 | is_active = True 38 | is_staff = False 39 | is_superuser = False 40 | 41 | class Meta: 42 | model = get_user_model() 43 | 44 | 45 | class FlwTransactionModelFactory(DjangoModelFactory): 46 | """Factory for the FlwTransactionModel""" 47 | 48 | plan = factory.SubFactory(FlwPlanModelFactory) 49 | user = factory.SubFactory(UserFactory) 50 | tx_ref = factory.Faker("word") 51 | flw_ref = factory.Faker("word") 52 | device_fingerprint = factory.Faker("word") 53 | amount = fuzzy.FuzzyDecimal(low=20, high=100, precision=2) 54 | currency = fuzzy.FuzzyChoice(choices=["USD", "ZAR", "EUR"]) 55 | charged_amount = fuzzy.FuzzyDecimal(low=20, high=100, precision=2) 56 | app_fee = fuzzy.FuzzyDecimal(low=1, high=5, precision=2) 57 | merchant_fee = fuzzy.FuzzyDecimal(low=1, high=5, precision=2) 58 | processor_response = factory.Faker("word") 59 | auth_model = factory.Faker("word") 60 | ip = factory.Faker("word") 61 | narration = factory.Faker("word") 62 | status = fuzzy.FuzzyChoice(choices=["successful", "failed"]) 63 | payment_type = fuzzy.FuzzyChoice(choices=["card", "ussd"]) 64 | created_at = fuzzy.FuzzyDateTime( 65 | start_dt=datetime(2018, 8, 15, tzinfo=pytz.UTC), 66 | end_dt=datetime(2020, 8, 15, tzinfo=pytz.UTC), 67 | ) 68 | account_id = fuzzy.FuzzyInteger(low=1, high=100) 69 | 70 | class Meta: 71 | model = FlwTransactionModel 72 | -------------------------------------------------------------------------------- /djangoflutterwave/tests/test_serializers.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.test import TestCase 5 | 6 | # 3rd party imports 7 | from rest_framework.exceptions import ValidationError 8 | 9 | # project imports 10 | from djangoflutterwave.serializers import DRTransactionSerializer 11 | from djangoflutterwave.tests.factories import FlwPlanModelFactory, UserFactory 12 | 13 | 14 | class TestDRTransactionSerializer(TestCase): 15 | """Test suite for the DRTransactionSerializer""" 16 | 17 | def test_validate_reference(self): 18 | """Ensure the serializer raises an exception for an invalid 19 | plan_id or user_id """ 20 | plan = FlwPlanModelFactory() 21 | user = UserFactory() 22 | 23 | expected_response = f"{plan.id}__test__{user.id}" 24 | actual_response = DRTransactionSerializer.validate_reference( 25 | self=None, value=expected_response 26 | ) 27 | self.assertEqual(expected_response, actual_response) 28 | 29 | with self.assertRaises(ValidationError) as e: 30 | DRTransactionSerializer.validate_reference( 31 | self=None, value=f"123__test__{user.id}" 32 | ) 33 | self.assertEqual(e.exception.detail[0], "Payment type does not exist") 34 | 35 | with self.assertRaises(ValidationError) as e: 36 | DRTransactionSerializer.validate_reference( 37 | self=None, value=f"{plan.id}__test__123" 38 | ) 39 | self.assertEqual(e.exception.detail[0], "User does not exist") 40 | -------------------------------------------------------------------------------- /djangoflutterwave/tests/test_template_tags.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | from unittest.mock import patch 3 | 4 | # django imports 5 | from django.test import TestCase 6 | 7 | # 3rd party imports 8 | 9 | # project imports 10 | from djangoflutterwave.tests.factories import FlwPlanModelFactory, UserFactory 11 | from djangoflutterwave.templatetags.djangoflutterwave_tags import pay_button_params 12 | 13 | 14 | class TestTemplateTags(TestCase): 15 | """Test suite for template tags""" 16 | 17 | @patch( 18 | "djangoflutterwave.templatetags.djangoflutterwave_tags.create_transaction_ref" 19 | ) 20 | @patch("djangoflutterwave.templatetags.djangoflutterwave_tags.settings") 21 | @patch("djangoflutterwave.templatetags.djangoflutterwave_tags.reverse") 22 | def test_pay_button_params( 23 | self, mock_reverse, mock_settings, mock_create_transaction_ref 24 | ): 25 | """Ensure a json string is returned containing the correct tx_ref, 26 | public_key and redirect_url""" 27 | mock_reverse.return_value = "test" 28 | mock_settings.FLW_PUBLIC_KEY = "test" 29 | mock_create_transaction_ref.return_value = "txref" 30 | plan = FlwPlanModelFactory() 31 | user = UserFactory() 32 | 33 | expected_response = ( 34 | '{"tx_ref": "txref"' ', "redirect_url": "test", "public_key": "test"}' 35 | ) 36 | actual_response = pay_button_params(user_pk=user.pk, plan_pk=plan.pk) 37 | mock_reverse.assert_called() 38 | self.assertEqual(expected_response, actual_response) 39 | -------------------------------------------------------------------------------- /djangoflutterwave/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | from unittest.mock import patch 3 | 4 | # django imports 5 | from django.test import TestCase 6 | 7 | # 3rd party imports 8 | 9 | # project imports 10 | from djangoflutterwave.tests.factories import FlwPlanModelFactory, UserFactory 11 | from djangoflutterwave.utils import create_transaction_ref 12 | 13 | 14 | class TestUtils(TestCase): 15 | """Test suite for utils""" 16 | 17 | @patch("djangoflutterwave.utils.timezone") 18 | def test_create_transaction_ref(self, mock_timezone): 19 | """Ensure the correct transaction ref is created""" 20 | mock_timezone.now.return_value.timestamp.return_value = "test" 21 | plan = FlwPlanModelFactory() 22 | user = UserFactory() 23 | res = create_transaction_ref(plan_pk=plan.pk, user_pk=user.pk) 24 | self.assertEqual(res, f"{plan.pk}__test__{user.pk}") 25 | -------------------------------------------------------------------------------- /djangoflutterwave/tests/test_views.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | from datetime import datetime 3 | from unittest.mock import patch 4 | 5 | # django imports 6 | from django.test import TestCase, RequestFactory 7 | 8 | # 3rd party imports 9 | from rest_framework.reverse import reverse 10 | from rest_framework.test import APITestCase, APIRequestFactory 11 | 12 | # project imports 13 | from djangoflutterwave.views import ( 14 | TransactionDetailView, 15 | TransactionCreateView, 16 | PaymentParamsView, 17 | ) 18 | from djangoflutterwave.models import FlwTransactionModel 19 | from djangoflutterwave.serializers import DRTransactionSerializer 20 | from djangoflutterwave.tests.factories import ( 21 | FlwPlanModelFactory, 22 | UserFactory, 23 | FlwTransactionModelFactory, 24 | ) 25 | 26 | 27 | class TestTransactionDetailView(TestCase): 28 | """Test suite for the TransactionDetailView""" 29 | 30 | def test_get_context_data(self): 31 | """Ensure a transaction is added to the context only if a valid user and 32 | reference is provided""" 33 | factory = RequestFactory() 34 | user = UserFactory() 35 | transaction = FlwTransactionModelFactory(user=user) 36 | request = factory.get("test") 37 | request.user = user 38 | view = TransactionDetailView() 39 | view.request = request 40 | 41 | view.kwargs = {"tx_ref": transaction.tx_ref} 42 | context_data = view.get_context_data() 43 | self.assertEqual(transaction, context_data["transaction"]) 44 | 45 | view.kwargs = {"tx_ref": "invalid"} 46 | context_data = view.get_context_data() 47 | self.assertIsNone(context_data["transaction"]) 48 | 49 | 50 | class TestTransactionCreateView(APITestCase): 51 | """Test suite for the TransactionCreateView""" 52 | 53 | def test_perform_create(self): 54 | """Ensure the user and plan are gotten from the reference and saved to the 55 | Transaction instance""" 56 | user = UserFactory() 57 | plan = FlwPlanModelFactory() 58 | factory = APIRequestFactory() 59 | data = { 60 | "tx_ref": f"{plan.id}__test__{user.id}", 61 | "flw_ref": "test", 62 | "device_fingerprint": "test", 63 | "amount": 10.00, 64 | "currency": "USD", 65 | "charged_amount": 9.00, 66 | "app_fee": 0.50, 67 | "merchant_fee": 0.50, 68 | "processor_response": "test", 69 | "auth_model": "test", 70 | "ip": "test", 71 | "narration": "test", 72 | "status": "test", 73 | "payment_type": "test", 74 | "created_at": datetime.now(), 75 | "account_id": 123, 76 | } 77 | request = factory.post("fake-url", data) 78 | request.user = user 79 | view = TransactionCreateView() 80 | view.request = request 81 | serializer = DRTransactionSerializer(data=data) 82 | serializer.is_valid() 83 | view.perform_create(serializer=serializer) 84 | 85 | self.assertEqual(FlwTransactionModel.objects.count(), 1) 86 | transaction = FlwTransactionModel.objects.first() 87 | self.assertEqual(transaction.user.id, user.id) 88 | self.assertEqual(transaction.plan.id, plan.id) 89 | 90 | 91 | class TestPaymentParamsView(APITestCase): 92 | """Test suite for the PaymentParamsView""" 93 | 94 | @patch("djangoflutterwave.views.settings") 95 | @patch("djangoflutterwave.views.create_transaction_ref") 96 | def test_get(self, mock_create_transaction_ref, mock_settings): 97 | """Ensure valid data is returned if a valid plan is provided""" 98 | mock_create_transaction_ref.return_value = "ref" 99 | mock_settings.FLW_PUBLIC_KEY = "pub_key" 100 | user = UserFactory() 101 | plan = FlwPlanModelFactory() 102 | factory = APIRequestFactory() 103 | view = PaymentParamsView() 104 | 105 | url = reverse("djangoflutterwave:payment_params") 106 | request = factory.get(url) 107 | request.user = user 108 | view.request = request 109 | res = view.get(request=request) 110 | self.assertEqual(res.data, "Plan does not exist") 111 | 112 | expected_res = { 113 | "public_key": "pub_key", 114 | "tx_ref": "ref", 115 | "amount": plan.amount, 116 | "currency": plan.currency, 117 | "payment_plan": plan.flw_plan_id, 118 | "customer": { 119 | "email": user.email, 120 | "name": f"{user.first_name} {user.last_name}", 121 | }, 122 | "customizations": {"title": plan.modal_title, "logo": plan.modal_logo_url}, 123 | } 124 | request = factory.get(f"{url}?plan={plan.name}") 125 | request.user = user 126 | view.request = request 127 | res = view.get(request=request) 128 | self.assertEqual(res.data, expected_res) 129 | -------------------------------------------------------------------------------- /djangoflutterwave/urls.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.urls import path 5 | 6 | # 3rd party imports 7 | 8 | # project imports 9 | from djangoflutterwave.views import ( 10 | TransactionCreateView, 11 | TransactionDetailView, 12 | PaymentParamsView, 13 | ) 14 | 15 | 16 | app_name = "djangoflutterwave" 17 | 18 | urlpatterns = [ 19 | path("transaction/", TransactionCreateView.as_view(), name="transaction_create"), 20 | path("payment-params/", PaymentParamsView.as_view(), name="payment_params"), 21 | path("/", TransactionDetailView.as_view(), name="transaction_detail"), 22 | ] 23 | -------------------------------------------------------------------------------- /djangoflutterwave/utils.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | from typing import Union 3 | 4 | # django imports 5 | from django.utils import timezone 6 | 7 | # 3rd party imports 8 | 9 | # project imports 10 | 11 | 12 | def create_transaction_ref(plan_pk: Union[str, int], user_pk: Union[str, int]): 13 | """Create transaction reference""" 14 | now = timezone.now().timestamp() 15 | return f"{plan_pk}__{now}__{user_pk}" 16 | -------------------------------------------------------------------------------- /djangoflutterwave/views.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.contrib.auth.mixins import LoginRequiredMixin 5 | from django.contrib.auth import get_user_model 6 | from django.views.generic import TemplateView 7 | 8 | # 3rd party imports 9 | from rest_framework import status 10 | from rest_framework.generics import CreateAPIView 11 | from rest_framework.permissions import IsAuthenticated 12 | from rest_framework.response import Response 13 | from rest_framework.views import APIView 14 | 15 | # project imports 16 | from djangoflutterwave import settings 17 | from djangoflutterwave.models import FlwTransactionModel, FlwPlanModel 18 | from djangoflutterwave.serializers import DRTransactionSerializer 19 | from djangoflutterwave.utils import create_transaction_ref 20 | 21 | 22 | UserModel = get_user_model() 23 | 24 | 25 | class TransactionDetailView(LoginRequiredMixin, TemplateView): 26 | """Returns a transaction template""" 27 | 28 | template_name = "djangoflutterwave/transaction.html" 29 | 30 | def get_context_data(self, **kwargs): 31 | """Add transaction to context data""" 32 | kwargs = super().get_context_data(**kwargs) 33 | try: 34 | kwargs["transaction"] = FlwTransactionModel.objects.get( 35 | user=self.request.user, tx_ref=self.kwargs["tx_ref"] 36 | ) 37 | except FlwTransactionModel.DoesNotExist: 38 | kwargs["transaction"] = None 39 | return kwargs 40 | 41 | 42 | class TransactionCreateView(CreateAPIView): 43 | """Api end point to create transactions. This is used as a webhook called by 44 | Flutterwave.""" 45 | 46 | queryset = FlwTransactionModel.objects.all() 47 | serializer_class = DRTransactionSerializer 48 | authentication_classes: list = [] 49 | 50 | def create(self, request, *args, **kwargs): 51 | """Override create to specify request.data['data'] for serializer data""" 52 | serializer = self.get_serializer(data=request.data.get("data", None)) 53 | serializer.is_valid(raise_exception=True) 54 | self.perform_create(serializer) 55 | headers = self.get_success_headers(serializer.data) 56 | return Response( 57 | serializer.data, status=status.HTTP_201_CREATED, headers=headers 58 | ) 59 | 60 | def perform_create(self, serializer: DRTransactionSerializer) -> None: 61 | """Add plan and user to Transaction instance, determined from the received 62 | reference""" 63 | reference = serializer.validated_data["tx_ref"] 64 | plan_id = reference.split("__")[0] 65 | user_id = reference.split("__")[2] 66 | serializer.save( 67 | user=UserModel.objects.get(id=user_id), 68 | plan=FlwPlanModel.objects.get(id=plan_id), 69 | ) 70 | 71 | 72 | class PaymentParamsView(APIView): 73 | """Api view for retrieving params required when submiting a payment request to 74 | Flutterwave. End point can be used by SPA's instead of using template payment 75 | button.""" 76 | 77 | permission_classes = [IsAuthenticated] 78 | 79 | def get(self, request, *args, **kwargs): 80 | """Return params based on provided plan name""" 81 | try: 82 | plan = FlwPlanModel.objects.get(name=request.GET.get("plan", None)) 83 | except FlwPlanModel.DoesNotExist: 84 | return Response("Plan does not exist", status=status.HTTP_404_NOT_FOUND) 85 | 86 | data = { 87 | "public_key": settings.FLW_PUBLIC_KEY, 88 | "tx_ref": create_transaction_ref(plan_pk=plan.pk, user_pk=request.user.pk), 89 | "amount": plan.amount, 90 | "currency": plan.currency, 91 | "payment_plan": plan.flw_plan_id, 92 | "customer": { 93 | "email": request.user.email, 94 | "name": f"{request.user.first_name} {request.user.last_name}", 95 | }, 96 | "customizations": {"title": plan.modal_title, "logo": plan.modal_logo_url}, 97 | } 98 | return Response(data) 99 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | 5 | django: 6 | build: . 7 | image: django-flutterwave 8 | command: poetry run ./manage.py runserver 0.0.0.0:8000 9 | volumes: 10 | - .:/code 11 | ports: 12 | - 8000:8000 13 | env_file: 14 | - ./example/env/dev.env -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "!86ss0!tg_+os_s2-tg-#c@ose*wb8cpj8l(-3^o_!sk9-734*" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ["*"] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "rest_framework", 41 | "rest_framework.authtoken", 42 | # project apps 43 | "djangoflutterwave", 44 | "paymanager", 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | "django.middleware.security.SecurityMiddleware", 49 | "django.contrib.sessions.middleware.SessionMiddleware", 50 | "django.middleware.common.CommonMiddleware", 51 | "django.middleware.csrf.CsrfViewMiddleware", 52 | "django.contrib.auth.middleware.AuthenticationMiddleware", 53 | "django.contrib.messages.middleware.MessageMiddleware", 54 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 55 | ] 56 | 57 | ROOT_URLCONF = "example.urls" 58 | 59 | TEMPLATES = [ 60 | { 61 | "BACKEND": "django.template.backends.django.DjangoTemplates", 62 | "DIRS": [os.path.join(BASE_DIR, "templates")], 63 | "APP_DIRS": True, 64 | "OPTIONS": { 65 | "context_processors": [ 66 | "django.template.context_processors.debug", 67 | "django.template.context_processors.request", 68 | "django.contrib.auth.context_processors.auth", 69 | "django.contrib.messages.context_processors.messages", 70 | ] 71 | }, 72 | } 73 | ] 74 | 75 | WSGI_APPLICATION = "example.wsgi.application" 76 | 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 80 | 81 | DATABASES = { 82 | "default": { 83 | "ENGINE": "django.db.backends.sqlite3", 84 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 85 | } 86 | } 87 | 88 | 89 | # Password validation 90 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 91 | 92 | AUTH_PASSWORD_VALIDATORS = [ 93 | { 94 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" 95 | }, 96 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 97 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 98 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 99 | ] 100 | 101 | 102 | # Internationalization 103 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 104 | 105 | LANGUAGE_CODE = "en-us" 106 | 107 | TIME_ZONE = "UTC" 108 | 109 | USE_I18N = True 110 | 111 | USE_L10N = True 112 | 113 | USE_TZ = True 114 | 115 | 116 | # Static files (CSS, JavaScript, Images) 117 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 118 | 119 | STATIC_URL = "/static/" 120 | STATIC_ROOT = "static/" 121 | 122 | # ############### Jupyter notebook ################ 123 | INSTALLED_APPS.append("django_extensions") 124 | NOTEBOOK_ARGUMENTS = [ 125 | "--ip", 126 | "0.0.0.0", 127 | "--port", 128 | "8888", 129 | "--allow-root", 130 | "--no-browser", 131 | "--notebook-dir", 132 | "jupyter_notebooks", 133 | ] 134 | 135 | # ############### rest framework ################ 136 | REST_FRAMEWORK = { 137 | "DEFAULT_AUTHENTICATION_CLASSES": ( 138 | "rest_framework.authentication.TokenAuthentication", 139 | ) 140 | } 141 | 142 | # ############### djangoflutterwave settings ################ 143 | FLW_PRODUCTION_PUBLIC_KEY = os.environ["FLW_PRODUCTION_PUBLIC_KEY"] 144 | FLW_PRODUCTION_SECRET_KEY = os.environ["FLW_PRODUCTION_SECRET_KEY"] 145 | FLW_SANDBOX_PUBLIC_KEY = os.environ["FLW_SANDBOX_PUBLIC_KEY"] 146 | FLW_SANDBOX_SECRET_KEY = os.environ["FLW_SANDBOX_SECRET_KEY"] 147 | FLW_SANDBOX = True 148 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | """example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from rest_framework.authtoken.views import obtain_auth_token 19 | 20 | urlpatterns = [ 21 | path("admin/", admin.site.urls), 22 | path("get-token/", obtain_auth_token, name="get_token"), 23 | path("", include("paymanager.urls", namespace="paymanager")), 24 | path( 25 | "djangoflutterwave/", 26 | include("djangoflutterwave.urls", namespace="djangoflutterwave"), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/jupyter_notebooks/ipython_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Add project base path to sys path. This allows jupyter notebooks to reside 3 | in the jupyter_notebook sub directory (as per notebook-dir in settings) but 4 | allows for clean imports as if jupyter is running from the project root. 5 | """ 6 | import sys 7 | import os 8 | 9 | FILE_PATH = os.path.abspath(os.path.dirname(__file__)) 10 | PROJECT_BASE_PATH = os.path.abspath(os.path.join(FILE_PATH, "..")) 11 | 12 | sys.path.insert(1, PROJECT_BASE_PATH) 13 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /example/paymanager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/example/paymanager/__init__.py -------------------------------------------------------------------------------- /example/paymanager/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /example/paymanager/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PaymanagerConfig(AppConfig): 5 | name = 'paymanager' 6 | -------------------------------------------------------------------------------- /example/paymanager/management/commands/import.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | import sys 3 | 4 | # django imports 5 | from django.contrib.auth import get_user_model 6 | from django.core.management.base import BaseCommand 7 | 8 | # 3rd party imports 9 | 10 | # project imports 11 | from djangoflutterwave.models import FlwPlanModel 12 | from djangoflutterwave.tests.factories import FlwPlanModelFactory, UserFactory 13 | 14 | 15 | class Command(BaseCommand): 16 | """Management command for import dev environment data""" 17 | 18 | help = "Creates payment types to enable a working example" 19 | 20 | def handle(self, *args, **kwargs): 21 | get_user_model().objects.all().delete() 22 | UserFactory(username="admin", is_staff=True, is_superuser=True) 23 | 24 | FlwPlanModel.objects.all().delete() 25 | FlwPlanModelFactory( 26 | name="Once off Purchase", 27 | modal_title="Purchase this item", 28 | currency="USD", 29 | pay_button_text="Buy Now", 30 | ) 31 | FlwPlanModelFactory( 32 | name="Subscription Plan", 33 | modal_title="Sign Up to this plan", 34 | currency="USD", 35 | pay_button_text="Sign Up", 36 | flw_plan_id=123, 37 | ) 38 | print("\nData imported") 39 | -------------------------------------------------------------------------------- /example/paymanager/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdelate/django-flutterwave/845b83e22baf00aa80053124432dffda19778ccc/example/paymanager/migrations/__init__.py -------------------------------------------------------------------------------- /example/paymanager/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /example/paymanager/templates/paymanager/signup.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Sign Up | Example 10 | 11 | 12 | 13 | 14 | 15 | {% include 'djangoflutterwave/pay_button.html' with plan=pro_plan %} 16 | {% include 'djangoflutterwave/pay_button.html' with plan=buy_now %} 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/paymanager/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /example/paymanager/urls.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.urls import path, include 5 | 6 | # 3rd party imports 7 | 8 | # project imports 9 | from paymanager.views import SignUpView 10 | 11 | app_name = "paymanager" 12 | 13 | urlpatterns = [path("", SignUpView.as_view(), name="signup")] 14 | -------------------------------------------------------------------------------- /example/paymanager/views.py: -------------------------------------------------------------------------------- 1 | # stdlib imports 2 | 3 | # django imports 4 | from django.views.generic import TemplateView 5 | 6 | # 3rd party imports 7 | 8 | # project imports 9 | from djangoflutterwave.models import FlwPlanModel 10 | 11 | 12 | class SignUpView(TemplateView): 13 | """Sign Up view""" 14 | 15 | template_name = "paymanager/signup.html" 16 | 17 | def get_context_data(self, **kwargs): 18 | """Add plan to context data""" 19 | context_data = super().get_context_data(**kwargs) 20 | context_data["pro_plan"] = FlwPlanModel.objects.filter( 21 | flw_plan_id__isnull=False 22 | ).first() 23 | context_data["buy_now"] = FlwPlanModel.objects.filter( 24 | flw_plan_id__isnull=True 25 | ).first() 26 | return context_data 27 | -------------------------------------------------------------------------------- /example/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "djangoflutterwave_example" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["bdelate "] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.6 <4" 9 | Django = "^3.1.4" 10 | djangoflutterwave = { path="../", develop = true } 11 | djangorestframework = "^3.12.2" 12 | 13 | [tool.poetry.dev-dependencies] 14 | black = {version = "=19.3b0",allow-prereleases = true} 15 | mypy = "^0.720.0" 16 | twine = "^1.13" 17 | coverage = "^4.5" 18 | django-extensions = "^2.2" 19 | jupyter = "^1.0" 20 | factory-boy = "^3.1.0" 21 | flake8 = "^3.8.4" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "appnope" 11 | version = "0.1.2" 12 | description = "Disable App Nap on macOS >= 10.9" 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "argon2-cffi" 19 | version = "20.1.0" 20 | description = "The secure Argon2 password hashing algorithm." 21 | category = "dev" 22 | optional = false 23 | python-versions = "*" 24 | 25 | [package.dependencies] 26 | cffi = ">=1.0.0" 27 | six = "*" 28 | 29 | [package.extras] 30 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "pre-commit"] 31 | docs = ["sphinx"] 32 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] 33 | 34 | [[package]] 35 | name = "asgiref" 36 | version = "3.3.1" 37 | description = "ASGI specs, helper code, and adapters" 38 | category = "main" 39 | optional = false 40 | python-versions = ">=3.5" 41 | 42 | [package.extras] 43 | tests = ["pytest", "pytest-asyncio"] 44 | 45 | [[package]] 46 | name = "async-generator" 47 | version = "1.10" 48 | description = "Async generators and context managers for Python 3.5+" 49 | category = "dev" 50 | optional = false 51 | python-versions = ">=3.5" 52 | 53 | [[package]] 54 | name = "attrs" 55 | version = "20.3.0" 56 | description = "Classes Without Boilerplate" 57 | category = "dev" 58 | optional = false 59 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 60 | 61 | [package.extras] 62 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 63 | docs = ["furo", "sphinx", "zope.interface"] 64 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 65 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 66 | 67 | [[package]] 68 | name = "backcall" 69 | version = "0.2.0" 70 | description = "Specifications for callback functions passed in to an API" 71 | category = "dev" 72 | optional = false 73 | python-versions = "*" 74 | 75 | [[package]] 76 | name = "black" 77 | version = "19.3b0" 78 | description = "The uncompromising code formatter." 79 | category = "dev" 80 | optional = false 81 | python-versions = ">=3.6" 82 | 83 | [package.dependencies] 84 | appdirs = "*" 85 | attrs = ">=18.1.0" 86 | click = ">=6.5" 87 | toml = ">=0.9.4" 88 | 89 | [package.extras] 90 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 91 | 92 | [[package]] 93 | name = "bleach" 94 | version = "3.2.1" 95 | description = "An easy safelist-based HTML-sanitizing tool." 96 | category = "dev" 97 | optional = false 98 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 99 | 100 | [package.dependencies] 101 | packaging = "*" 102 | six = ">=1.9.0" 103 | webencodings = "*" 104 | 105 | [[package]] 106 | name = "certifi" 107 | version = "2020.12.5" 108 | description = "Python package for providing Mozilla's CA Bundle." 109 | category = "dev" 110 | optional = false 111 | python-versions = "*" 112 | 113 | [[package]] 114 | name = "cffi" 115 | version = "1.14.4" 116 | description = "Foreign Function Interface for Python calling C code." 117 | category = "dev" 118 | optional = false 119 | python-versions = "*" 120 | 121 | [package.dependencies] 122 | pycparser = "*" 123 | 124 | [[package]] 125 | name = "chardet" 126 | version = "4.0.0" 127 | description = "Universal encoding detector for Python 2 and 3" 128 | category = "dev" 129 | optional = false 130 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 131 | 132 | [[package]] 133 | name = "click" 134 | version = "7.1.2" 135 | description = "Composable command line interface toolkit" 136 | category = "dev" 137 | optional = false 138 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 139 | 140 | [[package]] 141 | name = "colorama" 142 | version = "0.4.4" 143 | description = "Cross-platform colored terminal text." 144 | category = "dev" 145 | optional = false 146 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 147 | 148 | [[package]] 149 | name = "coverage" 150 | version = "4.5.4" 151 | description = "Code coverage measurement for Python" 152 | category = "dev" 153 | optional = false 154 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" 155 | 156 | [[package]] 157 | name = "decorator" 158 | version = "4.4.2" 159 | description = "Decorators for Humans" 160 | category = "dev" 161 | optional = false 162 | python-versions = ">=2.6, !=3.0.*, !=3.1.*" 163 | 164 | [[package]] 165 | name = "defusedxml" 166 | version = "0.6.0" 167 | description = "XML bomb protection for Python stdlib modules" 168 | category = "dev" 169 | optional = false 170 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 171 | 172 | [[package]] 173 | name = "django" 174 | version = "3.1.4" 175 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 176 | category = "main" 177 | optional = false 178 | python-versions = ">=3.6" 179 | 180 | [package.dependencies] 181 | asgiref = ">=3.2.10,<4" 182 | pytz = "*" 183 | sqlparse = ">=0.2.2" 184 | 185 | [package.extras] 186 | argon2 = ["argon2-cffi (>=16.1.0)"] 187 | bcrypt = ["bcrypt"] 188 | 189 | [[package]] 190 | name = "django-extensions" 191 | version = "2.2.9" 192 | description = "Extensions for Django" 193 | category = "dev" 194 | optional = false 195 | python-versions = "*" 196 | 197 | [package.dependencies] 198 | six = ">=1.2" 199 | 200 | [[package]] 201 | name = "djangorestframework" 202 | version = "3.12.2" 203 | description = "Web APIs for Django, made easy." 204 | category = "main" 205 | optional = false 206 | python-versions = ">=3.5" 207 | 208 | [package.dependencies] 209 | django = ">=2.2" 210 | 211 | [[package]] 212 | name = "docutils" 213 | version = "0.16" 214 | description = "Docutils -- Python Documentation Utilities" 215 | category = "dev" 216 | optional = false 217 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 218 | 219 | [[package]] 220 | name = "entrypoints" 221 | version = "0.3" 222 | description = "Discover and load entry points from installed packages." 223 | category = "dev" 224 | optional = false 225 | python-versions = ">=2.7" 226 | 227 | [[package]] 228 | name = "factory-boy" 229 | version = "3.1.0" 230 | description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." 231 | category = "dev" 232 | optional = false 233 | python-versions = ">=3.5" 234 | 235 | [package.dependencies] 236 | Faker = ">=0.7.0" 237 | 238 | [package.extras] 239 | dev = ["coverage", "django", "flake8", "isort", "pillow", "sqlalchemy", "mongoengine", "wheel (>=0.32.0)", "tox", "zest.releaser"] 240 | doc = ["sphinx", "sphinx-rtd-theme"] 241 | 242 | [[package]] 243 | name = "faker" 244 | version = "5.0.2" 245 | description = "Faker is a Python package that generates fake data for you." 246 | category = "dev" 247 | optional = false 248 | python-versions = ">=3.6" 249 | 250 | [package.dependencies] 251 | python-dateutil = ">=2.4" 252 | text-unidecode = "1.3" 253 | 254 | [[package]] 255 | name = "flake8" 256 | version = "3.8.4" 257 | description = "the modular source code checker: pep8 pyflakes and co" 258 | category = "dev" 259 | optional = false 260 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 261 | 262 | [package.dependencies] 263 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 264 | mccabe = ">=0.6.0,<0.7.0" 265 | pycodestyle = ">=2.6.0a1,<2.7.0" 266 | pyflakes = ">=2.2.0,<2.3.0" 267 | 268 | [[package]] 269 | name = "idna" 270 | version = "2.10" 271 | description = "Internationalized Domain Names in Applications (IDNA)" 272 | category = "dev" 273 | optional = false 274 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 275 | 276 | [[package]] 277 | name = "importlib-metadata" 278 | version = "3.3.0" 279 | description = "Read metadata from Python packages" 280 | category = "dev" 281 | optional = false 282 | python-versions = ">=3.6" 283 | 284 | [package.dependencies] 285 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 286 | zipp = ">=0.5" 287 | 288 | [package.extras] 289 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 290 | testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 291 | 292 | [[package]] 293 | name = "ipykernel" 294 | version = "5.4.2" 295 | description = "IPython Kernel for Jupyter" 296 | category = "dev" 297 | optional = false 298 | python-versions = ">=3.5" 299 | 300 | [package.dependencies] 301 | appnope = {version = "*", markers = "platform_system == \"Darwin\""} 302 | ipython = ">=5.0.0" 303 | jupyter-client = "*" 304 | tornado = ">=4.2" 305 | traitlets = ">=4.1.0" 306 | 307 | [package.extras] 308 | test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose"] 309 | 310 | [[package]] 311 | name = "ipython" 312 | version = "7.16.1" 313 | description = "IPython: Productive Interactive Computing" 314 | category = "dev" 315 | optional = false 316 | python-versions = ">=3.6" 317 | 318 | [package.dependencies] 319 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 320 | backcall = "*" 321 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 322 | decorator = "*" 323 | jedi = ">=0.10" 324 | pexpect = {version = "*", markers = "sys_platform != \"win32\""} 325 | pickleshare = "*" 326 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 327 | pygments = "*" 328 | traitlets = ">=4.2" 329 | 330 | [package.extras] 331 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] 332 | doc = ["Sphinx (>=1.3)"] 333 | kernel = ["ipykernel"] 334 | nbconvert = ["nbconvert"] 335 | nbformat = ["nbformat"] 336 | notebook = ["notebook", "ipywidgets"] 337 | parallel = ["ipyparallel"] 338 | qtconsole = ["qtconsole"] 339 | test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] 340 | 341 | [[package]] 342 | name = "ipython-genutils" 343 | version = "0.2.0" 344 | description = "Vestigial utilities from IPython" 345 | category = "dev" 346 | optional = false 347 | python-versions = "*" 348 | 349 | [[package]] 350 | name = "ipywidgets" 351 | version = "7.5.1" 352 | description = "IPython HTML widgets for Jupyter" 353 | category = "dev" 354 | optional = false 355 | python-versions = "*" 356 | 357 | [package.dependencies] 358 | ipykernel = ">=4.5.1" 359 | ipython = {version = ">=4.0.0", markers = "python_version >= \"3.3\""} 360 | nbformat = ">=4.2.0" 361 | traitlets = ">=4.3.1" 362 | widgetsnbextension = ">=3.5.0,<3.6.0" 363 | 364 | [package.extras] 365 | test = ["pytest (>=3.6.0)", "pytest-cov", "mock"] 366 | 367 | [[package]] 368 | name = "jedi" 369 | version = "0.17.2" 370 | description = "An autocompletion tool for Python that can be used for text editors." 371 | category = "dev" 372 | optional = false 373 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 374 | 375 | [package.dependencies] 376 | parso = ">=0.7.0,<0.8.0" 377 | 378 | [package.extras] 379 | qa = ["flake8 (3.7.9)"] 380 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] 381 | 382 | [[package]] 383 | name = "jinja2" 384 | version = "2.11.2" 385 | description = "A very fast and expressive template engine." 386 | category = "dev" 387 | optional = false 388 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 389 | 390 | [package.dependencies] 391 | MarkupSafe = ">=0.23" 392 | 393 | [package.extras] 394 | i18n = ["Babel (>=0.8)"] 395 | 396 | [[package]] 397 | name = "jsonschema" 398 | version = "3.2.0" 399 | description = "An implementation of JSON Schema validation for Python" 400 | category = "dev" 401 | optional = false 402 | python-versions = "*" 403 | 404 | [package.dependencies] 405 | attrs = ">=17.4.0" 406 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 407 | pyrsistent = ">=0.14.0" 408 | six = ">=1.11.0" 409 | 410 | [package.extras] 411 | format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] 412 | format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] 413 | 414 | [[package]] 415 | name = "jupyter" 416 | version = "1.0.0" 417 | description = "Jupyter metapackage. Install all the Jupyter components in one go." 418 | category = "dev" 419 | optional = false 420 | python-versions = "*" 421 | 422 | [package.dependencies] 423 | ipykernel = "*" 424 | ipywidgets = "*" 425 | jupyter-console = "*" 426 | nbconvert = "*" 427 | notebook = "*" 428 | qtconsole = "*" 429 | 430 | [[package]] 431 | name = "jupyter-client" 432 | version = "6.1.7" 433 | description = "Jupyter protocol implementation and client libraries" 434 | category = "dev" 435 | optional = false 436 | python-versions = ">=3.5" 437 | 438 | [package.dependencies] 439 | jupyter-core = ">=4.6.0" 440 | python-dateutil = ">=2.1" 441 | pyzmq = ">=13" 442 | tornado = ">=4.1" 443 | traitlets = "*" 444 | 445 | [package.extras] 446 | test = ["ipykernel", "ipython", "mock", "pytest", "pytest-asyncio", "async-generator", "pytest-timeout"] 447 | 448 | [[package]] 449 | name = "jupyter-console" 450 | version = "6.2.0" 451 | description = "Jupyter terminal console" 452 | category = "dev" 453 | optional = false 454 | python-versions = ">=3.6" 455 | 456 | [package.dependencies] 457 | ipykernel = "*" 458 | ipython = "*" 459 | jupyter-client = "*" 460 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 461 | pygments = "*" 462 | 463 | [package.extras] 464 | test = ["pexpect"] 465 | 466 | [[package]] 467 | name = "jupyter-core" 468 | version = "4.7.0" 469 | description = "Jupyter core package. A base package on which Jupyter projects rely." 470 | category = "dev" 471 | optional = false 472 | python-versions = ">=3.6" 473 | 474 | [package.dependencies] 475 | pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""} 476 | traitlets = "*" 477 | 478 | [[package]] 479 | name = "jupyterlab-pygments" 480 | version = "0.1.2" 481 | description = "Pygments theme using JupyterLab CSS variables" 482 | category = "dev" 483 | optional = false 484 | python-versions = "*" 485 | 486 | [package.dependencies] 487 | pygments = ">=2.4.1,<3" 488 | 489 | [[package]] 490 | name = "markupsafe" 491 | version = "1.1.1" 492 | description = "Safely add untrusted strings to HTML/XML markup." 493 | category = "dev" 494 | optional = false 495 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 496 | 497 | [[package]] 498 | name = "mccabe" 499 | version = "0.6.1" 500 | description = "McCabe checker, plugin for flake8" 501 | category = "dev" 502 | optional = false 503 | python-versions = "*" 504 | 505 | [[package]] 506 | name = "mistune" 507 | version = "0.8.4" 508 | description = "The fastest markdown parser in pure Python" 509 | category = "dev" 510 | optional = false 511 | python-versions = "*" 512 | 513 | [[package]] 514 | name = "mypy" 515 | version = "0.720" 516 | description = "Optional static typing for Python" 517 | category = "dev" 518 | optional = false 519 | python-versions = "*" 520 | 521 | [package.dependencies] 522 | mypy-extensions = ">=0.4.0,<0.5.0" 523 | typed-ast = ">=1.4.0,<1.5.0" 524 | typing-extensions = ">=3.7.4" 525 | 526 | [package.extras] 527 | dmypy = ["psutil (>=4.0)"] 528 | 529 | [[package]] 530 | name = "mypy-extensions" 531 | version = "0.4.3" 532 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 533 | category = "dev" 534 | optional = false 535 | python-versions = "*" 536 | 537 | [[package]] 538 | name = "nbclient" 539 | version = "0.5.1" 540 | description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." 541 | category = "dev" 542 | optional = false 543 | python-versions = ">=3.6" 544 | 545 | [package.dependencies] 546 | async-generator = "*" 547 | jupyter-client = ">=6.1.5" 548 | nbformat = ">=5.0" 549 | nest-asyncio = "*" 550 | traitlets = ">=4.2" 551 | 552 | [package.extras] 553 | dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] 554 | sphinx = ["Sphinx (>=1.7)", "sphinx-book-theme", "mock", "moto", "myst-parser"] 555 | test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"] 556 | 557 | [[package]] 558 | name = "nbconvert" 559 | version = "6.0.7" 560 | description = "Converting Jupyter Notebooks" 561 | category = "dev" 562 | optional = false 563 | python-versions = ">=3.6" 564 | 565 | [package.dependencies] 566 | bleach = "*" 567 | defusedxml = "*" 568 | entrypoints = ">=0.2.2" 569 | jinja2 = ">=2.4" 570 | jupyter-core = "*" 571 | jupyterlab-pygments = "*" 572 | mistune = ">=0.8.1,<2" 573 | nbclient = ">=0.5.0,<0.6.0" 574 | nbformat = ">=4.4" 575 | pandocfilters = ">=1.4.1" 576 | pygments = ">=2.4.1" 577 | testpath = "*" 578 | traitlets = ">=4.2" 579 | 580 | [package.extras] 581 | all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] 582 | docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"] 583 | serve = ["tornado (>=4.0)"] 584 | test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)"] 585 | webpdf = ["pyppeteer (0.2.2)"] 586 | 587 | [[package]] 588 | name = "nbformat" 589 | version = "5.0.8" 590 | description = "The Jupyter Notebook format" 591 | category = "dev" 592 | optional = false 593 | python-versions = ">=3.5" 594 | 595 | [package.dependencies] 596 | ipython-genutils = "*" 597 | jsonschema = ">=2.4,<2.5.0 || >2.5.0" 598 | jupyter-core = "*" 599 | traitlets = ">=4.1" 600 | 601 | [package.extras] 602 | fast = ["fastjsonschema"] 603 | test = ["fastjsonschema", "testpath", "pytest", "pytest-cov"] 604 | 605 | [[package]] 606 | name = "nest-asyncio" 607 | version = "1.4.3" 608 | description = "Patch asyncio to allow nested event loops" 609 | category = "dev" 610 | optional = false 611 | python-versions = ">=3.5" 612 | 613 | [[package]] 614 | name = "notebook" 615 | version = "6.1.5" 616 | description = "A web-based notebook environment for interactive computing" 617 | category = "dev" 618 | optional = false 619 | python-versions = ">=3.5" 620 | 621 | [package.dependencies] 622 | argon2-cffi = "*" 623 | ipykernel = "*" 624 | ipython-genutils = "*" 625 | jinja2 = "*" 626 | jupyter-client = ">=5.3.4" 627 | jupyter-core = ">=4.6.1" 628 | nbconvert = "*" 629 | nbformat = "*" 630 | prometheus-client = "*" 631 | pyzmq = ">=17" 632 | Send2Trash = "*" 633 | terminado = ">=0.8.3" 634 | tornado = ">=5.0" 635 | traitlets = ">=4.2.1" 636 | 637 | [package.extras] 638 | docs = ["sphinx", "nbsphinx", "sphinxcontrib-github-alt"] 639 | test = ["nose", "coverage", "requests", "nose-warnings-filters", "nbval", "nose-exclude", "selenium", "pytest", "pytest-cov", "requests-unixsocket"] 640 | 641 | [[package]] 642 | name = "packaging" 643 | version = "20.8" 644 | description = "Core utilities for Python packages" 645 | category = "dev" 646 | optional = false 647 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 648 | 649 | [package.dependencies] 650 | pyparsing = ">=2.0.2" 651 | 652 | [[package]] 653 | name = "pandocfilters" 654 | version = "1.4.3" 655 | description = "Utilities for writing pandoc filters in python" 656 | category = "dev" 657 | optional = false 658 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 659 | 660 | [[package]] 661 | name = "parso" 662 | version = "0.7.1" 663 | description = "A Python Parser" 664 | category = "dev" 665 | optional = false 666 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 667 | 668 | [package.extras] 669 | testing = ["docopt", "pytest (>=3.0.7)"] 670 | 671 | [[package]] 672 | name = "pexpect" 673 | version = "4.8.0" 674 | description = "Pexpect allows easy control of interactive console applications." 675 | category = "dev" 676 | optional = false 677 | python-versions = "*" 678 | 679 | [package.dependencies] 680 | ptyprocess = ">=0.5" 681 | 682 | [[package]] 683 | name = "pickleshare" 684 | version = "0.7.5" 685 | description = "Tiny 'shelve'-like database with concurrency support" 686 | category = "dev" 687 | optional = false 688 | python-versions = "*" 689 | 690 | [[package]] 691 | name = "pkginfo" 692 | version = "1.6.1" 693 | description = "Query metadatdata from sdists / bdists / installed packages." 694 | category = "dev" 695 | optional = false 696 | python-versions = "*" 697 | 698 | [package.extras] 699 | testing = ["nose", "coverage"] 700 | 701 | [[package]] 702 | name = "prometheus-client" 703 | version = "0.9.0" 704 | description = "Python client for the Prometheus monitoring system." 705 | category = "dev" 706 | optional = false 707 | python-versions = "*" 708 | 709 | [package.extras] 710 | twisted = ["twisted"] 711 | 712 | [[package]] 713 | name = "prompt-toolkit" 714 | version = "3.0.3" 715 | description = "Library for building powerful interactive command lines in Python" 716 | category = "dev" 717 | optional = false 718 | python-versions = ">=3.6" 719 | 720 | [package.dependencies] 721 | wcwidth = "*" 722 | 723 | [[package]] 724 | name = "ptyprocess" 725 | version = "0.6.0" 726 | description = "Run a subprocess in a pseudo terminal" 727 | category = "dev" 728 | optional = false 729 | python-versions = "*" 730 | 731 | [[package]] 732 | name = "py" 733 | version = "1.10.0" 734 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 735 | category = "dev" 736 | optional = false 737 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 738 | 739 | [[package]] 740 | name = "pycodestyle" 741 | version = "2.6.0" 742 | description = "Python style guide checker" 743 | category = "dev" 744 | optional = false 745 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 746 | 747 | [[package]] 748 | name = "pycparser" 749 | version = "2.20" 750 | description = "C parser in Python" 751 | category = "dev" 752 | optional = false 753 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 754 | 755 | [[package]] 756 | name = "pyflakes" 757 | version = "2.2.0" 758 | description = "passive checker of Python programs" 759 | category = "dev" 760 | optional = false 761 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 762 | 763 | [[package]] 764 | name = "pygments" 765 | version = "2.7.3" 766 | description = "Pygments is a syntax highlighting package written in Python." 767 | category = "dev" 768 | optional = false 769 | python-versions = ">=3.5" 770 | 771 | [[package]] 772 | name = "pyparsing" 773 | version = "2.4.7" 774 | description = "Python parsing module" 775 | category = "dev" 776 | optional = false 777 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 778 | 779 | [[package]] 780 | name = "pyrsistent" 781 | version = "0.17.3" 782 | description = "Persistent/Functional/Immutable data structures" 783 | category = "dev" 784 | optional = false 785 | python-versions = ">=3.5" 786 | 787 | [[package]] 788 | name = "python-dateutil" 789 | version = "2.8.1" 790 | description = "Extensions to the standard Python datetime module" 791 | category = "dev" 792 | optional = false 793 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 794 | 795 | [package.dependencies] 796 | six = ">=1.5" 797 | 798 | [[package]] 799 | name = "pytz" 800 | version = "2020.4" 801 | description = "World timezone definitions, modern and historical" 802 | category = "main" 803 | optional = false 804 | python-versions = "*" 805 | 806 | [[package]] 807 | name = "pywin32" 808 | version = "300" 809 | description = "Python for Window Extensions" 810 | category = "dev" 811 | optional = false 812 | python-versions = "*" 813 | 814 | [[package]] 815 | name = "pywinpty" 816 | version = "0.5.7" 817 | description = "Python bindings for the winpty library" 818 | category = "dev" 819 | optional = false 820 | python-versions = "*" 821 | 822 | [[package]] 823 | name = "pyzmq" 824 | version = "20.0.0" 825 | description = "Python bindings for 0MQ" 826 | category = "dev" 827 | optional = false 828 | python-versions = ">=3.5" 829 | 830 | [package.dependencies] 831 | cffi = {version = "*", markers = "implementation_name === \"pypy\""} 832 | py = {version = "*", markers = "implementation_name === \"pypy\""} 833 | 834 | [[package]] 835 | name = "qtconsole" 836 | version = "5.0.1" 837 | description = "Jupyter Qt console" 838 | category = "dev" 839 | optional = false 840 | python-versions = ">= 3.6" 841 | 842 | [package.dependencies] 843 | ipykernel = ">=4.1" 844 | ipython-genutils = "*" 845 | jupyter-client = ">=4.1" 846 | jupyter-core = "*" 847 | pygments = "*" 848 | pyzmq = ">=17.1" 849 | qtpy = "*" 850 | traitlets = "*" 851 | 852 | [package.extras] 853 | doc = ["Sphinx (>=1.3)"] 854 | test = ["flaky", "pytest", "pytest-qt"] 855 | 856 | [[package]] 857 | name = "qtpy" 858 | version = "1.9.0" 859 | description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5, PyQt4 and PySide) and additional custom QWidgets." 860 | category = "dev" 861 | optional = false 862 | python-versions = "*" 863 | 864 | [[package]] 865 | name = "readme-renderer" 866 | version = "28.0" 867 | description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" 868 | category = "dev" 869 | optional = false 870 | python-versions = "*" 871 | 872 | [package.dependencies] 873 | bleach = ">=2.1.0" 874 | docutils = ">=0.13.1" 875 | Pygments = ">=2.5.1" 876 | six = "*" 877 | 878 | [package.extras] 879 | md = ["cmarkgfm (>=0.2.0)"] 880 | 881 | [[package]] 882 | name = "requests" 883 | version = "2.25.1" 884 | description = "Python HTTP for Humans." 885 | category = "dev" 886 | optional = false 887 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 888 | 889 | [package.dependencies] 890 | certifi = ">=2017.4.17" 891 | chardet = ">=3.0.2,<5" 892 | idna = ">=2.5,<3" 893 | urllib3 = ">=1.21.1,<1.27" 894 | 895 | [package.extras] 896 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 897 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] 898 | 899 | [[package]] 900 | name = "requests-toolbelt" 901 | version = "0.9.1" 902 | description = "A utility belt for advanced users of python-requests" 903 | category = "dev" 904 | optional = false 905 | python-versions = "*" 906 | 907 | [package.dependencies] 908 | requests = ">=2.0.1,<3.0.0" 909 | 910 | [[package]] 911 | name = "send2trash" 912 | version = "1.5.0" 913 | description = "Send file to trash natively under Mac OS X, Windows and Linux." 914 | category = "dev" 915 | optional = false 916 | python-versions = "*" 917 | 918 | [[package]] 919 | name = "six" 920 | version = "1.15.0" 921 | description = "Python 2 and 3 compatibility utilities" 922 | category = "dev" 923 | optional = false 924 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 925 | 926 | [[package]] 927 | name = "sqlparse" 928 | version = "0.4.1" 929 | description = "A non-validating SQL parser." 930 | category = "main" 931 | optional = false 932 | python-versions = ">=3.5" 933 | 934 | [[package]] 935 | name = "terminado" 936 | version = "0.9.1" 937 | description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." 938 | category = "dev" 939 | optional = false 940 | python-versions = ">=3.6" 941 | 942 | [package.dependencies] 943 | ptyprocess = {version = "*", markers = "os_name != \"nt\""} 944 | pywinpty = {version = ">=0.5", markers = "os_name == \"nt\""} 945 | tornado = ">=4" 946 | 947 | [[package]] 948 | name = "testpath" 949 | version = "0.4.4" 950 | description = "Test utilities for code working with files and commands" 951 | category = "dev" 952 | optional = false 953 | python-versions = "*" 954 | 955 | [package.extras] 956 | test = ["pathlib2"] 957 | 958 | [[package]] 959 | name = "text-unidecode" 960 | version = "1.3" 961 | description = "The most basic Text::Unidecode port" 962 | category = "dev" 963 | optional = false 964 | python-versions = "*" 965 | 966 | [[package]] 967 | name = "toml" 968 | version = "0.10.2" 969 | description = "Python Library for Tom's Obvious, Minimal Language" 970 | category = "dev" 971 | optional = false 972 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 973 | 974 | [[package]] 975 | name = "tornado" 976 | version = "6.1" 977 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 978 | category = "dev" 979 | optional = false 980 | python-versions = ">= 3.5" 981 | 982 | [[package]] 983 | name = "tqdm" 984 | version = "4.54.1" 985 | description = "Fast, Extensible Progress Meter" 986 | category = "dev" 987 | optional = false 988 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 989 | 990 | [package.extras] 991 | dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown", "wheel"] 992 | 993 | [[package]] 994 | name = "traitlets" 995 | version = "4.3.3" 996 | description = "Traitlets Python config system" 997 | category = "dev" 998 | optional = false 999 | python-versions = "*" 1000 | 1001 | [package.dependencies] 1002 | decorator = "*" 1003 | ipython-genutils = "*" 1004 | six = "*" 1005 | 1006 | [package.extras] 1007 | test = ["pytest", "mock"] 1008 | 1009 | [[package]] 1010 | name = "twine" 1011 | version = "1.15.0" 1012 | description = "Collection of utilities for publishing packages on PyPI" 1013 | category = "dev" 1014 | optional = false 1015 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 1016 | 1017 | [package.dependencies] 1018 | pkginfo = ">=1.4.2" 1019 | readme-renderer = ">=21.0" 1020 | requests = ">=2.5.0,<2.15 || >2.15,<2.16 || >2.16" 1021 | requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" 1022 | tqdm = ">=4.14" 1023 | 1024 | [package.extras] 1025 | keyring = ["keyring"] 1026 | with-blake2 = ["pyblake2"] 1027 | 1028 | [[package]] 1029 | name = "typed-ast" 1030 | version = "1.4.1" 1031 | description = "a fork of Python 2 and 3 ast modules with type comment support" 1032 | category = "dev" 1033 | optional = false 1034 | python-versions = "*" 1035 | 1036 | [[package]] 1037 | name = "typing-extensions" 1038 | version = "3.7.4.3" 1039 | description = "Backported and Experimental Type Hints for Python 3.5+" 1040 | category = "dev" 1041 | optional = false 1042 | python-versions = "*" 1043 | 1044 | [[package]] 1045 | name = "urllib3" 1046 | version = "1.26.2" 1047 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1048 | category = "dev" 1049 | optional = false 1050 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 1051 | 1052 | [package.extras] 1053 | brotli = ["brotlipy (>=0.6.0)"] 1054 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 1055 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 1056 | 1057 | [[package]] 1058 | name = "wcwidth" 1059 | version = "0.2.5" 1060 | description = "Measures the displayed width of unicode strings in a terminal" 1061 | category = "dev" 1062 | optional = false 1063 | python-versions = "*" 1064 | 1065 | [[package]] 1066 | name = "webencodings" 1067 | version = "0.5.1" 1068 | description = "Character encoding aliases for legacy web content" 1069 | category = "dev" 1070 | optional = false 1071 | python-versions = "*" 1072 | 1073 | [[package]] 1074 | name = "widgetsnbextension" 1075 | version = "3.5.1" 1076 | description = "IPython HTML widgets for Jupyter" 1077 | category = "dev" 1078 | optional = false 1079 | python-versions = "*" 1080 | 1081 | [package.dependencies] 1082 | notebook = ">=4.4.1" 1083 | 1084 | [[package]] 1085 | name = "zipp" 1086 | version = "3.4.0" 1087 | description = "Backport of pathlib-compatible object wrapper for zip files" 1088 | category = "dev" 1089 | optional = false 1090 | python-versions = ">=3.6" 1091 | 1092 | [package.extras] 1093 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 1094 | testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 1095 | 1096 | [metadata] 1097 | lock-version = "1.1" 1098 | python-versions = ">=3.6 <4" 1099 | content-hash = "de01c0462c498229b735e36036e5c5902b532d4d1a893dd1786e6f9fda20af7f" 1100 | 1101 | [metadata.files] 1102 | appdirs = [ 1103 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 1104 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 1105 | ] 1106 | appnope = [ 1107 | {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, 1108 | {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, 1109 | ] 1110 | argon2-cffi = [ 1111 | {file = "argon2-cffi-20.1.0.tar.gz", hash = "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d"}, 1112 | {file = "argon2_cffi-20.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003"}, 1113 | {file = "argon2_cffi-20.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf"}, 1114 | {file = "argon2_cffi-20.1.0-cp27-cp27m-win32.whl", hash = "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5"}, 1115 | {file = "argon2_cffi-20.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc"}, 1116 | {file = "argon2_cffi-20.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe"}, 1117 | {file = "argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647"}, 1118 | {file = "argon2_cffi-20.1.0-cp35-cp35m-win32.whl", hash = "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361"}, 1119 | {file = "argon2_cffi-20.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b"}, 1120 | {file = "argon2_cffi-20.1.0-cp36-cp36m-win32.whl", hash = "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496"}, 1121 | {file = "argon2_cffi-20.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa"}, 1122 | {file = "argon2_cffi-20.1.0-cp37-abi3-macosx_10_6_intel.whl", hash = "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b"}, 1123 | {file = "argon2_cffi-20.1.0-cp37-cp37m-win32.whl", hash = "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5"}, 1124 | {file = "argon2_cffi-20.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203"}, 1125 | {file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"}, 1126 | {file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"}, 1127 | ] 1128 | asgiref = [ 1129 | {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, 1130 | {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, 1131 | ] 1132 | async-generator = [ 1133 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, 1134 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, 1135 | ] 1136 | attrs = [ 1137 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 1138 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 1139 | ] 1140 | backcall = [ 1141 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 1142 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 1143 | ] 1144 | black = [ 1145 | {file = "black-19.3b0-py36-none-any.whl", hash = "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf"}, 1146 | {file = "black-19.3b0.tar.gz", hash = "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"}, 1147 | ] 1148 | bleach = [ 1149 | {file = "bleach-3.2.1-py2.py3-none-any.whl", hash = "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"}, 1150 | {file = "bleach-3.2.1.tar.gz", hash = "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080"}, 1151 | ] 1152 | certifi = [ 1153 | {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, 1154 | {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, 1155 | ] 1156 | cffi = [ 1157 | {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, 1158 | {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, 1159 | {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, 1160 | {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, 1161 | {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, 1162 | {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, 1163 | {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, 1164 | {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, 1165 | {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, 1166 | {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, 1167 | {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, 1168 | {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, 1169 | {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, 1170 | {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, 1171 | {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, 1172 | {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, 1173 | {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, 1174 | {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, 1175 | {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, 1176 | {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, 1177 | {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, 1178 | {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, 1179 | {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, 1180 | {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, 1181 | {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, 1182 | {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, 1183 | {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, 1184 | {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, 1185 | {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, 1186 | {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, 1187 | {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, 1188 | {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, 1189 | {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, 1190 | {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, 1191 | {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, 1192 | {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, 1193 | ] 1194 | chardet = [ 1195 | {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, 1196 | {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, 1197 | ] 1198 | click = [ 1199 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 1200 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 1201 | ] 1202 | colorama = [ 1203 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 1204 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 1205 | ] 1206 | coverage = [ 1207 | {file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"}, 1208 | {file = "coverage-4.5.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c"}, 1209 | {file = "coverage-4.5.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce"}, 1210 | {file = "coverage-4.5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe"}, 1211 | {file = "coverage-4.5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888"}, 1212 | {file = "coverage-4.5.4-cp27-cp27m-win32.whl", hash = "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc"}, 1213 | {file = "coverage-4.5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24"}, 1214 | {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437"}, 1215 | {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6"}, 1216 | {file = "coverage-4.5.4-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5"}, 1217 | {file = "coverage-4.5.4-cp34-cp34m-macosx_10_12_x86_64.whl", hash = "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef"}, 1218 | {file = "coverage-4.5.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e"}, 1219 | {file = "coverage-4.5.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca"}, 1220 | {file = "coverage-4.5.4-cp34-cp34m-win32.whl", hash = "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0"}, 1221 | {file = "coverage-4.5.4-cp34-cp34m-win_amd64.whl", hash = "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1"}, 1222 | {file = "coverage-4.5.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7"}, 1223 | {file = "coverage-4.5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47"}, 1224 | {file = "coverage-4.5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"}, 1225 | {file = "coverage-4.5.4-cp35-cp35m-win32.whl", hash = "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e"}, 1226 | {file = "coverage-4.5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d"}, 1227 | {file = "coverage-4.5.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9"}, 1228 | {file = "coverage-4.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755"}, 1229 | {file = "coverage-4.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9"}, 1230 | {file = "coverage-4.5.4-cp36-cp36m-win32.whl", hash = "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f"}, 1231 | {file = "coverage-4.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5"}, 1232 | {file = "coverage-4.5.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca"}, 1233 | {file = "coverage-4.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650"}, 1234 | {file = "coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2"}, 1235 | {file = "coverage-4.5.4-cp37-cp37m-win32.whl", hash = "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5"}, 1236 | {file = "coverage-4.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351"}, 1237 | {file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"}, 1238 | {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, 1239 | ] 1240 | decorator = [ 1241 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, 1242 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, 1243 | ] 1244 | defusedxml = [ 1245 | {file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"}, 1246 | {file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"}, 1247 | ] 1248 | django = [ 1249 | {file = "Django-3.1.4-py3-none-any.whl", hash = "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2"}, 1250 | {file = "Django-3.1.4.tar.gz", hash = "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"}, 1251 | ] 1252 | django-extensions = [ 1253 | {file = "django-extensions-2.2.9.tar.gz", hash = "sha256:2f81b618ba4d1b0e58603e25012e5c74f88a4b706e0022a3b21f24f0322a6ce6"}, 1254 | {file = "django_extensions-2.2.9-py2.py3-none-any.whl", hash = "sha256:b19182d101a441fe001c5753553a901e2ef3ff60e8fbbe38881eb4a61fdd17c4"}, 1255 | ] 1256 | djangorestframework = [ 1257 | {file = "djangorestframework-3.12.2-py3-none-any.whl", hash = "sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7"}, 1258 | ] 1259 | docutils = [ 1260 | {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, 1261 | {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, 1262 | ] 1263 | entrypoints = [ 1264 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, 1265 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, 1266 | ] 1267 | factory-boy = [ 1268 | {file = "factory_boy-3.1.0-py2.py3-none-any.whl", hash = "sha256:d8626622550c8ba31392f9e19fdbcef9f139cf1ad643c5923f20490a7b3e2e3d"}, 1269 | {file = "factory_boy-3.1.0.tar.gz", hash = "sha256:ded73e49135c24bd4d3f45bf1eb168f8d290090f5cf4566b8df3698317dc9c08"}, 1270 | ] 1271 | faker = [ 1272 | {file = "Faker-5.0.2-py3-none-any.whl", hash = "sha256:5b17c95cfb013a22b062b8df18286f08ce4ea880f9948ec74295e5a42dbb2e44"}, 1273 | {file = "Faker-5.0.2.tar.gz", hash = "sha256:00ce4342c221b1931b2f35d46f5027d35bc62a4ca3a34628b2c5b514b4ca958a"}, 1274 | ] 1275 | flake8 = [ 1276 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 1277 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 1278 | ] 1279 | idna = [ 1280 | {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, 1281 | {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, 1282 | ] 1283 | importlib-metadata = [ 1284 | {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, 1285 | {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, 1286 | ] 1287 | ipykernel = [ 1288 | {file = "ipykernel-5.4.2-py3-none-any.whl", hash = "sha256:63b4b96c513e1138874934e3e783a8e5e13c02b9036e37107bfe042ac8955005"}, 1289 | {file = "ipykernel-5.4.2.tar.gz", hash = "sha256:e20ceb7e52cb4d250452e1230be76e0b2323f33bd46c6b2bc7abb6601740e182"}, 1290 | ] 1291 | ipython = [ 1292 | {file = "ipython-7.16.1-py3-none-any.whl", hash = "sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64"}, 1293 | {file = "ipython-7.16.1.tar.gz", hash = "sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"}, 1294 | ] 1295 | ipython-genutils = [ 1296 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 1297 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 1298 | ] 1299 | ipywidgets = [ 1300 | {file = "ipywidgets-7.5.1-py2.py3-none-any.whl", hash = "sha256:13ffeca438e0c0f91ae583dc22f50379b9d6b28390ac7be8b757140e9a771516"}, 1301 | {file = "ipywidgets-7.5.1.tar.gz", hash = "sha256:e945f6e02854a74994c596d9db83444a1850c01648f1574adf144fbbabe05c97"}, 1302 | ] 1303 | jedi = [ 1304 | {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, 1305 | {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, 1306 | ] 1307 | jinja2 = [ 1308 | {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, 1309 | {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, 1310 | ] 1311 | jsonschema = [ 1312 | {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, 1313 | {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, 1314 | ] 1315 | jupyter = [ 1316 | {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, 1317 | {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, 1318 | {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, 1319 | ] 1320 | jupyter-client = [ 1321 | {file = "jupyter_client-6.1.7-py3-none-any.whl", hash = "sha256:c958d24d6eacb975c1acebb68ac9077da61b5f5c040f22f6849928ad7393b950"}, 1322 | {file = "jupyter_client-6.1.7.tar.gz", hash = "sha256:49e390b36fe4b4226724704ea28d9fb903f1a3601b6882ce3105221cd09377a1"}, 1323 | ] 1324 | jupyter-console = [ 1325 | {file = "jupyter_console-6.2.0-py3-none-any.whl", hash = "sha256:1d80c06b2d85bfb10bd5cc731b3db18e9023bc81ab00491d3ac31f206490aee3"}, 1326 | {file = "jupyter_console-6.2.0.tar.gz", hash = "sha256:7f6194f4f4692d292da3f501c7f343ccd5e36c6a1becf7b7515e23e66d6bf1e9"}, 1327 | ] 1328 | jupyter-core = [ 1329 | {file = "jupyter_core-4.7.0-py3-none-any.whl", hash = "sha256:0a451c9b295e4db772bdd8d06f2f1eb31caeec0e81fbb77ba37d4a3024e3b315"}, 1330 | {file = "jupyter_core-4.7.0.tar.gz", hash = "sha256:aa1f9496ab3abe72da4efe0daab0cb2233997914581f9a071e07498c6add8ed3"}, 1331 | ] 1332 | jupyterlab-pygments = [ 1333 | {file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"}, 1334 | {file = "jupyterlab_pygments-0.1.2.tar.gz", hash = "sha256:cfcda0873626150932f438eccf0f8bf22bfa92345b814890ab360d666b254146"}, 1335 | ] 1336 | markupsafe = [ 1337 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 1338 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 1339 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 1340 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 1341 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 1342 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 1343 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 1344 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 1345 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 1346 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 1347 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 1348 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 1349 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 1350 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 1351 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 1352 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 1353 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 1354 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 1355 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 1356 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 1357 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 1358 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 1359 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 1360 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 1361 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 1362 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 1363 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 1364 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 1365 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 1366 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 1367 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 1368 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 1369 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 1370 | ] 1371 | mccabe = [ 1372 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 1373 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 1374 | ] 1375 | mistune = [ 1376 | {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, 1377 | {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, 1378 | ] 1379 | mypy = [ 1380 | {file = "mypy-0.720-cp35-cp35m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498"}, 1381 | {file = "mypy-0.720-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0"}, 1382 | {file = "mypy-0.720-cp35-cp35m-win_amd64.whl", hash = "sha256:0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a"}, 1383 | {file = "mypy-0.720-cp36-cp36m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76"}, 1384 | {file = "mypy-0.720-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba"}, 1385 | {file = "mypy-0.720-cp36-cp36m-win_amd64.whl", hash = "sha256:07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4"}, 1386 | {file = "mypy-0.720-cp37-cp37m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd"}, 1387 | {file = "mypy-0.720-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339"}, 1388 | {file = "mypy-0.720-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"}, 1389 | {file = "mypy-0.720-py3-none-any.whl", hash = "sha256:11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae"}, 1390 | {file = "mypy-0.720.tar.gz", hash = "sha256:49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4"}, 1391 | ] 1392 | mypy-extensions = [ 1393 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 1394 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 1395 | ] 1396 | nbclient = [ 1397 | {file = "nbclient-0.5.1-py3-none-any.whl", hash = "sha256:4d6b116187c795c99b9dba13d46e764d596574b14c296d60670c8dfe454db364"}, 1398 | {file = "nbclient-0.5.1.tar.gz", hash = "sha256:01e2d726d16eaf2cde6db74a87e2451453547e8832d142f73f72fddcd4fe0250"}, 1399 | ] 1400 | nbconvert = [ 1401 | {file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"}, 1402 | {file = "nbconvert-6.0.7.tar.gz", hash = "sha256:cbbc13a86dfbd4d1b5dee106539de0795b4db156c894c2c5dc382062bbc29002"}, 1403 | ] 1404 | nbformat = [ 1405 | {file = "nbformat-5.0.8-py3-none-any.whl", hash = "sha256:aa9450c16d29286dc69b92ea4913c1bffe86488f90184445996ccc03a2f60382"}, 1406 | {file = "nbformat-5.0.8.tar.gz", hash = "sha256:f545b22138865bfbcc6b1ffe89ed5a2b8e2dc5d4fe876f2ca60d8e6f702a30f8"}, 1407 | ] 1408 | nest-asyncio = [ 1409 | {file = "nest_asyncio-1.4.3-py3-none-any.whl", hash = "sha256:dbe032f3e9ff7f120e76be22bf6e7958e867aed1743e6894b8a9585fe8495cc9"}, 1410 | {file = "nest_asyncio-1.4.3.tar.gz", hash = "sha256:eaa09ef1353ebefae19162ad423eef7a12166bcc63866f8bff8f3635353cd9fa"}, 1411 | ] 1412 | notebook = [ 1413 | {file = "notebook-6.1.5-py3-none-any.whl", hash = "sha256:508cf9dad7cdb3188f1aa27017dc78179029dfe83814fc505329f689bc2ab50f"}, 1414 | {file = "notebook-6.1.5.tar.gz", hash = "sha256:3db37ae834c5f3b6378381229d0e5dfcbfb558d08c8ce646b1ad355147f5e91d"}, 1415 | ] 1416 | packaging = [ 1417 | {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, 1418 | {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, 1419 | ] 1420 | pandocfilters = [ 1421 | {file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"}, 1422 | ] 1423 | parso = [ 1424 | {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, 1425 | {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, 1426 | ] 1427 | pexpect = [ 1428 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 1429 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 1430 | ] 1431 | pickleshare = [ 1432 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 1433 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 1434 | ] 1435 | pkginfo = [ 1436 | {file = "pkginfo-1.6.1-py2.py3-none-any.whl", hash = "sha256:ce14d7296c673dc4c61c759a0b6c14bae34e34eb819c0017bb6ca5b7292c56e9"}, 1437 | {file = "pkginfo-1.6.1.tar.gz", hash = "sha256:a6a4ac943b496745cec21f14f021bbd869d5e9b4f6ec06918cffea5a2f4b9193"}, 1438 | ] 1439 | prometheus-client = [ 1440 | {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"}, 1441 | {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, 1442 | ] 1443 | prompt-toolkit = [ 1444 | {file = "prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"}, 1445 | {file = "prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e"}, 1446 | ] 1447 | ptyprocess = [ 1448 | {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, 1449 | {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, 1450 | ] 1451 | py = [ 1452 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 1453 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 1454 | ] 1455 | pycodestyle = [ 1456 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 1457 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 1458 | ] 1459 | pycparser = [ 1460 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 1461 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 1462 | ] 1463 | pyflakes = [ 1464 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 1465 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 1466 | ] 1467 | pygments = [ 1468 | {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, 1469 | {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, 1470 | ] 1471 | pyparsing = [ 1472 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 1473 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 1474 | ] 1475 | pyrsistent = [ 1476 | {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, 1477 | ] 1478 | python-dateutil = [ 1479 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1480 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1481 | ] 1482 | pytz = [ 1483 | {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, 1484 | {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, 1485 | ] 1486 | pywin32 = [ 1487 | {file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"}, 1488 | {file = "pywin32-300-cp35-cp35m-win_amd64.whl", hash = "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64"}, 1489 | {file = "pywin32-300-cp36-cp36m-win32.whl", hash = "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7"}, 1490 | {file = "pywin32-300-cp36-cp36m-win_amd64.whl", hash = "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85"}, 1491 | {file = "pywin32-300-cp37-cp37m-win32.whl", hash = "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc"}, 1492 | {file = "pywin32-300-cp37-cp37m-win_amd64.whl", hash = "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d"}, 1493 | {file = "pywin32-300-cp38-cp38-win32.whl", hash = "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae"}, 1494 | {file = "pywin32-300-cp38-cp38-win_amd64.whl", hash = "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190"}, 1495 | {file = "pywin32-300-cp39-cp39-win32.whl", hash = "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50"}, 1496 | {file = "pywin32-300-cp39-cp39-win_amd64.whl", hash = "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f"}, 1497 | ] 1498 | pywinpty = [ 1499 | {file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"}, 1500 | {file = "pywinpty-0.5.7-cp27-cp27m-win_amd64.whl", hash = "sha256:1e525a4de05e72016a7af27836d512db67d06a015aeaf2fa0180f8e6a039b3c2"}, 1501 | {file = "pywinpty-0.5.7-cp35-cp35m-win32.whl", hash = "sha256:2740eeeb59297593a0d3f762269b01d0285c1b829d6827445fcd348fb47f7e70"}, 1502 | {file = "pywinpty-0.5.7-cp35-cp35m-win_amd64.whl", hash = "sha256:33df97f79843b2b8b8bc5c7aaf54adec08cc1bae94ee99dfb1a93c7a67704d95"}, 1503 | {file = "pywinpty-0.5.7-cp36-cp36m-win32.whl", hash = "sha256:e854211df55d107f0edfda8a80b39dfc87015bef52a8fe6594eb379240d81df2"}, 1504 | {file = "pywinpty-0.5.7-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd838de92de1d4ebf0dce9d4d5e4fc38d0b7b1de837947a18b57a882f219139"}, 1505 | {file = "pywinpty-0.5.7-cp37-cp37m-win32.whl", hash = "sha256:5fb2c6c6819491b216f78acc2c521b9df21e0f53b9a399d58a5c151a3c4e2a2d"}, 1506 | {file = "pywinpty-0.5.7-cp37-cp37m-win_amd64.whl", hash = "sha256:dd22c8efacf600730abe4a46c1388355ce0d4ab75dc79b15d23a7bd87bf05b48"}, 1507 | {file = "pywinpty-0.5.7-cp38-cp38-win_amd64.whl", hash = "sha256:8fc5019ff3efb4f13708bd3b5ad327589c1a554cb516d792527361525a7cb78c"}, 1508 | {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"}, 1509 | ] 1510 | pyzmq = [ 1511 | {file = "pyzmq-20.0.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:523d542823cabb94065178090e05347bd204365f6e7cb260f0071c995d392fc2"}, 1512 | {file = "pyzmq-20.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:225774a48ed7414c0395335e7123ef8c418dbcbe172caabdc2496133b03254c2"}, 1513 | {file = "pyzmq-20.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:bc7dd697356b31389d5118b9bcdef3e8d8079e8181800c4e8d72dccd56e1ff68"}, 1514 | {file = "pyzmq-20.0.0-cp35-cp35m-win32.whl", hash = "sha256:d81184489369ec325bd50ba1c935361e63f31f578430b9ad95471899361a8253"}, 1515 | {file = "pyzmq-20.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:7113eb93dcd0a5750c65d123ed0099e036a3a3f2dcb48afedd025ffa125c983b"}, 1516 | {file = "pyzmq-20.0.0-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:b62113eeb9a0649cebed9b21fd578f3a0175ef214a2a91dcb7b31bbf55805295"}, 1517 | {file = "pyzmq-20.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f0beef935efe78a63c785bb21ed56c1c24448511383e3994927c8bb2caf5e714"}, 1518 | {file = "pyzmq-20.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:46250789730489009fe139cbf576679557c070a6a3628077d09a4153d52fd381"}, 1519 | {file = "pyzmq-20.0.0-cp36-cp36m-win32.whl", hash = "sha256:bf755905a7d30d2749079611b9a89924c1f2da2695dc09ce221f42122c9808e3"}, 1520 | {file = "pyzmq-20.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2742e380d186673eee6a570ef83d4568741945434ba36d92b98d36cdbfedbd44"}, 1521 | {file = "pyzmq-20.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1e9b75a119606732023a305d1c214146c09a91f8116f6aff3e8b7d0a60b6f0ff"}, 1522 | {file = "pyzmq-20.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:03638e46d486dd1c118e03c8bf9c634bdcae679600eac6573ae1e54906de7c2f"}, 1523 | {file = "pyzmq-20.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:63ee08e35be72fdd7568065a249a5b5cf51a2e8ab6ee63cf9f73786fcb9e710b"}, 1524 | {file = "pyzmq-20.0.0-cp37-cp37m-win32.whl", hash = "sha256:c95dda497a7c1b1e734b5e8353173ca5dd7b67784d8821d13413a97856588057"}, 1525 | {file = "pyzmq-20.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:cc09c5cd1a4332611c8564d65e6a432dc6db3e10793d0254da9fa1e31d9ffd6d"}, 1526 | {file = "pyzmq-20.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e24907857c80dc67692e31f5bf3ad5bf483ee0142cec95b3d47e2db8c43bdda"}, 1527 | {file = "pyzmq-20.0.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:53706f4a792cdae422121fb6a5e65119bad02373153364fc9d004cf6a90394de"}, 1528 | {file = "pyzmq-20.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:895695be380f0f85d2e3ec5ccf68a93c92d45bd298567525ad5633071589872c"}, 1529 | {file = "pyzmq-20.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d92c7f41a53ece82b91703ea433c7d34143248cf0cead33aa11c5fc621c764bf"}, 1530 | {file = "pyzmq-20.0.0-cp38-cp38-win32.whl", hash = "sha256:309d763d89ec1845c0e0fa14e1fb6558fd8c9ef05ed32baec27d7a8499cc7bb0"}, 1531 | {file = "pyzmq-20.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:0e554fd390021edbe0330b67226325a820b0319c5b45e1b0a59bf22ccc36e793"}, 1532 | {file = "pyzmq-20.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cfa54a162a7b32641665e99b2c12084555afe9fc8fe80ec8b2f71a57320d10e1"}, 1533 | {file = "pyzmq-20.0.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:dc2f48b575dff6edefd572f1ac84cf0c3f18ad5fcf13384de32df740a010594a"}, 1534 | {file = "pyzmq-20.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5efe02bdcc5eafcac0aab531292294298f0ab8d28ed43be9e507d0e09173d1a4"}, 1535 | {file = "pyzmq-20.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0af84f34f27b5c6a0e906c648bdf46d4caebf9c8e6e16db0728f30a58141cad6"}, 1536 | {file = "pyzmq-20.0.0-cp39-cp39-win32.whl", hash = "sha256:c63fafd2556d218368c51d18588f8e6f8d86d09d493032415057faf6de869b34"}, 1537 | {file = "pyzmq-20.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f110a4d3f8f01209eec304ed542f6c8054cce9b0f16dfe3d571e57c290e4e133"}, 1538 | {file = "pyzmq-20.0.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4d9259a5eb3f71abbaf61f165cacf42240bfeea3783bebd8255341abdfe206f1"}, 1539 | {file = "pyzmq-20.0.0.tar.gz", hash = "sha256:824ad5888331aadeac772bce27e1c2fbcab82fade92edbd234542c4e12f0dca9"}, 1540 | ] 1541 | qtconsole = [ 1542 | {file = "qtconsole-5.0.1-py3-none-any.whl", hash = "sha256:4d70967aeb62a5bd13a109d61b169a3cf844afc24a35c11f5518574bb8abe670"}, 1543 | {file = "qtconsole-5.0.1.tar.gz", hash = "sha256:4d7dd4eae8a90d0b2b19b31794b30f137238463998989734a3acb8a53b506bab"}, 1544 | ] 1545 | qtpy = [ 1546 | {file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"}, 1547 | {file = "QtPy-1.9.0.tar.gz", hash = "sha256:2db72c44b55d0fe1407be8fba35c838ad0d6d3bb81f23007886dc1fc0f459c8d"}, 1548 | ] 1549 | readme-renderer = [ 1550 | {file = "readme_renderer-28.0-py2.py3-none-any.whl", hash = "sha256:267854ac3b1530633c2394ead828afcd060fc273217c42ac36b6be9c42cd9a9d"}, 1551 | {file = "readme_renderer-28.0.tar.gz", hash = "sha256:6b7e5aa59210a40de72eb79931491eaf46fefca2952b9181268bd7c7c65c260a"}, 1552 | ] 1553 | requests = [ 1554 | {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, 1555 | {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, 1556 | ] 1557 | requests-toolbelt = [ 1558 | {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, 1559 | {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, 1560 | ] 1561 | send2trash = [ 1562 | {file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"}, 1563 | {file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"}, 1564 | ] 1565 | six = [ 1566 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, 1567 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, 1568 | ] 1569 | sqlparse = [ 1570 | {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, 1571 | {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, 1572 | ] 1573 | terminado = [ 1574 | {file = "terminado-0.9.1-py3-none-any.whl", hash = "sha256:c55f025beb06c2e2669f7ba5a04f47bb3304c30c05842d4981d8f0fc9ab3b4e3"}, 1575 | {file = "terminado-0.9.1.tar.gz", hash = "sha256:3da72a155b807b01c9e8a5babd214e052a0a45a975751da3521a1c3381ce6d76"}, 1576 | ] 1577 | testpath = [ 1578 | {file = "testpath-0.4.4-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"}, 1579 | {file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"}, 1580 | ] 1581 | text-unidecode = [ 1582 | {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, 1583 | {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, 1584 | ] 1585 | toml = [ 1586 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1587 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1588 | ] 1589 | tornado = [ 1590 | {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, 1591 | {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, 1592 | {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, 1593 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, 1594 | {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, 1595 | {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, 1596 | {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, 1597 | {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, 1598 | {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, 1599 | {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, 1600 | {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, 1601 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, 1602 | {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, 1603 | {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, 1604 | {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, 1605 | {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, 1606 | {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, 1607 | {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, 1608 | {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, 1609 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, 1610 | {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, 1611 | {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, 1612 | {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, 1613 | {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, 1614 | {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, 1615 | {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, 1616 | {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, 1617 | {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, 1618 | {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, 1619 | {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, 1620 | {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, 1621 | {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, 1622 | {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, 1623 | {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, 1624 | {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, 1625 | {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, 1626 | {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, 1627 | {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, 1628 | {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, 1629 | {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, 1630 | {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, 1631 | ] 1632 | tqdm = [ 1633 | {file = "tqdm-4.54.1-py2.py3-none-any.whl", hash = "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"}, 1634 | {file = "tqdm-4.54.1.tar.gz", hash = "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5"}, 1635 | ] 1636 | traitlets = [ 1637 | {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, 1638 | {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, 1639 | ] 1640 | twine = [ 1641 | {file = "twine-1.15.0-py2.py3-none-any.whl", hash = "sha256:630fadd6e342e725930be6c696537e3f9ccc54331742b16245dab292a17d0460"}, 1642 | {file = "twine-1.15.0.tar.gz", hash = "sha256:a3d22aab467b4682a22de4a422632e79d07eebd07ff2a7079effb13f8a693787"}, 1643 | ] 1644 | typed-ast = [ 1645 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 1646 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 1647 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 1648 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 1649 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1650 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1651 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1652 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1653 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1654 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1655 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1656 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1657 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1658 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1659 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1660 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1661 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1662 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1663 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1664 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1665 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1666 | ] 1667 | typing-extensions = [ 1668 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 1669 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 1670 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 1671 | ] 1672 | urllib3 = [ 1673 | {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, 1674 | {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, 1675 | ] 1676 | wcwidth = [ 1677 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1678 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1679 | ] 1680 | webencodings = [ 1681 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 1682 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 1683 | ] 1684 | widgetsnbextension = [ 1685 | {file = "widgetsnbextension-3.5.1-py2.py3-none-any.whl", hash = "sha256:bd314f8ceb488571a5ffea6cc5b9fc6cba0adaf88a9d2386b93a489751938bcd"}, 1686 | {file = "widgetsnbextension-3.5.1.tar.gz", hash = "sha256:079f87d87270bce047512400efd70238820751a11d2d8cb137a5a5bdbaf255c7"}, 1687 | ] 1688 | zipp = [ 1689 | {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, 1690 | {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, 1691 | ] 1692 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "djangoflutterwave" 3 | version = "0.3.0" 4 | description = "Django integration for Flutterwave payments and subscriptions" 5 | authors = ["Brendon Delate "] 6 | license = "MIT" 7 | repository = "https://github.com/bdelate/django-flutterwave.git" 8 | homepage = "https://github.com/bdelate/django-flutterwave.git" 9 | readme = "README.md" 10 | 11 | keywords=[ 12 | "flutterwave", 13 | "rave", 14 | "django", 15 | ] 16 | 17 | classifiers=[ 18 | "Development Status :: 1 - Planning", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python :: 3.6", 22 | "Programming Language :: Python :: 3.7", 23 | "Programming Language :: Python :: 3.8", 24 | "Operating System :: OS Independent", 25 | "Topic :: Software Development :: Libraries", 26 | "Topic :: Utilities", 27 | "Environment :: Web Environment", 28 | "Framework :: Django", 29 | ] 30 | 31 | packages = [ 32 | { include = "djangoflutterwave" }, 33 | ] 34 | 35 | [tool.poetry.dependencies] 36 | python = ">=3.6 <4" 37 | django = ">=2.2 <4" 38 | djangorestframework = "^3.10" 39 | 40 | [tool.poetry.dev-dependencies] 41 | black = {version = "=19.3b0",allow-prereleases = true} 42 | mypy = "^0.720.0" 43 | twine = "^1.13" 44 | coverage = "^4.5" 45 | django-extensions = "^2.2" 46 | jupyter = "^1.0" 47 | factory-boy = "^3.1.0" 48 | flake8 = "^3.8.4" 49 | 50 | [build-system] 51 | requires = ["poetry-core>=1.0.0"] 52 | build-backend = "poetry.core.masonry.api" 53 | --------------------------------------------------------------------------------