{ "info": { "author": "Matthew Planchard", "author_email": "msplanchard@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8" ], "description": "# safetywrap\n\n[![Build Status](https://dev.azure.com/msplanchard/safetywrap/_apis/build/status/mplanchard.safetywrap?branchName=master)](https://dev.azure.com/msplanchard/safetywrap/_build/latest?definitionId=3&branchName=master)\n[![coverage report](https://img.shields.io/azure-devops/coverage/msplanchard/safetywrap/3)](https://dev.azure.com/msplanchard/safetywrap/_build?definitionId=3)\n\nFully typesafe, Rust-inspired wrapper types for Python values\n\n## Summary\n\nThis library provides two main wrappers: `Result` and `Option`. These types\nallow you to specify typesafe code that effectively handles errors or\nabsent data, without resorting to deeply nested if-statements and lots\nof try-except blocks.\n\nThis is accomplished by allowing you to operate on an `Option` or `Result`\nin a sort of quantum superposition, where an `Option` could be `Some` or\n`Nothing` or a `Result` could be `Ok` or `Err`. In either case, all of the\nmethods on the type work just the same, allowing you to handle both cases\nelegantly.\n\nA `Result[T, E]` may be an instance of `Ok[T]` or `Err[E]`, while an `Option[T]`\nmay be an instance of `Some[T]` or `Nothing`. Either way, you get to treat\nthem just the same until you really need to get the wrapped value.\n\nSo, rather than this:\n\n```py\nfor something in \"value\", None:\n if something is not None:\n val = something.upper()\n else:\n val = \"DEFAULT\"\n print(val)\n```\n\nYou can do this:\n\n```py\nfor something in Some(\"value\"), Nothing():\n print(something.map(str.upper).unwrap_or(\"DEFAULT\"))\n```\n\nAnd rather than this:\n\n```py\nfor jsondata in '{\"value\": \"myvalue\"}', '{badjson':\n try:\n config = capitalize_keys(json.loads(jsondata))\n except Exception:\n config = get_default_config()\n print(config[\"value\"])\n```\n\nYou can do this:\n\n```py\nfor jsondata in '{\"value\": \"myvalue\"}', '{badjson':\n print(\n Result.of(json.loads, jsondata)\n .map(capitalize_keys)\n .unwrap_or_else(get_default_config)[\"value\"]\n )\n```\n\nThese two examples are super minimal samples of how using these typesafe\nwrappers can make things easier to write and reason about. Please see the\n[Examples](#examples) section for more, and [Usage](#usage) for the full\nsuite of offered functionality.\n\nThese types are heavily influenced by the [Result][rust-result] and\n[Option][rust-option] types in Rust.\n\nThorough type specifications for mypy or your favorite python type-checker\nare provided, so that you can decorate function inputs and outputs as\nreturning `Result` and `Option` types and get useful feedback when supplying\narguments or passing return values.\n\n### Sponsorship\n\nThis project was developed for and is graciously sponsored by my employer,\n[Bestow, Inc.](https://hellobestow.com/). At Bestow, we aim to democratize life\ninsurance by providing simple, easy coverage, purchasable online in five minutes\nwith no doctors' visits and no hassles.\n\nWe're pretty much always hiring great developers, so if you'd like to work\nwith us, please check out [our careers page](https://hellobestow.com/careers/)!\n\n## Table of Contents\n\n- [safetywrap](#safetywrap)\n - [Summary](#summary)\n - [Sponsorship](#sponsorship)\n - [Table of Contents](#table-of-contents)\n - [Examples](#examples)\n - [Get an enum member by its value, returning the member or None](#get-an-enum-member-by-its-value-returning-the-member-or-none)\n - [Get an enum member by its value, returning an Option](#get-an-enum-member-by-its-value-returning-an-option)\n - [Serialize a dict that may be missing keys, using default values](#serialize-a-dict-that-may-be-missing-keys-using-default-values)\n - [Make an HTTP request, and if the status code is 200, convert the body to JSON and return the `data` key. If there is an error or the `data` key does not exist, return an error string](#make-an-http-request-and-if-the-status-code-is-200-convert-the-body-to-json-and-return-the-data-key-if-there-is-an-error-or-the-data-key-does-not-exist-return-an-error-string)\n - [Usage](#usage)\n - [Result[T, E]](#resultt-e)\n - [Result Constructors](#result-constructors)\n - [Ok](#ok)\n - [Err](#err)\n - [Result.of](#resultof)\n - [Result.collect](#resultcollect)\n - [Result.err_if](#resulterr_if)\n - [Result.ok_if](#resultok_if)\n - [Result Methods](#result-methods)\n - [Result.and_](#resultand_)\n - [Result.or_](#resultor_)\n - [Result.and_then](#resultand_then)\n - [Result.flatmap](#resultflatmap)\n - [Result.or_else](#resultor_else)\n - [Result.err](#resulterr)\n - [Result.ok](#resultok)\n - [Result.expect](#resultexpect)\n - [Result.raise_if_err](#resultraise_if_err)\n - [Result.expect_err](#resultexpect_err)\n - [Result.is_err](#resultis_err)\n - [Result.is_ok](#resultis_ok)\n - [Result.iter](#resultiter)\n - [Result.map](#resultmap)\n - [Result.map_err](#resultmap_err)\n - [Result.unwrap](#resultunwrap)\n - [Result.unwrap_err](#resultunwrap_err)\n - [Result.unwrap_or](#resultunwrap_or)\n - [Result.unwrap_or_else](#resultunwrap_or_else)\n - [Result Magic Methods](#result-magic-methods)\n - [Option[T]](#optiont)\n - [Option Constructors](#option-constructors)\n - [Some](#some)\n - [Nothing](#nothing)\n - [Option.of](#optionof)\n - [Option.nothing_if](#optionnothing_if)\n - [Option.some_if](#optionsome_if)\n - [Option.collect](#optioncollect)\n - [Option Methods](#option-methods)\n - [Option.and_](#optionand_)\n - [Option.or_](#optionor_)\n - [Option.xor](#optionxor)\n - [Option.and_then](#optionand_then)\n - [Option.flatmap](#optionflatmap)\n - [Option.or_else](#optionor_else)\n - [Option.expect](#optionexpect)\n - [Option.raise_if_nothing](#optionraise_if_nothing)\n - [Option.filter](#optionfilter)\n - [Option.is_nothing](#optionis_nothing)\n - [Option.is_some](#optionis_some)\n - [Option.iter](#optioniter)\n - [Option.map](#optionmap)\n - [Option.map_or](#optionmap_or)\n - [Option.map_or_else](#optionmap_or_else)\n - [Option.ok_or](#optionok_or)\n - [Option.ok_or_else](#optionok_or_else)\n - [Option.unwrap](#optionunwrap)\n - [Option.unwrap_or](#optionunwrap_or)\n - [Option.unwrap_or_else](#optionunwrap_or_else)\n - [Option Magic Methods](#option-magic-methods)\n - [Performance](#performance)\n - [Results](#results)\n - [Discussion](#discussion)\n - [Contributing](#contributing)\n\n## Examples\n\nIn general, these examples build from simple to complex. See [Usage](#usage)\nbelow for the full API specification.\n\n### Get an enum member by its value, returning the member or None\n\n```py\nimport typing as t\nfrom enum import Enum\n\nfrom result_types import Option, Result, Some\n\nT = t.TypeVar(\"T\", bound=Enum)\n\ndef enum_member_for_val(enum: t.Type[T], value: t.Any) -> t.Optional[t.Any]:\n \"\"\"Return Some(enum_member) or Nothing().\"\"\"\n # Enums throw a `ValueError` if the value isn't present, so\n # we'll either have `Ok(enum_member)` or `Err(ValueError)`.\n # We unwrap and return the member if it's Ok, otherwise, we just\n # return None\n return Result.of(enum, value).unwrap_or(None)\n```\n\n### Get an enum member by its value, returning an Option\n\n```py\nimport typing as t\nfrom enum import Enum\n\nfrom result_types import Option, Result, Some\n\nT = t.TypeVar(\"T\", bound=Enum)\n\ndef enum_member_for_val(enum: t.Type[T], value: t.Any) -> Option[T]:\n \"\"\"Return Some(enum_member) or Nothing().\"\"\"\n # Enums throw a `ValueError` if the value isn't present, so\n # we'll either have `Ok(enum_member)` or `Err(ValueError)`.\n # Calling `ok()` on a `Result` returns an `Option`\n return Result.of(enum, value).ok()\n```\n\n### Serialize a dict that may be missing keys, using default values\n\n```py\nimport json\nfrom result_types import Result\n\ndef serialize(data: t.Dict[str, t.Union[int, str, float]]) -> str:\n \"\"\"Serialize the data.\n\n Absent keys are \"[absent]\", rather than null. This allows us to maintain\n information about whether a key was present or actually set to None.\n \"\"\"\n keys = (\"first\", \"second\", \"third\", \"fourth\")\n # We can even use Result to catch any JSON serialization errors, so that\n # this function will _always_ return a string!\n Result.of(\n json.dumps,\n # Result.of() will intercept the KeyError and return an Err. We use\n # `unwrap_or()` to discard the error and return the \"[absent]\" string\n # instead; if the key was present, the Result was Ok, and we just\n # return that value.\n {k: Result.of(lambda: data[k]).unwrap_or(\"[absent]\") for k in keys}\n ).unwrap_or(\"Could not serialize JSON from data!\")\n```\n\n### Make an HTTP request, and if the status code is 200, convert the body to JSON and return the `data` key. If there is an error or the `data` key does not exist, return an error string\n\n```py\nfrom functools import partial\n\nimport requests\nfrom requests import Response\nfrom result_types import Option, Result\n\n\ndef get_data(url: str) -> str:\n \"\"\"Get the data!\"\"\"\n # We need to do manual type assignment sometimes when the code\n # we're wrapping does not provide types.\n # If the wrapped function raises any Exception, `res` will be\n # Err(Exception). Otherwise it will be `Ok(Response)`.\n res: Result[Response, Exception] = Result.of(requests.get, url)\n return (\n # We start as a `Result[Response, Exception]`\n res\n # And if we were an Err, map to a `Result[Response, str]`\n .map_err(str)\n # If we were Ok, and_then (aka flatmap) to a new `Result[Response, str]`\n .and_then(lambda res: (\n # Our return value starts as a `Result[Response, Response]`\n Result.ok_if(lambda r: r.status_code == 200, res).map_err(\n # So we map it to a `Result[Response, str]`\n lambda r: str(f\"Bad status code: {r.status_code}\")\n )\n ))\n # We are now a `Result[Response, str]`, where we are only Ok if\n # our status code was 200.\n # Now we transition to a `Result[dict, str]`\n .and_then(lambda res: Result.of(res.json).map_err(str))\n # And to a `Result[Option[str], str]`\n .map(lambda js: Option.of(js.get(\"data\")).map(str))\n # And to a `Result[str, str]`\n .and_then(lambda data: data.ok_or(\"No data key in JSON!\"))\n # If we are an error, convert us to an Ok with the error string\n .or_else(Ok)\n # And now we get either the Ok string or the Err string!\n .unwrap()\n )\n```\n\n## Usage\n\n### Result[T, E]\n\nA Result represents some value that may either be in an `Ok` state or\nan `Err` state.\n\n#### Result Constructors\n\n##### Ok\n\n`Ok(value: T) -> Result[T, E]`\n\nConstruct an `Ok` Result directly with the value.\n\nExample:\n\n```py\ndef check_value_not_negative(val: int) -> Result[int, str]:\n \"\"\"Check that a value is not negative, or return an Err.\"\"\"\n if val >= 0:\n return Ok(val)\n return Err(f\"{val} is negative!\")\n```\n\n##### Err\n\n`Err(value: E) -> Result[T, E]`\n\nConstruct an `Err` Result directly with the value.\n\nExample:\n\n```py\ndef check_value_not_negative(val: int) -> Result[int, str]:\n \"\"\"Check that a value is not negative, or return an Err.\"\"\"\n if val >= 0:\n return Ok(val)\n return Err(f\"{val} is negative!\")\n```\n\n##### Result.of\n\n`Result.of(fn: Callable[..., T], *args: t.Any, catch: t.Type[E], **kwargs) -> Result[T, E]`\n\nCall a function with the provided arguments. If no error is thrown, return\n`Ok(result)`. Otherwise, return `Err(exception)`. By default, `Exception`\nis caught, but different error types may be provided with the `catch`\nkeyword argument.\n\nThe type of `E` MUST be `Exception` or one of its subclasses.\n\nThis constructor is designed to be useful in wrapping other APIs, builtin\nfunctions, etc.\n\nNote that due to a bug in mypy (see https://github.com/python/mypy/issues/3737),\nsometimes you need to explicitly specify the `catch` keyword argument,\neven if you're setting it to its default (`Exception`). This does not\nhappen consistently, but when it does, you will see mypy thinking\nthat the type of the `Result` is `Result[SomeType, ]`.\n\nExample:\n\n```py\nimport json\n\ndef parse_json(string: str) -> Result[dict, Exception]:\n \"\"\"Parse a JSON object into a dict.\"\"\"\n return Result.of(json.loads, string)\n```\n\n##### Result.collect\n\n`Result.collect(iterable: Iterable[T, E]) -> Result[Tuple[T, ...], E]`\n\nConvert an iterable of Results into a single Result. If all Results were\nOk, Ok values are collected into a Tuple in the final Result. If any Results\nwere Err, the Err result is returned directly.\n\nExample:\n\n```py\nassert Result.collect([Ok(1), Ok(2), Ok(3)]) == Ok((1, 2, 3))\nassert Result.collect([Ok(1), Err(\"no\"), Ok(3)]) == Err(\"no\")\n```\n\n##### Result.err_if\n\n`Result.err_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]`\n\nRun a predicate on some value, and return `Err(val)` if the predicate returns\nTrue, or `Ok(val)` if the predicate returns False.\n\nExample:\n\n```py\nfrom requests import Response\n\ndef checked_response(response: Response) -> Result[Response, Response]:\n \"\"\"Try to get a response from the server.\"\"\"\n return Result.err_if(lambda r: r.status_code >= 300, response)\n```\n\n##### Result.ok_if\n\n`Result.ok_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]`\n\nRun a predicate on some value, and return `Ok(val)` if the predicate returns\nTrue, or `Err(val)` if the predicate returns False.\n\nExample:\n\n```py\ndef checked_data(data: dict) -> Result[dict, dict]:\n \"\"\"Check if data has expected keys.\"\"\"\n expected_keys = (\"one\", \"two\", \"three\")\n return Result.ok_if(lambda d: all(k in d for k in expected_keys), data)\n```\n\n#### Result Methods\n\n##### Result.and_\n\n`Result.and_(self, res: Result[U, E]) -> Result[U, E]`\n\nIf this Result is `Ok`, return `res`. If this result is `Err`, return this\nResult. This can be used to short circuit a chain of Results on encountering\nthe first error.\n\nExample:\n\n```py\nassert Ok(5).and_(Ok(6)) == Ok(6)\nassert Err(1).and_(Ok(6)) == Err(1)\nassert Err(1).and_(Err(2)).and_(Ok(5)) == Err(1)\nassert Ok(5).and_(Err(1)).and_(Ok(6)) == Err(1)\n```\n\n##### Result.or_\n\n`Result.or_(self, res: Result[T, F]) -> Result[T, F]`\n\nIf this Result is `Err`, return `res`. Otherwise, return this Result.\n\nExample:\n\n```py\nassert Err(1).or_(Ok(5)) == Ok(5)\nassert Err(1).or_(Err(2)) == Err(2)\nassert Ok(5).or_(Ok(6)) == Ok(5)\nassert Ok(5).or_(Err(1)) == Ok(5)\n```\n\n##### Result.and_then\n\n`Result.and_then(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]`\n\nIf this Result is `Ok`, call the provided function with the wrapped value of\nthis Result and return the Result of that function. This allows easily\nchaining multiple Result-generating calls together to yield a final\nResult. This method is an alias of [`Result.flatmap`](#resultflatmap)\n\nExample:\n\n```py\nassert Ok(5).and_then(lambda val: Ok(val + 1)) == Ok(6)\nassert Err(1).and_then(lambda val: Ok(val + 1)) == Err(1)\n```\n\n##### Result.flatmap\n\n`Result.flatmap(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]`\n\nIf this Result is `Ok`, call the provided function with the wrapped value of\nthis Result and return the Result of that function. This allows easily\nchaining multiple Result-generating calls together to yield a final\nResult. This method is an alias of [`Result.and_then`](#resultand_then)\n\nExample:\n\n```py\nassert Ok(5).flatmap(lambda val: Ok(val + 1)) == Ok(6)\nassert Err(1).flatmap(lambda val: Ok(val + 1)) == Err(1)\n```\n\n##### Result.or_else\n\n`Result.or_else(self, fn: t.Callable[[E], Result[T, F]]) -> Result[T, F])`\n\nIf this result is `Err`, call the provided function with the wrapped error\nvalue of this Result and return the Result of that function. This allows\neasily handling potential errors in a way that still returns a final Result.\n\nExample:\n\n```py\nassert Ok(5).or_else(Ok) == Ok(5)\nassert Err(1).or_else(Ok) == Ok(1)\n```\n\n##### Result.err\n\n`Result.err(self) -> Option[E]`\n\nConvert this Result into an Option, returning Some(err_val) if this Result\nis `Err`, or Nothing() if this Result is `Ok`.\n\nExample:\n\n```py\nassert Ok(5).err() == Nothing()\nassert Err(1).err() == Some(1)\n```\n\n##### Result.ok\n\n`Result.ok(self) -> Option[T]`\n\nConvert this Result into an Option, returning `Some(val)` if this Result is\n`Ok`, or `Nothing()` if this result is `Err`.\n\nExample:\n\n```py\nassert Ok(5).ok() == Some(5)\nassert Err(1).ok() == Nothing()\n```\n\n##### Result.expect\n\n`Result.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T`\n\nReturn the wrapped value if this Result is `Ok`. Otherwise, raise an error,\ninstantiated with the provided message and the stringified error value.\nBy default, a `RuntimeError` is raised, but an alternative error may be\nprovided using the `exc_cls` keyword argument. This method is an alias for\n[`Result.raise_if_err`](#resultraise_if_err).\n\nExample:\n\n```py\nimport pytest\n\nwith pytest.raises(RuntimeError) as exc:\n Err(5).expect(\"Bad value\")\n assert str(exc.value) == \"Bad value: 5\"\n\nassert Ok(1).expect(\"Bad value\") == 1\n```\n\n##### Result.raise_if_err\n\n`Result.raise_if_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T`\n\nReturn the wrapped value if this Result is `Ok`. Otherwise, raise an error,\ninstantiated with the provided message and the stringified error value.\nBy default, a `RuntimeError` is raised, but an alternative error may be\nprovided using the `exc_cls` keyword argument. This method is an alias for\n[`Result.expect`](#resultexpect).\n\nExample:\n\n```py\nimport pytest\n\nwith pytest.raises(RuntimeError) as exc:\n Err(5).raise_if_err(\"Bad value\")\n assert str(exc.value) == \"Bad value: 5\"\n\nassert Ok(1).raise_if_err(\"Bad value\") == 1\n```\n\n##### Result.expect_err\n\n`Result.expect_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> E`\n\nReturn the wrapped value if this Result is `Err`. Otherwise, raise an error,\ninstantiated with the provided message and the stringified Ok value.\nBy default, a `RuntimeError` is raised, but an alternative error may be\nprovided using the `exc_cls` keyword argument.\n\nExample:\n\n```py\nimport pytest\n\nwith pytest.raises(RuntimeError) as exc:\n Ok(5).expect_err(\"Unexpected good value\")\n assert str(exc.value) == \"Unexpected good value: 5\"\n\nassert Err(1).expect_err(\"Unexpected good value\") == 1\n```\n\n##### Result.is_err\n\n`Result.is_err(self) -> bool`\n\nReturn True if this Result is `Err`, or `False` if this Result is `Ok`.\n\nExample:\n\n```py\nassert Err(1).is_err() is True\nassert Ok(1).is_err() is False\n```\n\n##### Result.is_ok\n\n`Result.is_ok(self) -> bool`\n\nReturn True if this Result is `Ok`, or `False` if this Result is `Err`.\n\nExample:\n\n```py\nassert Ok(1).is_err() is True\nassert Err(1).is_err() is False\n```\n\n##### Result.iter\n\n`Result.iter(self) -> Iterator[T]`\n\nReturn an iterator with length 1 over the wrapped value if this Result is `Ok`.\nOtherwise, return a 0-length iterator.\n\nExample:\n\n```py\nassert tuple(Ok(1).iter()) == (1,)\nassert tuple(Err(1).iter()) == ()\n```\n\n##### Result.map\n\n`Result.map(self, fn: t.Callable[[T], U]) -> Result[U, E]`\n\nIf this Result is `Ok`, apply the provided function to the wrapped value,\nand return a new `Ok` Result with the result of the function. If this Result\nis `Err`, do not apply the function and return this Result unchanged.\n\n**Warning:** no error checking is performed while applying the provided\nfunction, and exceptions applying the function are not caught. If you need\nto map with error handling, consider using `and_then` (aka `flatmap`) in\nconjunction with the `Result.of` constructor, e.g.\n`assert Ok(0).and_then(partial(Result.of, lambda i: 10 / i)) == Err(ZeroDivisionError('division by zero'))`\n\nExample:\n\n```py\nassert Ok(1).map(str) == Ok(\"1\")\nassert Err(1).map(str) == Err(1)\n```\n\n##### Result.map_err\n\n`Result.map_err(self, fn: t.Callable[[E], F]) -> Result[T, F]`\n\nIf this Result is `Err`, apply the provided function to the wrapped value,\nand return a new `Err` Result with the result of the function. If this Result\nis `Ok`, do not apply the function and return this Result unchanged.\n\n**Warning:** no error checking is performed while applying the provided\nfunction, and exceptions applying the function are not caught.\n\nExample:\n\n```py\nassert Err(1).map_err(lambda i: i + 1) == Err(2)\nassert Ok(1).map_err(lambda i: i + 1) == Ok(1)\n```\n\n##### Result.unwrap\n\n`Result.unwrap(self) -> T`\n\nIf this Result is `Ok`, return the wrapped value. If this Result is `Err`,\nthrow a `RuntimeError`.\n\nExample:\n\n```py\nimport pytest\n\nassert Ok(1).unwrap() == 1\n\nwith pytest.raises(RuntimeError):\n Err(1).unwrap()\n```\n\n##### Result.unwrap_err\n\n`Result.unwrap_err(self) -> E`\n\nIf this Result is `Err`, return the wrapped value. If this Result is `Ok`,\nthrow a `RuntimeError`.\n\nExample:\n\n```py\nimport pytest\n\nassert Err(1).unwrap() == 1\n\nwith pytest.raises(RuntimeError):\n Ok(1).unwrap()\n```\n\n##### Result.unwrap_or\n\n`Result.unwrap_or(self, alternative: U) -> t.Union[T, U]`\n\nIf this Result is `Ok`, return the wrapped value. Otherwise, if this Result\nis `Err`, return the provided alternative.\n\nExample:\n\n```py\nassert Ok(1).unwrap_or(5) == 1\nassert Err(1).unwrap_or(5) == 5\n```\n\n##### Result.unwrap_or_else\n\n`Result.unwrap_or_else(self, fn: t.Callable[[E], U]) -> t.Union[T, U]`\n\nIf this Result is `Ok`, return the wrapped value. Otherwise, if this Result\nis `Err`, call the supplied function with the wrapped error value and return\nthe result.\n\nExample:\n\n```py\nassert Ok(1).unwrap_or_else(str) == 1\nassert Err(1).unwrap_or_else(str) == \"1\"\n```\n\n#### Result Magic Methods\n\n##### Result.__iter__ \n\n`Result.__iter__(self) -> t.Iterator[T]`\n\nImplement the iterator protocol, allowing iteration over the results of\n[`Result.iter`](#resultiter). If this Result is `Ok`, return an iterator\nof length 1 containing the wrapped value. Otherwise, if this Result is `Err`,\nreturn a 0-length iterator.\n\nExample:\n\n```py\n# Can be passed to methods that take iterators\nassert tuple(Ok(1)) == (1,)\nassert tuple(Err(1)) == ()\n\n# Can be used in `for in` constructs, including comprehensions\nassert [val for val in Ok(5)] == [5]\nassert [val for val in Err(5)] == []\n\n\n# More for-in usage.\nfor val in Ok(5):\n pass\nassert val == 5\n\nval = None\nfor val in Err(1):\n pass\nassert val is None\n```\n\n##### Result.__eq__ \n\n`Result.__eq__(self, other: Any) -> bool`\n\nEnable equality checking using `==`.\n\nCompare the Result with `other`. Return True if `other` is the same type of\nResult with the same wrapped value. Otherwise, return False.\n\nExample:\n\n```py\nassert (Ok(5) == Ok(5)) is True\nassert (Ok(5) == Ok(6)) is False\nassert (Ok(5) == Err(5)) is False\nassert (Ok(5) == 5) is False\n```\n\n##### Result.__ne__ \n\n`Result.__ne__(self, other: Any) -> bool`\n\nEnable inequality checking using `!=`.\n\nCompare the Result with `other`. Return False if `other` is the same type of\nResult with the same wrapped value. Otherwise, return True.\n\nExample:\n\n```py\nassert (Ok(5) != Ok(5)) is False\nassert (Ok(5) != Ok(6)) is True\nassert (Ok(5) != Err(5)) is True\nassert (Ok(5) != 5) is True\n```\n\n##### Result.__str__ \n\n`Result.__str__(self) -> str`\n\nEnable useful stringification via `str()`.\n\nExample:\n\n```py\nassert str(Ok(5)) == \"Ok(5)\"\nassert str(Err(5)) == \"Err(5)\"\n```\n\n##### Result.__repr__ \n\n`Result.__repr__(self) -> str`\n\nEnable useful stringification via `repr()`.\n\nExample:\n\n```py\nassert repr(Ok(5)) == \"Ok(5)\"\nassert repr(Err(5)) == \"Err(5)\"\n```\n\n### Option[T]\n\nAn Option represents either `Some` value or `Nothing`.\n\n#### Option Constructors\n\n##### Some\n\n`Some(value: T) -> Option[T]`\n\nConstruct a `Some` Option directly with a value.\n\nExample:\n\n```py\ndef file_contents(path: str) -> Option[str]:\n \"\"\"Return the file contents or Nothing.\"\"\"\n try:\n with open(path) as f:\n return Some(f.read())\n except IOError:\n return Nothing()\n```\n\n##### Nothing\n\n`Nothing() -> Option[T]`\n\nConstruct a `Nothing` Option directly with a value.\n\nNote: as an implementation detail, `Nothing` is implemented as a singleton,\nto avoid instantiation time for any `Nothing` created after the first.\nHowever since this is an implementation detail, `Nothing` Options should\nstill be compared with `==` rather than `is`.\n\nExample:\n\n```py\ndef file_contents(path: str) -> Option[str]:\n \"\"\"Return the file contents or Nothing.\"\"\"\n try:\n with open(path) as f:\n return Some(f.read())\n except IOError:\n return Nothing()\n```\n\n##### Option.of\n\n`Option.of(value: t.Optional[T]) -> Option[T]`\n\nConvert an optional value into an Option. If the value is not `None`, return\n`Some(value)`. Otherwise, if the value is `None`, return `Nothing()`.\n\nExample:\n\n```py\nassert Option.of(None) == Nothing()\nassert Option.of({}.get(\"a\")) == Nothing()\nassert Option.of(\"a\") == Some(\"a\")\nassert Option.of({\"a\": \"b\"}) == Some(\"b\")\n```\n\n##### Option.nothing_if\n\n`Option.nothing_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]`\n\nCall the provided predicate function with the provided value. If the predicate\nreturns True, return `Nothing()`. If the predicate returns False, return\n`Some(value)`.\n\nExample:\n\n```py\nassert Option.nothing_if(lambda val: val.startswith(\"_\"), \"_private\") == Nothing()\nassert Option.nothing_if(lambda val: val.startswith(\"_\"), \"public\") == Some(\"public\")\n```\n\n##### Option.some_if\n\n`Option.some_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]`\n\nCall the provided predicate function with the provided value. If the predicate\nreturns True, return `Some(value)`. If the predicate returns False, return\n`Nothing()`.\n\nExample:\n\n```py\nassert Option.some_if(bool, [1, 2, 3]) == Some([1, 2, 3])\nassert Option.some_if(bool, []) == Nothing()\n```\n\n##### Option.collect\n\n`Option.collect(options: t.Iterable[Option[T]]) -> Option[t.Tuple[T, ...]]`\n\nCollect a series of Options into single Option.\n\nIf all options are `Some[T]`, the result is `Some[Tuple[T, ...j]]`. If\nany options are `Nothing`, the result is `Nothing`.\n\nExample:\n\n```py\nassert Option.collect([Some(1), Some(2), Some(3)]) == Some((1, 2, 3))\nassert Option.collect([Some(1), Nothing(), Some(3)]) == Nothing()\n```\n\n#### Option Methods\n\n##### Option.and_\n\n`Option.and_(alternative: Option[U]) -> Option[U]`\n\nIf this Option is `Nothing`, return it unchanged. Otherwise, if this Option\nis `Some`, return the provided `alternative` Option.\n\nExample:\n\n```py\nassert Some(1).and_(Some(2)) == Some(2)\nassert Nothing().and_(Some(2)) == Nothing()\nassert Some(1).and_(Nothing()) == Nothing()\nassert Nothing().and_(Nothing()) == Nothing()\nassert Some(1).and_(Nothing()).and_(Some(2)) == Nothing()\n```\n\n##### Option.or_\n\n`Option.or_(alternative: Option[T]) -> Option[T]`\n\nIf this Option is `Nothing`, return the provided `alternative` Option.\nOtherwise, if this Option is `Some`, return it unchanged.\n\nExample:\n\n```py\nassert Some(1).or_(Some(2)) == Some(1)\nassert Some(1).or_(Nothing()) == Some(1)\nassert Nothing().or_(Some(1)) == Some(1)\nassert Nothing().or_(Nothing()) == Nothing()\n```\n\n##### Option.xor\n\n`Option.xor(alternative: Option[T]) -> Option[T]`\n\nExclusive or. Return `Some` Option iff (if and only if) exactly one of\nthis Option and hte provided `alternative` are Some. Otherwise, return\n`Nothing`.\n\nExample:\n\n```py\nassert Some(1).xor(Nothing()) == Some(1)\nassert Nothing().xor(Some(1)) == Some(1)\nassert Some(1).xor(Some(2)) == Nothing()\nassert Nothing().xor(Nothing()) == Nothing()\n```\n\n##### Option.and_then\n\n`Option.and_then(self, fn: t.Callable[[T], Option[U]]) -> Option[U]`\n\nIf this Option is `Some`, call the provided, Option-returning function with\nthe contained value and return whatever Option it returns. If this Option\nis `Nothing`, return it unchanged. This method is an alias for\n[`Option.flatmap`](#optionflatmap)\n\nExample:\n\n```py\nassert Some(1).and_then(lambda i: Some(i + 1)) == Some(2)\nassert Nothing().and_then(lambda i: Some(i + 1)) == Nothing()\n```\n\n##### Option.flatmap\n\n`Option.flatmap(self, fn: t.Callable[[T], Option[U]]) -> Option[U]`\n\nIf this Option is `Some`, call the provided, Option-returning function with\nthe contained value and return whatever Option it returns. If this Option\nis `Nothing`, return it unchanged. This method is an alias for\n[`Option.and_then`](#optionand_then)\n\nExample:\n\n```py\nassert Some(1).flatmap(Some) == Some(1)\nassert Nothing().flatmap(Some) == Nothing()\n```\n\n##### Option.or_else\n\n`Option.or_else(self, fn: t.Callable[[], Option[T]]) -> Option[T]`\n\nIf this Option is `Nothing`, call the provided, Option-returning function\nand return whatever Option it returns. If this Option is `Some`, return it\nunchanged.\n\nExample:\n\n```py\nassert Nothing().or_else(lambda: Some(1)) == Some(1)\nassert Some(1).or_else(lambda: Some(2)) == Some(1)\n```\n\n##### Option.expect\n\n`Option.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T`\n\nIf this Option is `Some`, return the wrapped value. Otherwise, if this\nOption is `Nothing`, raise an error instantiated with the provided message.\nBy default, a `RuntimeError` is raised, but a custom exception class may be\nprovided via the `exc_cls` keyword argument. This method is an alias\nof [`Option.raise_if_nothing`](#optionraise_if_nothing).\n\nExample:\n\n```py\nimport pytest\n\nwith pytest.raises(RuntimeError) as exc:\n Nothing().expect(\"Nothing here\")\n assert str(exc.value) == \"Nothing here\"\n\nassert Some(1).expect(\"Nothing here\") == 1\n```\n\n##### Option.raise_if_nothing\n\n`Option.raise_if_nothing(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> T`\n\nIf this Option is `Some`, return the wrapped value. Otherwise, if this\nOption is `Nothing`, raise an error instantiated with the provided message.\nBy default, a `RuntimeError` is raised, but a custom exception class may be\nprovided via the `exc_cls` keyword argument. This method is an alias\nof [`Option.expect`](#optionexpect).\n\nExample:\n\n```py\nimport pytest\n\nwith pytest.raises(RuntimeError) as exc:\n Nothing().raise_if_nothing(\"Nothing here\")\n assert str(exc.value) == \"Nothing here\"\n\nassert Some(1).raise_if_nothing(\"Nothing here\") == 1\n```\n\n##### Option.filter\n\n`Option.filter(self, predicate: t.Callable[[T], bool]) -> Option[T]`\n\nIf this Option is `Some`, call the provided predicate function with the wrapped\nvalue. If the predicate returns True, return `Some` containing the wrapped\nvalue of this Option. If the predicate returns False, return `Nothing`. If\nthis Option is `Nothing`, return it unchanged.\n\nExample:\n\n```py\ndef is_even(val: int) -> bool:\n \"\"\"Return whether the value is even.\"\"\"\n return val % 2 == 0\n\nassert Some(2).filter(is_even) == Some(2)\nassert Some(1).filter(is_even) == Nothing()\nassert Nothing().filter(is_even) == Nothing()\n```\n\n##### Option.is_nothing\n\n`Option.is_nothing(self) -> bool`\n\nIf this Option is `Nothing`, return True. Otherwise, if this Option is\n`Some`, return False.\n\nExample:\n\n```py\nassert Nothing().is_nothing() is True\nassert Some(1).is_nothing() is False\n```\n\n##### Option.is_some\n\n`Option.is_some(self) -> bool`\n\nIf this Option is `Some`. Otherwise, if this Option is `Nothing`, return False.\n\nExample:\n\n```py\nassert Some(1).is_some() is True\nassert Nothing().is_some() is False\n```\n\n##### Option.iter\n\n`Option.iter(self) -> t.Iterator[T]`\n\nIf this Option is `Some`, return an iterator of length one over the wrapped\nvalue. Otherwise, if this Option is `Nothing`, return a 0-length iterator.\n\nExample:\n\n```py\nassert tuple(Some(1).iter()) == (1,)\nassert tuple(Nothing().iter()) == ()\n```\n\n##### Option.map\n\n`Option.map(self, fn: t.Callable[[T], U]) -> Option[U]`\n\nIf this Option is `Some`, apply the provided function to the wrapped value,\nand return `Some` wrapping the result of the function. If this Option is\n`Nothing`, return this Option unchanged.\n\nExample:\n\n```py\nassert Some(1).map(str) == Some(\"1\")\nassert Nothing().map(str) == Nothing()\nassert Some(1).map(str).map(lambda x: x + \"a\").map(str.upper) == Some(\"1A\")\n```\n\n##### Option.map_or\n\n`Option.map_or(self, default: U, fn: t.Callable[[T], U]) -> U`\n\nIf this Option is `Some`, apply the provided function to the wrapped value\nand return the result. If this Option is `Nothing`, return the provided\ndefault value.\n\nExample:\n\n```py\nassert Some(1).map_or(\"no value\", str) == \"1\"\nassert Nothing().map_or(\"no value\", str) == \"no value\"\n```\n\n##### Option.map_or_else\n\n`Option.map_or_else(self, default: t.Callable[[], U], fn: t.Callable[[T], U]) -> U`\n\nIf this Option is `Some`, apply the provided function to the wrapped value and\nreturn the result. If this Option is `Nothing`, call the provided default\nfunction with no arguments and return the result.\n\nExample:\n\n```py\nfrom datetime import datetime, date\n\nassert Some(\"2005-08-28\").map_or_else(\n date.today,\n lambda t: datetime.strptime(t, \"%Y-%m-%d\").date()\n) == datetime(2005, 8, 28).date()\n\nassert Nothing().map_or_else(\n date.today,\n lambda t: datetime.strptime(t, \"%Y-%m-%d\").date()\n) == date.today()\n```\n\n##### Option.ok_or\n\n`Option.ok_or(self, err: E) -> Result[T, E]`\n\nIf this Option is `Some`, return an `Ok` Result wrapping the contained\nvalue. Otherwise, return an `Err` result wrapping the provided error.\n\nExample:\n\n```py\nassert Some(1).ok_or(\"no value!\") == Ok(1)\nassert Nothing().ok_or(\"no value!\") == Err(\"no value!\")\n```\n\n##### Option.ok_or_else\n\n`Option.ok_or_else(self, err_fn: t.Callable[[], E]) -> Result[T, E]`\n\nIf this Option is `Some`, return an `Ok` Result wrapping the contained\nvalue. Otherwise, call the provided `err_fn` and wrap its return value\nin an `Err` Result.\n\nExample:\n\n```py\nfrom functools import partial\n\ndef make_err_msg(msg: str) -> str:\n \"\"\"Make an error message with some starting text.\"\"\"\n return f\"[MY_APP_ERROR] -- {msg}\"\n\nassert Some(1).ok_or_else(partial(make_err_msg, \"no value!\")) == Ok(1)\nassert Nothing().ok_or_else(partial(make_err_msg, \"no value!\")) == Err(\n \"[MY_APP_ERROR] -- no value!\"\n)\n```\n\n##### Option.unwrap\n\n`Option.unwrap(self) -> T`\n\nIf this Option is `Some`, return the wrapped value. Otherwise, raise a\n`RuntimeError`.\n\nExample:\n\n```py\nimport pytest\n\nassert Some(1).unwrap() == 1\n\nwith pytest.raises(RuntimeError):\n Nothing().unwrap()\n```\n\n##### Option.unwrap_or\n\n`Option.unwrap_or(self, default: U) -> t.Union[T, U]`\n\nIf this Option is `Some`, return the wrapped value. Otherwise, return the\nprovided default.\n\nExample:\n\n```py\nassert Some(1),unwrap_or(-1) == 1\nassert Nothing().unwrap_or(-1) == -1\n```\n\n##### Option.unwrap_or_else\n\n`Option.unwrap_or_else(self, fn: t.Callable[[], U]) -> t.Union[T, U]`\n\nIf this Option is `Some`, return the wrapped value. Otherwise, return the\nresult of the provided function.\n\nExample:\n\n```py\nfrom datetime import date\n\nassert Some(date(2001, 1, 1)).unwrap_or_else(date.today) == date(2001, 1, 1)\nassert Nothing().unwrap_or_else(date.today) == date.today()\n```\n\n#### Option Magic Methods\n\n##### Option.__iter__ \n\n`Option.__iter__(self) -> t.Iterator[T]`\n\nImplement the iterator protocol, allowing iteration over the results of\n[`Option.iter`](#optioniter). If this Option is `Ok`, return an iterator\nof length 1 containing the wrapped value. Otherwise, if this Option is `Nothing`,\nreturn a 0-length iterator.\n\nExample:\n\n```py\n# Can be passed to methods that take iterators\nassert tuple(Some(1)) == (1,)\nassert tuple(Nothing()j) == ()\n\n# Can be used in `for in` constructs, including comprehensions\nassert [val for val in Some(1)] == [1]\nassert [val for val in Nothing()] == []\n\n\n# More for-in usage.\nfor val in Some(1):\n pass\nassert val == 1\n\nval = None\nfor val in Nothing():\n pass\nassert val is None\n```\n\n##### Option.__eq__ \n\n`Option.__eq__(self, other: Any) -> bool`\n\nEnable equality checking using `==`.\n\nCompare this Option with `other`. Return True if `other` is the same type of\nOption with the same wrapped value. Otherwise, return False.\n\nExample:\n\n```py\nassert (Some(1) == Some(1)) is True\nassert (Some(1) == Some(2)) is False\nassert (Some(1) == Nothing()) is False\nassert (Some(1) == 1) is False\n```\n\n##### Option.__ne__ \n\n`Option.__ne__(self, other: Any) -> bool`\n\nEnable inequality checking using `!=`.\n\nCompare the Option with `other`. Return False if `other` is the same type of\nOption with the same wrapped value. Otherwise, return True.\n\nExample:\n\n```py\nassert (Some(1) != Some(1)) is False\nassert (Some(1) != Some(2)) is True\nassert (Some(1) != Nothing()) is True\nassert (Some(1) != 1) is True\n```\n\n##### Option.__str__ \n\n`Option.__str__(self) -> str`\n\nEnable useful stringification via `str()`.\n\nExample:\n\n```py\nassert str(Some(1)) == \"Some(1)\"\nassert str(Nothing()) == \"Nothing()\"\n```\n\n##### Option.__repr__ \n\n`Option.__repr__(self) -> str`\n\nEnable useful stringification via `repr()`.\n\nExample:\n\n```py\nassert repr(Some(1)) == \"Some(1)\"\nassert repr(Nothing()) == \"Nothing()\"\n```\n\n## Performance\n\nBenchmarks may be run with `make bench`. Benchmarking utilities are provided\nin [`bench/`](/bench).\n\nCurrently, the [`sample.py`](/bench/sample.py) benchmark defines two data\nstores, one using classical python error handling (or lack thereof), and\nthe other using this library's wrapper types. Some simple operations\nare performed using each data store for comparison.\n\n[`runner.sh`](/bench/runner.sh) runs the benchmarks two ways. First, it uses\n[hyperfine] to run the benchmarks as a normal python script 100 times and\ndisplay information about the run time. It then uses python's builtin\n[timeit](https://docs.python.org/3/library/timeit.html) module to measure\nthe code execution time in isolation over one million runs, without the\nadded overhead of spinning up the interpreter to parse and run the script.\n\n### Results\n\nThe `Result` and `Option` wrapper types add minimal overhead to\nexecution time, which will not be noticeable for most real-world workloads.\nHowever, care should be taken if using these types in \"hot paths.\"\n\nRun in isolation, the sample code using `Result` and `Option` types is\nabout six times slower than builtin exception handling:\n\n| Method | Number of Executions | Average Execution Time | Relative to Classical |\n| --------- | -------------------- | ---------------------- | --------------------- |\n| Classical | 1,000,000 (1E6) | 3.79E-6 s (3.79 μs) | 1x |\n| Wrapper | 1,000,000 (1E6) | 2.31E-5 s (23.1 μs) | 6.09x |\n\nWhen run as part of a Python script, there is no significant difference\nbetween using code with these wrapper types versus code that uses builtin\nexception handling and nested if statements.\n\n| Method | Number of Executions | Average Execution Time | Relative to Classical |\n| --------- | -------------------- | ---------------------- | --------------------- |\n| Classical | 100 | 32.2 ms | 1x |\n| Wrapper | 100 | 32.5 ms | 1.01x |\n\n### Discussion\n\nCare has been taken to make the wrapper types in this library as performant\nas possible. All types use `__slots__` to avoid allocating a dictionary for\ninstance variables, and wrapper variants (e.g. `Ok` and `Err` for `Result`)\nare implemented as separate subclasses of `Result` rather than a shared\nclass in order to avoid needing to perform if/else branching or `isinstance()`\nchecks, which are notoriously slow in Python.\n\nThat being said, using these types _is_ doing more than the builtin error\nhandling! Instances are being constructed and methods are being accessed.\nBoth of these are relatively quick in Python, but definitely not quicker\nthan doing nothing, so this library will probably never be quite as performant\nas raw exception handling. That being said, that is not its aim! The goal\nis to be as quick as possible, preferably within striking distance of\nregular old idiomatic python, while providing significantly more ergonomics\nand type safety around handling errors and absent data.\n\n## Contributing\n\nContributions are welcome! To get started, you'll just need a local install\nof Python 3.\n\nOnce you've forked and cloned the repo, you can run:\n\n- `make test` - run tests using your local interpreter\n- `make fmt` - format code using [black](https://github.com/python/black)\n- `make lint` - check code with a variety of analysis tools\n- `make bench` - run benchmarks\n\nSee the [`Makefile`](Makefile) for other commands.\n\nThe CI system requires that `make lint` and `make test` run successfully\n(exit status of 0) in order to merge code.\n\n`result_types` is compatible with Python >= 3.6. You can run against\nall supported python versions with `make test-all-versions`. This requires\nthat `docker` be installed on your local system. Alternatively, if you\nhave all required Python versions installed, you may run `make tox` to\nrun against your local interpreters.\n\n[hyperfine]: https://github.com/sharkdp/hyperfine\n[rust-result]: https://doc.rust-lang.org/std/result/\n[rust-option]: https://doc.rust-lang.org/std/option/\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://www.github.com/mplanchard/safetywrap", "keywords": "python,rust,result,option,typed,types,typesafe,monad,wrapper,safety", "license": "", "maintainer": "", "maintainer_email": "", "name": "safetywrap", "package_url": "https://pypi.org/project/safetywrap/", "platform": "", "project_url": "https://pypi.org/project/safetywrap/", "project_urls": { "Homepage": "https://www.github.com/mplanchard/safetywrap" }, "release_url": "https://pypi.org/project/safetywrap/1.5.0/", "requires_dist": [ "black ; extra == 'dev'", "coverage ; extra == 'dev'", "flake8 ; extra == 'dev'", "ipdb ; extra == 'dev'", "ipython ; extra == 'dev'", "mypy ; extra == 'dev'", "pydocstyle ; extra == 'dev'", "pylint ; extra == 'dev'", "pytest ; extra == 'dev'", "pytest-cov ; extra == 'dev'", "tox ; extra == 'dev'", "twine ; extra == 'dev'", "typeguard ; extra == 'dev'", "wheel ; extra == 'dev'" ], "requires_python": ">=3.6", "summary": "Rust-inspired typesafe result types", "version": "1.5.0", "yanked": false, "yanked_reason": null }, "last_serial": 8257246, "releases": { "0.0.0": [ { "comment_text": "", "digests": { "md5": "91a5185b310096f1a22ad101d73193b7", "sha256": "e59e69d0c935d94d271105127315a04b4289ce2c8966120089a04486fd13adf4" }, "downloads": -1, "filename": "safetywrap-0.0.0-py3-none-any.whl", "has_sig": true, "md5_digest": "91a5185b310096f1a22ad101d73193b7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 23210, "upload_time": "2020-09-23T22:52:56", "upload_time_iso_8601": "2020-09-23T22:52:56.575863Z", "url": "https://files.pythonhosted.org/packages/44/d2/2c22ae348ca14ff6b8f37ad177d7188d9fd71afb217ecebc8cd3a8b8aa2a/safetywrap-0.0.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "43005664caf4bdc4275ec1a90c1f7bc5", "sha256": "c65f8436a29c85266921131a7de2e4105cad1b5dacb34fd0f9355a75902eb67e" }, "downloads": -1, "filename": "safetywrap-0.0.0.tar.gz", "has_sig": true, "md5_digest": "43005664caf4bdc4275ec1a90c1f7bc5", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 39310, "upload_time": "2020-09-23T22:52:59", "upload_time_iso_8601": "2020-09-23T22:52:59.103391Z", "url": "https://files.pythonhosted.org/packages/63/b5/902b600e31dc2a849ee20c35bf96214ee2966ca3a47e1f9ae938160329d6/safetywrap-0.0.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "73d7c4fda27140c73480b373559bd57f", "sha256": "d09e236d205ee9baab67425a05a42f186ac16095018afce5d80faa2106ad8a8f" }, "downloads": -1, "filename": "safetywrap-1.0.0-py3-none-any.whl", "has_sig": true, "md5_digest": "73d7c4fda27140c73480b373559bd57f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 21340, "upload_time": "2019-10-25T01:01:57", "upload_time_iso_8601": "2019-10-25T01:01:57.797152Z", "url": "https://files.pythonhosted.org/packages/4a/e2/bd8ca5986ef0094ca7e52bd1b240dcef2ea1eb65248d61f3202715019ddc/safetywrap-1.0.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "b5b357f9bcfb4cf8bd0ac084a7e2d095", "sha256": "502d9cb747eab55385b970f9a6ca12959ee42102bcf80b4b010896386b8cc768" }, "downloads": -1, "filename": "safetywrap-1.0.0.tar.gz", "has_sig": true, "md5_digest": "b5b357f9bcfb4cf8bd0ac084a7e2d095", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 35852, "upload_time": "2019-10-25T01:02:01", "upload_time_iso_8601": "2019-10-25T01:02:01.436011Z", "url": "https://files.pythonhosted.org/packages/87/52/8267c1412cce4ee6cd586f261e94bc303036d7ce9d6e72d98bcc0a996c26/safetywrap-1.0.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "0a5efda3eb5e8c2b885bcc7903a53e08", "sha256": "0642a21b6bb9723aed1865bfd31b3be18cb16cbea7163d8a3f729d887bed00c7" }, "downloads": -1, "filename": "safetywrap-1.0.1-py3-none-any.whl", "has_sig": true, "md5_digest": "0a5efda3eb5e8c2b885bcc7903a53e08", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 21375, "upload_time": "2019-12-09T17:22:33", "upload_time_iso_8601": "2019-12-09T17:22:33.441494Z", "url": "https://files.pythonhosted.org/packages/c8/08/3a5b0d26000a8350be571223e393b38070a5a67187bbd73a2f92c4ad3e33/safetywrap-1.0.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "c477b30309f409d4544c15359e49c9fc", "sha256": "71e536fbc5007872bca39311c1e607c4f109e693ecaa0cdf851c6339fda91cc8" }, "downloads": -1, "filename": "safetywrap-1.0.1.tar.gz", "has_sig": true, "md5_digest": "c477b30309f409d4544c15359e49c9fc", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 35890, "upload_time": "2019-12-09T17:22:35", "upload_time_iso_8601": "2019-12-09T17:22:35.891727Z", "url": "https://files.pythonhosted.org/packages/77/91/8af8f1883e8b507a8226f3802f618af717ce7e674bfbdf5d1f51d8cdae4e/safetywrap-1.0.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "a9a0511a311192911f9c98273cfb7ebe", "sha256": "590863c8b1c85e1083fec3f47962cc1b1a3a56d159707a47eb86808fe80c1b76" }, "downloads": -1, "filename": "safetywrap-1.0.2-py3-none-any.whl", "has_sig": true, "md5_digest": "a9a0511a311192911f9c98273cfb7ebe", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 21523, "upload_time": "2019-12-12T21:35:29", "upload_time_iso_8601": "2019-12-12T21:35:29.256822Z", "url": "https://files.pythonhosted.org/packages/bf/df/47bbb31b2232791cc66ddb910d0b6f68a1c4d7ba5e259bd91c25a2610ba5/safetywrap-1.0.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "022cff05bb3e5ff51ef24dd1c8242a1f", "sha256": "ca3c3c637e05b87bbaed01efb38392fee702c47831761b498b0245c45fd199dc" }, "downloads": -1, "filename": "safetywrap-1.0.2.tar.gz", "has_sig": true, "md5_digest": "022cff05bb3e5ff51ef24dd1c8242a1f", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 36231, "upload_time": "2019-12-12T21:35:31", "upload_time_iso_8601": "2019-12-12T21:35:31.640663Z", "url": "https://files.pythonhosted.org/packages/1f/c8/1a05a5b93a4a7945e3c46fc431980ee2b8aae5fcd082f0c6467cfa899780/safetywrap-1.0.2.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "313ff7b49f32c5f5bdfe62e2c49e1cab", "sha256": "c09e30c8fabe332f70d5aba6b23582fc23fba2a79f40373b934e00217158967e" }, "downloads": -1, "filename": "safetywrap-1.1.0-py3-none-any.whl", "has_sig": true, "md5_digest": "313ff7b49f32c5f5bdfe62e2c49e1cab", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 22223, "upload_time": "2020-01-03T19:46:49", "upload_time_iso_8601": "2020-01-03T19:46:49.949312Z", "url": "https://files.pythonhosted.org/packages/8c/53/65f570e0cd9d9ad81c221ed3df187263b85a4f297c1a3c18827e9ae7da24/safetywrap-1.1.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "ee9fb55349f688f47ed7f799617ce9c0", "sha256": "3df38322fdd7ad3565de5f3a74ac69ee5703f12ce6d04b67c18318d44bbd093e" }, "downloads": -1, "filename": "safetywrap-1.1.0.tar.gz", "has_sig": true, "md5_digest": "ee9fb55349f688f47ed7f799617ce9c0", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 37153, "upload_time": "2020-01-03T19:46:52", "upload_time_iso_8601": "2020-01-03T19:46:52.581183Z", "url": "https://files.pythonhosted.org/packages/fa/77/505b588bb366167efd595ee1febc35ee9cfd05f0b6917c29995a102a351b/safetywrap-1.1.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "3659ad04a60c8e7dcf364ae450ab6808", "sha256": "e79a83d62a35669de13a5bd596b4fef9a45ec9c166b4047bc8dbc71c5f1d5967" }, "downloads": -1, "filename": "safetywrap-1.2.0-py3-none-any.whl", "has_sig": true, "md5_digest": "3659ad04a60c8e7dcf364ae450ab6808", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 22489, "upload_time": "2020-01-09T19:30:47", "upload_time_iso_8601": "2020-01-09T19:30:47.091818Z", "url": "https://files.pythonhosted.org/packages/02/94/5c31b3f2af594838a27b1bfb69cd9496fde9f1aa53805b2d15d70086319f/safetywrap-1.2.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "8d50d56ede188cc5ba6d6b5a81a1197b", "sha256": "eb2b1cc61a7cec6f15bbbd7fafc10cf2bbcf9ec5d65577fa0ff12653be83d765" }, "downloads": -1, "filename": "safetywrap-1.2.0.tar.gz", "has_sig": true, "md5_digest": "8d50d56ede188cc5ba6d6b5a81a1197b", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 37727, "upload_time": "2020-01-09T19:30:49", "upload_time_iso_8601": "2020-01-09T19:30:49.616282Z", "url": "https://files.pythonhosted.org/packages/66/0c/83e1fd632432b334fb5abccee912c9be55c451fc5c1e5a3ab743d452d298/safetywrap-1.2.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "3c4bc32c9208724da19175f20c48ebf5", "sha256": "df9f71754b7a4ccd992dfb4326761a8d8578242f98b3f584ca584c2cdf0fcab5" }, "downloads": -1, "filename": "safetywrap-1.3.0-py3-none-any.whl", "has_sig": true, "md5_digest": "3c4bc32c9208724da19175f20c48ebf5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 22681, "upload_time": "2020-01-12T15:24:17", "upload_time_iso_8601": "2020-01-12T15:24:17.890297Z", "url": "https://files.pythonhosted.org/packages/e9/99/c859b97b99592b941f76ffcf60fc52ef323f433529a4d00106d00aae7f94/safetywrap-1.3.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "27a5e2cd84ccb2256f5cb669f36b5246", "sha256": "44d2a5c7e7e016953199a7cb127138fee8a52cd10c6fb7bbe1ec28a47453edfd" }, "downloads": -1, "filename": "safetywrap-1.3.0.tar.gz", "has_sig": true, "md5_digest": "27a5e2cd84ccb2256f5cb669f36b5246", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 37842, "upload_time": "2020-01-12T15:24:20", "upload_time_iso_8601": "2020-01-12T15:24:20.489879Z", "url": "https://files.pythonhosted.org/packages/4b/38/cf8a38b8c384fa94d011a7a57954f32173742a148172d7677ea99aa0227d/safetywrap-1.3.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.3.1": [ { "comment_text": "", "digests": { "md5": "0110df1b50a1fc5b4719a53545f52255", "sha256": "b661e5581793b6dd42504fc5a4345d8af729d84fcc6a2539aa746bcbc809eed3" }, "downloads": -1, "filename": "safetywrap-1.3.1-py3-none-any.whl", "has_sig": true, "md5_digest": "0110df1b50a1fc5b4719a53545f52255", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 22773, "upload_time": "2020-02-21T22:13:48", "upload_time_iso_8601": "2020-02-21T22:13:48.462060Z", "url": "https://files.pythonhosted.org/packages/1b/0a/8b9337996d82b6e99802ddbd1697762df4c3c1976e75b144a20083b03785/safetywrap-1.3.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "77367faeff4274a1dc55a1d228d2f458", "sha256": "fe3ab9d09a2fa8f48f8aa30c646c62002e786aeb058cfe3bfb73491877f63567" }, "downloads": -1, "filename": "safetywrap-1.3.1.tar.gz", "has_sig": true, "md5_digest": "77367faeff4274a1dc55a1d228d2f458", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 38019, "upload_time": "2020-02-21T22:13:51", "upload_time_iso_8601": "2020-02-21T22:13:51.126200Z", "url": "https://files.pythonhosted.org/packages/5c/5d/718f9d37abb362c620d308eca9ef38c2aefce59b5275125347229be6c6b3/safetywrap-1.3.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.4.0": [ { "comment_text": "", "digests": { "md5": "abf2ff7d50bec19ed846941b17da4f5b", "sha256": "a36a162220db0aecae27da8d18241136871e406c5d98e12fcd447d141cc35ccc" }, "downloads": -1, "filename": "safetywrap-1.4.0-py3-none-any.whl", "has_sig": true, "md5_digest": "abf2ff7d50bec19ed846941b17da4f5b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 23152, "upload_time": "2020-03-09T20:26:43", "upload_time_iso_8601": "2020-03-09T20:26:43.765471Z", "url": "https://files.pythonhosted.org/packages/f8/88/c0db9fd98f14b83fb5c9cf0205be0f6cb7dc0290fe5b7f3ce6cac7749118/safetywrap-1.4.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "a9cf302eb384b65498b976befe8f76a4", "sha256": "3376d137c4cd8551748a41bf1c9e79b8d0351717c2c398bcb8340e6da140350c" }, "downloads": -1, "filename": "safetywrap-1.4.0.tar.gz", "has_sig": true, "md5_digest": "a9cf302eb384b65498b976befe8f76a4", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 38472, "upload_time": "2020-03-09T20:26:45", "upload_time_iso_8601": "2020-03-09T20:26:45.860805Z", "url": "https://files.pythonhosted.org/packages/e5/da/ee4b879666ad6efdfff6c293ba8fc37098f9fbf336ba2c92d87383e8d671/safetywrap-1.4.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.5.0": [ { "comment_text": "", "digests": { "md5": "6143467a430c38401f085ca6605f2839", "sha256": "90cd20e7fc90bb8e1cdbb147969bb3623e3e1febd206da3e5a157f298960b574" }, "downloads": -1, "filename": "safetywrap-1.5.0-py3-none-any.whl", "has_sig": true, "md5_digest": "6143467a430c38401f085ca6605f2839", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 23214, "upload_time": "2020-09-23T22:59:42", "upload_time_iso_8601": "2020-09-23T22:59:42.097310Z", "url": "https://files.pythonhosted.org/packages/bb/bd/1b5f2adde565b73b4358d1bc39f302410ee0202cc0a9e6a78d3783e5a17c/safetywrap-1.5.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "d8ddfdcf4b680ae322710af57ae308e2", "sha256": "e331b86fb33201e072e2e01f851b1ca8ac7b3788588844a3e451b1aef164ecb8" }, "downloads": -1, "filename": "safetywrap-1.5.0.tar.gz", "has_sig": true, "md5_digest": "d8ddfdcf4b680ae322710af57ae308e2", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 39345, "upload_time": "2020-09-23T22:59:44", "upload_time_iso_8601": "2020-09-23T22:59:44.256740Z", "url": "https://files.pythonhosted.org/packages/0e/27/a7c7902444fdc6781c38080f5e40ddff0d192acada78ed4bcdba3a7f4651/safetywrap-1.5.0.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "6143467a430c38401f085ca6605f2839", "sha256": "90cd20e7fc90bb8e1cdbb147969bb3623e3e1febd206da3e5a157f298960b574" }, "downloads": -1, "filename": "safetywrap-1.5.0-py3-none-any.whl", "has_sig": true, "md5_digest": "6143467a430c38401f085ca6605f2839", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 23214, "upload_time": "2020-09-23T22:59:42", "upload_time_iso_8601": "2020-09-23T22:59:42.097310Z", "url": "https://files.pythonhosted.org/packages/bb/bd/1b5f2adde565b73b4358d1bc39f302410ee0202cc0a9e6a78d3783e5a17c/safetywrap-1.5.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "d8ddfdcf4b680ae322710af57ae308e2", "sha256": "e331b86fb33201e072e2e01f851b1ca8ac7b3788588844a3e451b1aef164ecb8" }, "downloads": -1, "filename": "safetywrap-1.5.0.tar.gz", "has_sig": true, "md5_digest": "d8ddfdcf4b680ae322710af57ae308e2", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 39345, "upload_time": "2020-09-23T22:59:44", "upload_time_iso_8601": "2020-09-23T22:59:44.256740Z", "url": "https://files.pythonhosted.org/packages/0e/27/a7c7902444fdc6781c38080f5e40ddff0d192acada78ed4bcdba3a7f4651/safetywrap-1.5.0.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }