{ "info": { "author": "J Uryga", "author_email": "lolzatu2@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# `sumtype`\nA `namedtuple`-style library for defining immutable **sum types** in Python. ([Get it on PyPI](https://pypi.org/project/sumtype/))\n\n> *You may know **sum types** under a different name \u2013\n> they're also referred to as `tagged unions`, `enums` in Rust/Swift, and `variants` in C++. \n> If you haven't heard about them yet, [here's](https://chadaustin.me/2015/07/sum-types/) a nice introduction.*\n\nThe current version is `0.10.0`, quickly approaching `1.0`.\nThe library supports Python 3.x.\nThe core code has lived in various `utils` folders for about a year,\nbefore I got tired of copying it around and decided to release it as an independent package.\n(see also: [Should I use it?](https://github.com/lubieowoce/sumtype#should-i-use-it))\n\nSuggestions, feedback and contributions are very welcome!\n\n\n\n## A quick tour\n```python\n>>> from sumtype import sumtype\n>>> from typing import Tuple\n>>>\n>>> class Thing(sumtype):\n... def Foo(x: int, y: int): ...\n... def Bar(y: str, hmm: Tuple[str, str]): ...\n... def Zap(): ...\n...\n>>>\n```\nThis means that a `Thing` value can be one of three variants:\n- a `Foo` with two `int` fields, `x` and `y`\n- a `Bar` with a `str` field `y` and a `Tuple[str, str]` field `hmm`\n- a `Zap` with no fields\n\nIf type annotations are provided, the constructors will typecheck the arguments (see [Typechecking](https://github.com/lubieowoce/sumtype#typechecking))\nYou can also add your own docstring and methods in the class definition.\nIf you prefer `namedtuple`-style definitions, `sumtype` supports those too - see `Thing2` in `sumtype.sumtype.demo()` for an example.\n\n#### Creating values and attribute access\n```python\n>>> foo = Thing.Foo(x=3, y=5) # named arguments\n>>> bar = Thing.Bar('hello', ('wo', 'rld')) # positional arguments\n>>> zap = Thing.Zap()\n```\nNote that they're still just different values of the same type, not subclasses:\n```python\n>>> type(foo) is Thing and type(bar) is Thing and type(zap) is Thing\nTrue\n```\n\nEvery specified field gets an accessor:\n```python\n>>> foo.x, foo.y;\n(3, 5)\n>>> bar.y, bar.hmm\n('hello', ('wo', 'rld'))\n```\n...with checks if the access is valid and descriptive error messages:\n```python\n>>> Thing.Zap().hmm # only `Bar`s have a `hmm` field\nTraceback (most recent call last):\n ...\nAttributeError: Incorrect 'Thing' variant: Field 'hmm' not declared in variant 'Zap'...\n>>>\n>>> Thing.Foo(x=1, y=2).blah_blah_blah # no variant has a `blah_blah_blah` field \nTraceback (most recent call last):\n ...\nAttributeError: Unknown attribute: Field 'blah_blah_blah' not declared in any variant of 'Thing'...\n```\n\nThe values also have a nice `__repr__()`:\n```python\n>>> foo; bar; zap\nThing.Foo(x=3, y=5)\nThing.Bar(y='hello', hmm=('wo', 'rld'))\nThing.Zap()\n```\n\nThe library is designed with efficiency in mind\u00b9 \u2013 it uses `__slots__` for attribute storage\nand generates specialized versions of all the methods for each class.\nTo see the generated code, do ` class Thing(sumtype, verbose=True):`.\n\n\u00b9 At least I like to think so ;) I try to do my best with profiling things though!\n\n\n## Features\n\n### Typechecking\n\n`sumtype` uses [`typeguard`](https://github.com/agronholm/typeguard) to typecheck the fields:\n```python\n>>> # Foo(x: int, y: int) -> Thing\n>>> Thing.Foo(x=1, y=2)\nThing.Foo(x=1, y=2)\n>>> Thing.Foo(x='should be an int', y=2)\nTraceback (most recent call last):\n ...\nTypeError: type of argument \"x\" must be int; got str instead\n```\n`typing` annotations are supported too:\n```python\n>>> # Bar(y: str, hmm: Tuple[str, str]) -> Thing\n>>> Thing.Bar(y='a', hmm=('b', 'c'))\nThing.Bar(y='a', hmm=('b', 'c'))\n>>> Thing.Bar(y='a', hmm=(1, 2))\nTraceback (most recent call last):\n ...\nTypeError: type of argument \"hmm\"[0] must be str; got int instead\n```\n[`typeguard`](https://github.com/agronholm/typeguard) supports all `typing` constructs (`Tuple`, `List`, `Dict`, `Union`, etc).\n(See their [README](https://github.com/agronholm/typeguard/blob/master/README.rst) for a full list)\nHowever, as of `2.2.2` it doesn't support user-defined generic classes, so for a field like `z: UserDefinedList[float]`, `typeguard` will not check\nif the contents are actually `floats`. \nThis also prevents us from defining generic sumtypes (e.g. `ConsList[A]`, `Maybe[A]`, `Either[A, B]`), but I'm working on resolving this issue.\n\nTypechecking can be controlled with the `typecheck` argument: `class Thing(sumtype, typecheck='always'|'debug'|'never'):`.\nThe default mode is `'always'`\nFields with no annotations will not be typechecked, and you can mix annotated and non-annotated fields in a definition.\n\n\n### Equality and hashing\n```python\n>>> Thing.Foo(1,2) == Thing.Foo(1,2)\nTrue\n>>> Thing.Foo(1,2) == Thing.Bar('a', ('b', 'c'));\nFalse\n>>> {foo, foo, bar, zap} == {foo, bar, zap}\nTrue\n```\n`__eq__` and `__hash__` pay attention to variant - even if we had a variant `Moo(x: int, y: int)`,\n`Foo(1,2) != Moo(1,2)` and `hash(Foo(1,2)) != hash(Moo(1,2))`.\n\n> **Note**: *Just like tuples, `sumtypes` `__eq__`/`__hash__` work by `__eq__`ing/`__hash__`ing the values inside,\nso the values must all implement the relevant method for it to work.*\n\n\n### Modifying values\n```python\n>>> foo; foo.replace(x=99)\nThing.Foo(x=3, y=5)\nThing.Foo(x=99, y=5)\n>>>\n>>> bar; bar.replace(y='abc', hmm=('d', 'e'))\nThing.Bar(y='hello', hmm=('wo', 'rld'))\nThing.Bar(y='abc', hmm=('d', 'e'))\n```\n`foo.replace(x=99)` returns a new value, just like in `namedtuple`. \n`.replace` behaves just like the constructors w.r.t. typechecking.\n\n> **Note**: *`replace` and all the other methods have underscored aliases (`_replace`).\nSo even if you have a field called `replace`, you can still use `my_value._replace(x=15)`.*\n\n\n### Pattern matching\n##### Statement form:\n```python\n>>> def do_something(val: Thing):\n... if val.is_Foo():\n... print(val.x * val.y)\n... elif val.is_Bar():\n... print('The result is', val.y, ''.join(val.hmm))\n... elif val.is_Zap():\n... print('Whoosh!')\n... else: val.impossible() # throws an error - nice if you like having all cases covered\n...\n>>> for val in (foo, bar, zap):\n... do_something(val)\n...\n15\nThe result is hello world\nWhoosh!\n```\n##### Expression form:\n```python\n>>> [ val.match(\n... Foo = lambda x, y: x*y, \n... Zap = lambda: 999,\n... _ = lambda: -1 # default case\n... )\n... for val in (foo, bar, zap)]\n[15, -1, 999]\n```\n\n\n### Conversions between `sumtypes` and standard types\nTo...\n```python\n>>> foo.values(); foo.values_dict();\n(3, 5)\nOrderedDict([('x', 3), ('y', 5)])\n```\n```python\n>>> foo.as_tuple(); foo.as_dict()\n('Foo', 3, 5)\nOrderedDict([('variant', 'Foo'), ('x', 3), ('y', 5)])\n```\n...and from\n```python\n>>> Thing.from_tuple(('Foo', 10, 15)); Thing.from_dict({'variant':'Foo', 'x': 10, 'y': 15})\nThing.Foo(x=10, y=15)\nThing.Foo(x=10, y=15)\n```\nAlso, `x == Thing.from_tuple(x.as_tuple())` and `x == Thing.from_dict(x.as_dict())`.\n\n\n### Pickle support\n```python\n>>> import pickle\n>>> vals = [Thing.Foo(1, 2), Thing.Bar('one', ('two', 'three')), Thing.Zap()]\n>>> vals2 = pickle.loads(pickle.dumps(vals))\n>>> vals; vals == vals2\n[Thing.Foo(x=1, y=2), Thing.Bar(y='one', hmm=('two', 'three')), Thing.Zap()]\nTrue\n```\n\nThere's also tests in `sumtype.tests` to ensure that it all works correctly.\nAnd that's everything... for now!\n\n\n## Features planned for in `1.0`\n- Defining generic sumtypes like `Maybe[A]`/`Either[A, B]` in a typesafe way\n\n## Should I use it?\nYeah! I didn't just build this library because I thought it'd be nice \u2013\nI'm using it heavily in an app I'm developing and a few smaller projects.\nSaying that it's battle-tested is a bit much, but it's getting there.\n\n\n## Possible future features\n- Default values\n\n- `mypy` support.\nUnfortunately, last time I checked, `mypy` didn't handle metaclass-created classes too well, but that might have changed.\nAlternatively, we could provide a way to generate `mypy` stub files. Also, right now there's no way to tell `mypy` that\nthe return type of accessors depend on the variant \u2013 `Union[a, b]` is close, but `mypy` will complain that not all cases\nare handled even if they are.\n\n- Statically generating a class definition to a file\n\n- Dynamic alternatives to custom-generated methods \u2013\nmight be useful if startup time is more important than efficiency\n\n- An alternative implementation backed by tuples if true immutability is desired \u2013\nthere's currently no way to make a `__slots__`-based implementation watertight in that aspect, I'm doing my best.\n\n- **Maybe** opt-in mutability \u2013 currently, you can use `Thing._unsafe_set_Foo_x(foo, 10)` if you want that, but that's not a nice interface", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "sum variant tagged union enum type types", "license": "", "maintainer": "", "maintainer_email": "", "name": "sumtype", "package_url": "https://pypi.org/project/sumtype/", "platform": "", "project_url": "https://pypi.org/project/sumtype/", "project_urls": { "Source": "https://github.com/lubieowoce/sumtype" }, "release_url": "https://pypi.org/project/sumtype/0.10.0.post4/", "requires_dist": null, "requires_python": ">=3", "summary": "A namedtuple-style library for defining immutable sum types in Python.", "version": "0.10.0.post4" }, "last_serial": 4375123, "releases": { "0.10.0": [ { "comment_text": "", "digests": { "md5": "e9e18a1cc31d3829e6ae6c721ac6c176", "sha256": "0a38f37723bc9a31dfc00f9bb394336bdacf9f69e170f1a2cf37b576162dd4be" }, "downloads": -1, "filename": "sumtype-0.10.0.tar.gz", "has_sig": false, "md5_digest": "e9e18a1cc31d3829e6ae6c721ac6c176", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37565, "upload_time": "2018-09-16T15:00:30", "url": "https://files.pythonhosted.org/packages/f7/ec/0561870ee63d82446965b5ac6ce6dcefb8d6b31fff68bea0969aae3215c6/sumtype-0.10.0.tar.gz" } ], "0.10.0.post1": [ { "comment_text": "", "digests": { "md5": "c59930ec8b0f870795d588045840cdc9", "sha256": "2c79177c34bb3e495444a268c2bf4cb569047aa0d8d843ef807c1e74eea483c0" }, "downloads": -1, "filename": "sumtype-0.10.0.post1.tar.gz", "has_sig": false, "md5_digest": "c59930ec8b0f870795d588045840cdc9", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37598, "upload_time": "2018-09-16T15:34:13", "url": "https://files.pythonhosted.org/packages/69/f3/d69ac076f6322b8a2f52a42b9cc025e39a9b1eaf3f2586cb710bd99560da/sumtype-0.10.0.post1.tar.gz" } ], "0.10.0.post2": [ { "comment_text": "", "digests": { "md5": "f6e190aaa6fdaf85c00d469534578a99", "sha256": "9ca22f9b22e45fbef330d0fc7a39b924cf9ef91db67557e62a3d8bf6e884b383" }, "downloads": -1, "filename": "sumtype-0.10.0.post2.tar.gz", "has_sig": false, "md5_digest": "f6e190aaa6fdaf85c00d469534578a99", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37586, "upload_time": "2018-09-16T20:27:38", "url": "https://files.pythonhosted.org/packages/07/14/6a2289c01f9dc1056c2534eb7769371d80ec492211417bbd429269eaccd2/sumtype-0.10.0.post2.tar.gz" } ], "0.10.0.post3": [ { "comment_text": "", "digests": { "md5": "52de708b73edd044da4f7887a79873ec", "sha256": "338a9f1cfc2b960b06984fd44a3fb807cf2caa51e22221180ae9dabda164cfcb" }, "downloads": -1, "filename": "sumtype-0.10.0.post3.tar.gz", "has_sig": false, "md5_digest": "52de708b73edd044da4f7887a79873ec", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37603, "upload_time": "2018-09-18T15:35:13", "url": "https://files.pythonhosted.org/packages/49/f5/472b7dc9444479694b9f22c5e900bc1d1a9121926175e12c06b519e7725d/sumtype-0.10.0.post3.tar.gz" } ], "0.10.0.post4": [ { "comment_text": "", "digests": { "md5": "31e750e2186c8b99d50d596c20b66e5f", "sha256": "129e99b161c87f3c9c77d28706ac99f94d588bb7f20dcd1062683f6452da0900" }, "downloads": -1, "filename": "sumtype-0.10.0.post4.tar.gz", "has_sig": false, "md5_digest": "31e750e2186c8b99d50d596c20b66e5f", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37557, "upload_time": "2018-09-18T15:44:46", "url": "https://files.pythonhosted.org/packages/36/3b/33579162aee837195eb9924d73cb61e4120b63d2570a85b69dd6c175d563/sumtype-0.10.0.post4.tar.gz" } ], "0.11b0": [ { "comment_text": "", "digests": { "md5": "1df607d85a251328a1372ad650da3438", "sha256": "70dd7e3ce9fc6086e177c02d916eaa3c9970c48496e67ab29d7ee620d9c6ff72" }, "downloads": -1, "filename": "sumtype-0.11b0.tar.gz", "has_sig": false, "md5_digest": "1df607d85a251328a1372ad650da3438", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 39599, "upload_time": "2018-10-14T20:07:27", "url": "https://files.pythonhosted.org/packages/88/14/f65b3927dc39e1626492bb5c04c1d96d5f84c203b832cee14636abef6943/sumtype-0.11b0.tar.gz" } ], "0.9.5.post1": [ { "comment_text": "", "digests": { "md5": "6454d85ff63cc35f02fdef2e24d74f3a", "sha256": "6db9bbaf335bd11a65e247b9ecdf16408fe7273dd72dcd1e0ba35300a960e38d" }, "downloads": -1, "filename": "sumtype-0.9.5.post1.tar.gz", "has_sig": false, "md5_digest": "6454d85ff63cc35f02fdef2e24d74f3a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33496, "upload_time": "2018-08-30T20:26:10", "url": "https://files.pythonhosted.org/packages/45/f7/4fbd678045dbfba3bfa8673c8f285b7a78025fec94c1a9dfb883944c82ba/sumtype-0.9.5.post1.tar.gz" } ], "0.9.5.post2": [ { "comment_text": "", "digests": { "md5": "21659a4ac8d8a85c989beb63f0f7d1b9", "sha256": "c6ef7fbff821247a24158a1800d7bc6445b75fa403762beb09e395fb70022799" }, "downloads": -1, "filename": "sumtype-0.9.5.post2.tar.gz", "has_sig": false, "md5_digest": "21659a4ac8d8a85c989beb63f0f7d1b9", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 33534, "upload_time": "2018-08-30T21:32:56", "url": "https://files.pythonhosted.org/packages/98/a4/104b0b74862049fc00ea48602e80793f6108092e12965180b60223fad137/sumtype-0.9.5.post2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "31e750e2186c8b99d50d596c20b66e5f", "sha256": "129e99b161c87f3c9c77d28706ac99f94d588bb7f20dcd1062683f6452da0900" }, "downloads": -1, "filename": "sumtype-0.10.0.post4.tar.gz", "has_sig": false, "md5_digest": "31e750e2186c8b99d50d596c20b66e5f", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 37557, "upload_time": "2018-09-18T15:44:46", "url": "https://files.pythonhosted.org/packages/36/3b/33579162aee837195eb9924d73cb61e4120b63d2570a85b69dd6c175d563/sumtype-0.10.0.post4.tar.gz" } ] }