{"id":18574751,"url":"https://github.com/kaliv0/pyrio","last_synced_at":"2025-04-10T08:30:37.174Z","repository":{"id":255461245,"uuid":"848949572","full_name":"kaliv0/pyrio","owner":"kaliv0","description":"Functional-style Streams API","archived":false,"fork":false,"pushed_at":"2025-03-30T07:48:52.000Z","size":7434,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T08:12:31.930Z","etag":null,"topics":["file-processing","fluent-api","functional-programming","python-functional","streams-api"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/pyrio/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kaliv0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-28T17:45:29.000Z","updated_at":"2025-03-30T07:48:55.000Z","dependencies_parsed_at":"2024-09-05T14:41:06.647Z","dependency_job_id":"25b2521c-dac6-4c91-be53-0b72ddc0f11d","html_url":"https://github.com/kaliv0/pyrio","commit_stats":null,"previous_names":["kaliv0/pyrio"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaliv0%2Fpyrio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaliv0%2Fpyrio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaliv0%2Fpyrio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaliv0%2Fpyrio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kaliv0","download_url":"https://codeload.github.com/kaliv0/pyrio/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248185164,"owners_count":21061467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["file-processing","fluent-api","functional-programming","python-functional","streams-api"],"created_at":"2024-11-06T23:16:17.690Z","updated_at":"2025-04-10T08:30:37.160Z","avatar_url":"https://github.com/kaliv0.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/kaliv0/pyrio/blob/main/assets/Pyrio.jpg?raw=true\" width=\"400\" alt=\"Pyrio\"\u003e\n\u003c/p\u003e\n\n# PYRIO\n\n\n![Python 3.x](https://img.shields.io/badge/python-3.12-blue?style=flat-square\u0026logo=Python\u0026logoColor=white)\n[![tests](https://img.shields.io/github/actions/workflow/status/kaliv0/pyrio/ci.yml)](https://github.com/kaliv0/pyrio/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/kaliv0/pyrio/graph/badge.svg?token=7EEG43BL33)](https://codecov.io/gh/kaliv0/pyrio)\n[![docs](https://readthedocs.org/projects/pyrio/badge/?version=latest)](https://pyrio.readthedocs.io/en/latest)\n[![PyPI](https://img.shields.io/pypi/v/pyrio.svg)](https://pypi.org/project/pyrio/)\n[![Downloads](https://static.pepy.tech/badge/pyrio)](https://pepy.tech/projects/pyrio)\n\n\u003cbr\u003e\u003cb\u003eFunctional-style Streams API library\u003c/b\u003e\u003cbr\u003e\n\u003cbr\u003eFacilitates processing of collections and iterables using fluent APIs.\n\u003cbr\u003eGives access to files of various types (\u003ci\u003ejson\u003c/i\u003e, \u003ci\u003etoml\u003c/i\u003e, \u003ci\u003eyaml\u003c/i\u003e, \u003ci\u003exml\u003c/i\u003e, \u003ci\u003ecsv\u003c/i\u003e, \u003ci\u003etsv\u003c/i\u003e, \u003ci\u003eplain text\u003c/i\u003e) for reading and executing complex queries.\n\u003cbr\u003eProvides easy integration with \u003ci\u003eitertools\u003c/i\u003e.\n\u003cbr\u003e(NB: Commonly used \u003ci\u003eitertools 'recipes'\u003c/i\u003e are included as part of the main APIs.)\n\n## How to use\n### Creating streams\n- stream from iterable\n```python\nStream([1, 2, 3])\n```\n\n- from variadic arguments\n```python\nStream.of(1, 2, 3)\n```\n\n- empty stream\n```python\nStream.empty()\n```\n\n- infinite ordered stream\n```python\nStream.iterate(0, lambda x: x + 1)\n```\n\nNB: in similar fashion you can create \u003ci\u003efinite ordered stream\u003c/i\u003e by providing a \u003ci\u003econdition\u003c/i\u003e predicate\u003c/i\u003e\n```python\nStream.iterate(10, operation=lambda x: x + 1, condition=lambda x: x \u003c 15).to_list()\n# [10, 11, 12, 13, 14]\n```\n\n- infinite unordered stream\n```python\nimport random\n\nStream.generate(lambda: random.random())\n```\n\n- infinite stream with given value\n```python\nStream.constant(42)\n```\n\n- stream from range\n\u003cbr\u003e(from \u003ci\u003estart\u003c/i\u003e (inclusive) to \u003ci\u003estop\u003c/i\u003e (exclusive) by an incremental \u003ci\u003estep\u003c/i\u003e (defaults to 1))\n```python\nStream.from_range(0, 10).to_list()\nStream.from_range(0, 10, 3).to_list()\nStream.from_range(10, -1, -2).to_list()\n```\n(or from \u003ci\u003erange\u003c/i\u003e object)\n```python\nrange_obj = range(0, 10)\nStream.from_range(range_obj).to_list()\n```\n\n- concat\n\u003cbr\u003e(concatenate new streams/iterables with the current one)\n```python\nStream.of(1, 2, 3).concat(Stream.of(4, 5)).to_list()\nStream([1, 2, 3]).concat([5, 6]).to_list()\n```\n\n- prepend\n\u003cbr\u003e(prepend new stream/iterable to the current one)\n```python\nStream([2, 3, 4]).prepend(0, 1).to_list()\nStream.of(3, 4, 5).prepend(Stream.of([0, 1], 2)).to_list()\n```\n\nNB: creating new stream from None raises error.\n\u003cbr\u003eIn cases when the \u003ci\u003eiterable\u003c/i\u003e could potentially be None use the \u003ci\u003eof_nullable()\u003c/i\u003e method instead;\n\u003cbr\u003eit returns an \u003ci\u003eempty stream\u003c/i\u003e if None and a \u003ci\u003eregular\u003c/i\u003e one otherwise\n\n--------------------------------------------\n### Intermediate operations\n- filter\n```python\nStream([1, 2, 3]).filter(lambda x: x % 2 == 0)\n```\n\n- map\n```python\nStream([1, 2, 3]).map(str).to_list()\nStream([1, 2, 3]).map(lambda x: x + 5).to_list()\n```\n\n- filter_map\n\u003cbr\u003e(filter out all None or discard_falsy values (if discard_falsy=True) and applies mapper function to the elements of the stream)\n```python\nStream.of(None, \"foo\", \"\", \"bar\", 0, []).filter_map(str.upper, discard_falsy=True).to_list()\n# [\"FOO\", \"BAR\"]\n```\n\n- flat_map\n\u003cbr\u003e(map each element of the stream and yields the elements of the produced iterators)\n```python\nStream([[1, 2], [3, 4], [5]]).flat_map(lambda x: Stream(x)).to_list()\n# [1, 2, 3, 4, 5]\n```\n\n- flatten\n```python\nStream([[1, 2], [3, 4], [5]]).flatten().to_list()\n# [1, 2, 3, 4, 5]\n```\n\n- reduce\n\u003cbr\u003e(returns Optional)\n```python\nStream([1, 2, 3]).reduce(lambda acc, val: acc + val, identity=3).get()\n```\n\n- peek\n\u003cbr\u003e(perform the provided operation on each element of the stream without consuming it)\n```python\n(Stream([1, 2, 3, 4])\n    .filter(lambda x: x \u003e 2)\n    .peek(lambda x: print(f\"{x} \", end=\"\"))\n    .map(lambda x: x * 20)\n    .to_list())\n```\n\n- enumerate\n\u003cbr\u003e(returns each element of the Stream preceded by his corresponding index\n(by default starting from 0 if not specified otherwise))\n```python\niterable = [\"x\", \"y\", \"z\"]\nStream(iterable).enumerate().to_list()\nStream(iterable).enumerate(start=1).to_list()\n# [(0, \"x\"), (1, \"y\"), (2, \"z\")]\n# [(1, \"x\"), (2, \"y\"), (3, \"z\")]\n```\n\n- view\n\u003cbr\u003e(provides access to a selected part of the stream)\n```python\nStream([1, 2, 3, 4, 5, 6, 7, 8, 9]).view(start=1, stop=-3, step=2).to_list()\n# [2, 4, 6]\n```\n\n- distinct\n\u003cbr\u003e(returns a stream with the distinct elements of the current one)\n```python\nStream([1, 1, 2, 2, 2, 3]).distinct().to_list()\n```\n\n- skip\n\u003cbr\u003e(discards the first n elements of the stream and returns a new stream with the remaining ones)\n```python\nStream.iterate(0, lambda x: x + 1).skip(5).limit(5).to_list()\n```\n\n- limit / head\n\u003cbr\u003e(returns a stream with the first n elements, or fewer if the underlying iterator ends sooner)\n```python\nStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).limit(3).to_tuple()\nStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).head(3).to_tuple()\n```\n\n- tail\n\u003cbr\u003e(returns a stream with the last n elements, or fewer if the underlying iterator ends sooner)\n```python\nStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).tail(3).to_tuple()\n```\n\n- take_while\n\u003cbr\u003e(returns a stream that yields elements based on a predicate)\n```python\nStream.of(1, 2, 3, 4, 5, 6, 7, 2, 3).take_while(lambda x: x \u003c 5).to_list()\n# [1, 2, 3, 4]\n```\n\n- drop_while\n\u003cbr\u003e(returns a stream that skips elements based on a predicate and yields the remaining ones)\n```python\nStream.of(1, 2, 3, 5, 6, 7, 2).drop_while(lambda x: x \u003c 5).to_list()\n# [5, 6, 7, 2]\n```\n\n- sort\n\u003cbr\u003e(sorts the elements of the current stream according to natural order or based on the given comparator;\n\u003cbr\u003eif 'reverse' flag is True, the elements are sorted in descending order)\n```python\n(Stream.of((3, 30), (2, 30), (2, 20), (1, 20), (1, 10))\n    .sort(lambda x: (x[0], x[1]), reverse=True)\n    .to_list())\n# [(3, 30), (2, 30), (2, 20), (1, 20), (1, 10)]\n```\n\n- reverse\n\u003cbr\u003e(sorts the elements of the current stream in reverse order;\n\u003cbr\u003ealias for \u003ci\u003e'sort(collector, reverse=True)'\u003c/i\u003e)\n```python\n(Stream.of((3, 30), (2, 30), (2, 20), (1, 20), (1, 10))\n    .reverse(lambda x: (x[0], x[1]))\n    .to_list())\n# [(3, 30), (2, 30), (2, 20), (1, 20), (1, 10)]\n```\n\n\u003cbr\u003eNB: in case of stream of dicts all key-value pairs are represented internally as \u003ci\u003eDictItem\u003c/i\u003e objects\n\u003cbr\u003e(including recursively for nested Mapping structures)\n\u003cbr\u003eto provide more convenient intermediate operations syntax e.g.\n```python\nfirst_dict = {\"a\": 1, \"b\": 2}\nsecond_dict = {\"x\": 3, \"y\": 4}\n(Stream(first_dict).concat(second_dict)\n    .filter(lambda x: x.value % 2 == 0)\n    .map(lambda x: x.key)\n    .to_list())\n```\n\n- on_close\n\u003cbr\u003e(returns an equivalent Stream with an additional \u003ci\u003eclose handler\u003c/i\u003e to be invoked automatically by the \u003ci\u003eterminal operation\u003c/i\u003e)\n```python\n(Stream([1, 2, 3, 4])\n    .on_close(lambda: print(\"Sorry Montessori\"))\n    .peek(lambda x: print(f\"{'$' * x} \", end=\"\"))\n    .map(lambda x: x * 2)\n    .to_list())\n# \"$ $$ $$$ $$$$ Sorry Montessori\"\n# [2, 4, 6, 8]\n```\n--------------------------------------------\n### Terminal operations\n#### Collectors\n- collecting result into list, tuple, set\n```python\nStream([1, 2, 3]).to_list()\nStream([1, 2, 3]).to_tuple()\nStream([1, 2, 3]).to_set()\n```\n\n- into dict\n```python\nclass Foo:\n    def __init__(self, name, num):\n        self.name = name\n        self.num = num\n\nStream([Foo(\"fizz\", 1), Foo(\"buzz\", 2)]).to_dict(lambda x: (x.name, x.num))\n# {\"fizz\": 1, \"buzz\": 2}\n```\n\nIn the case of a collision (duplicate keys) the 'merger' functions indicates which entry should be kept\n```python\ncollection = [Foo(\"fizz\", 1), Foo(\"fizz\", 2), Foo(\"buzz\", 2)]\nStream(collection).to_dict(collector=lambda x: (x.name, x.num), merger=lambda old, new: old)\n# {\"fizz\": 1, \"buzz\": 2}\n```\n\n\u003ci\u003eto_dict\u003c/i\u003e method also supports creating dictionaries from dict DictItem objects\n```python\nfirst_dict = {\"x\": 1, \"y\": 2}\nsecond_dict = {\"p\": 33, \"q\": 44, \"r\": None}\nStream(first_dict).concat(Stream(second_dict)).to_dict(lambda x: DictItem(x.key, x.value or 0))\n# {\"x\": 1, \"y\": 2, \"p\": 33, \"q\": 44, \"r\": 0}\n```\ne.g. you could combine streams of dicts by writing:\n```python\nStream(first_dict).concat(Stream(second_dict)).to_dict()\n```\n(simplified from \u003ci\u003e'.to_dict(lambda x: x)'\u003c/i\u003e)\n\n- into string\n```python\nStream({\"a\": 1, \"b\": [2, 3]}).to_string()\n# \"Stream(DictItem(key=a, value=1), DictItem(key=b, value=[2, 3]))\"\n```\n```python\nStream({\"a\": 1, \"b\": [2, 3]}).map(lambda x: {x.key: x.value}).to_string(delimiter=\" | \")\n# \"Stream({'a': 1} | {'b': [2, 3]})\"\n```\n\n- alternative for working with collectors is using the \u003ci\u003ecollect\u003c/i\u003e method\n```python\nStream([1, 2, 3]).collect(tuple)\nStream.of(1, 2, 3).collect(list)\nStream.of(1, 1, 2, 2, 2, 3).collect(set)\nStream.of(1, 2, 3, 4).collect(dict, lambda x: (str(x), x * 10))\nStream.of(\"x\", \"y\", \"z\").collect(str, str_delimiter=\"-\u003e\")\n```\n\n- grouping\n```python\nStream(\"AAAABBBCCD\").group_by(collector=lambda key, grouper: (key, len(grouper)))\n# {\"A\": 4, \"B\": 3, \"C\": 2, \"D\": 1}\n```\n\n```python\ncoll = [Foo(\"fizz\", 1), Foo(\"fizz\", 2), Foo(\"fizz\", 3), Foo(\"buzz\", 2), Foo(\"buzz\", 3), Foo(\"buzz\", 4), Foo(\"buzz\", 5)]\nStream(coll).group_by(\n    classifier=lambda obj: obj.name,\n    collector=lambda key, grouper: (key, [(obj.name, obj.num) for obj in list(grouper)]))\n# {\"fizz\": [(\"fizz\", 1), (\"fizz\", 2), (\"fizz\", 3)],\n#  \"buzz\": [(\"buzz\", 2), (\"buzz\", 3), (\"buzz\", 4), (\"buzz\", 5)]}\n```\n#### Other terminal operations\n- for_each\n```python\nStream([1, 2, 3, 4]).for_each(lambda x: print(f\"{'#' * x} \", end=\"\"))\n```\n\n- count\n\u003cbr\u003e(returns the count of elements in the stream)\n```python\nStream([1, 2, 3, 4]).filter(lambda x: x % 2 == 0).count()\n```\n\n- sum\n```python\nStream.of(1, 2, 3, 4).sum()\n```\n\n- min\n\u003cbr\u003e(returns Optional with the minimum element of the stream)\n```python\nStream.of(2, 1, 3, 4).min().get()\n```\n\n- max\n\u003cbr\u003e(returns Optional with the maximum element of the stream)\n```python\nStream.of(2, 1, 3, 4).max().get()\n```\n\n- average\n\u003cbr\u003e(returns the average value of elements in the stream)\n```python\nStream.of(1, 2, 3, 4, 5).average()\n```\n\n- find_first\n\u003cbr\u003e(search for an element of the stream that satisfies a predicate,\nreturns an Optional with the first found value, if any, or None)\n```python\nStream.of(1, 2, 3, 4).filter(lambda x: x % 2 == 0).find_first().get()\n```\n\n- find_any\n\u003cbr\u003e(search for an element of the stream that satisfies a predicate,\nreturns an Optional with some of the found values, if any, or None)\n```python\nStream.of(1, 2, 3, 4).filter(lambda x: x % 2 == 0).find_any().get()\n```\n\n- any_match\n\u003cbr\u003e(returns whether any elements of the stream match the given predicate)\n```python\nStream.of(1, 2, 3, 4).any_match(lambda x: x \u003e 2)\n```\n\n- all_match\n\u003cbr\u003e(returns whether all elements of the stream match the given predicate)\n```python\nStream.of(1, 2, 3, 4).all_match(lambda x: x \u003e 2)\n```\n\n- none_match\n\u003cbr\u003e(returns whether no elements of the stream match the given predicate)\n```python\nStream.of(1, 2, 3, 4).none_match(lambda x: x \u003c 0)\n```\n\n- take_first\n\u003cbr\u003e(returns Optional with the first element of the stream or a default value)\n```python\nStream({\"a\": 1, \"b\": 2}).take_first().get()\nStream([]).take_first(default=33).get()\n# DictItem(key=\"a\", value=1)\n# 33\n```\n\n- take_last\n\u003cbr\u003e(returns Optional with the last element of the stream or a default value)\n```python\nStream({\"a\": 1, \"b\": 2}).take_last().get()\nStream([]).take_last(default=33).get()\n```\n\n- compare_with\n\u003cbr\u003e(compares linearly the contents of two streams based on a given comparator)\n```python\nfizz = Foo(\"fizz\", 1)\nbuzz = Foo(\"buzz\", 2)\nStream([buzz, fizz]).compare_with(Stream([fizz, buzz]), lambda x, y: x.num == y.num)\n```\n\n- quantify\n\u003cbr\u003e(count how many of the elements are Truthy or evaluate to True based on a given predicate)\n```python\nStream([2, 3, 4, 5, 6]).quantify(predicate=lambda x: x % 2 == 0)\n```\n\nNB: although the Stream is closed automatically by the \u003ci\u003eterminal operation\u003c/i\u003e\n\u003cbr\u003e you can still close it by hand (if needed) invoking the \u003ci\u003eclose()\u003c/i\u003e method.\n\u003cbr\u003e In turn that will trigger the \u003ci\u003eclose_handler\u003c/i\u003e (if such was provided)\n\n--------------------------------------------\n### Itertools integration\nInvoke \u003ci\u003euse\u003c/i\u003e method by passing the itertools function and it's arguments as **kwargs\n```python\nimport itertools\nimport operator\n\nStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).use(itertools.islice, start=3, stop=8)\nStream.of(1, 2, 3, 4, 5).use(itertools.accumulate, func=operator.mul).to_list()\nStream(range(3)).use(itertools.permutations, r=3).to_list()\n\n```\n#### Itertools 'recipes'\nInvoke the 'recipes' described [here](https://docs.python.org/3/library/itertools.html#itertools-recipes) as stream methods and pass required key-word arguments\n```python\nStream([1, 2, 3]).ncycles(count=2).to_list()\nStream.of(2, 3, 4).take_nth(10, default=66).get()\nStream([\"ABC\", \"D\", \"EF\"]).round_robin().to_list()\n```\n\n--------------------------------------------\n### FileStreams\n#### Querying files\n- working with \u003ci\u003ejson\u003c/i\u003e, \u003ci\u003etoml\u003c/i\u003e, \u003ci\u003eyaml\u003c/i\u003e, \u003ci\u003exml\u003c/i\u003e files\n\u003cbr\u003eNB: FileStream reads data as series of DictItem objects from underlying dict_items view\n```python\nFileStream(\"path/to/file\").map(lambda x: f\"{x.key}=\u003e{x.value}\").to_tuple()\n# (\"abc=\u003exyz\", \"qwerty=\u003e42\")\n```\n\n```python\nfrom operator import attrgetter\nfrom pyrio import DictItem\n\n(FileStream(\"path/to/file\")\n .filter(lambda x: \"a\" in x.key)\n .map(lambda x: DictItem(x.key, sum(x.value) * 10))\n .sort(attrgetter(\"value\"), reverse=True)\n .map(lambda x: f\"{str(x.value)}::{x.key}\")\n .to_list())\n# [\"230::xza\", \"110::abba\", \"30::a\"]\n```\n\n- querying \u003ci\u003ecsv\u003c/i\u003e and \u003ci\u003etsv\u003c/i\u003e files\n\u003cbr\u003e(each row is read as a dict with keys taken from the header)\n```python\nFileStream(\"path/to/file\").map(lambda x: f\"fizz: {x['fizz']}, buzz: {x['buzz']}\").to_tuple()\n# (\"fizz: 42, buzz: 45\", \"fizz: aaa, buzz: bbb\")\n```\n```python\nfrom operator import itemgetter\n\nFileStream(\"path/to/file\").map(itemgetter('fizz')).to_list()\n# ['42', 'aaa']\n```\nYou could query the nested dicts by creating streams out of them\n```python\n(FileStream(\"path/to/file\")\n    .map(lambda x: (Stream(x).to_dict(lambda y: DictItem(y.key, y.value or \"Unknown\"))))\n    .save())\n```\n\n- reading \u003ci\u003eplain text\u003c/i\u003e (if the file doesn't have one of the aforementioned extensions)\n```python\n(FileStream(\"path/to/lorem/ipsum\")\n    .map(lambda x: x.strip())\n    .enumerate()\n    .filter(lambda line: \"id\" in line[1])\n    .to_dict()\n)\n\n# {1: \"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n#  6: \"Excepteur sint occaecat cupidatat non proident, sunt in culpa\",\n#  7: \"qui officia deserunt mollit anim id est laborum.\"}\n```\n\n- reading a file with \u003ci\u003eprocess()\u003c/i\u003e method\n  - use extra \u003ci\u003ef_open_options\u003c/i\u003e (for the underlying \u003ci\u003eopen file\u003c/i\u003e function)\n  - \u003ci\u003ef_read_options\u003c/i\u003e (to be passed to the corresponding library function that is loading the file content e.g. tomllib, json)\n```python\nfrom decimal import Decimal\n\n(FileStream.process(\n    file_path=\"path/to/file.json\",\n    f_open_options={\"encoding\": \"utf-8\"},\n    f_read_options={\"parse_float\": Decimal})\n .map(lambda x:x.value).to_list())\n# ['foo', True, Decimal('1.22'), Decimal('5.456367654)]\n```\nTo include the \u003ci\u003eroot\u003c/i\u003e tag when loading an \u003ci\u003e.xml\u003c/i\u003e file pass \u003ci\u003e'include_root=True'\u003c/i\u003e\n```python\nFileStream.process(\"path/to/custom_root.xml\", include_root=True).map(\n    lambda x: f\"root={x.key}: inner_records={str(x.value)}\"\n).to_list()\n# [\"root=custom-root: inner_records={'abc': 'xyz', 'qwerty': '42'}\"]\n```\n\n--------------------------------------------\n#### Saving to a file\n- write the contents of a FileStream by passing a \u003ci\u003efile_path\u003c/i\u003e to the \u003ci\u003esave()\u003c/i\u003e method\n```python\nin_memory_dict = Stream(json_dict).filter(lambda x: len(x.key) \u003c 6).to_tuple()\nFileStream(\"path/to/file.json\").prepend(in_memory_dict).save(\"./tests/resources/updated.json\")\n```\nIf no path is given, the source file for the FileStream will be \u003ci\u003eupdated\u003c/i\u003e\n```python\nFileStream(\"path/to/file.json\").concat(in_memory_dict).save()\n```\nNB: if while updating the file something goes wrong, the original content will be restored/preserved\n\n- handle null values\n\u003cbr\u003e(pass \u003ci\u003enull_handler\u003c/i\u003e function to replace null values)\n```python\nFileStream(\"path/to/test.toml\").save(null_handler=lambda x: DictItem(x.key, x.value or \"N/A\"))\n```\nNB: useful for writing \u003ci\u003e.toml\u003c/i\u003e files which don't allow None values\n\n- passing advanced \u003ci\u003efile open\u003c/i\u003e and \u003ci\u003ewrite\u003c/i\u003e options\n\u003cbr\u003esimilarly to the \u003ci\u003eprocess\u003c/i\u003e method you could provide\n  - \u003ci\u003ef_open_options\u003c/i\u003e (for the underlying \u003ci\u003eopen\u003c/i\u003e function)\n  - \u003ci\u003ef_write_options\u003c/i\u003e (passed to the corresponding library that will 'dump' the contents of the stream e.g. tomli-w, pyyaml)\n```python\nFileStream(\"path/to/file.json\").concat(in_memory_dict).save(\n    file_path=\"merged.xml\",\n    f_open_options={\"encoding\": \"utf-8\"},\n    f_write_options={\"indent\": 4},\n)\n```\nE.g. to \u003ci\u003eappend\u003c/i\u003e to existing file pass \u003ci\u003ef_open_options={\"mode\": \"a\"}\u003c/i\u003e to the \u003ci\u003esave()\u003c/i\u003e method.\n\u003cbr\u003eNB: By default saving \u003ci\u003eplain text\u003c/i\u003e uses \u003ci\u003e\"\\n\"\u003c/i\u003e as \u003ci\u003edelimiter\u003c/i\u003e between items,\n\u003cbr\u003eyou can pass \u003ci\u003ecustom delimiter\u003c/i\u003e using \u003ci\u003ef_write_options\u003c/i\u003e\n```python\n(FileStream(\"path/to/lorem/ipsum\")\n    .map(lambda line: line.strip())\n    .enumerate()\n    .filter(lambda line: \"ad\" in line[1])\n    .map(lambda line: f\"line:{line[0]}, text='{line[1]}'\")\n    .save(f_open_options={\"mode\": \"a\"}, f_write_options={\"delimiter\": \" || \"})\n)\n\n# Lorem ipsum...\n# ...\n# line:0, text='Lorem ipsum dolor sit amet, consectetur adipisicing elit,' || line:2, text='Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris'\n```\nWhen working with \u003ci\u003eplain text\u003c/i\u003e you can pass \u003ci\u003e'header'\u003c/i\u003e and \u003ci\u003e'footer'\u003c/i\u003e as \u003ci\u003ef_write_options\u003c/i\u003e\n\u003cbr\u003eto be prepended or appended to the FileStream output\n```python\n(FileStream(\"path/to/lorem/ipsum\")\n  .map(lambda line: line.strip())\n  .enumerate()\n  .filter(lambda line: line[0] == 3)\n  .map(lambda line: f\"{line[0]}: {line[1]}\")\n  .save(f_open_options={\"mode\": \"a\"}, f_write_options={\"header\": \"\\nHeader\\n\", \"footer\": \"\\nFooter\\n\"})\n )\n\n# Lorem ipsum...\n# ...\n# qui officia deserunt mollit anim id est laborum.\n#\n# Header\n# 3: nisi ut aliquip ex ea commodo consequat.\n# Footer\n#\n```\n\nTo add \u003ci\u003ecustom root\u003c/i\u003e tag when saving an \u003ci\u003e.xml\u003c/i\u003e file pass \u003ci\u003e'xml_root=\"my-custom-root\"'\u003c/i\u003e\n```python\nFileStream(\"path/to/file.json\").concat(in_memory_dict).save(\n    file_path=\"path/to/custom.xml\",\n    f_open_options={\"encoding\": \"utf-8\"},\n    f_write_options={\"indent\": 4},\n    xml_root=\"my-custom-root\",\n)\n```\n\n--------------------------------------------\n### How far can we actually push it?\n```python\n(\n    FileStream(\"path/to/file.csv\")\n    .concat(\n        FileStream(\"path/to/other/file.json\")\n        .filter(\n            lambda x: (\n                Stream(x.value)\n                .find_first(lambda y: y.key == \"name\" and y.value != \"Snake\")\n                .or_else_get(lambda: None)\n            )\n            is not None\n        )\n        .map(lambda x: x.value)\n    )\n    .map(lambda x: (Stream(x).to_dict(lambda y: DictItem(y.key, y.value or \"N/A\"))))\n    .save(\"path/to/third/file.tsv\")\n)\n```\n- ...some leetcode maybe?\n```python\n#  check if given string is palindrome; string length is guaranteed to be \u003e 0\ndef validate_str(string):\n    stop = len(string) // 2 if len(string) \u003e 1 else 1\n    return Stream.from_range(0, stop).none_match(lambda x: string[x] != string[x - 1])\n\nvalidate_str(\"a1b2c3c2b1a\")\nvalidate_str(\"abc321\")\nvalidate_str(\"x\")\n\n# True\n# False\n# True\n```\n- ...and another one?\n```python\n# count vowels and constants in given string\ndef process_str(string):\n    ALL_VOWELS = \"AEIOUaeiou\"\n    return (Stream(string)\n        .filter(lambda ch: ch.isalpha())\n        .partition(lambda ch: ch in ALL_VOWELS)  # Partitions entries into true and false ones\n        .map(lambda p: tuple(p))\n        .enumerate()\n        .map(lambda x: (\"Vowels\" if x[0] == 0 else \"Consonants\", [len(x[1]), x[1]]))\n        .to_dict()\n    )\n\nprocess_str(\"123Ab5oc-E6db#bCi9\u003c\u003e\")\n\n# {'Vowels': [4, ('A', 'o', 'E', 'i')], 'Consonants': [6, ('b', 'c', 'd', 'b', 'b', 'C')]}\n```\n\nHow hideous can it get?\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/kaliv0/pyrio/blob/main/assets/Chubby.jpg?raw=true\" width=\"400\" alt=\"Chubby\"\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaliv0%2Fpyrio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkaliv0%2Fpyrio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaliv0%2Fpyrio/lists"}