funcdesc


Namefuncdesc JSON
Version 0.1.5.1 PyPI version JSON
download
home_pageNone
SummaryEstablish a general function description protocol, which can realize a comprehensive description of the input, output and side effects of an target function through an Python object. Provide a unified abstraction for parameter checking, interface generation and other functions in applications such as oneFace.
upload_time2024-12-06 06:00:48
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords funcdesc
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <div align="center">
  <h1> funcdesc </h1>

  <p> Establish a general function description protocol, which can realize a comprehensive description of the input, output and side effects of an target function through an Python object. Provide a unified abstraction for parameter checking, interface generation and other functions in applications such as oneFace. </p>

  <p>
    <a href="https://github.com/Nanguage/funcdesc/actions/workflows/build_and_test.yml">
        <img src="https://github.com/Nanguage/funcdesc/actions/workflows/build_and_test.yml/badge.svg" alt="Build Status">
    </a>
    <a href="https://app.codecov.io/gh/Nanguage/funcdesc">
        <img src="https://codecov.io/gh/Nanguage/funcdesc/branch/master/graph/badge.svg" alt="codecov">
    </a>
    <a href="https://pypi.org/project/funcdesc/">
      <img src="https://img.shields.io/pypi/v/funcdesc.svg" alt="Install with PyPi" />
    </a>
    <a href="https://github.com/Nanguage/funcdesc/blob/master/LICENSE">
      <img src="https://img.shields.io/github/license/Nanguage/funcdesc" alt="MIT license" />
    </a>
  </p>
</div>


## Features

* Parse function to get a description object.
* Mark function's inputs and outputs.
* Mark function's side effects.
* Generate checker(guard) for function.
  + Check inputs and outputs's type.
  + Check inputs and outputs's range.
  + Check side-effect.
* Serialization & Deserialization of the description.
  + Convert description object to JSON string.
  + Parse JSON string to get description object.
* Utility functions for edit function's signature.
* Function guard can be used for checking inputs, outputs and side effects.
* **TODO** Support docstring.
  + Parse docstring to get description object.
  + Convert description object to docstring.


## Concept

"Function description" is a Python object that contains descriptive information about a Python function, such as input parameters, output values, and side effects of the function. The description of the inputs and outputs includes their types, range of values, and default values.

The `Description` can be generated by parsing a function annotated with type annotations and decorated with the mark decorator, 
or it can be manually created. After obtaining the `Description`, 
we can use it to generate a  `Guard` object to check the types, ranges, or side effects of the inputs and outputs of the function at runtime. 
Additionally, the information in the `Description` can be used by downstream tools, such as [oneFace](https://github.com/Nanguage/oneFace), to automatically generate interfaces, including CLI, GUI, and WebUI

![concept](docs/images/concept.png)


## Demo

### Create description object

Parse a normal type hinted function:

```Python
# test.py
from funcdesc import parse_func

def add(a: int, b: int = 0) -> int:
    return a + b

desc = parse_func(add)
print(desc)
```

```
$ python test.py
<Description
        inputs=[<Value type=<class 'int'> range=None default=NotDef>, <Value type=<class 'int'> range=None default=0>]
        outputs=[<Value type=<class 'int'> range=None default=NotDef>]
        side_effects=[]
>
```

#### Mark inputs and outputs

`funcdesc` provides two ways to annotate inputs and outputs: 1) using decorators, and 2) using the "Val" object in type hints.
For example:

```Python
from funcdesc import mark_input, mark_output

@mark_input(0, type=int, range=[0, 10])
@mark_input(1, type=int, range=[0, 10])
@mark_output(0, type=int, range=[0, 20])
def add(a, b) -> int:
    return a + b
```

Is same to:

```Python
from funcdesc import Val

def add(a: Val[int, [0, 10]], b: Val[int, [0, 10]]) -> Val[int, [0, 20]]:
    return a + b
```

### Create function guard

The `make_guard` decorator can convert a marked function into a `Guard` object.
You can call the `Guard` just like the original function, and it will check the inputs, outputs,
and side effects of the function based on the marked information.
For example:

``` Python
from funcdesc import mark_input, mark_output, make_guard

@make_guard
@mark_input('a', range=[0, 10])
@mark_input('b', range=[0, 10])
@mark_output(0, name="sum", range=[0, 20])
def add(a: int, b: int) -> int:
    return a + b

print(add(5, 5))  # will print "10"
print(add(20, 20))  # will raise an CheckError
```

```bash
$ python tmp/test.py
10
Traceback (most recent call last):
  File ".\tmp\test2.py", line 11, in <module>
    print(add(20, 20))  # will raise an CheckError
  File "C:\Users\Nangu\Desktop\funcdesc\funcdesc\guard.py", line 46, in __call__
    self._check_inputs(pass_in, errors)
  File "C:\Users\Nangu\Desktop\funcdesc\funcdesc\guard.py", line 58, in _check_inputs
    raise CheckError(errors)
funcdesc.guard.CheckError: [ValueError('Value 20 is not in a valid range([0, 10]).'), ValueError('Value 20 is not in a valid range([0, 10]).')]
```


### Builtin types

`funcdesc` provides some built-in types to facilitate the use of the `guard`.

#### Builtin Value types

`OneOf` and `SubSet`.

```Python
from funcdesc import mark_input, make_guard
from funcdesc.types import SubSet, OneOf


member_list = ["Tom", "Jerry", "Jack"]
food_list = ["apple", "dumpling", "noodles", "banana"]


@make_guard
@mark_input(0, type=OneOf, range=member_list)
@mark_input(1, type=SubSet, range=food_list)
def eat(person, foods):
    print(f"{person} eats {' '.join(foods)}")


eat("Tom", ["apple", "dumpling"])
eat("Jack", ["banana", "noodles"])
eat("Jared", ["apple"])  # "Jared" not in member_list, will raise exception
eat("Tom", ["fish"])  # "fish" not in foods_list, will raise exception
```

`InputPath` and `OutputPath`

```Python
from funcdesc.types import InputPath, OutputPath
from funcdesc import make_guard


@make_guard
def copy_file(in_path: InputPath, out_path: OutputPath):
    with open(in_path) as fi, open(out_path, 'w') as fo:
        fo.write(fi.read())


copy_file("file_exist", "another_file")
copy_file("file_not_exist", "another_file")  # will raise exception
```


### Change function's signature

funcdesc also provides some utility functions for modifying function signature, in order to annotate functions with variable-length parameter types.

Change the parameters signature:

```Python
import inspect
from funcdesc.mark import sign_parameters

@sign_parameters("a", ("b", int), ("c", int, 10))
def f(*args) -> int:
    return sum(args)

# The signature of `f` is changed
sig = inspect.signature(f)
assert len(sig.parameters) == 3
assert sig.parameters["a"].annotation is inspect._empty
assert sig.parameters["b"].annotation is int
assert sig.parameters["c"].default == 10
```

Change the return signature:

```Python
import inspect
from funcdesc.mark import sign_return

@sign_return(str)
def f(a: int):
    return str(a)

# The signature of `f` is changed
sig = inspect.signature(f)
assert sig.return_annotation is str
```

Copy the signature of a function to another function:

```Python
import inspect
from funcdesc.mark import copy_signature

def f(a: int) -> str:
    return str(a)

@copy_signature(f)
def g(b):
    return str(b)

# The signature of `g` is copied from `f`
sig = inspect.signature(g)
assert sig.parameters["a"].annotation is int
assert sig.return_annotation is str
```

Generate a `Signature` object from `Description` object:

```Python
from funcdesc import parse_func

def f(a: int) -> str:
    return str(a)

desc = parse_func(f)
sig = desc.compose_signature()
print(sig) # will print: (a: int) -> str
```

## Related projects

+ [oneFace](https://github.com/Nanguage/oneFace): Generating interfaces(CLI, Qt GUI, Dash web app) from a Python function or a command program.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "funcdesc",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "funcdesc",
    "author": null,
    "author_email": "Weize Xu <vet.xwz@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/b8/e5/abcec8d9ec6b86861ad34310ac67b25504e346650e19299860932b0dab51/funcdesc-0.1.5.1.tar.gz",
    "platform": null,
    "description": "<div align=\"center\">\n  <h1> funcdesc </h1>\n\n  <p> Establish a general function description protocol, which can realize a comprehensive description of the input, output and side effects of an target function through an Python object. Provide a unified abstraction for parameter checking, interface generation and other functions in applications such as oneFace. </p>\n\n  <p>\n    <a href=\"https://github.com/Nanguage/funcdesc/actions/workflows/build_and_test.yml\">\n        <img src=\"https://github.com/Nanguage/funcdesc/actions/workflows/build_and_test.yml/badge.svg\" alt=\"Build Status\">\n    </a>\n    <a href=\"https://app.codecov.io/gh/Nanguage/funcdesc\">\n        <img src=\"https://codecov.io/gh/Nanguage/funcdesc/branch/master/graph/badge.svg\" alt=\"codecov\">\n    </a>\n    <a href=\"https://pypi.org/project/funcdesc/\">\n      <img src=\"https://img.shields.io/pypi/v/funcdesc.svg\" alt=\"Install with PyPi\" />\n    </a>\n    <a href=\"https://github.com/Nanguage/funcdesc/blob/master/LICENSE\">\n      <img src=\"https://img.shields.io/github/license/Nanguage/funcdesc\" alt=\"MIT license\" />\n    </a>\n  </p>\n</div>\n\n\n## Features\n\n* Parse function to get a description object.\n* Mark function's inputs and outputs.\n* Mark function's side effects.\n* Generate checker(guard) for function.\n  + Check inputs and outputs's type.\n  + Check inputs and outputs's range.\n  + Check side-effect.\n* Serialization & Deserialization of the description.\n  + Convert description object to JSON string.\n  + Parse JSON string to get description object.\n* Utility functions for edit function's signature.\n* Function guard can be used for checking inputs, outputs and side effects.\n* **TODO** Support docstring.\n  + Parse docstring to get description object.\n  + Convert description object to docstring.\n\n\n## Concept\n\n\"Function description\" is a Python object that contains descriptive information about a Python function, such as input parameters, output values, and side effects of the function. The description of the inputs and outputs includes their types, range of values, and default values.\n\nThe `Description` can be generated by parsing a function annotated with type annotations and decorated with the mark decorator, \nor it can be manually created. After obtaining the `Description`, \nwe can use it to generate a  `Guard` object to check the types, ranges, or side effects of the inputs and outputs of the function at runtime. \nAdditionally, the information in the `Description` can be used by downstream tools, such as [oneFace](https://github.com/Nanguage/oneFace), to automatically generate interfaces, including CLI, GUI, and WebUI\n\n![concept](docs/images/concept.png)\n\n\n## Demo\n\n### Create description object\n\nParse a normal type hinted function:\n\n```Python\n# test.py\nfrom funcdesc import parse_func\n\ndef add(a: int, b: int = 0) -> int:\n    return a + b\n\ndesc = parse_func(add)\nprint(desc)\n```\n\n```\n$ python test.py\n<Description\n        inputs=[<Value type=<class 'int'> range=None default=NotDef>, <Value type=<class 'int'> range=None default=0>]\n        outputs=[<Value type=<class 'int'> range=None default=NotDef>]\n        side_effects=[]\n>\n```\n\n#### Mark inputs and outputs\n\n`funcdesc` provides two ways to annotate inputs and outputs: 1) using decorators, and 2) using the \"Val\" object in type hints.\nFor example:\n\n```Python\nfrom funcdesc import mark_input, mark_output\n\n@mark_input(0, type=int, range=[0, 10])\n@mark_input(1, type=int, range=[0, 10])\n@mark_output(0, type=int, range=[0, 20])\ndef add(a, b) -> int:\n    return a + b\n```\n\nIs same to:\n\n```Python\nfrom funcdesc import Val\n\ndef add(a: Val[int, [0, 10]], b: Val[int, [0, 10]]) -> Val[int, [0, 20]]:\n    return a + b\n```\n\n### Create function guard\n\nThe `make_guard` decorator can convert a marked function into a `Guard` object.\nYou can call the `Guard` just like the original function, and it will check the inputs, outputs,\nand side effects of the function based on the marked information.\nFor example:\n\n``` Python\nfrom funcdesc import mark_input, mark_output, make_guard\n\n@make_guard\n@mark_input('a', range=[0, 10])\n@mark_input('b', range=[0, 10])\n@mark_output(0, name=\"sum\", range=[0, 20])\ndef add(a: int, b: int) -> int:\n    return a + b\n\nprint(add(5, 5))  # will print \"10\"\nprint(add(20, 20))  # will raise an CheckError\n```\n\n```bash\n$ python tmp/test.py\n10\nTraceback (most recent call last):\n  File \".\\tmp\\test2.py\", line 11, in <module>\n    print(add(20, 20))  # will raise an CheckError\n  File \"C:\\Users\\Nangu\\Desktop\\funcdesc\\funcdesc\\guard.py\", line 46, in __call__\n    self._check_inputs(pass_in, errors)\n  File \"C:\\Users\\Nangu\\Desktop\\funcdesc\\funcdesc\\guard.py\", line 58, in _check_inputs\n    raise CheckError(errors)\nfuncdesc.guard.CheckError: [ValueError('Value 20 is not in a valid range([0, 10]).'), ValueError('Value 20 is not in a valid range([0, 10]).')]\n```\n\n\n### Builtin types\n\n`funcdesc` provides some built-in types to facilitate the use of the `guard`.\n\n#### Builtin Value types\n\n`OneOf` and `SubSet`.\n\n```Python\nfrom funcdesc import mark_input, make_guard\nfrom funcdesc.types import SubSet, OneOf\n\n\nmember_list = [\"Tom\", \"Jerry\", \"Jack\"]\nfood_list = [\"apple\", \"dumpling\", \"noodles\", \"banana\"]\n\n\n@make_guard\n@mark_input(0, type=OneOf, range=member_list)\n@mark_input(1, type=SubSet, range=food_list)\ndef eat(person, foods):\n    print(f\"{person} eats {' '.join(foods)}\")\n\n\neat(\"Tom\", [\"apple\", \"dumpling\"])\neat(\"Jack\", [\"banana\", \"noodles\"])\neat(\"Jared\", [\"apple\"])  # \"Jared\" not in member_list, will raise exception\neat(\"Tom\", [\"fish\"])  # \"fish\" not in foods_list, will raise exception\n```\n\n`InputPath` and `OutputPath`\n\n```Python\nfrom funcdesc.types import InputPath, OutputPath\nfrom funcdesc import make_guard\n\n\n@make_guard\ndef copy_file(in_path: InputPath, out_path: OutputPath):\n    with open(in_path) as fi, open(out_path, 'w') as fo:\n        fo.write(fi.read())\n\n\ncopy_file(\"file_exist\", \"another_file\")\ncopy_file(\"file_not_exist\", \"another_file\")  # will raise exception\n```\n\n\n### Change function's signature\n\nfuncdesc also provides some utility functions for modifying function signature, in order to annotate functions with variable-length parameter types.\n\nChange the parameters signature:\n\n```Python\nimport inspect\nfrom funcdesc.mark import sign_parameters\n\n@sign_parameters(\"a\", (\"b\", int), (\"c\", int, 10))\ndef f(*args) -> int:\n    return sum(args)\n\n# The signature of `f` is changed\nsig = inspect.signature(f)\nassert len(sig.parameters) == 3\nassert sig.parameters[\"a\"].annotation is inspect._empty\nassert sig.parameters[\"b\"].annotation is int\nassert sig.parameters[\"c\"].default == 10\n```\n\nChange the return signature:\n\n```Python\nimport inspect\nfrom funcdesc.mark import sign_return\n\n@sign_return(str)\ndef f(a: int):\n    return str(a)\n\n# The signature of `f` is changed\nsig = inspect.signature(f)\nassert sig.return_annotation is str\n```\n\nCopy the signature of a function to another function:\n\n```Python\nimport inspect\nfrom funcdesc.mark import copy_signature\n\ndef f(a: int) -> str:\n    return str(a)\n\n@copy_signature(f)\ndef g(b):\n    return str(b)\n\n# The signature of `g` is copied from `f`\nsig = inspect.signature(g)\nassert sig.parameters[\"a\"].annotation is int\nassert sig.return_annotation is str\n```\n\nGenerate a `Signature` object from `Description` object:\n\n```Python\nfrom funcdesc import parse_func\n\ndef f(a: int) -> str:\n    return str(a)\n\ndesc = parse_func(f)\nsig = desc.compose_signature()\nprint(sig) # will print: (a: int) -> str\n```\n\n## Related projects\n\n+ [oneFace](https://github.com/Nanguage/oneFace): Generating interfaces(CLI, Qt GUI, Dash web app) from a Python function or a command program.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Establish a general function description protocol, which can realize a comprehensive description of the input, output and side effects of an target function through an Python object. Provide a unified abstraction for parameter checking, interface generation and other functions in applications such as oneFace.",
    "version": "0.1.5.1",
    "project_urls": {
        "Homepage": "https://github.com/Nanguage/funcdesc",
        "Repository": "https://github.com/Nanguage/funcdesc"
    },
    "split_keywords": [
        "funcdesc"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "08e708581995c89f4f57a6aa7b7cd998b055e6d1a0053b8d15ae91563a6d118f",
                "md5": "2812ae421eb7a05b3c83cccdd7f3b1b5",
                "sha256": "8e11ca482f610f337a49475dbc3b26b1521d02c9b889a78e4c4846df20d2873d"
            },
            "downloads": -1,
            "filename": "funcdesc-0.1.5.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2812ae421eb7a05b3c83cccdd7f3b1b5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 17484,
            "upload_time": "2024-12-06T06:00:47",
            "upload_time_iso_8601": "2024-12-06T06:00:47.151805Z",
            "url": "https://files.pythonhosted.org/packages/08/e7/08581995c89f4f57a6aa7b7cd998b055e6d1a0053b8d15ae91563a6d118f/funcdesc-0.1.5.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b8e5abcec8d9ec6b86861ad34310ac67b25504e346650e19299860932b0dab51",
                "md5": "2ef7a2b19d50f05597af022ad72d7287",
                "sha256": "52d2bd51b3af281eb7035621eeacc18ed8821e1e8ecd83adf18c54e405d9c731"
            },
            "downloads": -1,
            "filename": "funcdesc-0.1.5.1.tar.gz",
            "has_sig": false,
            "md5_digest": "2ef7a2b19d50f05597af022ad72d7287",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 16490,
            "upload_time": "2024-12-06T06:00:48",
            "upload_time_iso_8601": "2024-12-06T06:00:48.867158Z",
            "url": "https://files.pythonhosted.org/packages/b8/e5/abcec8d9ec6b86861ad34310ac67b25504e346650e19299860932b0dab51/funcdesc-0.1.5.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-06 06:00:48",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Nanguage",
    "github_project": "funcdesc",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "funcdesc"
}
        
Elapsed time: 0.38414s