{ "info": { "author": "Peter Gaultney", "author_email": "pgaultney@xoi.io", "bugtrack_url": null, "classifiers": [], "description": "# typecats\nStructure unstructured data for the purpose of static type\nchecking. An opinionated wrapper for `attrs` and `cattrs`.\n\nIn many web services it is common to consume or generate JSON or some\nJSON-like representation of data. JSON translates quite nicely to core\nPython objects such as dicts and lists. However, if your data is\nstructured, it is nice to be able to work with it in a structured\nmanner, i.e. with Python objects. Python objects give you better code\nreadability, and in more recent versions of Python they are also\ncapable of being statically type-checked with a tool like `mypy`.\n\n`attrs` is an excellent library for defining boilerplate-free Python\nclasses that are easy to work with and that make static type-checking\nwith `mypy` a breeze. You define your attributes and their types with\na very clean syntax, `attrs` gives you constructors and dunder\nmethods, and `mypy` brings the static type-checking.\n\nThrowing `cattrs` into the mix, you can have pleasant and simple\nconversions to and from unstructured data with extremely low\nboilerplate as well.\n\n`typecats`, and its core decorator `Cat`, is a thin opionated layer on\ntop of these two runtime libraries (`attrs` and `cattrs`) and the\ndevelop-time `mypy`. It defines an `attrs` class with a few additional\nfeatures. The 3 core features are:\n\n## Features\n\n1. ### Built-in `struc` and `unstruc`.\n Static class function `struc` and object method `unstruc` added to\n every class type defined as a Cat, which pass directly through to\n their underlying `structure` and `unstructure` implementations in\n `cattrs`.\n\n ```\n @Cat\n class TestCat:\n\t name: str\n\t age: int\n\n TestCat.struc(dict(name='Tom', age=9)) == TestCat(name='Tom', age=9)\n\n TestCat.struc(dict(name='Tom', age=9)).unstruc() == dict(name='Tom', age=9)\n ```\n\n #### Rationale\n\n Make your code easier to read, create a common pattern for\n defining, structuring, and unstructuring pure data objects, and\n require fewer imports - just import your defined type and go!\n Abbreviated forms of the verbs `structure` and `unstructure` were\n chosen to underscore the difference between the built-in `cattrs`\n verbs and to reduce code clutter slightly for what is intended to\n be a common and idiomatic operation.\n\n #### Considerations\n\n Note that a `mypy` plugin is provided to inform the type checker\n that these dynamically-added methods are real and provide the\n intended result types. Add to your `mypy.ini`:\n\n ```\n plugins = typecats.cats_mypy_plugin\n ```\n\n Additionally, `struc` and `unstruc` first-class functions are\n provided if you strongly prefer a functional approach. `struc`\n reverses the order of the `cattrs` function signature to make it\n suitable for the common case of partial application:\n\n ```\n TestCat_struc = functools.partial(struc, TestCat)\n TestCat_struc(dict(name='Tom', age=2))\n ```\n\n2. ### Non-empty validators defined for all attributes with no default provided.\n\n ```\n @Cat\n class TestCat:\n\t name: str\n\t age: int\n\t neutered: bool = True\n\t owner: Optional[Owner] = None\n\n works = TestCat.struc(dict(name='Tom', age=0))\n assert works.neutered == True\n\n try:\n\t TestCat.struc(dict(name='', age=0))\n except ValueError as ve:\n\t print(ve)\n\t # Attribute \"name\" on class with type cannot have empty value ''!\n ```\n\n #### Rationale\n\n For many types of data, a default value such as an empty string,\n empty list/set, or missing complex type is perfectly valid, and\n `typecats` takes the approach that such attributes should have a\n defined default value in order to simplify the use of those\n objects. This has been found to be particularly useful in the\n context of structuring data from APIs, where the API contract may\n not require all keys to be provided for a given type, and where new\n attributes/keys may be defined later on that old clients would not\n know about (backwards compatibility). In these cases, not defining\n a default value would complicate the code, by forcing developers to\n remember which keys needed to be added to a raw data `dict` before\n structuring it.\n\n On the other hand, there are some facets of the data that are\n absolutely required. A common example would be a database ID -\n without a defined ID, the object/data is meaningless. `typecats`\n allows you to enforce the most basic level of compliance by not\n defining defaults, which forces clients to provide not simply a\n value of the proper type, but a non-empty value of that type - for\n instance, the empty string would never be a valid database ID.\n\n3. ### Wildcats - partial/gradual types via classes.\n\n Objects may subclass `dict` in order to transparently retain\n untyped key/value pairs for a roundtrip\n structure-unstructure. These are called `Wildcats`, since they\n allow a significant amount of extra functionality at the cost of\n not fully enforcing type-checking.\n\n ```\n @Cat\n class TestWildcat(dict):\n\t name: str\n\t age: int\n\n cat_from_db = dict(name='Tom', age=8, gps_tracker=True)\n wc = TestWildcat.struc(cat_from_db)\n assert wc.name == Tom\n assert wc.age == 8\n assert wc['gps_tracker'] == True # cattrs would normally drop this key at structure time\n assert wc.unstruc() == cat_from_db # `gps_tracker` survived the roundtrip\n ```\n\n #### Rationale\n\n Effectively provides a partially-typed overlay on top of existing\n data, as gradual/partial typing within a specific data format can\n be very useful.\n\n In other static type-checking systems such as Flow for JavaScript,\n you may define a type as being a simple overlay on top of an object\n which does not prevent that object from containing other data for\n keys outside the typed set. A `Cat` is an `attrs` class with a\n defined set of attributes that will be structured from raw data,\n and as of `cattrs` 1.0.0rc0, unexpected keys are silently dropped\n in order to prevent users from needing to sanitize their data\n before structuring (as opposed to being a runtime error). This\n behavior means that a structured object is not suitable for being\n passed between different parts of a program if there may be other\n parts to the data that the structuring class does not know\n about. This is an unfortunately common requirement, for instance\n when operating a roundtrip read/write transaction to/from a\n database. Since the alternative of passing around the raw data and\n performing many separate structuring/unstructuring roundtrips can\n be prohibitively expensive, and additionally it is arguably (e.g.,\n the design philosophy behind Clojure's Maps, or simply\n duck/structural typing in general) better software design in many\n cases to allow code to operate on a limited subset of attributes\n without preventing objects with a superset of their functionality\n from being used, `typecats` provides the `Wildcat` functionality to\n mimic these more expressive and flexible type/data systems.\n\n #### Considerations\n\n Note that, as with the rest of `typecats`, this is a local optimum\n designed for specific, though arguably common, usecases. You don't\n need to use the Wildcat functionality to take advantage of features\n 1 and 2, and since it is presumably (for good reason) quite rare to\n explicity subclass `dict` for normal Python classes, it seems\n unlikely that this implementation choice to require inheritance\n would prevent most practical use cases of `Cat` even if the\n functionality of preserving unknown data was specifically not\n desirable for a given application.\n\n If an application attempts to get or set items within a Wildcat\n which are defined attributes on the class, this will (as of v1.1)\n be allowed but a warning will be logged. This seems to be a better\n in-practice balance for evolving codebases than the v1.0 behavior of\n raising an error. A future version could potentially allow this to\n be toggled globally or per Wildcat class, but the default will\n remain permissive for backwards compatiblity.\n\n A further design note on Wildcats: A non-inheriting implementation\n was considered and rejected (so far) for two reasons: first, that\n this would require major additional work in order to support\n `pylint` and `mypy` understanding that dict-like access was legal\n for these objects; and second, that *not* inheriting `dict` but\n overriding `__getitem__` and `__setitem__` would be even more\n likely to conflict with existing class hierarchies, since any\n object that already inherited from `dict` would appear to 'work' as\n a Wildcat but its underlying `dict` would be overlaid and\n inaccessible as a Wildcat.\n\n\n## Notes on intent, compatibility, and dependencies\n\n`typecats` and `Cat` are explictly intended to solve a *few* specific\nbut common uses, and though they do not intentionally override or\nreplace `attrs` or `cattrs` features, any complex use of those\nunderlying features may or may not be fully operational. If you want\nto write complex validator or constructor/builder logic of your own,\nthis library may not be for you.\n\nThat said, it is common in our experience to register a number of\nspecific structure and unstructure hooks with `cattrs` to make certain\nspecific scenarios work ideally with your data, and `typecats`\nprovides convenient wrappers to allow adding your hooks to its\ninternal `cattrs` `Converter` instance. By defining its own converter\ninstance, `typecats` does not interfere in any way with an existing\napplication's usage of `attrs` or `cattrs`, and may be used in\naddition to, rather than as a replacement for, those libraries.\n\nUse `register_struc_hook` and `register_unstruc_hook` to register on\nthe built-in converter instance.\n\n`typecats` uses newer-style static typing within its own codebase, and\nis therefore currently only compatible with Python 3.6 and up.\n\nAs core parts of the implementation, both `attrs` and `cattrs` are\nruntime dependencies.\n\n\n## Users/Stability\n\n`typecats` has been used in production in the Vision system at XOi\nTechnologies for over 6 months, with no significant changes or bugs\nfound in the past 3 months.\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": "https://github.com/xoeye/typecats", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "typecats", "package_url": "https://pypi.org/project/typecats/", "platform": "", "project_url": "https://pypi.org/project/typecats/", "project_urls": { "Homepage": "https://github.com/xoeye/typecats" }, "release_url": "https://pypi.org/project/typecats/1.2.0/", "requires_dist": [ "attrs (==19.1)", "cattrs (==1.0.0rc0)" ], "requires_python": ">=3.6", "summary": "Structure unstructured data for the purpose of static type checking", "version": "1.2.0" }, "last_serial": 5822904, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "ea15e12a2c6fa722485ed1d6f3565a57", "sha256": "2543ef7be75f7105f25c68f6e49dff6d3b1eec585f412456b9748aff06f1c7c6" }, "downloads": -1, "filename": "typecats-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "ea15e12a2c6fa722485ed1d6f3565a57", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 10089, "upload_time": "2019-08-15T01:11:20", "url": "https://files.pythonhosted.org/packages/e1/fc/23e4064fa43021d4acf89c184dafa8e71f5fe0e807cca85166a6af46e688/typecats-1.0.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ab4f1c5649d8b8c9afbacbc81a04e942", "sha256": "86393eac0c75e48662d3bcd3834668c903fad6869ca0aac9339eb1aeeae41380" }, "downloads": -1, "filename": "typecats-1.0.0.tar.gz", "has_sig": false, "md5_digest": "ab4f1c5649d8b8c9afbacbc81a04e942", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11176, "upload_time": "2019-08-15T01:11:22", "url": "https://files.pythonhosted.org/packages/3c/d1/de8faf64cc04a1314b0446ab0a7a810893e4ec867ea81f879756df57e4f3/typecats-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "682364cd303d2b1cbb4b40e25ad3679d", "sha256": "d474f7d676fd47bdd16fd72ec506a815ebdcd775cc96224ef5d53d1c0825d359" }, "downloads": -1, "filename": "typecats-1.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "682364cd303d2b1cbb4b40e25ad3679d", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 13956, "upload_time": "2019-08-15T02:08:56", "url": "https://files.pythonhosted.org/packages/51/2d/ceeb7ab878c048fa1d36ab9a77fe93a8e9afa8cced59f356bd22edf78f88/typecats-1.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bb5624b1a980bd6ea265737575757da1", "sha256": "2868c8bf617a6f6293d66b4fe54ebb135239e958821683e8aa506b456acf5e0d" }, "downloads": -1, "filename": "typecats-1.0.1.tar.gz", "has_sig": false, "md5_digest": "bb5624b1a980bd6ea265737575757da1", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 15642, "upload_time": "2019-08-15T02:08:58", "url": "https://files.pythonhosted.org/packages/4c/e3/c31ab3ac9c925e17264815ffc49c987724c20ab2bfc3bb91274775b41c0e/typecats-1.0.1.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "96546fdf469f4ce01dbfa32712351732", "sha256": "468529f39c38584bb9f8137c633c5bdc11b8a2d1227901ec19eea90b5a40df13" }, "downloads": -1, "filename": "typecats-1.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "96546fdf469f4ce01dbfa32712351732", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 14056, "upload_time": "2019-08-16T21:43:48", "url": "https://files.pythonhosted.org/packages/a5/5c/69b0a6ab6664eb99015731cff964e356edd5125f6ddc9b46f7e9372c897a/typecats-1.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f7d356704fe9e108ca57e7f02507754a", "sha256": "b0395426ad93fdb9d880fec330fb6aa9bcb4190335f837b3fb6ba39bce5eb3a2" }, "downloads": -1, "filename": "typecats-1.1.0.tar.gz", "has_sig": false, "md5_digest": "f7d356704fe9e108ca57e7f02507754a", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 15718, "upload_time": "2019-08-16T21:43:49", "url": "https://files.pythonhosted.org/packages/89/bc/da9d1bbe303b57d28dac4d7ebdf48e4d1c474b01b3d5871f7ad7b3cd4125/typecats-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "cdabb8c35f79776a1b066d30fa066f42", "sha256": "c997888515ba8ca2085b9d93c0dc923365db32942dfed4d53f8a95a363462d13" }, "downloads": -1, "filename": "typecats-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "cdabb8c35f79776a1b066d30fa066f42", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 14512, "upload_time": "2019-08-23T13:27:17", "url": "https://files.pythonhosted.org/packages/59/04/08ceebd9846f9096773590e9f3d75999a351e42f4eff6efe1185094dcda6/typecats-1.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fc1dff4128dee2e5ff2e5361f84449d6", "sha256": "a0ff10b176bb3933991cd8d4db949c1114237e0151489faf7825a309b40c6156" }, "downloads": -1, "filename": "typecats-1.1.1.tar.gz", "has_sig": false, "md5_digest": "fc1dff4128dee2e5ff2e5361f84449d6", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16586, "upload_time": "2019-08-23T13:27:20", "url": "https://files.pythonhosted.org/packages/85/01/cfb42e0d855aa51dc9b28c3a479939b5ba6f358782134413004c2ff2a2ec/typecats-1.1.1.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "c1439e78a8b0d30566a462182eaf0a9e", "sha256": "dff0025a9c9c2d71275cb8a334d9261fda3d9a9be041c81792c58bc891428db8" }, "downloads": -1, "filename": "typecats-1.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "c1439e78a8b0d30566a462182eaf0a9e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 14552, "upload_time": "2019-09-12T22:34:49", "url": "https://files.pythonhosted.org/packages/27/d4/c5af24a3168a936d4470c698a2cce176127e26d631db91e5e8a2f7ed302c/typecats-1.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1855bacac1ec8777b0d69a3eab49ffe7", "sha256": "a1dc2b76919773fc211e26821f09a751a261b7d542e34fc25d3a0bb6a8c2b10c" }, "downloads": -1, "filename": "typecats-1.2.0.tar.gz", "has_sig": false, "md5_digest": "1855bacac1ec8777b0d69a3eab49ffe7", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16602, "upload_time": "2019-09-12T22:34:51", "url": "https://files.pythonhosted.org/packages/9e/aa/361be9c0b5dd36e412f2827bc84403ab319def83e9451badbe230130bfcb/typecats-1.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "c1439e78a8b0d30566a462182eaf0a9e", "sha256": "dff0025a9c9c2d71275cb8a334d9261fda3d9a9be041c81792c58bc891428db8" }, "downloads": -1, "filename": "typecats-1.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "c1439e78a8b0d30566a462182eaf0a9e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 14552, "upload_time": "2019-09-12T22:34:49", "url": "https://files.pythonhosted.org/packages/27/d4/c5af24a3168a936d4470c698a2cce176127e26d631db91e5e8a2f7ed302c/typecats-1.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1855bacac1ec8777b0d69a3eab49ffe7", "sha256": "a1dc2b76919773fc211e26821f09a751a261b7d542e34fc25d3a0bb6a8c2b10c" }, "downloads": -1, "filename": "typecats-1.2.0.tar.gz", "has_sig": false, "md5_digest": "1855bacac1ec8777b0d69a3eab49ffe7", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 16602, "upload_time": "2019-09-12T22:34:51", "url": "https://files.pythonhosted.org/packages/9e/aa/361be9c0b5dd36e412f2827bc84403ab319def83e9451badbe230130bfcb/typecats-1.2.0.tar.gz" } ] }