Flask-Danger


NameFlask-Danger JSON
Version 1.0.9 PyPI version JSON
download
home_page
SummaryFlask extension for Danger (https://usedanger.com). Provides helpers to render the Danger widget on auth forms and to send server-side events.
upload_time2024-03-19 19:32:11
maintainer
docs_urlNone
author
requires_python
licenseMIT License Copyright (c) 2024 Danger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords security auth timezone country geolocation validation risk signup login
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![Flask-Danger: The official Flask extension for Danger](https://raw.githubusercontent.com/danger-org/flask-danger/main/.github/images/banner.png)

[Danger](https://usedanger.com) helps you to protect your Flask app from bad actors, validate user info, set a user's default country and timezone accurately, and detect repeat signups or credential sharing.

- **Country and timezone resolution:** Get the user's country and timezone, with built-in fallback so your signup is never broken.
- **Validate user details:** Validate and normalize user-provided inputs. Check email deliverability, disposable domains, phone number reachability, parse addresses, and much more. Simple `email_valid` and `email` type properties take the complication away.
- **Plug and play risk protection:** Ready made smart rules help you to protect your app from risks such as remote geolocation, automated bots, anonymous users (VPN, Tor, etc.), throwaway email addresses, and more. Or create custom rules to screen users based on your own criteria.
- **Act on repeat signups or credential sharing:** Danger can link events to the same person, even when they attempt evasions such as incognito mode, clearing cookies, or using VPNs. Danger will show you the person behind the signup.
- **Dashboard:** For reviewing events, configuring rules, and one-click allowlisting of safe emails or IP addresses.

For more information check out the [official website](https://usedanger.com) and our [platform documentation](https://docs.usedanger.com).

## How it works

Danger's implementation is similar to captcha services such as hCaptcha and reCAPTCHA, although the product solves different problems.

There is a client-side snippet that inserts a hidden form field into your signup or login form, then when your server processes the request it makes an API call with the user's details, alongside the contents of the hidden field that was generated by the client.

The result of this server-side call will provide a simple `True` (allow) or `False` (block), and give your app access to enriched data about the user and their device.

## Quickstart

Install with `pip`:

```bash
pip install flask-danger
```

You'll need a `site_key` and `secret_key` from Danger. You can get these by [signing up](https://usedanger.com/) for a free Danger account and [create your first site](https://app.usedanger.com/app/sites).

Once you have a `site_key` and `secret_key`, configure Flask-Danger through the standard [Flask configuration](https://flask.palletsprojects.com/en/3.0.x/config/). These are the available options:

- **`DANGER_SITE_KEY`**: Required. The public site key that you'll find in the site settings in the Danger dashboard.
- **`DANGER_SECRET_KEY`**: Required. The private key for the site. Always keep this a secret, only use it on the server.
- **`DANGER_TIMEOUT`**: Optional. Sets the lookup timeout. Value is in seconds. Defaults to 8.
- **`DANGER_FALLBACK_ALLOW`**: Optional. Whether to allow or block if the Danger platform can't be reached. Set to either `True` (allow) or `False` (block). Defaults to `True` so that the check fails open.
- **`DANGER_FALLBACK_COUNTRY`**: Optional. The [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code to use in the event Danger can't be reached. Defaults to `"US"`.
- **`DANGER_FALLBACK_TIMEZONE`**: Optional. The timezone in [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) format to use if Danger can't be reached. Defaults to `'UTC'`.

The full set of config options and defaults are shown below:

```python
app.config.update(
    DANGER_SITE_KEY = '<your_site_key>',
    DANGER_SECRET_KEY = '<your_secret_key>',

    # Optional from here
    DANGER_TIMEOUT = 8,
    DANGER_FALLBACK_ALLOW = True,
    DANGER_FALLBACK_COUNTRY = 'US',
    DANGER_FALLBACK_TIMEZONE = 'UTC'
)
```

Now you're ready to import Flask-Danger:

```python
from flask import Flask
from flask_danger import Danger

app = Flask(__name__)
danger = Danger(app)

# or with the factory pattern

danger = Danger()
danger.init_app(app)
```

In your signup or login form template, use this [Jinja](https://jinja.palletsprojects.com/) syntax to insert the client-side code into your form:

```html
{{ danger }}
```

Place it anywhere within the form. For example, you might insert it like this:

```html
...
<form id="form" method="post">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name">

    <label for="email">Email:</label>
    <input type="text" id="email" name="email">

    {{ danger }}
    <input type="submit" value="Submit">
</form>
...
```

> [!IMPORTANT]  
> It's important that the domain name you declared in your site settings matches the domain where the form will be served from.

> [!NOTE]  
> If you don't serve your form from a Flask view, for example if you use a JavaScript app for your front-end, you can include the client-side widget manually and only use Flask-Danger for the server call.

Finally, in the Flask route that handles the form, use `danger.event()` to get a result:

```python
@route("/submit", methods=["POST"])
def submit():
    # First perform basic validation on the inputs
    # ...
    # Now get the result from Danger
    result = danger.event(
        bundle=request.form["danger-bundle"],
        name=request.form["name"],
        email=request.form["email"]
    )
    if result.allow:
        # Continue processing here
        country = result.country
        timezone = result.timezone
        email = result.email
        phone = result.phone
        address = result.address
    else:
        # Block the signup
```

The `country` and `timezone` properties will either have the fetched values, or fallback, but can always be relied on to provide a value. `email`, `phone`, and `address` are similar, they have either normalized values, or the input value if a check is unsuccessful.

If you need to check validity, you can use `email_valid`, `phone_valid`, and `address_valid`:

```python
    if not result.email_valid:
        # Email is not valid (or couldn't be determined)
    if not result.phone_valid:
        # Phone is not valid (or couldn't be determined)
    if not result.address_valid
        # Address is not valid (or couldn't be determined)
```

`email_valid`, `phone_valid`, and `address_valid` all return `True` (valid), `False` (not valid), or `None` (if a result can't be determined). So check equality carefully depending on what level of certainty you need. For example:

```python
    if result.email_valid is False:
        # Email is definitely not valid
    if result.address_valid:
        # Email is definitely valid
    if not result.email_valid:
        # Email is either not valid, or couldn't check
    if result.email_valid is None:
        # Couldn't check email validity
```

You can also find the full Danger result in the `data` property:

```python
    device = result.data.get("device", {})
    browser = device.get("browser")
    # Chrome
```

[Read the docs](https://docs.usedanger.com/getting-started/success-response) to learn about what the result contains.

## Reference

### `{{ danger }}` template variable

Create the HTML to include in the client side HTML form. Use inside a template using [Jinja](https://jinja.palletsprojects.com/) syntax `{{ danger }}`. Add this anywhere within your form.

### `danger.event()`

Pass in the user's data (email, phone, etc.) along with the bundle that is sent in the 'danger-bundle' field on your form.

| **Argument** | **Description**                                                                                            |
|--------------|------------------------------------------------------------------------------------------------------------|
| email        | Person's email address                                                                                     |
| bundle       | The 'bundle' that Danger added to the form in the hidden field 'danger-bundle'                             |
| type         | (Optional) Event type, either `"new_user"` (default) or `"login"`                                          |
| name         | (Optional) Person's name                                                                                   |
| phone        | (Optional) Person's phone number                                                                           |
| address      | (Optional) Person's address, a dict with one or more of the keys `address1`, `address2`, `city`, `state`, `country`, `postal_code` |
| ip           | (Optional) The remote IP address, i.e. that of the person's connection. Defaults to `request.remote_addr`. |
| external_id  | (Optional) An external identifier for this person, i.e. your app's database ID                             |

See the [docs](https://docs.usedanger.com/getting-started/requests) for more information.

Returns a result object with the following properties:

| Property      | Description                                                                                                                                                                                    |
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allow         | Either `True` (allow) or `False` (block)                                                                                                                                                       |
| outcome       | Either the full outcome of the event (`'allow'`, `'allow_review'`, `'block'`, `'block_review'`, `'review'`), or `None` on failure                                                              |
| country       | The [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code for the user (e.g. `'US'`)                                                                             |
| timezone      | The [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) timezone for the user (`'America/New_York'`)                                                                   |
| address_valid | Validity of the address. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                        |
| address       | If `address_valid` is `True`, holds the parsed address. Otherwise, holds the input address. A dict with one or more of the keys `address1`, `address2`, `city`, `state`, `country`, `postal_code`. If no address provided, `None`. |
| email_valid   | Validity of the email address. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                  |
| email         | If `email_valid` is `True`, holds the normalized email address. Otherwise, holds the input email address.                                                                                      |
| phone_valid   | Validity of the phone number. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                   |
| phone         | If `phone_valid` is `True`, holds the E164 formatted phone number. Otherwise, holds the input phone number, or `None` if not provided.                                                         |
| ip            | The IP address of the user                                                                                                                                                                     |
| data          | A dict containing the full Danger result                                                                                                                                                       |

Read the [docs](https://docs.usedanger.com/getting-started/success-response) to find out how to work with the full Danger result.

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

## License

[MIT](https://choosealicense.com/licenses/mit/)

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "Flask-Danger",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "security,auth,timezone,country,geolocation,validation,risk,signup,login",
    "author": "",
    "author_email": "Danger <support@usedanger.com>",
    "download_url": "https://files.pythonhosted.org/packages/23/7e/cc12b737a12600dbb7991e83c1ce74ee3699349ca841f9ed3356a58c4b4f/Flask-Danger-1.0.9.tar.gz",
    "platform": null,
    "description": "![Flask-Danger: The official Flask extension for Danger](https://raw.githubusercontent.com/danger-org/flask-danger/main/.github/images/banner.png)\n\n[Danger](https://usedanger.com) helps you to protect your Flask app from bad actors, validate user info, set a user's default country and timezone accurately, and detect repeat signups or credential sharing.\n\n- **Country and timezone resolution:** Get the user's country and timezone, with built-in fallback so your signup is never broken.\n- **Validate user details:** Validate and normalize user-provided inputs. Check email deliverability, disposable domains, phone number reachability, parse addresses, and much more. Simple `email_valid` and `email` type properties take the complication away.\n- **Plug and play risk protection:** Ready made smart rules help you to protect your app from risks such as remote geolocation, automated bots, anonymous users (VPN, Tor, etc.), throwaway email addresses, and more. Or create custom rules to screen users based on your own criteria.\n- **Act on repeat signups or credential sharing:** Danger can link events to the same person, even when they attempt evasions such as incognito mode, clearing cookies, or using VPNs. Danger will show you the person behind the signup.\n- **Dashboard:** For reviewing events, configuring rules, and one-click allowlisting of safe emails or IP addresses.\n\nFor more information check out the [official website](https://usedanger.com) and our [platform documentation](https://docs.usedanger.com).\n\n## How it works\n\nDanger's implementation is similar to captcha services such as hCaptcha and reCAPTCHA, although the product solves different problems.\n\nThere is a client-side snippet that inserts a hidden form field into your signup or login form, then when your server processes the request it makes an API call with the user's details, alongside the contents of the hidden field that was generated by the client.\n\nThe result of this server-side call will provide a simple `True` (allow) or `False` (block), and give your app access to enriched data about the user and their device.\n\n## Quickstart\n\nInstall with `pip`:\n\n```bash\npip install flask-danger\n```\n\nYou'll need a `site_key` and `secret_key` from Danger. You can get these by [signing up](https://usedanger.com/) for a free Danger account and [create your first site](https://app.usedanger.com/app/sites).\n\nOnce you have a `site_key` and `secret_key`, configure Flask-Danger through the standard [Flask configuration](https://flask.palletsprojects.com/en/3.0.x/config/). These are the available options:\n\n- **`DANGER_SITE_KEY`**: Required. The public site key that you'll find in the site settings in the Danger dashboard.\n- **`DANGER_SECRET_KEY`**: Required. The private key for the site. Always keep this a secret, only use it on the server.\n- **`DANGER_TIMEOUT`**: Optional. Sets the lookup timeout. Value is in seconds. Defaults to 8.\n- **`DANGER_FALLBACK_ALLOW`**: Optional. Whether to allow or block if the Danger platform can't be reached. Set to either `True` (allow) or `False` (block). Defaults to `True` so that the check fails open.\n- **`DANGER_FALLBACK_COUNTRY`**: Optional. The [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code to use in the event Danger can't be reached. Defaults to `\"US\"`.\n- **`DANGER_FALLBACK_TIMEZONE`**: Optional. The timezone in [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) format to use if Danger can't be reached. Defaults to `'UTC'`.\n\nThe full set of config options and defaults are shown below:\n\n```python\napp.config.update(\n    DANGER_SITE_KEY = '<your_site_key>',\n    DANGER_SECRET_KEY = '<your_secret_key>',\n\n    # Optional from here\n    DANGER_TIMEOUT = 8,\n    DANGER_FALLBACK_ALLOW = True,\n    DANGER_FALLBACK_COUNTRY = 'US',\n    DANGER_FALLBACK_TIMEZONE = 'UTC'\n)\n```\n\nNow you're ready to import Flask-Danger:\n\n```python\nfrom flask import Flask\nfrom flask_danger import Danger\n\napp = Flask(__name__)\ndanger = Danger(app)\n\n# or with the factory pattern\n\ndanger = Danger()\ndanger.init_app(app)\n```\n\nIn your signup or login form template, use this [Jinja](https://jinja.palletsprojects.com/) syntax to insert the client-side code into your form:\n\n```html\n{{ danger }}\n```\n\nPlace it anywhere within the form. For example, you might insert it like this:\n\n```html\n...\n<form id=\"form\" method=\"post\">\n    <label for=\"name\">Name:</label>\n    <input type=\"text\" id=\"name\" name=\"name\">\n\n    <label for=\"email\">Email:</label>\n    <input type=\"text\" id=\"email\" name=\"email\">\n\n    {{ danger }}\n    <input type=\"submit\" value=\"Submit\">\n</form>\n...\n```\n\n> [!IMPORTANT]  \n> It's important that the domain name you declared in your site settings matches the domain where the form will be served from.\n\n> [!NOTE]  \n> If you don't serve your form from a Flask view, for example if you use a JavaScript app for your front-end, you can include the client-side widget manually and only use Flask-Danger for the server call.\n\nFinally, in the Flask route that handles the form, use `danger.event()` to get a result:\n\n```python\n@route(\"/submit\", methods=[\"POST\"])\ndef submit():\n    # First perform basic validation on the inputs\n    # ...\n    # Now get the result from Danger\n    result = danger.event(\n        bundle=request.form[\"danger-bundle\"],\n        name=request.form[\"name\"],\n        email=request.form[\"email\"]\n    )\n    if result.allow:\n        # Continue processing here\n        country = result.country\n        timezone = result.timezone\n        email = result.email\n        phone = result.phone\n        address = result.address\n    else:\n        # Block the signup\n```\n\nThe `country` and `timezone` properties will either have the fetched values, or fallback, but can always be relied on to provide a value. `email`, `phone`, and `address` are similar, they have either normalized values, or the input value if a check is unsuccessful.\n\nIf you need to check validity, you can use `email_valid`, `phone_valid`, and `address_valid`:\n\n```python\n    if not result.email_valid:\n        # Email is not valid (or couldn't be determined)\n    if not result.phone_valid:\n        # Phone is not valid (or couldn't be determined)\n    if not result.address_valid\n        # Address is not valid (or couldn't be determined)\n```\n\n`email_valid`, `phone_valid`, and `address_valid` all return `True` (valid), `False` (not valid), or `None` (if a result can't be determined). So check equality carefully depending on what level of certainty you need. For example:\n\n```python\n    if result.email_valid is False:\n        # Email is definitely not valid\n    if result.address_valid:\n        # Email is definitely valid\n    if not result.email_valid:\n        # Email is either not valid, or couldn't check\n    if result.email_valid is None:\n        # Couldn't check email validity\n```\n\nYou can also find the full Danger result in the `data` property:\n\n```python\n    device = result.data.get(\"device\", {})\n    browser = device.get(\"browser\")\n    # Chrome\n```\n\n[Read the docs](https://docs.usedanger.com/getting-started/success-response) to learn about what the result contains.\n\n## Reference\n\n### `{{ danger }}` template variable\n\nCreate the HTML to include in the client side HTML form. Use inside a template using [Jinja](https://jinja.palletsprojects.com/) syntax `{{ danger }}`. Add this anywhere within your form.\n\n### `danger.event()`\n\nPass in the user's data (email, phone, etc.) along with the bundle that is sent in the 'danger-bundle' field on your form.\n\n| **Argument** | **Description**                                                                                            |\n|--------------|------------------------------------------------------------------------------------------------------------|\n| email        | Person's email address                                                                                     |\n| bundle       | The 'bundle' that Danger added to the form in the hidden field 'danger-bundle'                             |\n| type         | (Optional) Event type, either `\"new_user\"` (default) or `\"login\"`                                          |\n| name         | (Optional) Person's name                                                                                   |\n| phone        | (Optional) Person's phone number                                                                           |\n| address      | (Optional) Person's address, a dict with one or more of the keys `address1`, `address2`, `city`, `state`, `country`, `postal_code` |\n| ip           | (Optional) The remote IP address, i.e. that of the person's connection. Defaults to `request.remote_addr`. |\n| external_id  | (Optional) An external identifier for this person, i.e. your app's database ID                             |\n\nSee the [docs](https://docs.usedanger.com/getting-started/requests) for more information.\n\nReturns a result object with the following properties:\n\n| Property      | Description                                                                                                                                                                                    |\n|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| allow         | Either `True` (allow) or `False` (block)                                                                                                                                                       |\n| outcome       | Either the full outcome of the event (`'allow'`, `'allow_review'`, `'block'`, `'block_review'`, `'review'`), or `None` on failure                                                              |\n| country       | The [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code for the user (e.g. `'US'`)                                                                             |\n| timezone      | The [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) timezone for the user (`'America/New_York'`)                                                                   |\n| address_valid | Validity of the address. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                        |\n| address       | If `address_valid` is `True`, holds the parsed address. Otherwise, holds the input address. A dict with one or more of the keys `address1`, `address2`, `city`, `state`, `country`, `postal_code`. If no address provided, `None`. |\n| email_valid   | Validity of the email address. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                  |\n| email         | If `email_valid` is `True`, holds the normalized email address. Otherwise, holds the input email address.                                                                                      |\n| phone_valid   | Validity of the phone number. Either `True` (valid), `False` (not valid), or `None` (couldn't be validated).                                                                                   |\n| phone         | If `phone_valid` is `True`, holds the E164 formatted phone number. Otherwise, holds the input phone number, or `None` if not provided.                                                         |\n| ip            | The IP address of the user                                                                                                                                                                     |\n| data          | A dict containing the full Danger result                                                                                                                                                       |\n\nRead the [docs](https://docs.usedanger.com/getting-started/success-response) to find out how to work with the full Danger result.\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\n## License\n\n[MIT](https://choosealicense.com/licenses/mit/)\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2024 Danger  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Flask extension for Danger (https://usedanger.com). Provides helpers to render the Danger widget on auth forms and to send server-side events.",
    "version": "1.0.9",
    "project_urls": {
        "Homepage": "https://github.com/danger-org/flask-danger"
    },
    "split_keywords": [
        "security",
        "auth",
        "timezone",
        "country",
        "geolocation",
        "validation",
        "risk",
        "signup",
        "login"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "93975e2d37207fdc8f1574973ec43ea405af0363b8ddeae4ba7a9fbcce62a91a",
                "md5": "07dd1bcd1af0667d6aac1b46cb1d5924",
                "sha256": "ba058cadaefd6aec5cf53b69800d2d52db45d96bfc5e4ab448f5f04373ef8a40"
            },
            "downloads": -1,
            "filename": "Flask_Danger-1.0.9-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "07dd1bcd1af0667d6aac1b46cb1d5924",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 9303,
            "upload_time": "2024-03-19T19:32:10",
            "upload_time_iso_8601": "2024-03-19T19:32:10.162037Z",
            "url": "https://files.pythonhosted.org/packages/93/97/5e2d37207fdc8f1574973ec43ea405af0363b8ddeae4ba7a9fbcce62a91a/Flask_Danger-1.0.9-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "237ecc12b737a12600dbb7991e83c1ce74ee3699349ca841f9ed3356a58c4b4f",
                "md5": "b6234b06b85f42240e37240d10e91840",
                "sha256": "d7d2e9d6641aae90efabd4779aaf7a9b09a50190f73225d681fd5e5b8ea1f35c"
            },
            "downloads": -1,
            "filename": "Flask-Danger-1.0.9.tar.gz",
            "has_sig": false,
            "md5_digest": "b6234b06b85f42240e37240d10e91840",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 8627,
            "upload_time": "2024-03-19T19:32:11",
            "upload_time_iso_8601": "2024-03-19T19:32:11.851404Z",
            "url": "https://files.pythonhosted.org/packages/23/7e/cc12b737a12600dbb7991e83c1ce74ee3699349ca841f9ed3356a58c4b4f/Flask-Danger-1.0.9.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-19 19:32:11",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "danger-org",
    "github_project": "flask-danger",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "flask-danger"
}
        
Elapsed time: 0.20891s