{ "info": { "author": "Channel Cat", "author_email": "channelcat@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 2 - Pre-Alpha", "Environment :: Web Environment", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7" ], "description": "# Sanic with attrs towards Swagger 2.0 / OpenAPI support\n\nSupercharge your [Sanic](https://github.com/channelcat/sanic>) app with:\n\n- [attrs](http://www.attrs.org/)\n- [Swagger](https://swagger.io/docs/specification/2-0/basic-structure/)\n\n**Note**: This is a fork of Sanic OpenAPI implementation from [@channelcat](https://github.com/channelcat), which I like a lot but it lacks some of the functionality I wanted (and I also went sideways by using a third-party lib ([`attrs`](http://www.attrs.org/)) as default for modeling input / output model classes).\n\n[![PyPI](https://img.shields.io/pypi/v/sanic-attrs.svg)](https://pypi.python.org/pypi/sanic-attrs/)\n[![PyPI](https://img.shields.io/pypi/pyversions/sanic-attrs.svg)](https://pypi.python.org/pypi/sanic-attrs/)\n\n## Super quick introduction\n\nGive your Sanic API an UI and OpenAPI documentation, all for the price of free!\n\n![Example Swagger UI](https://github.com/vltr/sanic-attrs/blob/master/images/code-to-ui.png?raw=true \"Swagger UI\")\n\n## Installation\n\n**Attention**: since this fork came from a necessity of mine, a lot of features I want to implement are still not available, hence the status of `pre-alpha` to this library! Also, _don't try the examples folder_, it was not converted (yet)! Shame on me ...\n\n```shell\npip install sanic-attrs\n```\n\nAdd OpenAPI and Swagger UI:\n\n```python\nfrom sanic_attrs import swagger_blueprint, openapi_blueprint\n\napp.blueprint(openapi_blueprint)\napp.blueprint(swagger_blueprint)\n```\n\nYou'll now have a Swagger UI at the URL `/swagger`. Your routes will be automatically categorized by their blueprints. This is the default usage, but more advanced usage can be seen. Keep reading!\n\n_Note_: the `swagger_blueprint` is awesome but sometimes you don't want it open-wide for whatever reason you have (security, etc), so you can make it available only if running with `debug=True`, for example. That's how I actually use it :smile:\n\n## [typing](https://docs.python.org/3/library/typing.html)\n\nSince `sanic-attrs` is, of course, based on `attrs` and the Python target version is 3.5+, most of the typing definitions for your model will be made entirely using Python types, either global ones or from the `typing` library. Also, `enums` are supported as well! :sparkles:\n\nHere's the types supported (so far):\n\n- `int`\n- `float`\n- `str`\n- `bool`\n- `date`\n- `datetime`\n- `bytes`\n- `typing.Any`\n- `typing.Collection`\n- `typing.Dict`\n- `typing.Iterable`\n- `typing.List`\n- `typing.Mapping`\n- `typing.Optional`\n- `typing.Sequence`\n- `typing.Set`\n- `typing.Union`\n\n**A note on `list` and `dict`**: Please, use `typing.List` and `typing.Dict` for this.\n\n## Usage\n\n### Use simple decorators to document routes\n\n```python\nfrom sanic_attrs import doc\n\n@app.get(\"/user/\")\n@doc.summary(\"Fetches a user by ID\")\n@doc.produces(SomeOutputModel)\nasync def get_user(request, user_id):\n ...\n\n@app.post(\"/user\")\n@doc.summary(\"Creates a user\")\n@doc.consumes(SomeInputModel, location=\"body\")\nasync def create_user(request):\n ...\n```\n\n### Model your input/output\n\nYes, in this version you **need** to be descriptive :wink:\n\n```python\nimport typing\n\nfrom sanic_attrs import doc\n\n\nclass Car(doc.Model):\n make: str = doc.field(description=\"Who made the car\")\n model: str = doc.field(description=\"Type of car. This will vary by make\")\n year: int = doc.field(description=\"4-digit year of the car\", required=False)\n\n\nclass Garage(doc.Model):\n spaces: int = doc.field(description=\"How many cars can fit in the garage\")\n cars: typing.List[Car] = doc.field(description=\"All cars in the garage\")\n\n\n@app.get(\"/garage\")\n@doc.summary(\"Gets the whole garage\")\n@doc.produces(Garage)\nasync def get_garage(request):\n return json({\n \"spaces\": 2,\n \"cars\": [{\"make\": \"Nissan\", \"model\": \"370Z\"}]\n })\n\n```\n\n### Advanced usage\n\nSince `doc.Model` and `doc.field` are nothing more as syntatic sugar for the `@attr.s` decorator and `attr.ib` function, you can express your models using these provided classes and methods or use vanilla `attrs` in your models. Here's a complex example that shows a mixed model:\n\n```python\nfrom enum import Enum, IntEnum\nfrom typing import (Any, Collection, Dict, Iterable, List, Mapping, Optional,\n Sequence, Set, Union)\n\nimport attr\n\nfrom sanic_attrs import doc\n\n\nclass PlatformEnum(str, Enum):\n XBOX1 = \"XBOX1\"\n PLAYSTATION4 = \"PLAYSTATION4\"\n PC = \"PC\"\n\n\nclass LanguageEnum(IntEnum):\n ENGLISH = 1\n JAPANESE = 2\n SPANISH = 3\n GERMAN = 4\n PORTUGUESE = 5\n\n\nclass Something(doc.Model):\n some_name: str = doc.field(description=\"Something name\")\n\n\n@attr.s\nclass AnotherSomething:\n another_name: str = attr.ib(metadata={\"description\": \"Another field\"})\n\n\nclass Game(doc.Model):\n name: str = doc.field(description=\"The name of the game\")\n platform: PlatformEnum = doc.field(description=\"Which platform it runs on\")\n score: float = doc.field(description=\"The average score of the game\")\n resolution_tested: str = doc.field(description=\"The resolution which the game was tested\")\n genre: List[str] = doc.field(description=\"One or more genres this game is part of\")\n genre_extra: Sequence[str] = doc.field(description=\"One or more genres this game is part of\")\n rating: Dict[str, float] = doc.field(description=\"Ratings given on each country\")\n rating_outside: Mapping[str, float] = doc.field(description=\"Ratings given on each country\")\n screenshots: Set[bytes] = doc.field(description=\"Screenshots of the game\")\n screenshots_extra: Collection[bytes] = doc.field(description=\"Screenshots of the game\")\n players: Iterable[str] = doc.field(description=\"Some of the notorious players of this game\")\n review_link: Optional[str] = doc.field(description=\"The link of the game review (if exists)\")\n junk: Union[str, bytes] = doc.field(description=\"This should be strange\")\n more_junk: Any = doc.field(description=\"The more junk field\")\n language: LanguageEnum = doc.field(description=\"The language of the game\")\n something: List[Something] = doc.field(description=\"Something to go along the game\")\n another: AnotherSomething = doc.field(description=\"Another something to go along the game\")\n```\n\n### A note on typing hints or `type` argument\n\nYou may have noticed that in the example above, all variables have been created using typing hints. While this is somewhat interesting, you may also want to use the `type` argument as provided from the `attr` package, and `sanic-attrs` is absolutely fine with that. So, our `Game` class would rather looks like:\n\n```python\nclass Game(doc.Model):\n name = doc.field(type=str, description=\"The name of the game\")\n platform = doc.field(type=PlatformEnum, description=\"Which platform it runs on\")\n score = doc.field(type=float, description=\"The average score of the game\")\n resolution_tested = doc.field(type=str, description=\"The resolution which the game was tested\")\n genre = doc.field(type=List[str], description=\"One or more genres this game is part of\")\n genre_extra = doc.field(type=Sequence[str], description=\"One or more genres this game is part of\")\n rating = doc.field(type=Dict[str, float], description=\"Ratings given on each country\")\n rating_outside = doc.field(type=Mapping[str, float], description=\"Ratings given on each country\")\n screenshots = doc.field(type=Set[bytes], description=\"Screenshots of the game\")\n screenshots_extra = doc.field(type=Collection[bytes], description=\"Screenshots of the game\")\n players = doc.field(type=Iterable[str], description=\"Some of the notorious players of this game\")\n review_link = doc.field(type=Optional[str], description=\"The link of the game review (if exists)\")\n junk = doc.field(type=Union[str, bytes], description=\"This should be strange\")\n more_junk = doc.field(type=Any, description=\"The more junk field\")\n language = doc.field(type=LanguageEnum, description=\"The language of the game\")\n something = doc.field(type=List[Something], description=\"Something to go along the game\")\n another = doc.field(type=AnotherSomething, description=\"Another something to go along the game\")\n```\n\n### A note on a lot of features of `attrs`\n\nThere are a lot of features in `attrs` that can be handy while declaring a model, such as validators, factories and etc. For this release, some syntatic sugar is planned regarding validators (since most of the rules can be provided to `doc.field`). Other features, like `factories`, are not encourage at this time (or for the lifetime of this project, undecided) while declaring models since there wasn't enough time to actually test them (so far) :confused:\n\n## On-the-fly input model parsing\n\nThere are a few surprises inside `sanic-attrs`. Let's say you have already declared your model, your endpoint and you still have to take the `request.json` and load it as your model? That doesn't seems right ... Fortunatelly, a small middleware was written to handle these cases :wink:\n\nTo enable on-the-fly input model parsing, all you need to do is add a `blueprint` to your Sanic app and access the object using the `input_obj` keyword directly from the request:\n\n```python\nfrom sanic_attrs import parser_blueprint\n\n# ...\n\napp.blueprint(parser_blueprint)\n\n# ...\n\n@app.post(\"/game\", strict_slashes=True)\n@doc.summary(\"Inserts the game data into the database\")\n@doc.response(\"200\", \"Game inserted successfuly\", model=SuccessOutput)\n@doc.response(\"403\", \"The user couldn't insert game to application\", model=ErrorOutput)\n@doc.consumes(Game, location=\"body\", content_type=\"application/json\")\n@doc.produces(SuccessOutput)\nasync def insert_game(request):\n my_object = request[\"input_obj\"]\n assert isinstance(my_object, Game)\n # your logic here\n```\n\n**Note**: there are no validations to deal with (really) broken data. If an exception occurs while populating your model, you will find that your `input_obj` keyword will be `None`, along with another key, `input_exc`, that will contain the exception given (if any). If you want to further customize this behavior so you won't need to check for `None` in every request, you can add your own `middleware` **after** adding the `parser_blueprint` to the `app` instance, like the following:\n\n```python\nfrom sanic.response import json\nfrom sanic_attrs import parser_blueprint\n\n# ...\n\napp.blueprint(parser_blueprint)\n\n# ...\n\n@app.middleware(\"request\")\nasync def check_if_input_is_none(request):\n if \"input_obj\" in request:\n if request[\"input_obj\"] is None:\n # error handling here\n return json({\"error\": request[\"input_exc\"].args[0]}, 500)\n```\n\n## On-the-fly output model serialization\n\nTo keep things simple, it is also possible to handle the direct return of `attrs` objects, instead of having to create a dictionary and then serialize or call `sanic.responses.json`, although this is exactly what's running under the hood:\n\n```python\nfrom sanic_attrs import response\n\n# ...\n\n@app.get(\"/game\", strict_slashes=True)\n@doc.summary(\"Gets the most played game in our database\")\n@doc.response(\"200\", \"Game data\", model=Game)\n@doc.response(\"403\", \"The user can't access this endpoint\", model=ErrorOutput)\n@doc.produces(Game)\nasync def get_game(request):\n game = Game(\n name=\"Cities: Skylines\",\n platform=\"PC\",\n score=9.0,\n resolution_tested=\"1920x1080\",\n genre=[\"Simulators\", \"City Building\"],\n rating={\n \"IGN\": 8.5,\n \"Gamespot\": 8.0,\n \"Steam\": 4.5\n },\n players=[\"Flux\", \"strictoaster\"],\n language=1\n )\n return response.model(game) # <--- the game instance, to be further serialized\n```\n\n**Note**: remember to create models that can have all its values serializable to JSON :+1:\n\n### Configure everything else\n\n```python\napp.config.API_VERSION = '1.0.0'\napp.config.API_TITLE = 'Car API'\napp.config.API_DESCRIPTION = 'Car API'\napp.config.API_TERMS_OF_SERVICE = 'Use with caution!'\napp.config.API_PRODUCES_CONTENT_TYPES = ['application/json']\napp.config.API_CONTACT_EMAIL = 'channelcat@gmail.com'\n```\n\n### Types not *yet* avaiable\n\nThese are the types not available from [`typing`](https://docs.python.org/3/library/typing.html) in the current version (with some notes so I can remember what to do later (if necessary)):\n\n- `AbstractSet` - would be like set?\n- `AnyStr` - this is mostly like Optional[str] or just str?\n- `AsyncContextManager` - not a variable I think\n- `AsyncGenerator` - not a variable I think\n- `AsyncIterable` - not a variable I think\n- `AsyncIterator` - not a variable I think\n- `Awaitable` - not a variable I think\n- `BinaryIO` - hmmm, I don't know ... Bytes maybe?\n- `ByteString` - could be like bytes, for openapi is `{\"type\":\"string\", \"format\": \"byte\"}`\n- `CT_co` - I don't even know what this is ...\n- `Callable` - not a variable\n- `CallableMeta` - not a variable\n- `ChainMap` - not a variable (?)\n- `ClassVar` - generic ...\n- `Container` - generic\n- `ContextManager` - not a variable\n- `Coroutine` - not a variable\n- `Counter` - not a variable\n- `DefaultDict` - perhaps like dict?\n- `Deque` - like List ?\n- `FrozenSet` - a \"view-only list?\n- `Generator` - not a variable\n- `Generic` - no way - or Any?\n- `Hashable` - a hashmap?\n- `IO` - hmmm, from docs: \"Generic base class for TextIO and BinaryIO.\", so ...\n- `ItemsView` - what is an Item? it inherits from AbstractSet ... from docs: \"A set is a finite, iterable container.\"\n- `Iterator` - not a variable\n- `KT` - generics\n- `KeysView` - dict \"readonly\" ?\n- `MappingView` - dict \"readonly\" ?\n- `Match` - generic (I think)\n- `MethodDescriptorType` - not a variable\n- `MethodWrapperType` - not a variable\n- `MutableMapping` - base class of Mapping, docs: \"Abstract base class for generic types.\"\n- `MutableSequence` - same as above, but for Sequence\n- `MutableSet` - same as above, but for Set\n- `NamedTuple` - what to do here? NamedTuple is just an object with variables that can be *anything* I guess ...\n- `NamedTupleMeta` - baseclass of NamedTuple\n- `NewType` - not a variable / generic ?\n- `NoReturn` - not a variable\n- `Pattern` - generic\n- `Reversible` - generic (Iterable)\n- `Sized` - generic\n- `SupportsAbs` - not a variable\n- `SupportsBytes` - not a variable\n- `SupportsComplex` - not a variable\n- `SupportsFloat` - not a variable\n- `SupportsInt` - not a variable\n- `SupportsRound` - not a variable\n- `T` - generic\n- `TYPE_CHECKING` - ???\n- `T_co` - ???\n- `T_contra` - ???\n- `Text` - returns a str object if created, so I'll stick with str or map it too?\n- `TextIO` - buffer, like bytes ... map it?\n- `Tuple` - well ... Tuple like lists or Tuple like Tuple[int, str, float] ?\n- `TupleMeta` - baseclass of Tuple\n- `Type` - generics\n- `TypeVar` - generics\n- `TypingMeta` - generics\n\nIf there's anything missing or required, please fill in a issue or contribute with a PR. PR's are most welcome :smiley:\n\n## TODO\n\n- [ ] Property deal with `required` fields (in OpenAPI `object` schema)\n- [ ] Use type hinting to document the return of a function (as output schema / model)\n- [ ] Proper testing\n- [ ] Increase use cases\n- [ ] Find out if I can get the request model without calling the router\n- [ ] Documentation\n\n## License\n\nMIT, the same as [`sanic-openapi`](https://github.com/channelcat/sanic-openapi/blob/ffe8a5c7443810f1dfe65ad7dd1991e776931dc1/LICENSE).\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/vltr/sanic-attrs/", "keywords": "", "license": "MIT", "maintainer": "Richard Kuesters", "maintainer_email": "rkuesters@gmail.com", "name": "sanic-attrs", "package_url": "https://pypi.org/project/sanic-attrs/", "platform": "any", "project_url": "https://pypi.org/project/sanic-attrs/", "project_urls": { "Homepage": "http://github.com/vltr/sanic-attrs/" }, "release_url": "https://pypi.org/project/sanic-attrs/0.2.1/", "requires_dist": [ "sanic (>=0.7.0)", "attrs (>=18.0.0)" ], "requires_python": "", "summary": "OpenAPI / Swagger support for Sanic using attrs", "version": "0.2.1" }, "last_serial": 3954193, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "a22540289d5993f458e00eecfc26add8", "sha256": "f50a56083b22889199dc024e5e74c610f2885a36352831b339b55eb43edf2c1d" }, "downloads": -1, "filename": "sanic_attrs-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "a22540289d5993f458e00eecfc26add8", "packagetype": "bdist_wheel", "python_version": "3.6", "requires_python": null, "size": 13116, "upload_time": "2018-06-08T17:30:08", "url": "https://files.pythonhosted.org/packages/31/5b/b14032aa32c707be48ae594b07add5210fc177eb80a4245290d6a6b3b9b2/sanic_attrs-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a93432291c954da34e11594c5c7793e7", "sha256": "2cfce8010d9af5962468b9d5c16de0b941a0e5a75cb80e7a75f21cceea392867" }, "downloads": -1, "filename": "sanic-attrs-0.1.0.tar.gz", "has_sig": false, "md5_digest": "a93432291c954da34e11594c5c7793e7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16103, "upload_time": "2018-06-08T17:30:05", "url": "https://files.pythonhosted.org/packages/24/2c/9f91ea620d46354cd3d72feb76cdc437429c6f2505ed86d7ced58342e653/sanic-attrs-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "3ec9641b0cd6f21532dfb61d00393c79", "sha256": "8a2d47e20d36de3b2e035f38ffb2e5b9902f728aeea9c7779d2e1c44957cffab" }, "downloads": -1, "filename": "sanic_attrs-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "3ec9641b0cd6f21532dfb61d00393c79", "packagetype": "bdist_wheel", "python_version": "3.6", "requires_python": null, "size": 18204, "upload_time": "2018-06-11T15:04:59", "url": "https://files.pythonhosted.org/packages/08/a5/1969a8676300c77b63b7c1c919a27a0b2d0ddcb787c73ef8ff47be9de901/sanic_attrs-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "49a4662553d74f5c1c4e281f7b7ed9fc", "sha256": "a530876246f027778dcaed9040409f1a81cef07d8cb203db07050a5b2d7b15a3" }, "downloads": -1, "filename": "sanic-attrs-0.1.1.tar.gz", "has_sig": false, "md5_digest": "49a4662553d74f5c1c4e281f7b7ed9fc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22605, "upload_time": "2018-06-11T15:04:56", "url": "https://files.pythonhosted.org/packages/04/d4/99d2bd8eb4cba970f69bd697d8dd983e28658511e1fb2cde3f6b1e3b3da5/sanic-attrs-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "7bcb3635cff305123362a87c2e2b4f73", "sha256": "0fcbaa380d87d4b19ef5ef5a5181a004d0d18de2c2cd73e525f3ee8397950bc1" }, "downloads": -1, "filename": "sanic_attrs-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "7bcb3635cff305123362a87c2e2b4f73", "packagetype": "bdist_wheel", "python_version": "3.6", "requires_python": null, "size": 18232, "upload_time": "2018-06-11T15:13:42", "url": "https://files.pythonhosted.org/packages/2f/84/5ea19cbc631b3be82fd5df6b52e47deb855e12b10045b79af03d63e617a7/sanic_attrs-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "c8fbb119716ab50953747be19bd4a27f", "sha256": "ef9ab3ea5fbb61ee381e7677215885fdad2f465d0c93d9b656ef1398f276e3ee" }, "downloads": -1, "filename": "sanic-attrs-0.1.2.tar.gz", "has_sig": false, "md5_digest": "c8fbb119716ab50953747be19bd4a27f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22665, "upload_time": "2018-06-11T15:13:40", "url": "https://files.pythonhosted.org/packages/82/ae/f7404c80e34cb3270d77b660f744f48069ffaff546e9c5257ec7bdd3f927/sanic-attrs-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "7ab6128a7bc846b0334e41fa7cadac8a", "sha256": "bc0a9e6d078674caaece2387f3ca864c25304cb18a84159ccc5d5a4c6d0dd275" }, "downloads": -1, "filename": "sanic_attrs-0.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "7ab6128a7bc846b0334e41fa7cadac8a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 18298, "upload_time": "2018-06-11T17:46:33", "url": "https://files.pythonhosted.org/packages/82/71/db6adf902e13e2bd6fd385e2c7a807ab2af67ebc026c311c1f4350e2ac80/sanic_attrs-0.1.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "533ad46e424311cf11df2d5817c7af7e", "sha256": "41c6bff5d07d23200bb20b4073c8bdd8e38cc1e202169dee707b1122fe437ddd" }, "downloads": -1, "filename": "sanic-attrs-0.1.3.tar.gz", "has_sig": false, "md5_digest": "533ad46e424311cf11df2d5817c7af7e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22818, "upload_time": "2018-06-11T17:45:49", "url": "https://files.pythonhosted.org/packages/dc/68/f784f2144116ea5603027220569f9ae614fe4efd51da2ea5dad261dcc00e/sanic-attrs-0.1.3.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "2c2903c81655c05639578882735088c8", "sha256": "9084044987e5d245b99d74e73c6855243398dbe4dcaad2a0fcf66c246b53e825" }, "downloads": -1, "filename": "sanic_attrs-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "2c2903c81655c05639578882735088c8", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19418, "upload_time": "2018-06-12T14:56:21", "url": "https://files.pythonhosted.org/packages/5a/5e/03d5fa435ad9f596ded64484c57a36292e1f565aa45abd098b7eb15aa194/sanic_attrs-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3be557a9583445f43c199d276621524f", "sha256": "f9bcea1c762d54461d9d759d56a9d69c9cd5d33fa5d39acd54acb37141d002bb" }, "downloads": -1, "filename": "sanic-attrs-0.2.0.tar.gz", "has_sig": false, "md5_digest": "3be557a9583445f43c199d276621524f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24070, "upload_time": "2018-06-12T14:56:25", "url": "https://files.pythonhosted.org/packages/b8/e3/21f02bef6443b95d8be4f9a80ec7147d1e288198103fe578cd1540a4b490/sanic-attrs-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "73be98ebf6935e93f053931e02166531", "sha256": "f35554f350fe36faa5526daa935759c53f94735aaa477a79ac28b53cdb9d7850" }, "downloads": -1, "filename": "sanic_attrs-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "73be98ebf6935e93f053931e02166531", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19446, "upload_time": "2018-06-12T15:06:30", "url": "https://files.pythonhosted.org/packages/52/26/d035d1b2ec3833c6515e9dfb2761d27578aa20c36e4952cd51bb333cdc64/sanic_attrs-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6edc46dea2c3847ead743aec991f4d31", "sha256": "4a0bbb6881ac91b966d6789e5c30acab96a2fd4de6c203376ce5680e9b2c527e" }, "downloads": -1, "filename": "sanic-attrs-0.2.1.tar.gz", "has_sig": false, "md5_digest": "6edc46dea2c3847ead743aec991f4d31", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24099, "upload_time": "2018-06-12T15:06:32", "url": "https://files.pythonhosted.org/packages/b6/8d/e0a1bdaddb8407d00bf9b9e28be562abaf697d3c4539440bf55e2ae35bb2/sanic-attrs-0.2.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "73be98ebf6935e93f053931e02166531", "sha256": "f35554f350fe36faa5526daa935759c53f94735aaa477a79ac28b53cdb9d7850" }, "downloads": -1, "filename": "sanic_attrs-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "73be98ebf6935e93f053931e02166531", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 19446, "upload_time": "2018-06-12T15:06:30", "url": "https://files.pythonhosted.org/packages/52/26/d035d1b2ec3833c6515e9dfb2761d27578aa20c36e4952cd51bb333cdc64/sanic_attrs-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6edc46dea2c3847ead743aec991f4d31", "sha256": "4a0bbb6881ac91b966d6789e5c30acab96a2fd4de6c203376ce5680e9b2c527e" }, "downloads": -1, "filename": "sanic-attrs-0.2.1.tar.gz", "has_sig": false, "md5_digest": "6edc46dea2c3847ead743aec991f4d31", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24099, "upload_time": "2018-06-12T15:06:32", "url": "https://files.pythonhosted.org/packages/b6/8d/e0a1bdaddb8407d00bf9b9e28be562abaf697d3c4539440bf55e2ae35bb2/sanic-attrs-0.2.1.tar.gz" } ] }