amyrose


Nameamyrose JSON
Version 0.6.7 PyPI version JSON
download
home_pagehttps://github.com/sunset-developer/Amy-Rose
SummaryA powerful, simple, and async authentication and authorization library for Sanic.
upload_time2021-01-17 02:08:24
maintainer
docs_urlNone
authorsunset-developer
requires_python
licenseGNU General Public License v3.0
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <!-- PROJECT SHIELDS -->
<!--
*** I'm using markdown "reference style" links for readability.
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
*** See the bottom of this document for the declaration of the reference variables
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
-->
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]



<!-- PROJECT LOGO -->
<br />
<p align="center">

  <h3 align="center">Amy Rose</h3>

  <p align="center">
    A powerful, simple, and async authentication and authorization library for Sanic.
    <br />
    <a href="https://github.com/sunset-developer/Amy-Rose/tree/master/examples">View Demo</a>
    ·
    <a href="https://github.com/sunset-developer/Amy-Rose/issues">Report Bug</a>
    ·
    <a href="https://github.com/sunset-developer/Amy-Rose/issues">Request Feature</a>
  </p>
</p>



<!-- TABLE OF CONTENTS -->
## Table of Contents

* [About the Project](#about-the-project)
  * [Built With](#built-with)
* [Getting Started](#getting-started)
  * [Prerequisites](#prerequisites)
  * [Installation](#installation)
* [Usage](#usage)
    * [Initial Setup](#initial-setup)
    * [Authentication](#authentication)
    * [Captcha](#captcha)
    * [Authorization](#authorization)
    * [Error Handling](#error-handling)
    * [DTO](#DTO)
* [Roadmap](#roadmap)
* [Contributing](#contributing)
* [License](#license)
* [Contact](#contact)
* [Acknowledgements](#acknowledgements)



<!-- ABOUT THE PROJECT -->
## About The Project

Amy Rose is an authentication and authorization library made easy. Specifically designed for use with [Sanic](https://github.com/huge-success/sanic).
Amy Rose comes packed with features such as:

* SMS verification
* JWT
* Out of the box database integration
* Wildcard permissions
* Role permissions
* Captcha

Amy Rose contains all of your basic security needs.

### Built With
* [Tortoise](https://tortoise.github.io/)
* [Sanic](https://github.com/huge-success/sanic)
* [Twilio](https://www.twilio.com/)



<!-- GETTING STARTED -->
## Getting Started

In order to get started, please install pip.

### Prerequisites

* pip
```sh
sudo apt-get install python3-pip
```


### Installation

* Clone the repo
```sh
git clone https://github.com/sunset-developer/Amy-Rose
```
* Install pip packages
```sh
pip3 install amyrose
```


## Usage

Once Amy Rose is all setup and good to go, implementing is easy as pie.

### Initial Setup

First you have to create a configuration file called rose.ini. Below is an example of it's contents: 

```
[ROSE]
secret=05jF8cSMAdjlXcXeS2ZJ

[TORTOISE]
username=admin
password=8KjLQtVKTCtItAi
endpoint=amyrose.cbwyreqgyzf6b.us-west-1.rds.amazonaws.com
schema=amyrose
models=['amyrose.core.models']
generate=true

[TWILIO]
from=+12058469963
token=1bcioi878ygO8fi766Fb34750e82a5ab
sid=AC6156Jg67OOYe75c26dgtoTICifIe51cbf
```

If you're initializing Tortoise yourself you do not have to configure it here.

If you're not using Twilio as your verification method, you do not have to configure it here. 

Once you've configured Amy Rose, you can initialize Sanic with the example below:

```python
if __name__ == '__main__':
    initialize(app)
    app.run(host='0.0.0.0', port=8000, debug=True)
``` 

All request bodies should be sent as `form-data`

## Authentication

* Registration

Key | Value |
--- | --- |
**username** | test 
**email** | test@test.com 
**phone** | +19811354186
**password** | testpass

```python
@app.post('/register')
async def on_register(request):
    account, verification_session = await register(request)
    await text_verification_code(account.phone, verification_session.code)
    response = text('Registration successful')
    verification_session.encode(response)
    return response
```

* Verification

Key | Value |
--- | --- |
**code** | GUmrRLD


```python
@app.post('/verify')
async def on_verify(request):
    account, verification_session = await verify_account(request)
    return text('Verification successful')
```

* Login

Key | Value |
--- | --- |
**email** | test@test.com
**password** | testpass

```python
@app.post('/login')
async def on_login(request):
    account, authentication_session = await login(request)
    response = text('Login successful')
    authentication_session.encode(response)
    return response
```

* Resend Verification Request

```python
@app.post('/resend')
async def resend_verification_request(request):
    account, verification_session = await request_verification(request)
    await text_verification_code(account.phone, verification_session.code)
    response = text('Resend request successful.')
    verification_session.encode(response)
    return response
```

* Logout

```python
@app.post('/logout')
async def on_logout(request):
    account, authentication_session = await logout(request)
    response = text('Logout successful')
    return response
```

* Requires Authentication

```python
@app.get("/get")
@requires_authentication()
async def get_user_info(request):
    return text('Sensitive user information')
```

## Captcha

* Request Captcha

```python
@app.get('/captcha')
async def on_request_captcha(request):
    captcha_session = await request_captcha(request)
    response = text('Captcha request successful!')
    captcha_session.encode(response)
    return response
```

* Captcha Image

```python
@app.get('/captcha/img')
async def on_captcha_img(request):
    img_path = await CaptchaSessionDTO().get_client_img(request)
    response = await file(img_path)
    return response
```

* Register (with captcha)

Key | Value |
--- | --- |
**username** | test 
**email** | test@test.com 
**phone** | +19811354186
**password** | testpass
**captcha** | ah17ek

```python
@app.post('/register/')
async def on_register_captcha(request):
    await captcha(request)
    account, verification_session = await register(request)
    await text_verification_code(account.phone, verification_session.code)
    response = text('Registration successful')
    verification_session.encode(response)
    return response
```

## Authorization

Examples of wildcard permissions are:

```
admin:add,update,delete
admin:add
admin:*
employee:add,delete
employee:delete
employee:*
```

A library called [Apache Shiro](https://shiro.apache.org/permissions.html) explains this concept incredibly well. I 
absolutely recommend this library for Java developers.

* Requires Permission

```python
@app.get('/update')
@requires_permission('admin:update')
async def on_test_perm(request):
    return text('Admin has manipulated very sensitive data') 
```

* Requires Role

```python
@app.get('/get')
@requires_role('Admin')
async def on_test_role(request):
    return text('Admin has retrieved very sensitive data')
```

## Error Handling

```python
@app.exception(RoseError)
async def on_rose_error_test(request, exception: ServerError):
    payload = {
        'error': str(exception),
        'code': exception.status_code
    }
    return json(payload, status=exception.status_code)
```

## DTO

A DTO object is a simple way to organize interactions with the database. It's completely abstract, so it can be used 
with any model. If you choose not to use the DTO and instead work directly with Tortoise, that's completely fine.

* Role DTO (Example)

```python
class RoleDTO(DTO):
    def __init__(self):
        super().__init__(Role)

    async def has_role(self, account: Account, role: str):
        """
        Checks if the account has the required role being requested.

        :param account: Account being checked.

        :param role: The role that is required for validation.

        :return: has_role
        """

        return await self.t().filter(parent_uid=account.uid, name=role).exists()

    async def assign_role(self, account: Account, role: str):
        """
        Creates a role associated with an account

        :param account: Account associated with role.

        :param role: role to be associated with account.

        :return: role
        """

        return await self.create(parent_uid=account.uid, name=role)
```

* Usage Examples

```python
if not await role_dto.has_role(account, required_role):
    raise Role.InsufficientRoleError()
```

```python 
client = await account_dto.get_client(request)
await role_dto.assign_role(client, 'Admin')
```

```python
params = request.form
account = await account_dto.create(email=params.get('email'), username=params.get('username'),
                                    password=account_dto.hash_password(params.get('password')),
                                    phone=params.get('phone'))
```

```python
account, authentication_session = await authenticate(request)
authentication_session.valid = False
await authentication_session_dto.update(authentication_session, fields=['valid'])
```



<!-- ROADMAP -->
## Roadmap

Keep up with Amy Rose's [Trello](https://trello.com/b/aRKzFlRL/amy-rose) board for a list of proposed features, known issues, and in progress development.


<!-- CONTRIBUTING -->
## Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request



<!-- LICENSE -->
## License

Distributed under the GNU General Public License v3.0. See `LICENSE` for more information.



<!-- CONTACT -->
## Contact

Aidan Stewart - aidanstewart@sunsetdeveloper.com

Project Link: [https://github.com/sunset-developer/Amy-Rose](https://github.com/sunset-developer/Amy-Rose)


<!-- ACKNOWLEDGEMENTS -->
## Acknowledgements

* [Be the first! Submit a pull request.](https://github.com/sunset-developer/PyBus3/pulls)






<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[contributors-shield]: https://img.shields.io/github/contributors/sunset-developer/Amy-Rose.svg?style=flat-square
[contributors-url]: https://github.com/sunset-developer/Amy-Rose/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/sunset-developer/Amy-Rose.svg?style=flat-square
[forks-url]: https://github.com/sunset-developer/Amy-Rose/network/members
[stars-shield]: https://img.shields.io/github/stars/sunset-developer/Amy-Rose.svg?style=flat-square
[stars-url]: https://github.com/sunset-developer/Amy-Rose/stargazers
[issues-shield]: https://img.shields.io/github/issues/sunset-developer/Amy-Rose.svg?style=flat-square
[issues-url]: https://github.com/sunset-developer/Amy-Rose/issues
[license-shield]: https://img.shields.io/github/license/sunset-developer/Amy-Rose.svg?style=flat-square
[license-url]: https://github.com/sunset-developer/Amy-Rose/blob/master/LICENSE



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/sunset-developer/Amy-Rose",
    "name": "amyrose",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "",
    "author": "sunset-developer",
    "author_email": "aidanstewart@sunsetdeveloper.com",
    "download_url": "https://files.pythonhosted.org/packages/00/71/e6a90c58525b257c5455f56edd4d69dca50cbe3c47e64ec70d28f7057d01/amyrose-0.6.7.tar.gz",
    "platform": "",
    "description": "<!-- PROJECT SHIELDS -->\n<!--\n*** I'm using markdown \"reference style\" links for readability.\n*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).\n*** See the bottom of this document for the declaration of the reference variables\n*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.\n*** https://www.markdownguide.org/basic-syntax/#reference-style-links\n-->\n[![Contributors][contributors-shield]][contributors-url]\n[![Forks][forks-shield]][forks-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n\n\n\n<!-- PROJECT LOGO -->\n<br />\n<p align=\"center\">\n\n  <h3 align=\"center\">Amy Rose</h3>\n\n  <p align=\"center\">\n    A powerful, simple, and async authentication and authorization library for Sanic.\n    <br />\n    <a href=\"https://github.com/sunset-developer/Amy-Rose/tree/master/examples\">View Demo</a>\n    \u00b7\n    <a href=\"https://github.com/sunset-developer/Amy-Rose/issues\">Report Bug</a>\n    \u00b7\n    <a href=\"https://github.com/sunset-developer/Amy-Rose/issues\">Request Feature</a>\n  </p>\n</p>\n\n\n\n<!-- TABLE OF CONTENTS -->\n## Table of Contents\n\n* [About the Project](#about-the-project)\n  * [Built With](#built-with)\n* [Getting Started](#getting-started)\n  * [Prerequisites](#prerequisites)\n  * [Installation](#installation)\n* [Usage](#usage)\n    * [Initial Setup](#initial-setup)\n    * [Authentication](#authentication)\n    * [Captcha](#captcha)\n    * [Authorization](#authorization)\n    * [Error Handling](#error-handling)\n    * [DTO](#DTO)\n* [Roadmap](#roadmap)\n* [Contributing](#contributing)\n* [License](#license)\n* [Contact](#contact)\n* [Acknowledgements](#acknowledgements)\n\n\n\n<!-- ABOUT THE PROJECT -->\n## About The Project\n\nAmy Rose is an authentication and authorization library made easy. Specifically designed for use with [Sanic](https://github.com/huge-success/sanic).\nAmy Rose comes packed with features such as:\n\n* SMS verification\n* JWT\n* Out of the box database integration\n* Wildcard permissions\n* Role permissions\n* Captcha\n\nAmy Rose contains all of your basic security needs.\n\n### Built With\n* [Tortoise](https://tortoise.github.io/)\n* [Sanic](https://github.com/huge-success/sanic)\n* [Twilio](https://www.twilio.com/)\n\n\n\n<!-- GETTING STARTED -->\n## Getting Started\n\nIn order to get started, please install pip.\n\n### Prerequisites\n\n* pip\n```sh\nsudo apt-get install python3-pip\n```\n\n\n### Installation\n\n* Clone the repo\n```sh\ngit clone https://github.com/sunset-developer/Amy-Rose\n```\n* Install pip packages\n```sh\npip3 install amyrose\n```\n\n\n## Usage\n\nOnce Amy Rose is all setup and good to go, implementing is easy as pie.\n\n### Initial Setup\n\nFirst you have to create a configuration file called rose.ini. Below is an example of it's contents: \n\n```\n[ROSE]\nsecret=05jF8cSMAdjlXcXeS2ZJ\n\n[TORTOISE]\nusername=admin\npassword=8KjLQtVKTCtItAi\nendpoint=amyrose.cbwyreqgyzf6b.us-west-1.rds.amazonaws.com\nschema=amyrose\nmodels=['amyrose.core.models']\ngenerate=true\n\n[TWILIO]\nfrom=+12058469963\ntoken=1bcioi878ygO8fi766Fb34750e82a5ab\nsid=AC6156Jg67OOYe75c26dgtoTICifIe51cbf\n```\n\nIf you're initializing Tortoise yourself you do not have to configure it here.\n\nIf you're not using Twilio as your verification method, you do not have to configure it here. \n\nOnce you've configured Amy Rose, you can initialize Sanic with the example below:\n\n```python\nif __name__ == '__main__':\n    initialize(app)\n    app.run(host='0.0.0.0', port=8000, debug=True)\n``` \n\nAll request bodies should be sent as `form-data`\n\n## Authentication\n\n* Registration\n\nKey | Value |\n--- | --- |\n**username** | test \n**email** | test@test.com \n**phone** | +19811354186\n**password** | testpass\n\n```python\n@app.post('/register')\nasync def on_register(request):\n    account, verification_session = await register(request)\n    await text_verification_code(account.phone, verification_session.code)\n    response = text('Registration successful')\n    verification_session.encode(response)\n    return response\n```\n\n* Verification\n\nKey | Value |\n--- | --- |\n**code** | GUmrRLD\n\n\n```python\n@app.post('/verify')\nasync def on_verify(request):\n    account, verification_session = await verify_account(request)\n    return text('Verification successful')\n```\n\n* Login\n\nKey | Value |\n--- | --- |\n**email** | test@test.com\n**password** | testpass\n\n```python\n@app.post('/login')\nasync def on_login(request):\n    account, authentication_session = await login(request)\n    response = text('Login successful')\n    authentication_session.encode(response)\n    return response\n```\n\n* Resend Verification Request\n\n```python\n@app.post('/resend')\nasync def resend_verification_request(request):\n    account, verification_session = await request_verification(request)\n    await text_verification_code(account.phone, verification_session.code)\n    response = text('Resend request successful.')\n    verification_session.encode(response)\n    return response\n```\n\n* Logout\n\n```python\n@app.post('/logout')\nasync def on_logout(request):\n    account, authentication_session = await logout(request)\n    response = text('Logout successful')\n    return response\n```\n\n* Requires Authentication\n\n```python\n@app.get(\"/get\")\n@requires_authentication()\nasync def get_user_info(request):\n    return text('Sensitive user information')\n```\n\n## Captcha\n\n* Request Captcha\n\n```python\n@app.get('/captcha')\nasync def on_request_captcha(request):\n    captcha_session = await request_captcha(request)\n    response = text('Captcha request successful!')\n    captcha_session.encode(response)\n    return response\n```\n\n* Captcha Image\n\n```python\n@app.get('/captcha/img')\nasync def on_captcha_img(request):\n    img_path = await CaptchaSessionDTO().get_client_img(request)\n    response = await file(img_path)\n    return response\n```\n\n* Register (with captcha)\n\nKey | Value |\n--- | --- |\n**username** | test \n**email** | test@test.com \n**phone** | +19811354186\n**password** | testpass\n**captcha** | ah17ek\n\n```python\n@app.post('/register/')\nasync def on_register_captcha(request):\n    await captcha(request)\n    account, verification_session = await register(request)\n    await text_verification_code(account.phone, verification_session.code)\n    response = text('Registration successful')\n    verification_session.encode(response)\n    return response\n```\n\n## Authorization\n\nExamples of wildcard permissions are:\n\n```\nadmin:add,update,delete\nadmin:add\nadmin:*\nemployee:add,delete\nemployee:delete\nemployee:*\n```\n\nA library called [Apache Shiro](https://shiro.apache.org/permissions.html) explains this concept incredibly well. I \nabsolutely recommend this library for Java developers.\n\n* Requires Permission\n\n```python\n@app.get('/update')\n@requires_permission('admin:update')\nasync def on_test_perm(request):\n    return text('Admin has manipulated very sensitive data') \n```\n\n* Requires Role\n\n```python\n@app.get('/get')\n@requires_role('Admin')\nasync def on_test_role(request):\n    return text('Admin has retrieved very sensitive data')\n```\n\n## Error Handling\n\n```python\n@app.exception(RoseError)\nasync def on_rose_error_test(request, exception: ServerError):\n    payload = {\n        'error': str(exception),\n        'code': exception.status_code\n    }\n    return json(payload, status=exception.status_code)\n```\n\n## DTO\n\nA DTO object is a simple way to organize interactions with the database. It's completely abstract, so it can be used \nwith any model. If you choose not to use the DTO and instead work directly with Tortoise, that's completely fine.\n\n* Role DTO (Example)\n\n```python\nclass RoleDTO(DTO):\n    def __init__(self):\n        super().__init__(Role)\n\n    async def has_role(self, account: Account, role: str):\n        \"\"\"\n        Checks if the account has the required role being requested.\n\n        :param account: Account being checked.\n\n        :param role: The role that is required for validation.\n\n        :return: has_role\n        \"\"\"\n\n        return await self.t().filter(parent_uid=account.uid, name=role).exists()\n\n    async def assign_role(self, account: Account, role: str):\n        \"\"\"\n        Creates a role associated with an account\n\n        :param account: Account associated with role.\n\n        :param role: role to be associated with account.\n\n        :return: role\n        \"\"\"\n\n        return await self.create(parent_uid=account.uid, name=role)\n```\n\n* Usage Examples\n\n```python\nif not await role_dto.has_role(account, required_role):\n    raise Role.InsufficientRoleError()\n```\n\n```python \nclient = await account_dto.get_client(request)\nawait role_dto.assign_role(client, 'Admin')\n```\n\n```python\nparams = request.form\naccount = await account_dto.create(email=params.get('email'), username=params.get('username'),\n                                    password=account_dto.hash_password(params.get('password')),\n                                    phone=params.get('phone'))\n```\n\n```python\naccount, authentication_session = await authenticate(request)\nauthentication_session.valid = False\nawait authentication_session_dto.update(authentication_session, fields=['valid'])\n```\n\n\n\n<!-- ROADMAP -->\n## Roadmap\n\nKeep up with Amy Rose's [Trello](https://trello.com/b/aRKzFlRL/amy-rose) board for a list of proposed features, known issues, and in progress development.\n\n\n<!-- CONTRIBUTING -->\n## Contributing\n\nContributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n\n\n<!-- LICENSE -->\n## License\n\nDistributed under the GNU General Public License v3.0. See `LICENSE` for more information.\n\n\n\n<!-- CONTACT -->\n## Contact\n\nAidan Stewart - aidanstewart@sunsetdeveloper.com\n\nProject Link: [https://github.com/sunset-developer/Amy-Rose](https://github.com/sunset-developer/Amy-Rose)\n\n\n<!-- ACKNOWLEDGEMENTS -->\n## Acknowledgements\n\n* [Be the first! Submit a pull request.](https://github.com/sunset-developer/PyBus3/pulls)\n\n\n\n\n\n\n<!-- MARKDOWN LINKS & IMAGES -->\n<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->\n[contributors-shield]: https://img.shields.io/github/contributors/sunset-developer/Amy-Rose.svg?style=flat-square\n[contributors-url]: https://github.com/sunset-developer/Amy-Rose/graphs/contributors\n[forks-shield]: https://img.shields.io/github/forks/sunset-developer/Amy-Rose.svg?style=flat-square\n[forks-url]: https://github.com/sunset-developer/Amy-Rose/network/members\n[stars-shield]: https://img.shields.io/github/stars/sunset-developer/Amy-Rose.svg?style=flat-square\n[stars-url]: https://github.com/sunset-developer/Amy-Rose/stargazers\n[issues-shield]: https://img.shields.io/github/issues/sunset-developer/Amy-Rose.svg?style=flat-square\n[issues-url]: https://github.com/sunset-developer/Amy-Rose/issues\n[license-shield]: https://img.shields.io/github/license/sunset-developer/Amy-Rose.svg?style=flat-square\n[license-url]: https://github.com/sunset-developer/Amy-Rose/blob/master/LICENSE\n\n\n",
    "bugtrack_url": null,
    "license": "GNU General Public License v3.0",
    "summary": "A powerful, simple, and async authentication and authorization library for Sanic.",
    "version": "0.6.7",
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "936399fc8c0b0c734b878e9e11ae98f9",
                "sha256": "12a50e49be837f5429fa9d019b1c402e4d9af930619532b9fa67ad4cd4a95c17"
            },
            "downloads": -1,
            "filename": "amyrose-0.6.7-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "936399fc8c0b0c734b878e9e11ae98f9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 30407,
            "upload_time": "2021-01-17T02:08:22",
            "upload_time_iso_8601": "2021-01-17T02:08:22.817802Z",
            "url": "https://files.pythonhosted.org/packages/13/b5/9cc1a67f8c0c6f50b7d40b180b37effd3ff0808812022f489591cd05e1ec/amyrose-0.6.7-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "md5": "47410b9661fc9c82194f05fa213c6ee7",
                "sha256": "2ec888a28945598da56c6c252a7785933cfbfc140bd9a87a2bb947b01a8d6e05"
            },
            "downloads": -1,
            "filename": "amyrose-0.6.7.tar.gz",
            "has_sig": false,
            "md5_digest": "47410b9661fc9c82194f05fa213c6ee7",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 18393,
            "upload_time": "2021-01-17T02:08:24",
            "upload_time_iso_8601": "2021-01-17T02:08:24.432997Z",
            "url": "https://files.pythonhosted.org/packages/00/71/e6a90c58525b257c5455f56edd4d69dca50cbe3c47e64ec70d28f7057d01/amyrose-0.6.7.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2021-01-17 02:08:24",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": null,
    "github_project": "sunset-developer",
    "error": "Could not fetch GitHub repository",
    "lcname": "amyrose"
}
        
Elapsed time: 0.21181s