# function-pipes
[](https://pypi.org/project/function-pipes/)
[](https://github.com/inkleby/function-pipes/blob/main/LICENSE.md)
[](#install)
Fast, type-hinted python equivalent for R pipes.
This decorator only relies on the standard library, so can just be copied into a project as a single file.
# Why is this needed?
Various languages have versions of a 'pipe' syntax, where a value can be passed through a succession of different functions before returning the final value.
This means you can avoid syntax like the below, where the sequence is hard to read (especially if extra arguments are introduced).
```python
a = c(b(a(value)))
```
In Python, there is not a good built-in way of doing this, and other attempts at a pipe do not play nice with type hinting.
This library has a very simple API, and does the fiddly bits behind the scenes to keep the pipe fast.
## The pipe
There is a `pipe`, function which expects a value and then a list of callables.
```python
from function_pipes import pipe
value = pipe(5, lambda x: x + 2, str)
value == "7"
```
## No special form for extra arguments, small special case for functions that don't return a value
There is no bespoke syntax for passing in extra arguments or moving where the pipe's current value is placed - just use a lambda. This is a well understood approach, that is compatible with type hinting. In the above, `value` will be recognised as a string, but the x is understood as an int.
There is a small bit of bespoke syntax for when you want to pass something through a function, but that function doesn't return the result to the next function. Here the `pipe_bridge` function will wrap another function, pass the function into it, and continue onwards. The following will print `7`, before passing the value on.
```python
from function_pipes import pipe, pipe_bridge
value = pipe(5, lambda x: x + 2, pipe_bridge(print), str)
value == "7"
```
## Merging functions to use later
There is also a `pipeline`, which given a set of functions will return a function which a value can be passed into. Where possible based on other hints, this will hint the input and output variable types.
```python
from function_pipes import pipeline
func = pipeline(lambda x: x + 2, str)
func(5) == "7"
```
## Optimising use of pipes
There's work behind the scenes to minimise the overhead of using the pipe, but it is still adding a function call. If you want the readability of the pipe *and* the speed of the native ugly approach you can use the `@fast_pipes` decorator. This rewrites the function it is called on to expand out the pipe and any lambdas into the fastest native equivalent.
e.g. These two functions should have equivalent AST trees:
```python
@fast_pipes
def function_that_has_a_pipe(v: int) -> str:
value = pipe(v, a, lambda x: b(x, foo="other_input"), c)
return pipe
```
```python
def function_that_has_a_pipe(v: int) -> str:
value = c(b(a(v),foo="other_input"))
return pipe
```
This version of the function is solving three versions of the same puzzle at the same time:
* The type hinting is unpacking the structure when it is being written.
* The pipe function solves the problem in standard python.
* The fast_pipes decorator is rewriting the AST tree to get the same outcome faster.
But to the user, it all looks the same - pipes!
There is a limit of 20 functions that can be passed to a pipe or pipeline. If you *really* want to do more, you could chain multiple pipelines together.
## Install
You can install from pip: `python -m pip install function-pipes`
Or you can copy the module directly into your projects.
* For python 3.10+: [with_paramspec/function_pipes.py](src/function_pipes/with_paramspec/function_pipes.py)
* For python 3.8, 3.9: [without_paramspec/function_pipes.py](src/function_pipes/without_paramspec/function_pipes.py)
## Development
This project comes with a Dockerfile and devcontainer that should get a good environment set up.
The actual code is generated from `src/function_pipes/pipes.jinja-py` using jinja to generate the code and the seperate versions with and without use of paramspec.
Use `make` to regenerate the files. The number of allowed arguments is specified in `Makefile`.
There is a test suite that does checks for equivalence between this syntax and the raw syntax, as well as checking that fast_pipes and other optimisations are faster.
This can be run with `script/test`.
Raw data
{
"_id": null,
"home_page": "https://github.com/ajparsons/function-pipes",
"name": "function-pipes",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8,<4.0",
"maintainer_email": "",
"keywords": "",
"author": "Alex Parsons",
"author_email": "alex@alexparsons.co.uk",
"download_url": "https://files.pythonhosted.org/packages/85/92/8fd20179059fddb16290819ec758b44dad19069cddf121d3a02927d05c6c/function_pipes-0.1.2.tar.gz",
"platform": null,
"description": "# function-pipes\n\n[](https://pypi.org/project/function-pipes/)\n[](https://github.com/inkleby/function-pipes/blob/main/LICENSE.md)\n[](#install)\n\nFast, type-hinted python equivalent for R pipes.\n\nThis decorator only relies on the standard library, so can just be copied into a project as a single file.\n\n# Why is this needed?\n\nVarious languages have versions of a 'pipe' syntax, where a value can be passed through a succession of different functions before returning the final value. \n\nThis means you can avoid syntax like the below, where the sequence is hard to read (especially if extra arguments are introduced).\n\n```python\na = c(b(a(value)))\n```\n\nIn Python, there is not a good built-in way of doing this, and other attempts at a pipe do not play nice with type hinting. \n\nThis library has a very simple API, and does the fiddly bits behind the scenes to keep the pipe fast. \n\n## The pipe\n\nThere is a `pipe`, function which expects a value and then a list of callables.\n\n```python\nfrom function_pipes import pipe\n\nvalue = pipe(5, lambda x: x + 2, str)\nvalue == \"7\"\n\n```\n\n## No special form for extra arguments, small special case for functions that don't return a value\n\nThere is no bespoke syntax for passing in extra arguments or moving where the pipe's current value is placed - just use a lambda. This is a well understood approach, that is compatible with type hinting. In the above, `value` will be recognised as a string, but the x is understood as an int. \n\nThere is a small bit of bespoke syntax for when you want to pass something through a function, but that function doesn't return the result to the next function. Here the `pipe_bridge` function will wrap another function, pass the function into it, and continue onwards. The following will print `7`, before passing the value on. \n\n```python\nfrom function_pipes import pipe, pipe_bridge\n\nvalue = pipe(5, lambda x: x + 2, pipe_bridge(print), str)\nvalue == \"7\"\n\n```\n\n## Merging functions to use later\n\nThere is also a `pipeline`, which given a set of functions will return a function which a value can be passed into. Where possible based on other hints, this will hint the input and output variable types.\n\n```python\nfrom function_pipes import pipeline\n\nfunc = pipeline(lambda x: x + 2, str)\nfunc(5) == \"7\"\n\n```\n\n## Optimising use of pipes\n\nThere's work behind the scenes to minimise the overhead of using the pipe, but it is still adding a function call. If you want the readability of the pipe *and* the speed of the native ugly approach you can use the `@fast_pipes` decorator. This rewrites the function it is called on to expand out the pipe and any lambdas into the fastest native equivalent. \n\ne.g. These two functions should have equivalent AST trees:\n\n```python\n\n@fast_pipes\ndef function_that_has_a_pipe(v: int) -> str:\n value = pipe(v, a, lambda x: b(x, foo=\"other_input\"), c)\n return pipe\n```\n\n```python\ndef function_that_has_a_pipe(v: int) -> str:\n value = c(b(a(v),foo=\"other_input\"))\n return pipe\n```\n\nThis version of the function is solving three versions of the same puzzle at the same time:\n\n* The type hinting is unpacking the structure when it is being written.\n* The pipe function solves the problem in standard python.\n* The fast_pipes decorator is rewriting the AST tree to get the same outcome faster.\n\nBut to the user, it all looks the same - pipes!\n\nThere is a limit of 20 functions that can be passed to a pipe or pipeline. If you *really* want to do more, you could chain multiple pipelines together.\n\n## Install\n\nYou can install from pip: `python -m pip install function-pipes`\n\nOr you can copy the module directly into your projects.\n\n* For python 3.10+: [with_paramspec/function_pipes.py](src/function_pipes/with_paramspec/function_pipes.py)\n* For python 3.8, 3.9: [without_paramspec/function_pipes.py](src/function_pipes/without_paramspec/function_pipes.py)\n\n## Development\n\nThis project comes with a Dockerfile and devcontainer that should get a good environment set up. \n\nThe actual code is generated from `src/function_pipes/pipes.jinja-py` using jinja to generate the code and the seperate versions with and without use of paramspec.\n\nUse `make` to regenerate the files. The number of allowed arguments is specified in `Makefile`.\n\nThere is a test suite that does checks for equivalence between this syntax and the raw syntax, as well as checking that fast_pipes and other optimisations are faster. \n\nThis can be run with `script/test`.",
"bugtrack_url": null,
"license": "MIT",
"summary": "Typed python equivalent for R pipes.",
"version": "0.1.2",
"project_urls": {
"Homepage": "https://github.com/ajparsons/function-pipes",
"Repository": "https://github.com/ajparsons/function-pipes"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4f01f16a54b7c4a4d08570c6d8ad4366422ade40549b114d5fcd2e69656463ce",
"md5": "796b01301ecf1aa84a7b1e2bb817577d",
"sha256": "0c0abde12d02d8b1190e60cdc91c5f36c871f2287477cf299bff258523ccc4bf"
},
"downloads": -1,
"filename": "function_pipes-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "796b01301ecf1aa84a7b1e2bb817577d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8,<4.0",
"size": 19490,
"upload_time": "2022-10-16T18:51:55",
"upload_time_iso_8601": "2022-10-16T18:51:55.911593Z",
"url": "https://files.pythonhosted.org/packages/4f/01/f16a54b7c4a4d08570c6d8ad4366422ade40549b114d5fcd2e69656463ce/function_pipes-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "85928fd20179059fddb16290819ec758b44dad19069cddf121d3a02927d05c6c",
"md5": "b04bc34d63ad6461e5cda3c99562c138",
"sha256": "029b40bae69417f287a6a1a566b05c8ffe2e513f1791882fe8d489cf89f7fce8"
},
"downloads": -1,
"filename": "function_pipes-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "b04bc34d63ad6461e5cda3c99562c138",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8,<4.0",
"size": 16372,
"upload_time": "2022-10-16T18:51:57",
"upload_time_iso_8601": "2022-10-16T18:51:57.780239Z",
"url": "https://files.pythonhosted.org/packages/85/92/8fd20179059fddb16290819ec758b44dad19069cddf121d3a02927d05c6c/function_pipes-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-10-16 18:51:57",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ajparsons",
"github_project": "function-pipes",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "function-pipes"
}