makefun-1.9.5/0000775000372000037200000000000013761735350014030 5ustar travistravis00000000000000makefun-1.9.5/docs/0000775000372000037200000000000013761735350014760 5ustar travistravis00000000000000makefun-1.9.5/docs/long_description.md0000664000372000037200000000253313761735245020652 0ustar travistravis00000000000000# makefun [![Python versions](https://img.shields.io/pypi/pyversions/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Build Status](https://travis-ci.com/smarie/python-makefun.svg?branch=master)](https://travis-ci.com/smarie/python-makefun) [![Tests Status](https://smarie.github.io/python-makefun/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-makefun/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-makefun/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-makefun) [![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-makefun/) [![PyPI](https://img.shields.io/pypi/v/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Downloads](https://pepy.tech/badge/makefun)](https://pepy.tech/project/makefun) [![Downloads per week](https://pepy.tech/badge/makefun/week)](https://pepy.tech/project/makefun) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-makefun.svg)](https://github.com/smarie/python-makefun/stargazers) Small library to dynamically create python functions. The documentation for users is available here: [https://smarie.github.io/python-makefun/](https://smarie.github.io/python-makefun/) A readme for developers is available here: [https://github.com/smarie/python-makefun](https://github.com/smarie/python-makefun) makefun-1.9.5/docs/changelog.md0000664000372000037200000003034013761735245017234 0ustar travistravis00000000000000# Changelog ### 1.9.5 - Bugfix with `partial` when f has no args - `partial` can now be used to create a copy of a function with no args. Fixed `ValueError: Cannot preset 0 positional args, function case_second has only 0 args.`. Fixed [#59](https://github.com/smarie/python-makefun/issues/59) ### 1.9.4 - removed six dependency - Removed unused `six` dependency. Note: this version will ship in conda forge. ### 1.9.3 - Minor compatibility fixes with functools.partial - `@wraps` can now be used to wrap a `functools.partial`. Fixed [#57](https://github.com/smarie/python-makefun/issues/57) - `create_function` now correctly reuses information from the partialized function if a `functools.partial` is used as the implementation. - `create_function` now raises a more explicit error when no `func_name` can be found. - `makefun.partial` now generates a signature that is more consistent with `functools.partial`. Fixes [#58](https://github.com/smarie/python-makefun/issues/58) ### 1.9.2 - packaging improvements - packaging improvements: set the "universal wheel" flag to 1, and cleaned up the `setup.py`. In particular removed dependency to `six` for setup and added `py.typed` file, as well as set the `zip_safe` flag to False. Removed tests folder from package. Fixes [#54](https://github.com/smarie/python-makefun/issues/54) ### 1.9.1 - `@compile_fun` bugfix Fixed `OSError: could not get source code` or `IOError: could not get source code` when `@compile_fun` is used on a function that depends on an already-compiled function. Fixed [#51](https://github.com/smarie/python-makefun/issues/51) ### 1.9.0 - `@compile_fun` improvements, bugfix and better exception `@compile_fun`: added capability to disable recursive compilation (`recurse` arg) , and to exclude some names from compilation (`except_names` arg). Fixed [#49](https://github.com/smarie/python-makefun/issues/49) and [#50](https://github.com/smarie/python-makefun/issues/50) Fixed issue `ValueError: Cell is empty` with `@compile_fun`. Fixed [#48](https://github.com/smarie/python-makefun/issues/48) Now raising an `UndefinedSymbolError` when a symbol is not known at compilation time. One step towards [#47](https://github.com/smarie/python-makefun/issues/47) ### 1.8.0 - new `@compile_fun` goodie New goodie `@compile_fun` decorator to `compile` a function so that it can not be navigated to using the debugger. Fixes [#46](https://github.com/smarie/python-makefun/issues/46) ### 1.7.0 - minor goodies update `add_signature_parameters` now accepts that one specifies a custom index where to insert the new parameters. ### 1.6.11 - Added __version__ attribute Added `__version__` attribute to comply with PEP396, following [this guide](https://smarie.github.io/python-getversion/#package-versioning-best-practices). Fixes [#45](https://github.com/smarie/python-makefun/issues/45). ### 1.6.10 - Fixed dependencies 2 Fixed `six` dependency: also declared as a setup dependency. ### 1.6.9 - Fixed dependencies Added missing `six` dependency explicitly. ### 1.6.8 - Improved performance * Improved performance of inner method `get_signature_string` (used by all entry points) after profiling. ### 1.6.7 - Increased tolerance to function signatures in python 2 * In python 2 some libraries such as `attrs` can modify the annotations manually, making `signature` return a string representation that is not compliant with the language version. This raised a `SyntaxError` in previous versions. The new version silently removes all these annotations in python versions that do not support them. Fixes [#39](https://github.com/smarie/python-makefun/issues/39). ### 1.6.6 - Bug fix * Fixed yet another nasty varpositional-related bug :). Fixes [#38](https://github.com/smarie/python-makefun/issues/38). ### 1.6.5 - Bug fix * Fixed `NameError` in case of unknown symbols in type hints. Fixes [#37](https://github.com/smarie/python-makefun/issues/37). ### 1.6.4 - Bug fix and minor improvement * Fixed PEP8 error in source code. Fixes [#35](https://github.com/smarie/python-makefun/issues/35). * Now string signatures can contain a colon. Fixes [#36](https://github.com/smarie/python-makefun/issues/36) ### 1.6.3 - Bug fix with type hints in signature Fixed bug when the return type annotation of the function to create contains non-locally available type hints. Fixes [#33](https://github.com/smarie/python-makefun/issues/33). ### 1.6.2 - Bug fix with type hints in signature Fixed bug when the signature of the function to create contains non-locally available type hints. Fixes [#32](https://github.com/smarie/python-makefun/issues/32). ### 1.6.1 - `with_partial` and `partial` minor bug fix Fixed `partial` to support missing and empty docstring. Fixes [#31](https://github.com/smarie/python-makefun/issues/31). ### 1.6.0 - added `with_partial` and `partial` New method `partial` that behaves like `functools.partial`, and equivalent decorator `@with_partial`. Fixes [#30](https://github.com/smarie/python-makefun/issues/30). ### 1.5.1 - bug fix `add_signature_parameters` now correctly inserts parameters in the right order when they are prepended (using `first=`). Fixed [#29](https://github.com/smarie/python-makefun/issues/29). ### 1.5.0 - Major refactoring and bugfixes **Function creation API:** - renamed all `handler` into `impl` for clarity. Fixes [#27](https://github.com/smarie/python-makefun/issues/27). - renamed `addsource` and `addhandler` arguments as `add_source` and `add_impl` respectively, for consistency - signatures can not be provided as a callable anymore - that was far too confusing. If the reference signature is a callable, then use `@wraps` or `create_wrapper`, because that's probably what you want to do (= reuse not only the signature but also all metadata). Fixes [#26](https://github.com/smarie/python-makefun/issues/26). - the function name is now optional in signatures provided as string. - now setting `__qualname__` attribute - default function name, qualname, doc and module name are the ones from `func_impl` in `create_function` and `@with_signature`, and are the ones from the wrapped function in `create_wrapper` and `@wraps` as intuitively expected. Fixes [#28](https://github.com/smarie/python-makefun/issues/28). **Wrappers:** - `@wraps` and `create_wrapper` now offer a `new_sig` argument. In that case the `__wrapped__` attribute is not set. Fixes [#25](https://github.com/smarie/python-makefun/issues/25). - `@wraps` and `create_wrapper` now correctly preserve the `__dict__` and other metadata from the wrapped item. Fixes [#24](https://github.com/smarie/python-makefun/issues/24) ### 1.4.0 - Non-representable default values are now handled correctly When a non-representable default value was used in the signature to generate, the code failed with a `SyntaxError`. This case is now correctly handled, by storing the corresponding variable in the generated function's context. Fixes [#23](https://github.com/smarie/python-makefun/issues/23). ### 1.3.0 - Aliases for signature-preserving wrapper scenarios - Now providing a `@wraps`, equivalent of `functools.wraps`; and a `create_wrapper` equivalent of `functools.update_wrapper`. Fixes [#21](https://github.com/smarie/python-makefun/issues/21) - `@with_signature` now does not override the `__name__` when signature is provided as a function. Fixes [#22](https://github.com/smarie/python-makefun/issues/22) - `add_signature_parameters` now accepts that parameters are provided as single elements (not necessarily iterables) - Updated documentation ### 1.2.0 - `@with_signature` supports `None` `None` can be used as the desired signature of `@with_signature`. This indicated that the user does not want to create a new function but only wants to update the metadata. Fixes [#20](https://github.com/smarie/python-makefun/issues/20). ### 1.1.2 - Fixes Fixed `isgeneratorfunction` for old python versions, see [decorator#63](https://github.com/micheles/decorator/pull/63). Python<3.3-specific function body is now not loaded at all if not needed. ### 1.1.1 - `@with_signature` fix `inject_as_first_arg` was missing from `@with_signature`, added it. Fixed [#18](https://github.com/smarie/python-makefun/issues/18). ### 1.1.0 - Support for generators and coroutines Now `create_function` and `@with_signature` create the same kind of function than the handler. So if it is a generator, a generator-based coroutine, or an async coroutine, the generated function will adapt. Fixes [#6](https://github.com/smarie/python-makefun/issues/6). ### 1.0.2 - Fixed `@with_signature` Now a string signature can be provided to `@with_signature` without problem. Fixed [#17](https://github.com/smarie/python-makefun/issues/17). ### 1.0.1 - minor: fixed PyPi doc ### 1.0.0 - New parameters, new goodie, and bugfix `@with_signature` : - now exposes all options of `create_function`. Fixed [#12](https://github.com/smarie/python-makefun/issues/12). - now correctly sets the module name by default. Fixes [#13](https://github.com/smarie/python-makefun/issues/13) - now accepts `None` as the new `func_signature` to declare that the signature is identical to the decorated function. This can be handy to just change the docstring or module name of a function for example. Fixes [#15](https://github.com/smarie/python-makefun/issues/15) `create_function` and `@with_signature`: - New `module_name` parameter to override the module name. Fixes [#14](https://github.com/smarie/python-makefun/issues/14) - the handler is now available as a field of the generated function (under `__func_impl__`). New `addhandler` parameter (default: True) controls this behaviour. Fixes [#16](https://github.com/smarie/python-makefun/issues/16) Misc: - New goodie to manipulate signatures: `add_signature_parameters`. - Fixed dependencies for documentation auto-build. ### 0.5.0 - New helper function, and bugfix New helper function `remove_signature_parameters`. Fixed issue with `@with_signature` when argument is a `Signature`. Fixes [#11](https://github.com/smarie/python-makefun/issues/11) ### 0.4.0 - New `@with_signature` decorator, and `create_function` accepts functions New decorator `@with_signature` to change the signature of a callable. Fixes [#3](https://github.com/smarie/python-makefun/issues/3) `create_function` now accepts that a function be passed as a signature template. Fixes [#10](https://github.com/smarie/python-makefun/issues/10) ### 0.3.0 - Ability to generate functions from `Signature` Functions can now be created from a `Signature` object, in addition to string signatures. This unlocks many useful use cases, among easily creating function wrappers. Note: the inner function that provides this feature is `get_signature_from_string`. Fixes [#8](https://github.com/smarie/python-makefun/issues/8) Improved design by getting rid of the regular expression parser to check parameters definition. This assumes that the compiler will correctly raise exceptions when a string signature is not correct, and that `inspect.signature` or `funcsigs.signature` works correctly at detecting all the parameter kinds and annotations on the resulting function. It seems like a fair assumption... Fixes [#9](https://github.com/smarie/python-makefun/issues/9). ### 0.2.0 - Various new features and improvements `create_function`: - `create_function` does not require users to prepend `"def "` to the signature anymore. Fixed [#5](https://github.com/smarie/python-makefun/issues/5) - Return annotations are now supported. Fixes [#4](https://github.com/smarie/python-makefun/issues/4). - Type hint as comments are supported but the generated function loses the annotations because `inspect.signature` loses the annotation too in that case. Fixes [#7](https://github.com/smarie/python-makefun/issues/7) - Variable-length arguments such as `*args` and `**kwargs` are now properly handled. Fixes [#2](https://github.com/smarie/python-makefun/issues/2) - Handler functions can now receive the dynamically created function as first argument, by using `create_function(func_signature, func_handler, inject_as_first_arg=True)`. Fixes [#1](https://github.com/smarie/python-makefun/issues/1) - Renamed `_call_` into `_func_impl_` in the generated code. Misc: - Added `pytest-cases` dependency for tests. ### 0.1.0 - First public version First version created, largely inspired by [`decorator`](https://github.com/micheles/decorator) makefun-1.9.5/docs/mkdocs.yml0000664000372000037200000000102113761735245016760 0ustar travistravis00000000000000site_name: makefun # site_description: 'A short description of my project' repo_url: https://github.com/smarie/python-makefun docs_dir: . site_dir: ../site nav: - Home: index.md # - API reference: api_reference.md - Changelog: changelog.md # - Advanced Usage: advanced_usage.md theme: material # readthedocs mkdocs markdown_extensions: - admonition # to add notes such as http://squidfunk.github.io/mkdocs-material/extensions/admonition/ - codehilite: guess_lang: false - toc: permalink: truemakefun-1.9.5/docs/index.md0000664000372000037200000004734213761735245016426 0ustar travistravis00000000000000# makefun *Dynamically create python functions with a proper signature.* [![Python versions](https://img.shields.io/pypi/pyversions/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Build Status](https://travis-ci.com/smarie/python-makefun.svg?branch=master)](https://travis-ci.com/smarie/python-makefun) [![Tests Status](https://smarie.github.io/python-makefun/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-makefun/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-makefun/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-makefun) [![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-makefun/) [![PyPI](https://img.shields.io/pypi/v/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Downloads](https://pepy.tech/badge/makefun)](https://pepy.tech/project/makefun) [![Downloads per week](https://pepy.tech/badge/makefun/week)](https://pepy.tech/project/makefun) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-makefun.svg)](https://github.com/smarie/python-makefun/stargazers) `makefun` helps you create functions dynamically, with the signature of your choice. It was largely inspired by [`decorator`](https://github.com/micheles/decorator) and `functools`, and created mainly to cover some of their limitations. The typical use cases are: - creating [**signature-preserving function wrappers**](#signature-preserving-function-wrappers) - just like `functools.wraps` but with accurate `TypeError` exception raising when user-provided arguments are wrong, and with a very convenient way to access argument values. - creating **function wrappers that have more or less arguments** that the function they wrap. A bit like `functools.partial` but a lot more flexible and friendly for your users. For example, I use it in [my pytest plugins](https://github.com/smarie/OVERVIEW#tests) to add a `requests` parameter to users' tests or fixtures when they do not already have it. - more generally, creating **functions with a signature derived from a reference signature**, - or even creating **functions with a signature completely defined at runtime**. It currently supports three ways to define the signature of the created function - from a given reference function, e.g. `foo`. - from strings, e.g. `'foo(a, b=1)'` - from `Signature` objects, either manually created, or obtained by using the `inspect.signature` (or its backport `funcsigs.signature`) method. !!! note "creating signature-preserving decorators" Creating decorators and creating signature-preserving function wrappers are two independent problems. `makefun` is solely focused on the second problem. If you wish to solve the first problem you can look at [`decopatch`](https://smarie.github.io/python-decopatch/). It provides a compact syntax, relying on `makefun`, if you wish to tackle both at once. ## Installing ```bash > pip install makefun ``` ## Usage ### 1- Ex-nihilo creation Let's create a function `foo(b, a=0)` implemented by `func_impl`. The easiest way to provide the signature is as a `str`: ```python from makefun import create_function # (1) define the signature. Warning: do not put 'def' keyword here! func_sig = "foo(b, a=0)" # (2) define the function implementation def func_impl(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") return args, kwargs # (3) create the dynamic function gen_func = create_function(func_sig, func_impl) ``` We can test it: ```python >>> args, kwargs = gen_func(2) func_impl called ! >>> assert args == () >>> assert kwargs == {'a': 0, 'b': 2} ``` You can also: * remove the name from the signature string (e.g. `'(b, a=0)'`) to directly use the function name of `func_impl`. * override the function name, docstring, qualname and module name if you pass a non-None `func_name`, `doc`, `qualname` and `module_name` argument * add other attributes on the generated function if you pass additional keyword arguments See `help(create_function)` for details. #### Arguments mapping We can see above that `args` is empty, even if we called `gen_func` with a positional argument. This is completely normal: this is because the created function does not expose `(*args, **kwargs)` but exposes the desired signature `(b, a=0)`. So as for usual python function calls, we lose the information about what was provided as positional and what was provided as keyword. You can try it yourself: write a function `def foo(b, a=0)` and now try to guess from the function body what was provided as positional, and what was provided as keyword... This behaviour is actually a great feature because it makes it much easier to develop the `func_impl`! Indeed, except if your desired signature contains *positional-only* (not yet available as of python 3.7) or *var-positional* (e.g. `*args`) arguments, you will **always** find all named arguments in `**kwargs`. #### More compact syntax You can use the `@with_signature` decorator to perform exactly the same things than `create_function`, but in a more compact way: ```python from makefun import with_signature @with_signature("foo(b, a=0)") def gen_func(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") return args, kwargs ``` It also has the capability to take `None` as a signature, if you just want to update the metadata (`func_name`, `doc`, `qualname`, `module_name`) without creating any function: ```python @with_signature(None, func_name='f') def foo(a): return a assert foo.__name__ == 'f' ``` See `help(with_signature)` for details. #### PEP484 type hints in `str` PEP484 type hints are supported in string function definitions: ```python func_sig = "foo(b: int, a: float = 0) -> str" ``` PEP484 type comments are also supported: ```python func_signature = """ foo(b, # type: int a = 0, # type: float ): # type: (...) -> str """ ``` but unfortunately `inspect.signature` is not able to detect them so the generated function does not contain the annotations. See [this example](https://github.com/smarie/python-makefun/issues/7#issuecomment-459353197). #### Using `Signature` objects `create_function` and `@with_signature` are able to accept a `Signature` object as input, instead of a `str`. That might be more convenient than using strings to programmatically define signatures. For example we can rewrite the above script using `Signature`: ```python from makefun import with_signature from inspect import Signature, Parameter # (1) define the signature using objects. parameters = [Parameter('b', kind=Parameter.POSITIONAL_OR_KEYWORD), Parameter('a', kind=Parameter.POSITIONAL_OR_KEYWORD, default=0), ] func_sig = Signature(parameters) func_name = 'foo' # (2) define the function @with_signature(func_sig, func_name=func_name) def gen_func(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") return args, kwargs ``` Note that `Signature` objects do not contain any function name information. You therefore have to provide an explicit `func_name` argument to `@with_signature` (or to `create_function`) as shown above. !!! note "`Signature` availability in python 2" In python 2 the `inspect` package does not provide any signature-related features, but a complete backport is available: [`funcsigs`](https://github.com/testing-cabal/funcsigs). ### 2- Deriving from existing signatures In many real-world applications we want to reuse "as is", or slightly modify, an existing signature. #### Copying a signature If you just want to expose the same signature as a reference function (and not wrap it nor appear like it), the easiest way to copy the signature from another function `f` is to use `signature(f)` from `inspect`/`funcsigs`. #### Signature-preserving function wrappers [`@functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps) is a famous decorator to create "signature-preserving" function wrappers. However it does not actually preserve the signature, it just uses a trick (setting the `__wrapped__` attribute) to trigger special dedicated behaviour in `stdlib`'s `help()` and `signature()` methods. See [here](https://stackoverflow.com/questions/308999/what-does-functools-wraps-do/55102697#55102697). This has two major limitations: 1. the wrapper code will execute *even when the provided arguments are invalid*. 2. the wrapper code can not easily access an argument using its name, from the received `*args, **kwargs`. Indeed one would have to handle all cases (positional, keyword, default) and therefore to use something like `Signature.bind()`. `makefun` provides a convenient replacement for `@wraps` that fixes these two issues: ```python from makefun import wraps # a dummy function def foo(a, b=1): """ foo doc """ return a + b # our signature-preserving wrapper @wraps(foo) def enhanced_foo(*args, **kwargs): print('hello!') print('b=%s' % kwargs['b']) # we can reliably access 'b' return foo(*args, **kwargs) ``` We can check that the wrapper behaves correctly whatever the call modes: ```python >>> assert enhanced_foo(1, 2) == 3 # positional 'b' hello! b=2 >>> assert enhanced_foo(b=0, a=1) == 1 # keyword 'b' hello! b=0 >>> assert enhanced_foo(1) == 2 # default 'b' hello! b=1 ``` And let's pass wrong arguments to it: we see that the wrapper is **not** executed. ```python >>> enhanced_foo() TypeError: foo() missing 1 required positional argument: 'a' ``` You can try to do the same experiment with `functools.wraps` to see the difference. Finally note that a `create_wrapper` function is also provided for convenience ; it is the equivalent of `@wraps` but as a standard function - not a decorator. !!! note "creating signature-preserving decorators" Creating decorators and creating signature-preserving function wrappers are two independent problems. `makefun` is solely focused on the second problem. If you wish to solve the first problem you can look at [`decopatch`](https://smarie.github.io/python-decopatch/). It provides a compact syntax, relying on `makefun`, if you wish to tackle both at once. #### Editing a signature Below we show how to add a parameter to a function. We first capture its `Signature` using `inspect.signature(f)`, we modify it to add a parameter, and finally we use it in `wraps` to create our final function: ```python from makefun import wraps from inspect import signature, Parameter # (0) the reference function def foo(b, a=0): print("foo called: b=%s, a=%s" % (b, a)) return b, a # (1a) capture the signature of reference function `foo` foo_sig = signature(foo) print("Original Signature: %s" % foo_sig) # (1b) modify the signature to add a new parameter 'z' as first argument params = list(foo_sig.parameters.values()) params.insert(0, Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD)) new_sig = foo_sig.replace(parameters=params) print("New Signature: %s" % new_sig) # (2) define the wrapper implementation @wraps(foo, new_sig=new_sig) def foo_wrapper(z, *args, **kwargs): print("foo_wrapper called ! z=%s" % z) # call the foo function output = foo(*args, **kwargs) # return augmented output return z, output # call it assert foo_wrapper(3, 2) == 3, (2, 0) ``` yields ``` Original Signature: (b, a=0) New Signature: (z, b, a=0) foo_wrapper called ! z=3 foo called: b=2, a=0 ``` This way you can therefore easily create function wrappers with different signatures: not only adding, but also removing parameters, changing their kind (forcing keyword-only for example), etc. The possibilities are as endless as the capabilities of the `Signature` objects. Two helper functions are provided in this toolbox to make it a bit easier for you to edit `Signature` objects: - `remove_signature_parameters` creates a new signature from an existing one by removing all parameters corresponding to the names provided - `add_signature_parameters` prepends the `Parameter`s provided in its `first=` argument, and appends the ones provided in its `last` argument. ```python from makefun import add_signature_parameters, remove_signature_parameters def foo(b, c, a=0): pass # original signature foo_sig = signature(foo) print("original signature: %s" % foo_sig) # let's modify it new_sig = add_signature_parameters(foo_sig, first=Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD), last=Parameter('o', kind=Parameter.POSITIONAL_OR_KEYWORD, default=True) ) new_sig = remove_signature_parameters(new_sig, 'b', 'a') print("modified signature: %s" % new_sig) ``` yields ```bash original signature: (b, c, a=0) modified signature: (z, c, o=True) ``` They might save you a few lines of code if your use-case is not too specific. #### Removing parameters easily As goodies, `makefun` provides a `partial` function that are equivalent to [`functools.partial`](https://docs.python.org/2/library/functools.html#functools.partial), except that it is fully signature-preserving and modifies the documentation with a nice helper message explaining that this is a partial view: ```python def foo(x, y): """ a `foo` function :param x: :param y: :return: """ return x + y from makefun import partial bar = partial(foo, x=12) ``` we can test it: ```python >>> assert bar(1) == 13 >>> help(bar) Help on function bar in module makefun.tests.test_partial_and_macros: bar(y) a `foo` function :param x: :param y: :return: ``` A decorator is also available to create partial views easily for quick tests: ```python @with_partial(x=12) def foo(x, y): """ a `foo` function :param x: :param y: :return: """ return x + y ``` ### 3- Advanced topics #### Generators and Coroutines `create_function` and `@with_signature` will automatically create a generator if your implementation is a generator: ```python # define the implementation def my_generator_impl(b, a=0): for i in range(a, b): yield i * i # create the dynamic function gen_func = create_function("foo(a, b)", my_generator_impl) # verify that the new function is a generator and behaves as such assert isgeneratorfunction(gen_func) assert list(gen_func(1, 4)) == [1, 4, 9] ``` The same goes for generator-based coroutines: ```python # define the impl that should be called def my_gencoroutine_impl(first_msg): second_msg = (yield first_msg) yield second_msg # create the dynamic function gen_func = create_function("foo(first_msg='hello')", my_gencoroutine_impl) # verify that the new func is a generator-based coroutine and behaves correctly cor = gen_func('hi') assert next(cor) == 'hi' assert cor.send('chaps') == 'chaps' cor.send('ola') # raises StopIteration ``` and asyncio coroutines as well ```python # define the impl that should be called async def my_native_coroutine_impl(sleep_time): await sleep(sleep_time) return sleep_time # create the dynamic function gen_func = create_function("foo(sleep_time=2)", my_native_coroutine_impl) # verify that the new function is a native coroutine and behaves correctly from asyncio import get_event_loop out = get_event_loop().run_until_complete(gen_func(5)) assert out == 5 ``` #### Generated source code The generated source code is in the `__source__` field of the generated function: ```python print(gen_func.__source__) ``` prints the following source code: ```python def foo(b, a=0): return _func_impl_(b=b, a=a) ``` The `_func_impl_` symbol represents your implementation. As [already mentioned](#arguments_mapping), you see that the variables are passed to it *as keyword arguments* when possible (`_func_impl_(b=b)`, not simply `_func_impl_(b)`). Of course if it is not possible it adapts: ```python gen_func = create_function("foo(a=0, *args, **kwargs)", func_impl) print(gen_func.__source__) ``` prints the following source code: ```python def foo(a=0, *args, **kwargs): return _func_impl_(a=a, *args, **kwargs) ``` #### Function reference injection In some scenarios you may wish to share the same implementation among several created functions, for example to expose slightly different signatures on top of the same core. In that case you may wish your implementation to know from which dynamically generated function it is being called. For this, simply use `inject_as_first_arg=True`, and the called function will be injected as the first argument: ```python def core_impl(f, *args, **kwargs): print("This is generic core called by %s" % f.__name__) # here you could use f.__name__ in a if statement to determine what to do if f.__name__ == "func1": print("called from func1 !") return args, kwargs # generate 2 functions func1 = create_function("func1(a, b)", core_impl, inject_as_first_arg=True) func2 = create_function("func2(a, d)", core_impl, inject_as_first_arg=True) func1(1, 2) func2(1, 2) ``` yields ``` This is generic core called by func1 called from func1 ! This is generic core called by func2 ``` ### 4. Other goodies #### `@compile_fun` A draft decorator to `compile` any existing function so that users cant debug through it. It can be handy to mask some code from your users for convenience (note that this does not provide any obfuscation, people can still reverse engineer your code easily. Actually the source code even gets copied in the function's `__source__` attribute for convenience): ```python from makefun import compile_fun @compile_fun def foo(a, b): return a + b assert foo(5, -5.0) == 0 print(foo.__source__) ``` yields ``` @compile_fun def foo(a, b): return a + b ``` If the function closure includes functions, they are recursively replaced with compiled versions too (only for this closure, this does not modify them otherwise). You may disable this behaviour entirely with `recurse=False`, or exclude some symbols from this recursion with the `except_names=(...)` arg (a tuple of names to exclude). **IMPORTANT** this decorator is a "goodie" in early stage and has not been extensively tested. Feel free to contribute ! Note that according to [this post](https://stackoverflow.com/a/471227/7262247) compiling does not make the code run any faster. Known issues: `NameError` may appear if your function code depends on symbols that have not yet been defined. Make sure all symbols exist first ! See [this issue](https://github.com/smarie/python-makefun/issues/47). ## Main features / benefits * **Generate functions with a dynamically defined signature**: the signature can be provided as a string or as a `Signature` object, thus making it handy to derive from other functions. * **Implement them easily**: the generated functions redirect their calls to the provided implementation function. As long as the signature is compliant, it will work as expected. For example the signature can be specific (`a: int, b=None`), and the implementation more generic (`*args, **kwargs`). Arguments will always be passed as keywords arguments when possible. * Replace **`@functools.wraps** so that it correctly preserves signatures, and enable you to easily access named arguments. ## See Also - [decorator](https://github.com/micheles/decorator), which largely inspired this code - [PEP362 - Function Signature Object](https://www.python.org/dev/peps/pep-0362) - [A blog entry on dynamic function creation](http://block.arch.ethz.ch/blog/2016/11/creating-functions-dynamically/) - [functools.wraps](https://docs.python.org/3/library/functools.html#functools.wraps) ### Others *Do you like this library ? You might also like [my other python libraries](https://github.com/smarie/OVERVIEW#python)* ## Want to contribute ? Details on the github page: [https://github.com/smarie/python-makefun](https://github.com/smarie/python-makefun) makefun-1.9.5/PKG-INFO0000664000372000037200000000462613761735350015135 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: makefun Version: 1.9.5 Summary: Small library to dynamically create python functions. Home-page: https://github.com/smarie/python-makefun Author: Sylvain MARIE Author-email: sylvain.marie@se.com Maintainer: Sylvain MARIE Maintainer-email: sylvain.marie@se.com License: BSD 3-Clause Download-URL: https://github.com/smarie/python-makefun/tarball/1.9.5 Description: # makefun [![Python versions](https://img.shields.io/pypi/pyversions/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Build Status](https://travis-ci.com/smarie/python-makefun.svg?branch=master)](https://travis-ci.com/smarie/python-makefun) [![Tests Status](https://smarie.github.io/python-makefun/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-makefun/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-makefun/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-makefun) [![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-makefun/) [![PyPI](https://img.shields.io/pypi/v/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Downloads](https://pepy.tech/badge/makefun)](https://pepy.tech/project/makefun) [![Downloads per week](https://pepy.tech/badge/makefun/week)](https://pepy.tech/project/makefun) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-makefun.svg)](https://github.com/smarie/python-makefun/stargazers) Small library to dynamically create python functions. The documentation for users is available here: [https://smarie.github.io/python-makefun/](https://smarie.github.io/python-makefun/) A readme for developers is available here: [https://github.com/smarie/python-makefun](https://github.com/smarie/python-makefun) Keywords: decorate decorator compile make dynamic function generate generation define definition signature args wrapper Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Description-Content-Type: text/markdown makefun-1.9.5/README.md0000664000372000037200000000667013761735245015323 0ustar travistravis00000000000000# python-makefun *Small library to dynamically create python functions.* [![Python versions](https://img.shields.io/pypi/pyversions/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Build Status](https://travis-ci.com/smarie/python-makefun.svg?branch=master)](https://travis-ci.com/smarie/python-makefun) [![Tests Status](https://smarie.github.io/python-makefun/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-makefun/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-makefun/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-makefun) [![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-makefun/) [![PyPI](https://img.shields.io/pypi/v/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Downloads](https://pepy.tech/badge/makefun)](https://pepy.tech/project/makefun) [![Downloads per week](https://pepy.tech/badge/makefun/week)](https://pepy.tech/project/makefun) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-makefun.svg)](https://github.com/smarie/python-makefun/stargazers) **This is the readme for developers.** The documentation for users is available here: [https://smarie.github.io/python-makefun/](https://smarie.github.io/python-makefun/) ## Want to contribute ? Contributions are welcome ! Simply fork this project on github, commit your contributions, and create pull requests. Here is a non-exhaustive list of interesting open topics: [https://github.com/smarie/python-makefun/issues](https://github.com/smarie/python-makefun/issues) ## Running the tests This project uses `pytest`. ```bash pytest -v makefun/tests/ ``` You may need to install requirements for setup beforehand, using ```bash pip install -r ci_tools/requirements-test.txt ``` ## Packaging This project uses `setuptools_scm` to synchronise the version number. Therefore the following command should be used for development snapshots as well as official releases: ```bash python setup.py egg_info bdist_wheel rotate -m.whl -k3 ``` You may need to install requirements for setup beforehand, using ```bash pip install -r ci_tools/requirements-setup.txt ``` ## Generating the documentation page This project uses `mkdocs` to generate its documentation page. Therefore building a local copy of the doc page may be done using: ```bash mkdocs build -f docs/mkdocs.yml ``` You may need to install requirements for doc beforehand, using ```bash pip install -r ci_tools/requirements-doc.txt ``` ## Generating the test reports The following commands generate the html test report and the associated badge. ```bash pytest --junitxml=junit.xml -v makefun/tests/ ant -f ci_tools/generate-junit-html.xml python ci_tools/generate-junit-badge.py ``` ### PyPI Releasing memo This project is now automatically deployed to PyPI when a tag is created. Anyway, for manual deployment we can use: ```bash twine upload dist/* -r pypitest twine upload dist/* ``` ### Merging pull requests with edits - memo Ax explained in github ('get commandline instructions'): ```bash git checkout -b - master git pull https://github.com//python-makefun.git --no-commit --ff-only ``` if the second step does not work, do a normal auto-merge (do not use **rebase**!): ```bash git pull https://github.com//python-makefun.git --no-commit ``` Finally review the changes, possibly perform some modifications, and commit. makefun-1.9.5/makefun.egg-info/0000775000372000037200000000000013761735350017150 5ustar travistravis00000000000000makefun-1.9.5/makefun.egg-info/PKG-INFO0000664000372000037200000000462613761735347020263 0ustar travistravis00000000000000Metadata-Version: 2.1 Name: makefun Version: 1.9.5 Summary: Small library to dynamically create python functions. Home-page: https://github.com/smarie/python-makefun Author: Sylvain MARIE Author-email: sylvain.marie@se.com Maintainer: Sylvain MARIE Maintainer-email: sylvain.marie@se.com License: BSD 3-Clause Download-URL: https://github.com/smarie/python-makefun/tarball/1.9.5 Description: # makefun [![Python versions](https://img.shields.io/pypi/pyversions/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Build Status](https://travis-ci.com/smarie/python-makefun.svg?branch=master)](https://travis-ci.com/smarie/python-makefun) [![Tests Status](https://smarie.github.io/python-makefun/junit/junit-badge.svg?dummy=8484744)](https://smarie.github.io/python-makefun/junit/report.html) [![codecov](https://codecov.io/gh/smarie/python-makefun/branch/master/graph/badge.svg)](https://codecov.io/gh/smarie/python-makefun) [![Documentation](https://img.shields.io/badge/doc-latest-blue.svg)](https://smarie.github.io/python-makefun/) [![PyPI](https://img.shields.io/pypi/v/makefun.svg)](https://pypi.python.org/pypi/makefun/) [![Downloads](https://pepy.tech/badge/makefun)](https://pepy.tech/project/makefun) [![Downloads per week](https://pepy.tech/badge/makefun/week)](https://pepy.tech/project/makefun) [![GitHub stars](https://img.shields.io/github/stars/smarie/python-makefun.svg)](https://github.com/smarie/python-makefun/stargazers) Small library to dynamically create python functions. The documentation for users is available here: [https://smarie.github.io/python-makefun/](https://smarie.github.io/python-makefun/) A readme for developers is available here: [https://github.com/smarie/python-makefun](https://github.com/smarie/python-makefun) Keywords: decorate decorator compile make dynamic function generate generation define definition signature args wrapper Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Description-Content-Type: text/markdown makefun-1.9.5/makefun.egg-info/dependency_links.txt0000664000372000037200000000000113761735347023224 0ustar travistravis00000000000000 makefun-1.9.5/makefun.egg-info/top_level.txt0000664000372000037200000000001013761735347021677 0ustar travistravis00000000000000makefun makefun-1.9.5/makefun.egg-info/not-zip-safe0000664000372000037200000000000113761735347021404 0ustar travistravis00000000000000 makefun-1.9.5/makefun.egg-info/SOURCES.txt0000664000372000037200000000221513761735347021042 0ustar travistravis00000000000000.gitignore .travis.yml LICENSE README.md pyproject.toml setup.cfg setup.py ci_tools/.gitignore ci_tools/.pylintrc ci_tools/Readme-travis.md ci_tools/generate-junit-badge.py ci_tools/generate-junit-html.xml ci_tools/github_release.py ci_tools/github_travis_rsa.enc ci_tools/py_install.py ci_tools/requirements-pip.txt ci_tools/run_tests.sh ci_tools/write_version.py docs/changelog.md docs/index.md docs/long_description.md docs/mkdocs.yml makefun/__init__.py makefun/_main_latest_py.py makefun/_main_legacy_py.py makefun/_version.py makefun/main.py makefun/py.typed makefun.egg-info/PKG-INFO makefun.egg-info/SOURCES.txt makefun.egg-info/dependency_links.txt makefun.egg-info/not-zip-safe makefun.egg-info/requires.txt makefun.egg-info/top_level.txt makefun/tests/__init__.py makefun/tests/_test_py35.py makefun/tests/test_advanced.py makefun/tests/test_compile_deco.py makefun/tests/test_create_from_signature.py makefun/tests/test_create_from_string.py makefun/tests/test_create_from_string_cases.py makefun/tests/test_doc.py makefun/tests/test_generators_coroutines.py makefun/tests/test_issues.py makefun/tests/test_partial_and_macros.py makefun/tests/test_so.pymakefun-1.9.5/makefun.egg-info/requires.txt0000664000372000037200000000004413761735347021554 0ustar travistravis00000000000000 [:python_version < "3.3"] funcsigs makefun-1.9.5/ci_tools/0000775000372000037200000000000013761735350015643 5ustar travistravis00000000000000makefun-1.9.5/ci_tools/github_release.py0000664000372000037200000001316013761735245021203 0ustar travistravis00000000000000# a clone of the ruby example https://gist.github.com/valeriomazzeo/5491aee76f758f7352e2e6611ce87ec1 import os from os import path import re import click from click import Path from github import Github, UnknownObjectException # from valid8 import validate not compliant with python 2.7 @click.command() @click.option('-u', '--user', help='GitHub username') @click.option('-p', '--pwd', help='GitHub password') @click.option('-s', '--secret', help='GitHub access token') @click.option('-r', '--repo-slug', help='Repo slug. i.e.: apple/swift') @click.option('-cf', '--changelog-file', help='Changelog file path') @click.option('-d', '--doc-url', help='Documentation url') @click.option('-df', '--data-file', help='Data file to upload', type=Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True)) @click.argument('tag') def create_or_update_release(user, pwd, secret, repo_slug, changelog_file, doc_url, data_file, tag): """ Creates or updates (TODO) a github release corresponding to git tag . """ # 1- AUTHENTICATION if user is not None and secret is None: # using username and password # validate('user', user, instance_of=str) assert isinstance(user, str) # validate('pwd', pwd, instance_of=str) assert isinstance(pwd, str) g = Github(user, pwd) elif user is None and secret is not None: # or using an access token # validate('secret', secret, instance_of=str) assert isinstance(secret, str) g = Github(secret) else: raise ValueError("You should either provide username/password OR an access token") click.echo("Logged in as {user_name}".format(user_name=g.get_user())) # 2- CHANGELOG VALIDATION regex_pattern = "[\s\S]*[\n][#]+[\s]*(?P[\S ]*%s[\S ]*)[\n]+?(?P<body>[\s\S]*?)[\n]*?(\n#|$)" % re.escape(tag) changelog_section = re.compile(regex_pattern) if changelog_file is not None: # validate('changelog_file', changelog_file, custom=os.path.exists, # help_msg="changelog file should be a valid file path") assert os.path.exists(changelog_file), "changelog file should be a valid file path" with open(changelog_file) as f: contents = f.read() match = changelog_section.match(contents).groupdict() if match is None or len(match) != 2: raise ValueError("Unable to find changelog section matching regexp pattern in changelog file.") else: title = match['title'] message = match['body'] else: title = tag message = '' # append footer if doc url is provided message += "\n\nSee [documentation page](%s) for details." % doc_url # 3- REPOSITORY EXPLORATION # validate('repo_slug', repo_slug, instance_of=str, min_len=1, help_msg="repo_slug should be a non-empty string") assert isinstance(repo_slug, str) and len(repo_slug) > 0, "repo_slug should be a non-empty string" repo = g.get_repo(repo_slug) # -- Is there a tag with that name ? try: tag_ref = repo.get_git_ref("tags/" + tag) except UnknownObjectException: raise ValueError("No tag with name %s exists in repository %s" % (tag, repo.name)) # -- Is there already a release with that tag name ? click.echo("Checking if release %s already exists in repository %s" % (tag, repo.name)) try: release = repo.get_release(tag) if release is not None: raise ValueError("Release %s already exists in repository %s. Please set overwrite to True if you wish to " "update the release (Not yet supported)" % (tag, repo.name)) except UnknownObjectException: # Release does not exist: we can safely create it. click.echo("Creating release %s on repo: %s" % (tag, repo.name)) click.echo("Release title: '%s'" % title) click.echo("Release message:\n--\n%s\n--\n" % message) repo.create_git_release(tag=tag, name=title, message=message, draft=False, prerelease=False) # add the asset file if needed if data_file is not None: release = None while release is None: release = repo.get_release(tag) release.upload_asset(path=data_file, label=path.split(data_file)[1], content_type="application/gzip") # --- Memo --- # release.target_commitish # 'master' # release.tag_name # '0.5.0' # release.title # 'First public release' # release.body # markdown body # release.draft # False # release.prerelease # False # # # release.author # release.created_at # datetime.datetime(2018, 11, 9, 17, 49, 56) # release.published_at # datetime.datetime(2018, 11, 9, 20, 11, 10) # release.last_modified # None # # # release.id # 13928525 # release.etag # 'W/"dfab7a13086d1b44fe290d5d04125124"' # release.url # 'https://api.github.com/repos/smarie/python-makefun/releases/13928525' # release.html_url # 'https://github.com/smarie/python-makefun/releases/tag/0.5.0' # release.tarball_url # 'https://api.github.com/repos/smarie/python-makefun/tarball/0.5.0' # release.zipball_url # 'https://api.github.com/repos/smarie/python-makefun/zipball/0.5.0' # release.upload_url # 'https://uploads.github.com/repos/smarie/python-makefun/releases/13928525/assets{?name,label}' if __name__ == '__main__': create_or_update_release() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/Readme-travis.md�������������������������������������������������������������0000664�0003720�0003720�00000012602�13761735245�020674� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This is a reminder on how to grant travis the rights to deploy your site on github pages, python package to pypi and release files to github # PREREQUISITE ## Get access to a linux machine The following does not work on windows as explained [here](https://github.com/travis-ci/travis-ci/issues/4746) Note: if you get tlsV1 errors below, create_function sure that you have the latest OpenSSL. If it is not available from you package manager, look at this [compilation sequance](https://github.com/curl/curl/issues/1583#issuecomment-309477196) (replace_fixture all versions with the ones you need / the latest). No need to use nghttp2. Dont forget to add `--with-libssh2` to curl compilation step, as mentioned in the [script](https://github.com/dertin/lemp-stack-debian/blob/develop/install.sh)! ## Install travis commandline You have to be outside of the proxy for everything to work correctly, otherwise you will get strange errors mentioning ipaddr... either here or later in the process. **1- Install ruby** using RVM : (**DO NOT USE su NOR sudo**) ```bash > \curl -sSL https://get.rvm.io | bash -s stable --ruby > source /home/ubuntu/.rvm/scripts/rvm > rvm install 2.5.1 (this installs to /home/ubuntu/.rvm/src/ruby...) ``` *Note: if you already have an old version of rvm you can update it to see the latest ruby versions*: ```bash > rvm get master > rvm list known ``` *Note: if at some point there is an openssl issue inside ruby it is possible to either create_function it use the path you like or have it use its own version as explained [here](https://stackoverflow.com/questions/15511943/troubles-with-rvm-and-openssl)*: Either ```bash > rvm install 2.5.1 --with-openssl-dir=/usr/local/ssl or (does not work) /usr/bin/openssl ``` or ```bash > rvm pkg install openssl > rvm install 2.5.1 --with-openssl-dir=$HOME/.rvm/usr ``` **2- install travis commandline** by following [these instructions](https://github.com/travis-ci/travis.rb#installation): ```bash > gem install travis -v 1.8.8 --no-rdoc --no-ri ``` source: * http://railsapps.github.io/installrubyonrails-ubuntu.html * http://sirupsen.com/get-started-right-with-rvm/ ## Optional: setup a shared folder between your development machine and the linux machine If possible the shared folder should be the git folder, so that travis automatically detects the git project. # Generating the access keys for travis ## To deploy a site on gh-pages using `mkdocs gh-deploy` (or for any `git push` operation) Generate an asymetric security key (public + private): * On windows: open git bash (not windows cmd) * Execute the following but **DO NOT provide any passphrase when prompted (simply press <enter>)** ```bash ssh-keygen -t rsa -b 4096 -C "<your_github_email_address>" -f ci_tools/github_travis_rsa ``` On the github repository page, `Settings > Deploy Keys > Add deploy key > add` the PUBLIC generated key (the file `ci_tools/github_travis_rsa.pub`) with write access Use travis CLI to encrypt your PRIVATE key: ```bash > cd to the shared folder (/media/...) > source /home/ubuntu/.rvm/scripts/rvm > travis login > travis encrypt-file -r <git-username>/<repo-name> ci_tools/github_travis_rsa (DO NOT USE --add option since it will remove all comments in your travis.yml file!) ``` Follow the instructions on screen : - copy the line starting with `openssl ...` to your `travis.yml` file. - modify the relative path to the generated file by adding 'ci_tools/' in front of 'github_travis_rsa_...enc'. - git add the generated file 'github_travis_rsa_...enc' but DO NOT ADD the private key Note: if you find bug 'no implicit conversion of nil intro String' as mentioned [here](https://github.com/travis-ci/travis.rb/issues/190#issuecomment-377823703), [here](https://github.com/travis-ci/travis.rb/issues/585#issuecomment-374307229) and especially [here](https://github.com/travis-ci/travis.rb/issues/586) it can either be a network proxy error (check that http_proxy is not set...) or a ruby/travis cli version issue. Or worse: an openssl version issue (you check check with wireshark). Best is to reinstall at least the gems: `rvm gemset empty` and then `gem install travis ...` (see above). Note that reinstalling ruby takes a *lot* more time than reinstalling the gems :). source: * https://djw8605.github.io/2017/02/08/deploying-docs-on-github-with-travisci/ (rejecting https://docs.travis-ci.com/user/deployment/pages/ as this would grant full access to travis) * https://docs.travis-ci.com/user/encrypting-files/ * https://gist.github.com/domenic/ec8b0fc8ab45f39403dd ## To deploy python wheels on PyPi Similar procedure to encrypt the PyPi password for deployments: ```bash > (cd, source, travis login) > travis encrypt -r <git-username>/<repo-name> <pypi_password> ``` Copy the resulting string in the `travis.yml` file under deploy > provider: pypi > password > secure source: https://docs.travis-ci.com/user/deployment/pypi/ ## To deploy file releases on github Similar procedure to encrypt the OAuth password for github releases. **WARNING** unlike 'travis encrypt', this WILL modify your `travis.yml` file. Therefore you should create_function a backup of it beforehand, and then execute this command with the '--force' option. ```bash > (cd, source, travis login) > travis login > travis setup releases ``` Copy the string in the `travis.yml` file under deploy > provider: releases > api-key > secure source: https://docs.travis-ci.com/user/deployment/releases/������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/run_tests.sh�����������������������������������������������������������������0000664�0003720�0003720�00000001563�13761735347�020240� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env bash cleanup() { rv=$? # on exit code 1 this is normal (some tests failed), do not stop the build if [ "$rv" = "1" ]; then exit 0 else exit $rv fi } trap "cleanup" INT TERM EXIT #if hash pytest 2>/dev/null; then # echo "pytest found" #else # echo "pytest not found. Trying py.test" #fi # First the raw for coverage echo -e "\n\n****** Running tests ******\n\n" if [ "${TRAVIS_PYTHON_VERSION}" = "3.5" ]; then # full coverage run --source makefun -m pytest --junitxml=reports/junit/junit.xml --html=reports/junit/report.html -v makefun/tests/ # python -m pytest --junitxml=reports/junit/junit.xml --html=reports/junit/report.html --cov-report term-missing --cov=./makefun -v makefun/tests/ else # faster - skip coverage and html report python -m pytest --junitxml=reports/junit/junit.xml -v makefun/tests/ fi���������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/.pylintrc��������������������������������������������������������������������0000664�0003720�0003720�00000042003�13761735245�017512� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). # init-hook="import makefun" # Add files or directories to the blacklist. They should be base names, not # paths. ignore= # Pickle collected data for later comparisons. persistent=no # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. # DO NOT CHANGE THIS VALUES >1 HIDE RESULTS!!!!! jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. See also the "--disable" option for examples. disable=all enable=import-error, import-self, reimported, wildcard-import, misplaced-future, relative-import, deprecated-module, unpacking-non-sequence, invalid-all-object, undefined-all-variable, used-before-assignment, cell-var-from-loop, global-variable-undefined, redefined-builtin, redefine-in-handler, unused-import, unused-wildcard-import, global-variable-not-assigned, undefined-loop-variable, global-statement, global-at-module-level, bad-open-mode, redundant-unittest-assert, boolean-datetime, # Has common issues with our style due to # https://github.com/PyCQA/pylint/issues/210 unused-variable # Things we'd like to enable someday: # redefined-outer-name (requires a bunch of work to clean up our code first) # undefined-variable (re-enable when pylint fixes https://github.com/PyCQA/pylint/issues/760) # no-name-in-module (giving us spurious warnings https://github.com/PyCQA/pylint/issues/73) # unused-argument (need to clean up or code a lot, e.g. prefix unused_?) # Things we'd like to try. # Procedure: # 1. Enable a bunch. # 2. See if there's spurious ones; if so disable. # 3. Record above. # 4. Remove from this list. # deprecated-method, # anomalous-unicode-escape-in-string, # anomalous-backslash-in-string, # not-in-loop, # function-redefined, # continue-in-finally, # abstract-class-instantiated, # star-needs-assignment-target, # duplicate-argument-name, # return-in-init, # too-many-star-expressions, # nonlocal-and-global, # return-outside-function, # return-arg-in-generator, # invalid-star-assignment-target, # bad-reversed-sequence, # nonexistent-operator, # yield-outside-function, # init-is-generator, # nonlocal-without-binding, # lost-exception, # assert-on-tuple, # dangerous-default-value, # duplicate-key, # useless-else-on-loop, # expression-not-assigned, # confusing-with-statement, # unnecessary-lambda, # pointless-statement, # pointless-string-statement, # unnecessary-pass, # unreachable, # eval-used, # exec-used, # bad-builtin, # using-constant-test, # deprecated-lambda, # bad-super-call, # missing-super-argument, # slots-on-old-class, # super-on-old-class, # property-on-old-class, # not-an-iterable, # not-a-mapping, # format-needs-mapping, # truncated-format-string, # missing-format-string-key, # mixed-format-string, # too-few-format-args, # bad-str-strip-call, # too-many-format-args, # bad-format-character, # format-combined-specification, # bad-format-string-key, # bad-format-string, # missing-format-attribute, # missing-format-argument-key, # unused-format-string-argument, # unused-format-string-key, # invalid-format-index, # bad-indentation, # mixed-indentation, # unnecessary-semicolon, # lowercase-l-suffix, # fixme, # invalid-encoded-data, # unpacking-in-except, # import-star-module-level, # parameter-unpacking, # long-suffix, # old-octal-literal, # old-ne-operator, # backtick, # old-raise-syntax, # print-statement, # metaclass-assignment, # next-method-called, # dict-iter-method, # dict-view-method, # indexing-exception, # raising-string, # standarderror-builtin, # using-cmp-argument, # cmp-method, # coerce-method, # delslice-method, # getslice-method, # hex-method, # nonzero-method, # oct-method, # setslice-method, # apply-builtin, # basestring-builtin, # buffer-builtin, # cmp-builtin, # coerce-builtin, # old-division, # execfile-builtin, # file-builtin, # filter-builtin-not-iterating, # no-absolute-import, # input-builtin, # intern-builtin, # long-builtin, # map-builtin-not-iterating, # range-builtin-not-iterating, # raw_input-builtin, # reduce-builtin, # reload-builtin, # round-builtin, # unichr-builtin, # unicode-builtin, # xrange-builtin, # zip-builtin-not-iterating, # logging-format-truncated, # logging-too-few-args, # logging-too-many-args, # logging-unsupported-format, # logging-not-lazy, # logging-format-interpolation, # invalid-unary-operand-type, # unsupported-binary-operation, # no-member, # not-callable, # redundant-keyword-arg, # assignment-from-no-return, # assignment-from-none, # not-context-manager, # repeated-keyword, # missing-kwoa, # no-value-for-parameter, # invalid-sequence-index, # invalid-slice-index, # too-many-function-args, # unexpected-keyword-arg, # unsupported-membership-test, # unsubscriptable-object, # access-member-before-definition, # method-hidden, # assigning-non-slot, # duplicate-bases, # inconsistent-mro, # inherit-non-class, # invalid-slots, # invalid-slots-object, # no-method-argument, # no-self-argument, # unexpected-special-method-signature, # non-iterator-returned, # protected-access, # arguments-differ, # attribute-defined-outside-init, # no-init, # abstract-method, # signature-differs, # bad-staticmethod-argument, # non-parent-init-called, # super-init-not-called, # bad-except-order, # catching-non-exception, # bad-exception-context, # notimplemented-raised, # raising-bad-type, # raising-non-exception, # misplaced-bare-raise, # duplicate-except, # broad-except, # nonstandard-exception, # binary-op-exception, # bare-except, # not-async-context-manager, # yield-inside-async-function, # ... [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=parseable # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [FORMAT] # Maximum number of characters on a single line. max-line-length=100 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )?<?https?://\S+>?$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). This supports can work # with qualified names. ignored-classes= # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=^_|^dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [BASIC] # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,input # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/.gitignore�������������������������������������������������������������������0000664�0003720�0003720�00000000076�13761735245�017641� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# private key github_travis_rsa* # coverage results .coverage������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/write_version.py�������������������������������������������������������������0000664�0003720�0003720�00000000667�13761735245�021130� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Authors: Sylvain Marie <sylvain.marie@se.com> # # License: BSD 3 clause from os.path import abspath import click from setuptools_scm import get_version @click.command() @click.argument('dest_folder') def write_version(dest_folder): file_name = '%s/_version.py' % dest_folder print("Writing version to file: %s" % abspath(file_name)) get_version('.', write_to=file_name) if __name__ == '__main__': write_version() �������������������������������������������������������������������������makefun-1.9.5/ci_tools/generate-junit-badge.py������������������������������������������������������0000664�0003720�0003720�00000005734�13761735245�022212� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys try: # python 3 from urllib.parse import quote_plus except ImportError: # python 2 from urllib import quote_plus import requests import shutil from os import makedirs, path import xunitparser class TestStats(object): def __init__(self, success_percentage, success, runned, skipped): self.success_percentage = success_percentage self.success = success self.runned = runned self.skipped = skipped def get_test_stats(junit_xml='reports/junit/junit.xml' # type: str ): # type: (...) -> TestStats """ read the junit test file and extract the success percentage :param junit_xml: the junit xml file path :return: the success percentage (an int) """ ts, tr = xunitparser.parse(open(junit_xml)) skipped = len(tr.skipped) runned = tr.testsRun - skipped failed = len(tr.failures) success = runned - failed success_percentage = round(success * 100 / runned) return TestStats(success_percentage, success, runned, skipped) def download_badge(test_stats, # type: TestStats dest_folder='reports/junit' # type: str ): """ Downloads the badge corresponding to the provided success percentage, from https://img.shields.io. :param test_stats: :param dest_folder: :return: """ if not path.exists(dest_folder): makedirs(dest_folder) # , exist_ok=True) not python 2 compliant if test_stats.success_percentage < 50: color = 'red' elif test_stats.success_percentage < 75: color = 'orange' elif test_stats.success_percentage < 90: color = 'green' else: color = 'brightgreen' left_txt = "tests" # right_txt = "%s%%" % test_stats.success_percentage right_txt = "%s/%s" % (test_stats.success, test_stats.runned) url = 'https://img.shields.io/badge/%s-%s-%s.svg' % (left_txt, quote_plus(right_txt), color) dest_file = path.join(dest_folder, 'junit-badge.svg') print('Generating junit badge from : ' + url) response = requests.get(url, stream=True) with open(dest_file, 'wb') as out_file: response.raw.decode_content = True shutil.copyfileobj(response.raw, out_file) del response if __name__ == "__main__": # Execute only if run as a script. # Check the arguments assert len(sys.argv[1:]) == 1, "a single mandatory argument is required: <threshold>" threshold = float(sys.argv[1]) # First retrieve the success percentage from the junit xml test_stats = get_test_stats() # Validate against the threshold print("Success percentage is %s%%. Checking that it is >= %s" % (test_stats.success_percentage, threshold)) if test_stats.success_percentage < threshold: raise Exception("Success percentage %s%% is strictly lower than required threshold %s%%" "" % (test_stats.success_percentage, threshold)) # Download the badge download_badge(test_stats) ������������������������������������makefun-1.9.5/ci_tools/generate-junit-html.xml������������������������������������������������������0000664�0003720�0003720�00000000661�13761735245�022256� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <project name="MXUnitTask" basedir="." default="generate-report"> <mkdir dir="../reports/junit"/> <target name="generate-report" description="Generates junit report"> <junitreport todir="../reports/junit"> <fileset dir=".."> <include name="junit.xml"/> </fileset> <report todir="../reports/junit"/> </junitreport> </target> </project>�������������������������������������������������������������������������������makefun-1.9.5/ci_tools/requirements-pip.txt���������������������������������������������������������0000664�0003720�0003720�00000001035�13761735245�021717� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# --- to execute setup.py whatever the goal setuptools_scm pytest-runner # --- to run the tests pytest #$PYTEST_VERSION pytest-logging # ==2015.11.4 pytest-cases>=1.2.0 # --- to generate the reports (see scripts in ci_tools, called by .travis) # pytest-cov==2.6.0 # after 2.6.1 it requires pytest 3.6 pytest-html==1.9.0 # otherwise requires pytest 5 xunitparser # --- to generate the doc (see .travis) # mkdocs-material #==3.3.0 # mkdocs # ==1.0.4 # this is to prevent a version non-compliant with mkdocs-material to be installed. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/py_install.py����������������������������������������������������������������0000664�0003720�0003720�00000004371�13761735245�020403� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" equivalent of pip install -r <file> - with environment variables replacement - and all dependencies are installed in one 'pip install' call (solving potential complex deps) """ import os import re import sys import subprocess def check_cmd(cmd): assert isinstance(cmd, str), "cmd should be a string" assert cmd in {"pip", "conda"}, "cmd should be conda or pip. Unknown: " + str(cmd) def install(cmd, packages): """ Installs all packages provided at once :param packages: :return: """ check_cmd(cmd) all_pkgs_str = " ".join(all_pkgs) print("INSTALLING: " + cmd + " install " + all_pkgs_str) subprocess.check_call([cmd, 'install'] + packages) # install pkg env_var_regexp = re.compile(".*\$(\S+).*") if __name__ == '__main__': assert len(sys.argv[1:]) >= 2, "at least two mandatory arguments are required: <cmd> <filename>" cmd = sys.argv[1] check_cmd(cmd) filenames = sys.argv[2:] all_pkgs = [] for filename in filenames: with open(filename) as f: for line in f.readlines(): # First remove any comment on that line splitted = line.split('#', 1) # (maxsplit=1) but python 2 does not support it :) splitted = splitted[0].strip().rstrip() if splitted != '': # the replace env vars env_var_found=True while env_var_found: res = env_var_regexp.match(splitted) env_var_found = res is not None if env_var_found: env_var_name = res.groups()[0] try: env_var_val = os.environ[env_var_name] print("replacing $%s with %s" % (env_var_name, env_var_val)) splitted = splitted.replace("$%s" % env_var_name, env_var_val) except KeyError: raise Exception("Environment variable does not exist in file %s: $%s" "" % (filename, env_var_name)) else: all_pkgs.append(splitted) install(cmd, all_pkgs) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/ci_tools/github_travis_rsa.enc��������������������������������������������������������0000664�0003720�0003720�00000006540�13761735245�022061� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‡QĒcŸäcōöŠŁDć‰TŚ=%«ģƒƒĀY ŁĀó*Œf"a‡ænćp>”#Sł3aŗW“»Owž~’Ä-vœ_š,é%Ķźś\ģZO@ĄLĪ‚­½čüG}›ŻĮ‰Q§s$ńŸŗŌ—SdLaÖH–(äוO “)[p6nq–į(’Œ§†pP—2‰äZ ćN Ę9`­ēķ!€ÜQLJYŪ>qT§]Ud|t“ÆĻŃFҵ²&:7¶øÅ]Gfr™ŽµG/`įŝ< ‘mā=²7^8Ģ|ƒX­’.ßńūPkr}ś{&ĢļżfJˆž®n™’ŲóC„v'Gżu®sŠŹŗ§æ©óŒa9ō žŁ;e8Fœ¬6’ <¾_R"Į€ŹXJėķĘ` ›Õl&…ŗąrŁļ†É<īF²y5³(2ĻŻ*zµÓĀĶöĶ)Ķ?s­}JnE8S”'³¢¢Z26é¼ōc@ć2¶&—OL×ČT?^°ščŪŚfƒX1éęę{Ž`^øĒ3 TƒÓż¶Ž D„u”¶ĶēÜLĪŻĮJ»Zˆ©0ü¼{ ŁŖ5”“Ž\Ž!½ye4†;įŲ;d¦µą›:JŅįī2õÜĄJÖ8ރĒó)kŠ)Œ‹YC2×C"˜Tń5›¤œ ¼&ƚ|pž ‡US4’ŽŻL4Ā„,Ü?ę>†šmĪ–žģŽfƂdY6’ wi‹…] ŁCē |ó%`—ĻāÓȤŠž¬”Éƒsüj‚šs½e0b•uפż^ē˜åw†D…÷?^üN+Ŗ;R(ńo‡ €ē-³Ø=ųŪaį°`ś†Gts‡ĘП®pŠ’•žņ×Ö¦š#Š›Łī8Žę$ņžĆÅm[n¼G²1Čē.é0(ź\7ś‹Čž‰£„ėöÓLŽį„‡ įwhi2'eõÕĄ®PÖD•EüŪ¦rY¬÷ļĮöœåėÜ0#%,ßģ”ź) ø’}ćė’“œœ³€ÆjµMqwŚM^YLTwóʛwKsq–ņ"6‰ķĶJłī0d¬ņ£*Ē’¾„o„>‚c Øü®ģEŚFA}„”„Ńi #°œ”łr*óĈ¢¾4ß<²ˆēšļ„Š£LvĘ`R °Ā¢W¢ė,MĶ;“źģ/1&·Šį‘ÕŽ~QߦdPƘÜĆ[J Ø0©ńUhŚ3JE ČĪVńēāLżĢ]‰�.ž‡@Gj ¢•óĶā‚Ż°·dźÓI»#Oƅõ*Ū–ķZßŽŽš0 ÉŲĀq5~=ēåpXq=Ø{Ža·Qņ~čLį’Kf77mŒčņČĪĪ"Ī“ØėĻå×3x²Ć{ŗ#iQ€ź\Õ=%¾Żn]¶”ĘF€ÆBŗN FW)ßL”lwi—ąŒāNRCźåēA³$½ĀĶ6¬õ9 g{.«żHä T’§Ó«»‡QYŅcNaz-ohoĘfµ'˜' yC–Œ[Nŗ‘D—3ż Ł=ŻźīW,NöŁĀq*m*ęc _Ū/<käD^ QN|[}żĀž.‰ÆeēŁ•lü>V“lÓ kæJĘPŗ¶^v†*ģĻē3�ˆ3  Bp[™CXß6?\ł©Ļg—-ĒJµ$ČZK™5ČäŠöxŃ·Lµ":IøõēŖśŽ\“›ÆrhGß9˧r ?捂`…“}) *ØņŌÖ/ŃīĪXmÄāįA$p^Ś×a5öźņG‘‘¦_Ø<ūé/Ŗā×F{: `:ęUę}Ž+÷ō½zammÕ~r`'„M hśoLļ¤&kk !ĒGPQs\[AŸyĶ—DT:̒҆­i“sõ ź|ųRōēŠƒRCž ĆN| 94ų]§å²^&žOЬšĮ`\ŅŚ$a7^XĪɆ¶GQQÕ]ģ@āĶ;L=ß0ā§ņ*Ł'|ĢŻŃSĒ}˜¶%OZj5x’…?Å%µ†€"æ%Éü¬°‡»­ĖnĆĄµŗ M§ój‚{é@ Ż’r/GNń™Šõö eŲ’—×­<§Ę®=L=ÕšW[ŗLՎ;ŲRˆECd”āžTŽAįcæWłÆ®_‹/ŚcD$4>¶ć„ß[E‚>ØČ ėųn•Ä\źäčO|`1Ļ×§ŅŽDA<’øš’Up2?HzœMŠņ¦;°b{Ÿ³Y¬æl�uf߂ꢻ¦Jźõā ?ŠĶ½ÅšĄśEĮ ±m9#Œ¦VÆaBذ6ʱN±r]ć¬ķѝ8}|5ŒsīO «‹vƒrݘ«ęÕT×7æ%Bxą4iŖ!?‡_—@|³é–@šģ“iĘ~żN^žšÜ^¼›å<@Ļņ|!ĢY÷¤(äq gHb·Łó?kbxż¹_rc¼Ļ®Ž ‚iÕā@D)’)ųƒäÜĶśĢŚQķŚz@ėfą“į /ߖ6< ļ±»Ö>a‡VÄ39n¤ do ©}č“āžŹ…&Ąż-ķъ‡”I§Eq¢ eD1ęž3s^ć«/Guõė¬źqeņޱ…rFĒQ9µƒ`)Ž™Ąāāš®Sf˜^ŁĖCżģq\«ÅžšĀ•Ł8ÆZ“vńj ž±‡„ĖB=/†_€(- 9ūĶI§=¾ŃŒ’ŗĆ.Ég®6÷Tq’MƒA¾˜Ģ€ÕV6Ņš­”ķš§�Ż­Ēa]Ģa/"=Xīzœ 2OMŸböŦöŚ­U殎ntV;—‡˜–§Ōō<‹ŻˆFń‹ēų=c?t¦äKĄuCžr槑³öŪ‡õõ53׏E”cŠ-4mēJÕ2ż"ņų9ķe‡v¬Čļg\æ#©"7ŪtŠ4 ˆE=ĄDĶl~‡ćūź­9µöe>fqdq·uŁ`k®O#łø·­ū p4Ń:2ˆ1S®÷ćą0JŌT׋Ø[šš)•U dų£Ģ'1CĒ®q餙Qōe³e³UAČp#¼‘ Š„”|‰Ö™Z\¼ņ5!_Åąš¤÷ˆõ¼žMĪŗĀZµ Ūčk© øQJ”P§¬ģ^֘ƹ”}'‘¾ÉńiūŲčó&¦Téi„ģÉõ1{66ęÉZčŻA–,¤§źXaYN_)ø@Ÿ±ĒŌĖ 2xjDßjÆ|#ä^…ŻšŪ·×/Ē%c>˜hK“ų[Bšż½8w*©˜õĶĪ-Ӛ7wūÜ՚U–lồ²,_qŪM)"™³Uż²’Z“5¬ÉŃ%Ž [z$#r‹#ŖX$±ć­]ϱĪ˃ ʈWÕR@N©v|LIĮóŪ‘õø"¹¹§˜ ± ?ó\ē$¢`ŻF!ņśsĮV2©ählr¶Ą6TāÄbM¾Ņ3Ņ;Ķ Õ Bß~�h1Aœ“kō¼ZēŖå{T|#ęĶ Ā=C\m–É$|¬ļüFņo¼CŹX ²ØˆDüÆ©8÷Ŗu7äx1-y:¦ŌįĶKYưēĖļŹ«°īUźŠĄQÆhJ9ģ{%­ƒUõ¤ŒĶąŖÅ:0¦łųż'ČG“¤t‘œƒ¶fš.ļāŗ®ĄKĶVĀUE¦XŻĪFŗ�Xž‰›ŖĒ?±£mōž^žŅ'{»µ6»ÉžCŃEėŌy³V6āĆfNō LČU’ƒšŽ/M `^_WƗŲać»hė€�ĘĻč »Kī¬÷Ęn„UŒŒAj­dźø-Lu:Ī÷l(Eß 2ķ ŲĪä’šnĮś}·€eŚU.3ā&.éā e£ĒŠhdž$Ÿ]’ŗÉ“^‹„ŗsŲßźιĻ:e—uŌ|zo^¦-McÕŌš’ŹJ_««JņkÄ žkćŽqķs޾bż„‰E±€Ń[ ÓZoč½—Xv…­æ†ĒƒInšq&Žł  rh®]:ŽŚs�!‡c“¬ØF“ž¬ŗ#2-%ōQ?ęŲ‰ē칩\œø˜ōū‹ū€eL�Œ ·õaķŒX)tr’J~ł)J`æ%łķÄüpA W”w!oYA}ö8CtT#\ŠÅ„™™¶j=f eĆŦ=NĀۘŠŃžb£v77kčŸS «ŽkJbUXü¦÷%žĮ•©īu”Æ&ūt›ŖŸWÖÄöÖKōQµ+%żE\¤ł ^/ėžHA’÷č_Kµ”Į°pŒQ�|hS~äÓĘ*~³ĘqÓ„(Hb¹`Ŗ¹ęõō²Ži…„Ż$B€&ėęĶLŹ’t§@×ć(‘H�Ń—}ĖŪ„÷›~QŲ} ‹ūÜQ?Ɗoj›ØŽ�&*ø šč<L”Ūń«}Ų¤lČćłŖ‡‡`žTōĢELüߒ¾ü»¾·,„ēf˜Gņ—f¾¼ÅŠģČŌ\©B.’·T’śwĖļ‡ū_‘Ü0ś)CR= ą½‰Ń ž;D£ĀżĀjc ęÖńŠ8阄#üÄ`iŠE‘JĶљÄsĀŲ š<.ńØęž°!ė����������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/.gitignore����������������������������������������������������������������������������0000664�0003720�0003720�00000002263�13761735245�016026� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/������������������������������������������������������������������������������0000775�0003720�0003720�00000000000�13761735350�015456� 5����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/_main_latest_py.py������������������������������������������������������������0000664�0003720�0003720�00000001165�13761735245�021205� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from itertools import chain from makefun.main import wraps def make_partial_using_yield_from(new_sig, f, *preset_pos_args, **preset_kwargs): """ Makes a 'partial' when f is a generator and python is new enough to support `yield from` :param new_sig: :param f: :param presets: :return: """ @wraps(f, new_sig) def partial_f(*args, **kwargs): # since the signature does the checking for us, no need to check for redundancy. kwargs.update(preset_kwargs) # for python 3.4: explicit dict update yield from f(*chain(preset_pos_args, args), **kwargs) return partial_f �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/_version.py�������������������������������������������������������������������0000664�0003720�0003720�00000000164�13761735347�017663� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '1.9.5' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/_main_legacy_py.py������������������������������������������������������������0000664�0003720�0003720�00000006547�13761735245�021166� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys from itertools import chain from makefun.main import wraps def make_partial_using_yield(new_sig, f, *preset_pos_args, **preset_kwargs): """ Makes a 'partial' when f is a generator and python is new enough to support `yield from` :param new_sig: :param f: :param preset_pos_args: :param preset_kwargs: :return: """ @wraps(f, new_sig=new_sig) def partial_f(*args, **kwargs): # since the signature does the checking for us, no need to check for redundancy. kwargs.update(preset_kwargs) gen = f(*chain(preset_pos_args, args), **kwargs) _i = iter(gen) # initialize the generator _y = next(_i) # first iteration while 1: try: _s = yield _y # yield the first output and retrieve the new input except GeneratorExit as _e: # ---generator exit error--- try: _m = _i.close # if there is a close method except AttributeError: pass else: _m() # use it first raise _e # then re-raise exception except BaseException as _e: # ---other exception _x = sys.exc_info() # if captured exception, grab info try: _m = _i.throw # if there is a throw method except AttributeError: raise _e # otherwise re-raise else: _y = _m(*_x) # use it else: # --- nominal case: the new input was received # if _s is None: # _y = next(_i) # else: _y = _i.send(_s) # let the implementation decide if None means "no new input" or "new input = None" return partial_f def get_legacy_py_generator_body_template(): """ In Python 2 we cannot use `yield from` in the generated function body. This is a replacement, from PEP380 - see https://www.python.org/dev/peps/pep-0380/#formal-semantics note: we removed a few lines so that `StopIteration` exceptions are re-raised :return: """ return """def %s _i = iter(_func_impl_(%s)) # initialize the generator _y = next(_i) # first iteration while 1: try: _s = yield _y # yield the first output and retrieve the new input except GeneratorExit as _e: # ---generator exit error--- try: _m = _i.close # if there is a close method except AttributeError: pass else: _m() # use it first raise _e # then re-raise exception except BaseException as _e: # ---other exception _x = sys.exc_info() # if captured exception, grab info try: _m = _i.throw # if there is a throw method except AttributeError: raise _e # otherwise re-raise else: _y = _m(*_x) # use it else: # --- nominal case: the new input was received # if _s is None: # _y = next(_i) # else: _y = _i.send(_s) # let the implementation decide if None means "no new input" or "new input = None" """ ���������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/main.py�����������������������������������������������������������������������0000664�0003720�0003720�00000141635�13761735245�016771� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import print_function import functools import re import sys import itertools from collections import OrderedDict from copy import copy from inspect import getsource from textwrap import dedent from types import FunctionType try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: from funcsigs import signature, Signature, Parameter try: from inspect import iscoroutinefunction except ImportError: # let's assume there are no coroutine functions in old Python def iscoroutinefunction(f): return False try: from inspect import isgeneratorfunction except ImportError: # assume no generator function in old Python versions def isgeneratorfunction(f): return False try: # python 3.5+ from typing import Callable, Any, Union, Iterable, Dict except ImportError: pass # macroscopic signature strings checker (we do not look inside params, `signature` will do it for us) FUNC_DEF = re.compile('(?s)^\\s*(?P<funcname>[_\\w][_\\w\\d]*)?\\s*' '\\(\\s*(?P<params>.*?)\\s*\\)\\s*' '(((?P<typed_return_hint>->\\s*[^:]+)?(?P<colon>:)?\\s*)|:\\s*#\\s*(?P<comment_return_hint>.+))*$') def create_wrapper(wrapped, wrapper, new_sig=None, # type: Union[str, Signature] func_name=None, # type: str inject_as_first_arg=False, # type: bool add_source=True, # type: bool add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str module_name=None, # type: str **attrs ): """ Creates a signature-preserving wrapper function. See `@makefun.wraps` """ func_name, func_sig, doc, qualname, module_name, all_attrs = _get_args_for_wrapping(wrapped, new_sig, func_name, doc, qualname, module_name, attrs) return create_function(func_sig, wrapper, func_name=func_name, inject_as_first_arg=inject_as_first_arg, add_source=add_source, add_impl=add_impl, doc=doc, qualname=qualname, module_name=module_name, **all_attrs) def getattr_partial_aware(obj, att_name, *att_default): """ Same as getattr but recurses in obj.func if obj is a partial """ val = getattr(obj, att_name, *att_default) if isinstance(obj, functools.partial) and \ (val is None or att_name == '__dict__' and len(val) == 0): return getattr_partial_aware(obj.func, att_name, *att_default) else: return val def create_function(func_signature, # type: Union[str, Signature] func_impl, # type: Callable[[Any], Any] func_name=None, # type: str inject_as_first_arg=False, # type: bool add_source=True, # type: bool add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str module_name=None, # type: str **attrs): """ Creates a function with signature <func_signature> that will call <func_impl> with its arguments when called. Arguments are passed as keyword-arguments when it is possible (so all the time, except for var-positional or positional-only arguments that get passed as *args. Note that pos-only does not yet exist in python but this case is already covered because it is supported by `Signature` objects). `func_signature` can be provided: - as a string containing the name and signature without 'def' keyword, such as `'foo(a, b: int, *args, **kwargs)'`. In which case the name in the string will be used for the `__name__` and `__qualname__` of the created function by default - as a `Signature` object, for example created using `signature(f)` or handcrafted. In this case the `__name__` and `__qualname__` of the created function will be copied from `func_impl` by default. All the other metadata of the created function are defined as follows: - default `__name__` attribute (see above) can be overriden by providing a non-None `func_name` - default `__qualname__` attribute (see above) can be overridden by providing a non-None `qualname` - `__annotations__` attribute is created to match the annotations in the signature. - `__doc__` attribute is copied from `func_impl.__doc__` except if overridden using `doc` - `__module__` attribute is copied from `func_impl.__module__` except if overridden using `module_name` Finally two new attributes are optionally created - `__source__` attribute: set if `add_source` is `True` (default), this attribute contains the source code of the generated function - `__func_impl__` attribute: set if `add_impl` is `True` (default), this attribute contains a pointer to `func_impl` :param func_signature: either a string without 'def' such as "foo(a, b: int, *args, **kwargs)" or "(a, b: int)", or a `Signature` object, for example from the output of `inspect.signature` or from the `funcsigs.signature` backport. Note that these objects can be created manually too. If the signature is provided as a string and contains a non-empty name, this name will be used instead of the one of the decorated function. :param func_impl: the function that will be called when the generated function is executed. Its signature should be compliant with (=more generic than) `func_signature` :param inject_as_first_arg: if `True`, the created function will be injected as the first positional argument of `func_impl`. This can be handy in case the implementation is shared between several facades and needs to know from which context it was called. Default=`False` :param func_name: provide a non-`None` value to override the created function `__name__` and `__qualname__`. If this is `None` (default), the `__name__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param add_source: a boolean indicating if a '__source__' annotation should be added to the generated function (default: True) :param add_impl: a boolean indicating if a '__func_impl__' annotation should be added to the generated function (default: True) :param doc: a string representing the docstring that will be used to set the __doc__ attribute on the generated function. If None (default), the doc of func_impl will be used. :param qualname: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param module_name: the name of the module to be set on the function (under __module__ ). If None (default), `func_impl.__module__` will be used. :param attrs: other keyword attributes that should be set on the function. Note that `func_impl.__dict__` is not automatically copied. :return: """ # grab context from the caller frame try: attrs.pop('_with_sig_') # called from `@with_signature` frame = _get_callerframe(offset=1) except KeyError: frame = _get_callerframe() evaldict, _ = extract_module_and_evaldict(frame) # name defaults user_provided_name = True if func_name is None: # allow None for now, we'll raise a ValueError later if needed func_name = getattr_partial_aware(func_impl, '__name__', None) user_provided_name = False # qname default user_provided_qname = True if qualname is None: qualname = getattr_partial_aware(func_impl, '__qualname__', None) user_provided_qname = False # doc default if doc is None: doc = getattr(func_impl, '__doc__', None) # note: as opposed to what we do in `@wraps`, we cannot easily generate a better doc for partials here. # Indeed the new signature may not easily match the one in the partial. # module name default if module_name is None: module_name = getattr_partial_aware(func_impl, '__module__', None) # input signature handling if isinstance(func_signature, str): # transform the string into a Signature and make sure the string contains ":" func_name_from_str, func_signature, func_signature_str = get_signature_from_string(func_signature, evaldict) # if not explicitly overridden using `func_name`, the name in the string takes over if func_name_from_str is not None: if not user_provided_name: func_name = func_name_from_str if not user_provided_qname: qualname = func_name # fix the signature if needed if func_name_from_str is None: if func_name is None: raise ValueError("Invalid signature for created function: `None` function name. This " "probably happened because the decorated function %s has no __name__. You may " "wish to pass an explicit `func_name` or to complete the signature string" "with the name before the parenthesis." % func_impl) func_signature_str = func_name + func_signature_str elif isinstance(func_signature, Signature): # create the signature string if func_name is None: raise ValueError("Invalid signature for created function: `None` function name. This " "probably happened because the decorated function %s has no __name__. You may " "wish to pass an explicit `func_name` or to provide the new signature as a " "string containing the name" % func_impl) func_signature_str = get_signature_string(func_name, func_signature, evaldict) else: raise TypeError("Invalid type for `func_signature`: %s" % type(func_signature)) # extract all information needed from the `Signature` params_to_kw_assignment_mode = get_signature_params(func_signature) params_names = list(params_to_kw_assignment_mode.keys()) # Note: in decorator the annotations were extracted using getattr(func_impl, '__annotations__') instead. # This seems equivalent but more general (provided by the signature, not the function), but to check annotations, defaults, kwonlydefaults = get_signature_details(func_signature) # create the body of the function to compile # The generated function body should dispatch its received arguments to the inner function. # For this we will pass as much as possible the arguments as keywords. # However if there are varpositional arguments we cannot assignments = [("%s=%s" % (k, k)) if is_kw else k for k, is_kw in params_to_kw_assignment_mode.items()] params_str = ', '.join(assignments) if inject_as_first_arg: params_str = "%s, %s" % (func_name, params_str) if _is_generator_func(func_impl): if sys.version_info >= (3, 3): body = "def %s\n yield from _func_impl_(%s)\n" % (func_signature_str, params_str) else: from makefun._main_legacy_py import get_legacy_py_generator_body_template body = get_legacy_py_generator_body_template() % (func_signature_str, params_str) else: body = "def %s\n return _func_impl_(%s)\n" % (func_signature_str, params_str) if iscoroutinefunction(func_impl): body = ("async " + body).replace('return', 'return await') # create the function by compiling code, mapping the `_func_impl_` symbol to `func_impl` protect_eval_dict(evaldict, func_name, params_names) evaldict['_func_impl_'] = func_impl f = _make(func_name, params_names, body, evaldict) # add the source annotation if needed if add_source: attrs['__source__'] = body # add the handler if needed if add_impl: attrs['__func_impl__'] = func_impl # update the signature _update_fields(f, name=func_name, qualname=qualname, doc=doc, annotations=annotations, defaults=tuple(defaults), kwonlydefaults=kwonlydefaults, module=module_name, **attrs) return f def _is_generator_func(func_impl): """ Return True if the func_impl is a generator :param func_impl: :return: """ if (3, 5) <= sys.version_info < (3, 6): # with Python 3.5 isgeneratorfunction returns True for all coroutines # however we know that it is NOT possible to have a generator # coroutine in python 3.5: PEP525 was not there yet return isgeneratorfunction(func_impl) and not iscoroutinefunction(func_impl) else: return isgeneratorfunction(func_impl) class _SymbolRef: """ A class used to protect signature default values and type hints when the local context would not be able to evaluate them properly when the new function is created. In this case we store them under a known name, we add that name to the locals(), and we use this symbol that has a repr() equal to the name. """ __slots__ = 'varname' def __init__(self, varname): self.varname = varname def __repr__(self): return self.varname def get_signature_string(func_name, func_signature, evaldict): """ Returns the string to be used as signature. If there is a non-native symbol in the defaults, it is created as a variable in the evaldict :param func_name: :param func_signature: :return: """ no_type_hints_allowed = sys.version_info < (3, 5) # protect the parameters if needed new_params = [] params_changed = False for p_name, p in func_signature.parameters.items(): # if default value can not be evaluated, protect it default_needs_protection = _signature_symbol_needs_protection(p.default, evaldict) new_default = _protect_signature_symbol(p.default, default_needs_protection, "DEFAULT_%s" % p_name, evaldict) if no_type_hints_allowed: new_annotation = Parameter.empty annotation_needs_protection = new_annotation is not p.annotation else: # if type hint can not be evaluated, protect it annotation_needs_protection = _signature_symbol_needs_protection(p.annotation, evaldict) new_annotation = _protect_signature_symbol(p.annotation, annotation_needs_protection, "HINT_%s" % p_name, evaldict) # only create if necessary (inspect __init__ methods are slow) if default_needs_protection or annotation_needs_protection: # replace the parameter with the possibly new default and hint p = Parameter(p.name, kind=p.kind, default=new_default, annotation=new_annotation) params_changed = True new_params.append(p) if no_type_hints_allowed: new_return_annotation = Parameter.empty return_needs_protection = new_return_annotation is not func_signature.return_annotation else: # if return type hint can not be evaluated, protect it return_needs_protection = _signature_symbol_needs_protection(func_signature.return_annotation, evaldict) new_return_annotation = _protect_signature_symbol(func_signature.return_annotation, return_needs_protection, "RETURNHINT", evaldict) # only create new signature if necessary (inspect __init__ methods are slow) if params_changed or return_needs_protection: s = Signature(parameters=new_params, return_annotation=new_return_annotation) else: s = func_signature # return the final string representation return "%s%s:" % (func_name, s) def _signature_symbol_needs_protection(symbol, evaldict): """ Helper method for signature symbols (defaults, type hints) protection. Returns True if the given symbol needs to be protected - that is, if its repr() can not be correctly evaluated with current evaldict. :param symbol: :return: """ if symbol is not None and symbol is not Parameter.empty and not isinstance(symbol, (int, str, float, bool)): try: # check if the repr() of the default value is equal to itself. return eval(repr(symbol), evaldict) != symbol except (NameError, SyntaxError): # in case of error this needs protection return True else: return False def _protect_signature_symbol(val, needs_protection, varname, evaldict): """ Helper method for signature symbols (defaults, type hints) protection. Returns either `val`, or a protection symbol. In that case the protection symbol is created with name `varname` and inserted into `evaldict` :param val: :param needs_protection: :param varname: :param evaldict: :return: """ if needs_protection: # store the object in the evaldict and insert name evaldict[varname] = val return _SymbolRef(varname) else: return val def get_signature_from_string(func_sig_str, evaldict): """ Creates a `Signature` object from the given function signature string. :param func_sig_str: :return: (func_name, func_sig, func_sig_str). func_sig_str is guaranteed to contain the ':' symbol already """ # escape leading newline characters if func_sig_str.startswith('\n'): func_sig_str = func_sig_str[1:] # match the provided signature. note: fullmatch is not supported in python 2 def_match = FUNC_DEF.match(func_sig_str) if def_match is None: raise SyntaxError('The provided function template is not valid: "%s" does not match ' '"<func_name>(<func_args>)[ -> <return-hint>]".\n For information the regex used is: "%s"' '' % (func_sig_str, FUNC_DEF.pattern)) groups = def_match.groupdict() # extract function name and parameter names list func_name = groups['funcname'] if func_name is None or func_name == '': func_name_ = 'dummy' func_name = None else: func_name_ = func_name # params_str = groups['params'] # params_names = extract_params_names(params_str) # find the keyword parameters and the others # posonly_names, kwonly_names, unrestricted_names = separate_positional_and_kw(params_names) colon_end = groups['colon'] cmt_return_hint = groups['comment_return_hint'] if (colon_end is None or len(colon_end) == 0) \ and (cmt_return_hint is None or len(cmt_return_hint) == 0): func_sig_str = func_sig_str + ':' # Create a dummy function # complete the string if name is empty, so that we can actually use _make func_sig_str_ = (func_name_ + func_sig_str) if func_name is None else func_sig_str body = 'def %s\n pass\n' % func_sig_str_ dummy_f = _make(func_name_, [], body, evaldict) # return its signature return func_name, signature(dummy_f), func_sig_str # def extract_params_names(params_str): # return [m.groupdict()['name'] for m in PARAM_DEF.finditer(params_str)] # def separate_positional_and_kw(params_names): # """ # Extracts the names that are positional-only, keyword-only, or non-constrained # :param params_names: # :return: # """ # # by default all parameters can be passed as positional or keyword # posonly_names = [] # kwonly_names = [] # other_names = params_names # # # but if we find explicit separation we have to change our mind # for i in range(len(params_names)): # name = params_names[i] # if name == '*': # del params_names[i] # posonly_names = params_names[0:i] # kwonly_names = params_names[i:] # other_names = [] # break # elif name[0] == '*' and name[1] != '*': # # # that's a *args. Next one will be keyword-only # posonly_names = params_names[0:(i + 1)] # kwonly_names = params_names[(i + 1):] # other_names = [] # break # else: # # continue # pass # # return posonly_names, kwonly_names, other_names def get_signature_params(s): """ Utility method to return the parameter names in the provided `Signature` object, by group of kind :param s: :return: """ # this ordered dictionary will contain parameters and True/False whether we should use keyword assignment or not params_to_assignment_mode = OrderedDict() for p_name, p in s.parameters.items(): if p.kind is Parameter.POSITIONAL_ONLY: params_to_assignment_mode[p_name] = False elif p.kind is Parameter.KEYWORD_ONLY: params_to_assignment_mode[p_name] = True elif p.kind is Parameter.POSITIONAL_OR_KEYWORD: params_to_assignment_mode[p_name] = True elif p.kind is Parameter.VAR_POSITIONAL: # We have to pass all the arguments that were here in previous positions, as positional too. for k in params_to_assignment_mode.keys(): params_to_assignment_mode[k] = False params_to_assignment_mode["*" + p_name] = False elif p.kind is Parameter.VAR_KEYWORD: params_to_assignment_mode["**" + p_name] = False else: raise ValueError("Unknown kind: %s" % p.kind) return params_to_assignment_mode def get_signature_details(s): """ Utility method to extract the annotations, defaults and kwdefaults from a `Signature` object :param s: :return: """ annotations = dict() defaults = [] kwonlydefaults = dict() if s.return_annotation is not s.empty: annotations['return'] = s.return_annotation for p_name, p in s.parameters.items(): if p.annotation is not s.empty: annotations[p_name] = p.annotation if p.default is not s.empty: # if p_name not in kwonly_names: if p.kind is not Parameter.KEYWORD_ONLY: defaults.append(p.default) else: kwonlydefaults[p_name] = p.default return annotations, defaults, kwonlydefaults def extract_module_and_evaldict(frame): """ Utility function to extract the module name from the given frame, and to return a dictionary containing globals and locals merged together :param frame: :return: """ try: # get the module name module_name = frame.f_globals.get('__name__', '?') # construct a dictionary with all variables # this is required e.g. if a symbol is used in a type hint evaldict = copy(frame.f_globals) evaldict.update(frame.f_locals) except AttributeError: # either the frame is None of the f_globals and f_locals are not available module_name = '?' evaldict = dict() return evaldict, module_name def protect_eval_dict(evaldict, func_name, params_names): """ remove all symbols that could be harmful in evaldict :param evaldict: :param func_name: :param params_names: :return: """ try: del evaldict[func_name] except KeyError: pass for n in params_names: try: del evaldict[n] except KeyError: pass return evaldict # Atomic get-and-increment provided by the GIL _compile_count = itertools.count() def _make(funcname, params_names, body, evaldict=None): """ Make a new function from a given template and update the signature :param func_name: :param params_names: :param body: :param evaldict: :param add_source: :return: """ evaldict = evaldict or {} for n in params_names: if n in ('_func_', '_func_impl_'): raise NameError('%s is overridden in\n%s' % (n, body)) if not body.endswith('\n'): # newline is needed for old Pythons raise ValueError("body should end with a newline") # Ensure each generated function has a unique filename for profilers # (such as cProfile) that depend on the tuple of (<filename>, # <definition line>, <function name>) being unique. filename = '<makefun-gen-%d>' % (next(_compile_count),) try: code = compile(body, filename, 'single') exec(code, evaldict) except: print('Error in generated code:', file=sys.stderr) print(body, file=sys.stderr) raise # extract the function from compiled code func = evaldict[funcname] return func def _update_fields(func, name, qualname=None, doc=None, annotations=None, defaults=(), kwonlydefaults=None, module=None, **kw): """ Update the signature of func with the provided information This method merely exists to remind which field have to be filled. :param self: :param func: :param kw: :return: """ func.__name__ = name if qualname is not None: func.__qualname__ = qualname func.__doc__ = doc func.__dict__ = kw func.__defaults__ = defaults if len(kwonlydefaults) == 0: kwonlydefaults = None func.__kwdefaults__ = kwonlydefaults func.__annotations__ = annotations func.__module__ = module def _get_callerframe(offset=0): try: # inspect.stack is extremely slow, the fastest is sys._getframe or inspect.currentframe(). # See https://gist.github.com/JettJones/c236494013f22723c1822126df944b12 frame = sys._getframe(2 + offset) # frame = currentframe() # for _ in range(2 + offset): # frame = frame.f_back except AttributeError: # for IronPython and similar implementations frame = None return frame def wraps(f, new_sig=None, # type: Union[str, Signature] func_name=None, # type: str inject_as_first_arg=False, # type: bool add_source=True, # type: bool add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str module_name=None, # type: str **attrs ): """ Decorator to create a signature-preserving wrapper function. It is similar to `functools.wraps`, but relies on a proper dynamically-generated function. Therefore as opposed to `functools.wraps`, the wrapper body will not be executed if the arguments provided are not compliant with the signature - instead a `TypeError` will be raised before entering the wrapper body. `@wraps(f)` is equivalent to `@with_signature(signature(f), func_name=f.__name__, doc=f.__doc__, module_name=f.__module__, qualname=f.__qualname__, __wrapped__=f, **f.__dict__, **attrs)` In other words, as opposed to `@with_signature`, the metadata (doc, module name, etc.) is provided by the wrapped `f`, so that the created function seems to be identical (except for the signature if a non-None `new_sig` is provided). If `new_sig` is None, we set the additional `__wrapped__` attribute on the created function, to stay compliant with the `functools.wraps` convention. See https://docs.python.org/3/library/functools.html#functools.wraps """ func_name, func_sig, doc, qualname, module_name, all_attrs = _get_args_for_wrapping(f, new_sig, func_name, doc, qualname, module_name, attrs) return with_signature(func_sig, func_name=func_name, inject_as_first_arg=inject_as_first_arg, add_source=add_source, add_impl=add_impl, doc=doc, qualname=qualname, module_name=module_name, **all_attrs) def _get_args_for_wrapping(wrapped, new_sig, func_name, doc, qualname, module_name, attrs): """ Internal method used by @wraps and create_wrapper :param wrapped: :param new_sig: :param func_name: :param doc: :param qualname: :param module_name: :param attrs: :return: """ # the desired signature func_sig = signature(wrapped) if new_sig is None else new_sig # the desired metadata if func_name is None: func_name = getattr_partial_aware(wrapped, '__name__', None) if doc is None: doc = getattr(wrapped, '__doc__', None) if isinstance(wrapped, functools.partial) and new_sig is None \ and doc == functools.partial(lambda: True).__doc__: # this is the default generic partial doc. generate a better doc, since we know that the sig is not messed with doc = gen_partial_doc(getattr_partial_aware(wrapped.func, '__name__', None), getattr_partial_aware(wrapped.func, '__doc__', None), func_sig, wrapped.args, wrapped.keywords) if qualname is None: qualname = getattr_partial_aware(wrapped, '__qualname__', None) if module_name is None: module_name = getattr_partial_aware(wrapped, '__module__', None) # attributes: start from the wrapped dict, add '__wrapped__' if needed, and override with all attrs. all_attrs = copy(getattr_partial_aware(wrapped, '__dict__')) if new_sig is None: # there was no change of signature so we can safely set the __wrapped__ attribute all_attrs['__wrapped__'] = wrapped all_attrs.update(attrs) return func_name, func_sig, doc, qualname, module_name, all_attrs def with_signature(func_signature, # type: Union[str, Signature] func_name=None, # type: str inject_as_first_arg=False, # type: bool add_source=True, # type: bool add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str module_name=None, # type: str **attrs ): """ A decorator for functions, to change their signature. The new signature should be compliant with the old one. ```python @with_signature(<arguments>) def impl(...): ... ``` is totally equivalent to `impl = create_function(<arguments>, func_impl=impl)` except for one additional behaviour: - If `func_signature` is set to `None`, there is no `TypeError` as in create_function. Instead, this simply applies the new metadata (name, doc, module_name, attrs) to the decorated function without creating a wrapper. `add_source`, `add_impl` and `inject_as_first_arg` should not be set in this case. :param func_signature: the new signature of the decorated function. Either a string without 'def' such as "foo(a, b: int, *args, **kwargs)" of "(a, b: int)", or a `Signature` object, for example from the output of `inspect.signature` or from the `funcsigs.signature` backport. Note that these objects can be created manually too. If the signature is provided as a string and contains a non-empty name, this name will be used instead of the one of the decorated function. Finally `None` can be provided to indicate that user wants to only change the medatadata (func_name, doc, module_name, attrs) of the decorated function, without generating a new function. :param inject_as_first_arg: if `True`, the created function will be injected as the first positional argument of the decorated function. Default=`False` :param func_name: provide a non-`None` value to override the created function `__name__` and `__qualname__`. If this is `None` (default), the `__name__` and `__qualname__` will default to the ones of the decorated function if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param add_source: a boolean indicating if a '__source__' annotation should be added to the generated function (default: True) :param add_impl: a boolean indicating if a '__func_impl__' annotation should be added to the generated function (default: True) :param doc: a string representing the docstring that will be used to set the __doc__ attribute on the generated function. If None (default), the doc of func_impl will be used. :param qualname: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param module_name: the name of the module to be set on the function (under __module__ ). If None (default), `func_impl.__module__` will be used. :param attrs: other keyword attributes that should be set on the function """ if func_signature is None: # make sure that user does not provide non-default other args if inject_as_first_arg or not add_source or not add_impl: raise ValueError("If `func_signature=None` no new signature will be generated so only `func_name`, " "`module_name`, `doc` and `attrs` should be provided, to modify the metadata.") else: def replace_f(f): # manually apply all the non-None metadata, but do not call create_function - that's useless if func_name is not None: f.__name__ = func_name if doc is not None: f.__doc__ = doc if qualname is not None: f.__qualname__ = qualname if module_name is not None: f.__module__ = module_name for k, v in attrs.items(): setattr(f, k, v) return f else: def replace_f(f): return create_function(func_signature=func_signature, func_impl=f, func_name=func_name, inject_as_first_arg=inject_as_first_arg, add_source=add_source, add_impl=add_impl, doc=doc, qualname=qualname, module_name=module_name, _with_sig_=True, # special trick to tell create_function that we're @with_signature **attrs ) return replace_f def remove_signature_parameters(s, *param_names): """ Removes the provided parameters from the signature s (returns a new signature instance). :param s: :param param_names: a list of parameter names to remove :return: """ params = OrderedDict(s.parameters.items()) for param_name in param_names: del params[param_name] return s.replace(parameters=params.values()) def add_signature_parameters(s, # type: Signature first=(), # type: Union[Parameter, Iterable[Parameter]] last=(), # type: Union[Parameter, Iterable[Parameter]] custom=(), # type: Union[Parameter, Iterable[Parameter]] custom_idx=-1 # type: int ): """ Adds the provided parameters to the signature `s` (returns a new signature instance). :param s: the original signature to edit :param first: a single element or a list of `Parameter` instances to be added at the beginning of the parameter's list :param last: a single element or a list of `Parameter` instances to be added at the end of the parameter's list :param custom: a single element or a list of `Parameter` instances to be added at a custom position in the list. that position is determined with `custom_idx` :param custom_idx: the custom position to insert the `custom` parameters to. :return: a new signature created from the original one by adding the specified parameters. """ params = OrderedDict(s.parameters.items()) lst = list(params.values()) # insert at custom position (but keep the order, that's why we use 'reversed') try: for param in reversed(custom): if param.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % param.name) else: lst.insert(custom_idx, param) except TypeError: # a single argument if custom.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % custom.name) else: lst.insert(custom_idx, custom) # prepend but keep the order try: for param in reversed(first): if param.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % param.name) else: lst.insert(0, param) except TypeError: # a single argument if first.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % first.name) else: lst.insert(0, first) # append try: for param in last: if param.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % param.name) else: lst.append(param) except TypeError: # a single argument if last.name in params: raise ValueError("Parameter with name '%s' is present twice in the signature to create" % last.name) else: lst.append(last) return s.replace(parameters=lst) def with_partial(*preset_pos_args, **preset_kwargs): """ Decorator to 'partialize' a function using `partial` :param preset_pos_args: :param preset_kwargs: :return: """ def apply_decorator(f): return partial(f, *preset_pos_args, **preset_kwargs) return apply_decorator def partial(f, *preset_pos_args, **preset_kwargs): """ :param preset_pos_args: :param preset_kwargs: :return: """ # TODO do we need to mimic `partial`'s behaviour concerning positional args? # (1) remove/change all preset arguments from the signature new_sig = gen_partial_sig(f, preset_pos_args, preset_kwargs) if _is_generator_func(f): if sys.version_info >= (3, 3): from makefun._main_latest_py import make_partial_using_yield_from partial_f = make_partial_using_yield_from(new_sig, f, *preset_pos_args, **preset_kwargs) else: from makefun._main_legacy_py import make_partial_using_yield partial_f = make_partial_using_yield(new_sig, f, *preset_pos_args, **preset_kwargs) else: @wraps(f, new_sig=new_sig) def partial_f(*args, **kwargs): # since the signature does the checking for us, no need to check for redundancy. kwargs.update(preset_kwargs) return f(*itertools.chain(preset_pos_args, args), **kwargs) # update the doc. # Note that partial_f is generated above with a proper __name__ and __doc__ identical to the wrapped ones partial_f.__doc__ = gen_partial_doc(partial_f.__name__, partial_f.__doc__, new_sig, preset_pos_args, preset_kwargs) return partial_f def gen_partial_sig(f, preset_pos_args, preset_kwargs): """ Returns the signature of partial(f, *preset_pos_args, **preset_kwargs) Raises explicit errors in case of non-matching argument names. :param f: :param preset_pos_args: :param preset_kwargs: :return: """ orig_sig = signature(f) preset_kwargs = copy(preset_kwargs) # remove the first n positional, and assign/change default values for the keyword if len(orig_sig.parameters) < len(preset_pos_args): raise ValueError("Cannot preset %s positional args, function %s has only %s args." "" % (len(preset_pos_args), getattr(f, '__name__', f), len(orig_sig.parameters))) # then the keywords. If they have a new value override it new_params = [] new_params_with_default = [] for i, (p_name, p) in enumerate(orig_sig.parameters.items()): if i < len(preset_pos_args): break try: overridden_p_default = preset_kwargs.pop(p_name) # override definition p = Parameter(name=p.name, kind=p.kind, default=overridden_p_default, annotation=p.annotation) except KeyError: pass if p.default is not p.empty: new_params_with_default.append(p) else: new_params.append(p) new_sig = Signature(parameters=tuple(new_params + new_params_with_default), return_annotation=orig_sig.return_annotation) if len(preset_kwargs) > 0: raise ValueError("Cannot preset keyword argument(s), not present in the signature of %s: %s" "" % (getattr(f, '__name__', f), preset_kwargs)) return new_sig def gen_partial_doc(wrapped_name, wrapped_doc, new_sig, preset_pos_args, preset_kwargs): """ Generate a documentation indicating which positional arguments and keyword arguments are set in this partial implementation, and appending the wrapped function doc. :param wrapped_name: :param wrapped_doc: :param new_sig: :param preset_pos_args: :param preset_kwargs: :return: """ # First include all preset positional argument names... argstring = ', '.join([("%s" % a) for a in preset_pos_args]) # then all arguments remaining in the signature (changing their default value if overridden) for p_name, p in new_sig.parameters.items(): try: p_overridden_default = preset_kwargs[p_name] p = Parameter(name=p.name, kind=p.kind, default=p_overridden_default, annotation=p.annotation) except KeyError: pass argstring = "%s, %s" % (argstring, p) if len(argstring) > 0 else str(p) # Write the final docstring if wrapped_doc is None or len(wrapped_doc) == 0: partial_doc = "<This function is equivalent to '%s(%s)'.>\n" % (wrapped_name, argstring) else: new_line = "<This function is equivalent to '%s(%s)', see original '%s' doc below.>\n" \ "" % (wrapped_name, argstring, wrapped_name) partial_doc = new_line + wrapped_doc return partial_doc class UnsupportedForCompilation(TypeError): """ Exception raised by @compile_fun when decorated target is not supported """ pass class UndefinedSymbolError(NameError): """ Exception raised by @compile_fun when the function requires a name not yet defined """ pass class SourceUnavailable(OSError): """ Exception raised by @compile_fun when the function source is not available (inspect.getsource raises an error) """ pass def compile_fun(recurse=True, # type: Union[bool, Callable] except_names=(), # type: Iterable[str] ): """ A draft decorator to `compile` any existing function so that users cant debug through it. It can be handy to mask some code from your users for convenience (note that this does not provide any obfuscation, people can still reverse engineer your code easily. Actually the source code even gets copied in the function's `__source__` attribute for convenience): ```python from makefun import compile_fun @compile_fun def foo(a, b): return a + b assert foo(5, -5.0) == 0 print(foo.__source__) ``` yields ``` @compile_fun def foo(a, b): return a + b ``` If the function closure includes functions, they are recursively replaced with compiled versions too (only for this closure, this does not modify them otherwise). **IMPORTANT** this decorator is a "goodie" in early stage and has not been extensively tested. Feel free to contribute ! Note that according to [this post](https://stackoverflow.com/a/471227/7262247) compiling does not make the code run any faster. Known issues: `NameError` will appear if your function code depends on symbols that have not yet been defined. Make sure all symbols exist first ! See https://github.com/smarie/python-makefun/issues/47 :param recurse: a boolean (default `True`) indicating if referenced symbols should be compiled too :param except_names: an optional list of symbols to exclude from compilation when `recurse=True` :return: """ if callable(recurse): # called with no-args, apply immediately target = recurse # noinspection PyTypeChecker return compile_fun_manually(target, _evaldict=True) else: # called with parenthesis, return a decorator def apply_compile_fun(target): return compile_fun_manually(target, recurse=recurse, except_names=except_names, _evaldict=True) return apply_compile_fun def compile_fun_manually(target, recurse=True, # type: Union[bool, Callable] except_names=(), # type: Iterable[str] _evaldict=None # type: Union[bool, Dict] ): """ :param target: :return: """ if not isinstance(target, FunctionType): raise UnsupportedForCompilation("Only functions can be compiled by this decorator") if _evaldict is None or _evaldict is True: if _evaldict is True: frame = _get_callerframe(offset=1) else: frame = _get_callerframe() _evaldict, _ = extract_module_and_evaldict(frame) # first make sure that source code is available for compilation try: lines = getsource(target) except (OSError, IOError) as e: if 'could not get source code' in str(e): raise SourceUnavailable(target, e) else: raise # compile all references first try: # python 3 func_closure = target.__closure__ func_code = target.__code__ except AttributeError: # python 2 func_closure = target.func_closure func_code = target.func_code # Does not work: if `self.i` is used in the code, `i` will appear here # if func_code is not None: # for name in func_code.co_names: # try: # eval(name, _evaldict) # except NameError: # raise UndefinedSymbolError("Symbol `%s` does not seem to be defined yet. Make sure you apply " # "`compile_fun` *after* all required symbols have been defined." % name) if recurse and func_closure is not None: # recurse-compile for name, cell in zip(func_code.co_freevars, func_closure): if name in except_names: continue if name not in _evaldict: raise UndefinedSymbolError("Symbol %s does not seem to be defined yet. Make sure you apply " "`compile_fun` *after* all required symbols have been defined." % name) try: value = cell.cell_contents except ValueError: # empty cell continue else: # non-empty cell try: # note : not sure the compilation will be made in the appropriate order of dependencies... # if not, users will have to do it manually _evaldict[name] = compile_fun_manually(value, recurse=recurse, except_names=except_names, _evaldict=_evaldict) except (UnsupportedForCompilation, SourceUnavailable): pass # now compile from sources lines = dedent(lines) source_lines = lines if lines.startswith('@compile_fun'): lines = '\n'.join(lines.splitlines()[1:]) if '@compile_fun' in lines: raise ValueError("@compile_fun seems to appear several times in the function source") if lines[-1] != '\n': lines += '\n' # print("compiling: ") # print(lines) new_f = _make(target.__name__, (), lines, _evaldict) new_f.__source__ = source_lines return new_f ���������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/py.typed����������������������������������������������������������������������0000664�0003720�0003720�00000000000�13761735245�017146� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/__init__.py�������������������������������������������������������������������0000664�0003720�0003720�00000001737�13761735245�017602� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from makefun.main import create_function, with_signature, remove_signature_parameters, add_signature_parameters, \ wraps, create_wrapper, partial, with_partial, compile_fun, UndefinedSymbolError, UnsupportedForCompilation, \ SourceUnavailable try: # -- Distribution mode: import from _version.py generated by setuptools_scm during release from ._version import version as __version__ except ImportError: # -- Source mode: use setuptools_scm to get the current version from src using git from setuptools_scm import get_version as _gv from os import path as _path __version__ = _gv(_path.join(_path.dirname(__file__), _path.pardir)) __all__ = [ '__version__', # submodules 'main', # symbols 'create_function', 'with_signature', 'remove_signature_parameters', 'add_signature_parameters', 'wraps', 'create_wrapper', 'partial', 'with_partial', 'compile_fun', 'UndefinedSymbolError', 'UnsupportedForCompilation', 'SourceUnavailable' ] ���������������������������������makefun-1.9.5/makefun/tests/������������������������������������������������������������������������0000775�0003720�0003720�00000000000�13761735350�016620� 5����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_compile_deco.py����������������������������������������������������0000664�0003720�0003720�00000010046�13761735245�022657� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Authors: Sylvain Marie <sylvain.marie@se.com> # # Copyright (c) Schneider Electric Industries, 2020. All right reserved. from textwrap import dedent import pytest from makefun import compile_fun, UnsupportedForCompilation, UndefinedSymbolError def test_compilefun(): """tests that @compile_fun works correctly""" @compile_fun def foo(a, b): return a + b res = foo(5, -5.0) assert res == 0 ref = """ @compile_fun def foo(a, b): return a + b """ assert foo.__source__ == dedent(ref[1:]) def get_code(target): try: # python 3 func_code = target.__code__ except AttributeError: # python 2 func_code = target.func_code return func_code def is_compiled(target): fname = get_code(target).co_filename return fname != __file__ and 'makefun-gen' in fname def test_compilefun_nested(): """tests that @compile_fun correctly compiles nested functions recursively""" def foo(a, b): return a + b @compile_fun def bar(a, b): assert is_compiled(foo) return foo(a, b) res = bar(5, -5.0) assert res == 0 def test_compilefun_nested_already_compiled(): """tests that @compile_fun correctly handles when a required function was already compiled""" @compile_fun def foo(a, b): return a + b @compile_fun def bar(a, b): assert is_compiled(foo) return foo(a, b) res = bar(5, -5.0) assert res == 0 @pytest.mark.parametrize("variant", ['all', 'named'], ids="variant={}".format) def test_compilefun_nested_exclude(variant): """tests that the `except_names` argument of @compile_fun works correctly""" def foo(a, b): return a + b if variant == 'all': @compile_fun(recurse=False) def bar(a, b): assert not is_compiled(foo) return foo(a, b) else: @compile_fun(except_names=('foo', )) def bar(a, b): assert not is_compiled(foo) return foo(a, b) res = bar(5, -5.0) assert res == 0 def test_compilefun_co_names(): """Test that today we do not compile imported names.""" @compile_fun def foo(): # TODO one day it would be great to selectively recurse through such imported names. Unfortunately, # this comes with *many* side effects including compilation order, appropriate propagation or # non-propagation of globals(), locals() # See https://github.com/smarie/python-makefun/issues/52 assert not is_compiled(dedent) return dedent(" hoho") res = foo() assert res == "hoho" def test_compilefun_nameerror(): """Tests that the `NameError` is raised at creation time and not at call time""" with pytest.raises(UndefinedSymbolError): @compile_fun def fun_requiring_unknown_name(a, b): return unknown_name(a, b) def unknown_name(a, b): return a + b def test_compilefun_method(): """Tests that @compilefun works for class methods""" class A: @compile_fun def meth1(self, par1): print("in A.meth1: par1 =", par1) a = A() a.meth1("via meth1") class A: def __init__(self): self.i = 1 @compile_fun def add(self, a): return self.i + a a = A() assert A().add(-1) == 0 def test_compileclass_decorator(): """tests that applying decorator on a class raises an error """ with pytest.raises(UnsupportedForCompilation): @compile_fun class A(object): pass # def test_compileclass_decorator(): # # @compile_fun # class A(object): # pass # # assert A() is not None # # @compile_fun # class A(int, object): # pass # # assert A() is not None # # @compile_fun # class A(object): # def __init__(self): # pass # # assert A() is not None # # @compile_fun # class A(int): # pass # # def compute(self): # # return self + 2 # # assert A(2) + 2 == 4 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_create_from_string_cases.py����������������������������������������0000664�0003720�0003720�00000003523�13761735245�025271� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import pytest params_type_hints_allowed = sys.version_info.major >= 3 and sys.version_info.minor >= 5 star_followed_by_arg_allowed = sys.version_info.major >= 3 def case_simple(): params_str = "b, a = 0" case_simple.__name__ = params_str # param_names = ['b', 'a'] inputs = "12" args = () kwargs = {'a': 0, 'b': 12} return params_str, inputs, (args, kwargs) @pytest.mark.skipif(not star_followed_by_arg_allowed, reason='not allowed in this version of python') def case_simple_with_star(): params_str = "b, *, a = 0" case_simple_with_star.__name__ = params_str # param_names = ['b', '*', 'a'] inputs = "12" args = () kwargs = {'a': 0, 'b': 12} return params_str, inputs, (args, kwargs) @pytest.mark.skipif(not star_followed_by_arg_allowed, reason='not allowed in this version of python') def case_simple_with_star_args1(): params_str = "b, *args, a = 0" case_simple_with_star_args1.__name__ = params_str # param_names = ['b', 'a'] inputs = "12" # args = () # kwargs = {'a': 0, 'b': 12} args = (12,) kwargs = {'a': 0} return params_str, inputs, (args, kwargs) @pytest.mark.skipif(not star_followed_by_arg_allowed, reason='not allowed in this version of python') def case_simple_with_star_args2(): params_str = "*args, a = 0" case_simple_with_star_args2.__name__ = params_str # param_names = ['b', 'a'] inputs = "12" args = (12, ) kwargs = {'a': 0} return params_str, inputs, (args, kwargs) def case_with_type_comments_and_newlines(): params_str = "b, # type: int\n" \ "a = 0 # type: float\n" case_with_type_comments_and_newlines.__name__ = params_str # param_names = ['b', 'a'] inputs = "12" args = () kwargs = {'a': 0, 'b': 12} return params_str, inputs, (args, kwargs) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/_test_py35.py�����������������������������������������������������������0000664�0003720�0003720�00000001171�13761735245�021173� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from asyncio import sleep def make_native_coroutine_handler(): """Returns a native coroutine to be used in tests""" async def my_native_coroutine_handler(sleep_time): await sleep(sleep_time) return sleep_time return my_native_coroutine_handler def make_ref_function(): """Returns a function with a type hint that is locally defined """ # the symbol is defined here, so it is not seen outside class A: pass def ref(a: A) -> A: pass return ref def make_ref_function2(): """ """ from typing import Any def ref(a: Any): pass return ref �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_create_from_signature.py�������������������������������������������0000664�0003720�0003720�00000001644�13761735245�024610� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from makefun import create_function try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: from funcsigs import signature, Signature, Parameter def my_handler(*args, **kwargs): """This docstring will be used in the generated function by default""" print("my_handler called !") return args, kwargs def test_positional_only(): """Tests that as of today one cannot create positional-only functions""" params = [Parameter('a', kind=Parameter.POSITIONAL_ONLY), Parameter('args', kind=Parameter.VAR_POSITIONAL), Parameter('kwargs', kind=Parameter.VAR_KEYWORD)] func_signature = Signature(parameters=params) with pytest.raises(SyntaxError): dynamic_fun = create_function(func_signature, my_handler, func_name="foo") print(dynamic_fun.__source__) assert dynamic_fun(0, 1) == ((1,), {'a': 0}) ��������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_advanced.py��������������������������������������������������������0000664�0003720�0003720�00000011572�13761735245�022007� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import logging import sys import pytest from makefun.main import get_signature_from_string, with_signature from makefun import wraps try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: from funcsigs import signature, Signature, Parameter def test_non_representable_defaults(): """ Tests that non-representable default values are handled correctly """ def foo(logger=logging.getLogger('default')): pass @wraps(foo) def bar(*args, **kwargs): pass bar() def test_preserve_attributes(): """ Tests that attributes are preserved """ def foo(): pass setattr(foo, 'a', True) @wraps(foo) def bar(*args, **kwargs): pass assert bar.a def test_empty_name_in_string(): """ Tests that string signatures can now be provided without function name""" if sys.version_info < (3, 0): str_sig = '(a)' else: str_sig = '(a:int)' func_name, func_sig, func_sig_str = get_signature_from_string(str_sig, locals()) assert func_name is None # to handle type hints in signatures in python 3.5 we have to always remove the spaces assert str(func_sig).replace(' ', '') == str_sig assert func_sig_str == str_sig + ':' def test_same_than_wraps_basic(): """Tests that the metadata set by @wraps is correct""" from makefun.tests.test_doc import test_from_sig_wrapper from functools import wraps as functools_wraps def foo_wrapper(*args, **kwargs): """ hoho """ pass functool_wrapped = functools_wraps(test_from_sig_wrapper)(foo_wrapper) # WARNING: functools.wraps irremediably contaminates foo_wrapper, we have to redefine it def foo_wrapper(*args, **kwargs): """ hoho """ pass makefun_wrapped = wraps(test_from_sig_wrapper)(foo_wrapper) # compare with the default behaviour of with_signature, that is to copy metadata from the decorated makefun_with_signature_inverted = with_signature(signature(test_from_sig_wrapper))(test_from_sig_wrapper) makefun_with_signature_normal = with_signature(signature(test_from_sig_wrapper))(foo_wrapper) for field in ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'): if sys.version_info < (3, 0) and field in {'__qualname__', '__annotations__'}: pass else: assert getattr(functool_wrapped, field) == getattr(makefun_wrapped, field), "field %s is different" % field assert getattr(functool_wrapped, field) == getattr(makefun_with_signature_inverted, field), "field %s is different" % field if field != '__annotations__': assert getattr(functool_wrapped, field) != getattr(makefun_with_signature_normal, field), "field %s is identical" % field def tests_wraps_sigchange(): """ Tests that wraps can be used to change the signature """ def foo(a): """ hoho """ return a @wraps(foo, new_sig="(a, b=0)") def goo(*args, **kwargs): kwargs.pop('b') return foo(*args, **kwargs) for field in ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'): if sys.version_info < (3, 0) and field in {'__qualname__', '__annotations__'}: pass else: assert getattr(goo, field) == getattr(foo, field), "field %s is different" % field assert str(signature(goo)) == "(a, b=0)" assert goo('hello') == 'hello' @pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3 or higher") def test_qualname_when_nested(): """ Tests that qualname is correctly set when `@with_signature` is applied on nested functions """ class C: def f(self): pass class D: @with_signature("(self, a)") def g(self): pass assert C.__qualname__ == 'test_qualname_when_nested.<locals>.C' assert C.f.__qualname__ == 'test_qualname_when_nested.<locals>.C.f' assert C.D.__qualname__ == 'test_qualname_when_nested.<locals>.C.D' # our mod assert C.D.g.__qualname__ == 'test_qualname_when_nested.<locals>.C.D.g' assert str(signature(C.D.g)) == "(self, a)" @pytest.mark.skipif(sys.version_info < (3, 5), reason="requires python 3.5 or higher (non-comment type hints)") def test_type_hint_error(): """ Test for https://github.com/smarie/python-makefun/issues/32 """ from makefun.tests._test_py35 import make_ref_function ref_f = make_ref_function() @wraps(ref_f) def foo(a): return a assert foo(10) == 10 @pytest.mark.skipif(sys.version_info < (3, 5), reason="requires python 3.5 or higher (non-comment type hints)") def test_type_hint_error2(): """ Test for https://github.com/smarie/python-makefun/issues/32 """ from makefun.tests._test_py35 import make_ref_function2 ref_f = make_ref_function2() @wraps(ref_f) def foo(a): return a assert foo(10) == 10 ��������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_generators_coroutines.py�������������������������������������������0000664�0003720�0003720�00000005760�13761735245�024667� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import pytest from makefun import with_signature, create_function try: from inspect import iscoroutinefunction except ImportError: # let's assume there are no coroutine functions in old Python def iscoroutinefunction(f): return False try: from inspect import isgeneratorfunction except ImportError: # assume no generator function in old Python versions def isgeneratorfunction(f): return False def test_generator(): """ Tests that we can use a generator as function_handler in `create_function`""" # define the handler that should be called def my_generator_handler(b, a=0): for i in range(a, b): yield i * i # create the dynamic function dynamic_fun = create_function("foo(a, b)", my_generator_handler) assert isgeneratorfunction(dynamic_fun) assert list(dynamic_fun(1, 4)) == [1, 4, 9] def test_generator_with_signature(): """ Tests that we can write a generator and change its signature: it will still be a generator """ @with_signature("foo(a)") def foo(*args, **kwargs): for i in range(1, 4): yield i * i assert isgeneratorfunction(foo) with pytest.raises(TypeError): foo() assert list(foo('dummy')) == [1, 4, 9] def test_generator_based_coroutine(): """ Tests that we can use a generator coroutine as function_handler in `create_function`""" # define the handler that should be called def my_gencoroutine_handler(first_msg): second_msg = (yield first_msg) yield second_msg # create the dynamic function dynamic_fun = create_function("foo(first_msg='hello')", my_gencoroutine_handler) # a legacy (generator-based) coroutine is not an asyncio coroutine.. assert not iscoroutinefunction(dynamic_fun) assert isgeneratorfunction(dynamic_fun) cor = dynamic_fun('hi') first_result = next(cor) assert first_result == 'hi' second_result = cor.send('chaps') assert second_result == 'chaps' with pytest.raises(StopIteration): cor.send('ola') @pytest.mark.skipif(sys.version_info < (3, 5), reason="native coroutines with async/await require python3.6 or higher") def test_native_coroutine(): """ Tests that we can use a native async coroutine as function_handler in `create_function`""" # define the handler that should be called from makefun.tests._test_py35 import make_native_coroutine_handler my_native_coroutine_handler = make_native_coroutine_handler() # create the dynamic function dynamic_fun = create_function("foo(sleep_time=2)", my_native_coroutine_handler) # check that this is a coroutine for inspect and for asyncio assert iscoroutinefunction(dynamic_fun) from asyncio import iscoroutinefunction as is_native_co assert is_native_co(dynamic_fun) # verify that the new function is a native coroutine and behaves correctly from asyncio import get_event_loop out = get_event_loop().run_until_complete(dynamic_fun(0.1)) assert out == 0.1 ����������������makefun-1.9.5/makefun/tests/test_partial_and_macros.py����������������������������������������������0000664�0003720�0003720�00000004777�13761735245�024075� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import functools import sys import makefun try: from inspect import signature except ImportError: from funcsigs import signature def test_doc(): def foo(x, y): """ a `foo` function :param x: :param y: :return: """ return x + y ref_bar = functools.partial(foo, x=12) bar = makefun.partial(foo, x=12) # not possible: the signature from functools is clunky # assert str(signature(ref_bar)) == str(signature(bar)) PY3 = not sys.version_info < (3, ) assert str(signature(ref_bar)) == "(x=12, y)" if not PY3 else "(*, x=12, y)" # todo we could also add keyword-only.. assert str(signature(bar)) == "(y, x=12)" bar.__name__ = 'bar' help(bar) assert bar(1) == 13 assert bar.__doc__ == """<This function is equivalent to 'foo(y, x=12)', see original 'foo' doc below.> a `foo` function :param x: :param y: :return: """ def test_partial(): """Tests that `with_partial` works""" @makefun.with_partial(y='hello') def foo(x, y, a): """ a `foo` function :param x: :param y: :param a: :return: """ print(a) print(x, y) foo(1, 2) help(foo) assert foo.__doc__ == """<This function is equivalent to 'foo(x, a, y='hello')', see original 'foo' doc below.> a `foo` function :param x: :param y: :param a: :return: """ def test_issue_57(): def f(b=0): """hey""" return b f.i = 1 # creating the decorator dec = makefun.wraps(functools.partial(f, b=2), func_name='foo') # applying the decorator n = dec(functools.partial(f, b=1)) # check metadata assert n.i == 1 # check signature assert n.__doc__ == """<This function is equivalent to 'f(b=2)', see original 'f' doc below.> hey""" # check implementation: the default value from the signature (from @wraps) is the one that applies here assert n() == 2 def test_create_with_partial(): def f(b=0): """hey""" return b f.i = 1 m = makefun.create_function("(b=-1)", functools.partial(f, b=2), **f.__dict__) assert str(signature(m)) == "(b=-1)" assert m() == -1 assert m.i == 1 # the doc remains untouched in create_function as opposed to wraps, this is normal assert m.__doc__ == """partial(func, *args, **keywords) - new function with partial application of the given arguments and keywords. """ �makefun-1.9.5/makefun/tests/test_doc.py�������������������������������������������������������������0000664�0003720�0003720�00000022322�13761735245�021002� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import pytest try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: from funcsigs import signature, Signature, Parameter from makefun import create_function, add_signature_parameters, remove_signature_parameters, with_signature, wraps, \ create_wrapper python_version = sys.version_info.major @pytest.mark.parametrize('decorator', [False, True], ids="decorator={}".format) @pytest.mark.parametrize('type', ['str', 'Signature'], ids="type={}".format) def test_ex_nihilo(type, decorator): """ First example from the documentation: tests that we can generate a function from a string """ # (1) define the signature. if type == 'str': func_sig = "foo(b, a=0)" func_name = None else: parameters = [Parameter('b', kind=Parameter.POSITIONAL_OR_KEYWORD), Parameter('a', kind=Parameter.POSITIONAL_OR_KEYWORD, default=0), ] func_sig = Signature(parameters) func_name = 'foo' # (2) define the function implementation def func_impl(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") return args, kwargs # (3) create the dynamic function if decorator: gen_func = with_signature(func_sig, func_name=func_name)(func_impl) else: gen_func = create_function(func_sig, func_impl, func_name=func_name) # first check the source code ref_src = "def foo(b, a=0):\n return _func_impl_(b=b, a=a)\n" print("Generated Source :\n" + gen_func.__source__) assert gen_func.__source__ == ref_src # then the behaviour args, kwargs = gen_func(2) assert args == () assert kwargs == {'a': 0, 'b': 2} @pytest.mark.skipif(sys.version_info < (3, 0), reason="keyword-only signatures require python 3+") def test_ex_nihilo_kw_only(): """Same than ex nihilo but keyword only""" def func_impl(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") return args, kwargs func_sig = "foo(b, *, a=0, **kwargs)" gen_func = create_function(func_sig, func_impl) ref_src = "def foo(b, *, a=0, **kwargs):\n return _func_impl_(b=b, a=a, **kwargs)\n" print(gen_func.__source__) assert gen_func.__source__ == ref_src @pytest.mark.parametrize('use_decorator', [False, True], ids="use_decorator={}".format) def test_from_sig_wrapper(use_decorator): """ Tests that we can create a function from a Signature object """ def foo(b, a=0): print("foo called: b=%s, a=%s" % (b, a)) return b, a # capture the name and signature of existing function `foo` func_name = foo.__name__ original_func_sig = signature(foo) print("Original Signature: %s" % original_func_sig) # modify the signature to add a new parameter params = list(original_func_sig.parameters.values()) params.insert(0, Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD)) func_sig = original_func_sig.replace(parameters=params) print("New Signature: %s" % func_sig) # define the implementation def func_impl(z, *args, **kwargs): print("func_impl called ! z=%s" % z) # call the foo function output = foo(*args, **kwargs) # return augmented output return z, output # create the dynamic function if use_decorator: gen_func = wraps(foo, new_sig=func_sig)(func_impl) else: gen_func = create_wrapper(foo, func_impl, new_sig=func_sig) # check the source code ref_src = "def foo(z, b, a=0):\n return _func_impl_(z=z, b=b, a=a)\n" print("Generated Source :\n" + gen_func.__source__) assert gen_func.__source__ == ref_src # then the behaviour assert gen_func(3, 2) == (3, (2, 0)) def test_helper_functions(): """ Tests that the signature modification helpers work """ def foo(b, c, a=0): pass # original signature foo_sig = signature(foo) print("original signature: %s" % foo_sig) # let's modify it new_sig = add_signature_parameters(foo_sig, first=Parameter('z', kind=Parameter.POSITIONAL_OR_KEYWORD), last=Parameter('o', kind=Parameter.POSITIONAL_OR_KEYWORD, default=True) ) new_sig = remove_signature_parameters(new_sig, 'b', 'a') print("modified signature: %s" % new_sig) assert str(new_sig) == '(z, c, o=True)' def test_injection(): """ Tests that the function can be injected as first argument when inject_as_first_arg=True """ def generic_handler(f, *args, **kwargs): print("This is generic handler called by %s" % f.__name__) # here you could use f.__name__ in a if statement to determine what to do if f.__name__ == "func1": print("called from func1 !") return args, kwargs # generate 2 functions func1 = create_function("func1(a, b)", generic_handler, inject_as_first_arg=True) func2 = create_function("func2(a, d)", generic_handler, inject_as_first_arg=True) func1(1, 2) func2(1, 2) def test_var_length(): """Demonstrates how variable-length arguments are passed to the handler """ # define the handler that should be called def generate_function(func_sig, dummy_call): def func_impl(*args, **kwargs): """This docstring will be used in the generated function by default""" print("func_impl called !") dummy_call(*args, **kwargs) return args, kwargs return create_function(func_sig, func_impl) func_sig = "foo(a, b=0, *args, **kwargs)" def dummy_call(a, b=0, *args, **kwargs): print() gen_func = generate_function(func_sig, dummy_call) print(gen_func.__source__) # unfortunately we can not have this because as soon as users provide a bit more positional args they there # are TypeErrors "got multiple values for argument 'a'" # assert gen_func(0, 1, 2) == ((2), {'a': 0, 'b': 1}) assert gen_func(0, 1, 2) == ((0, 1, 2), {}) assert gen_func(0, b=1) == ((0, 1), {}) # checks that the order is correctly set assert gen_func(b=1, a=0) == ((0, 1), {}) with pytest.raises(TypeError): gen_func(2, a=0, b=1) # -- func_sig = "foo(b=0, *args, **kwargs)" def dummy_call(b=0, *args, **kwargs): print() gen_func = generate_function(func_sig, dummy_call) print(gen_func.__source__) assert gen_func(1, 0) == ((1, 0), {}) assert gen_func(b=1) == ((1, ), {}) with pytest.raises(TypeError): gen_func(1, b=0) def test_positional_only(): """Tests that as of today positional-only signatures translate to bad strings """ params = [Parameter('a', kind=Parameter.POSITIONAL_ONLY), Parameter('b', kind=Parameter.POSITIONAL_OR_KEYWORD)] assert str(Signature(parameters=params)) in {"(<a>, b)", "(a, /, b)"} def test_with_signature(): """ Tests that @with_signature works as expected """ @with_signature("foo(a)") def foo(**kwargs): return 'hello' with pytest.raises(TypeError): foo() assert str(signature(foo)) == "(a)" assert foo('dummy') == 'hello' def test_with_signature_none(): """""" def foo(a): return a new = with_signature(None, func_name='f')(foo) assert new('hello') == 'hello' assert str(signature(new)) == "(a)" # check that the object was not wrapped assert new == foo assert new.__name__ == 'f' def test_wraps(capsys): """ """ # we want to wrap this function f to add some prints before calls def foo(a, b=1): return a + b # create our wrapper: it will have the same signature than f @wraps(foo) def enhanced_foo(*args, **kwargs): # we can very reliably access the value for 'b' print('hello!') print('b=%s' % kwargs['b']) # then call f as usual return foo(*args, **kwargs) assert enhanced_foo(1, 2) == 3 assert enhanced_foo(b=0, a=1) == 1 assert enhanced_foo(1) == 2 with pytest.raises(TypeError): # does not print anything in case of error enhanced_foo() captured = capsys.readouterr() with capsys.disabled(): print(captured.out) assert captured.out == """hello! b=2 hello! b=0 hello! b=1 """ def test_wraps_functools(capsys): """ same with functools.wraps """ from functools import wraps # we want to wrap this function f to add some prints before calls def foo(a, b=1): return a + b # create our wrapper: it will have the same signature than f @wraps(foo) def enhanced_foo(*args, **kwargs): # we can very reliably access the value for 'b' print('hello!') print('b=%s' % kwargs['b']) # then call f as usual return foo(*args, **kwargs) # assert enhanced_foo(1, 2) == 3 assert enhanced_foo(b=0, a=1) == 1 # assert enhanced_foo(1) == 2 with pytest.raises(KeyError): # prints a message in case of error enhanced_foo() captured = capsys.readouterr() with capsys.disabled(): print(captured.out) assert captured.out == """hello! b=0 hello! """ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_so.py��������������������������������������������������������������0000664�0003720�0003720�00000020650�13761735245�020660� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from __future__ import print_function import sys from inspect import getmodule import pytest from makefun import create_function, wraps, partial, with_partial try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: from funcsigs import signature, Signature, Parameter def test_create_facades(capsys): """ Simple test to create multiple functions with the same body This corresponds to the answer at https://stackoverflow.com/questions/13184281/python-dynamic-function-creation-with-custom-names/55105893#55105893 :return: """ # generic core implementation def generic_impl(f, *args, **kwargs): print("This is generic impl called by %s" % f.__name__) # here you could use f.__name__ in a if statement to determine what to do if f.__name__ == "func1": print("called from func1 !") return args, kwargs my_module = getmodule(generic_impl) # generate 3 facade functions with various signatures for f_name, f_params in [("func1", "b, *, a"), ("func2", "*args, **kwargs"), ("func3", "c, *, a, d=None")]: if f_name in {"func1", "func3"} and sys.version_info < (3, 0): # Python 2 does not support function annotations; Python 3.0-3.4 do not support variable annotations. pass else: # the signature to generate f_sig = "%s(%s)" % (f_name, f_params) # create the function dynamically f = create_function(f_sig, generic_impl, inject_as_first_arg=True) # assign the symbol somewhere (local context, module...) setattr(my_module, f_name, f) # grab each function and use it if sys.version_info >= (3, 0): func1 = getattr(my_module, 'func1') assert func1(25, a=12) == ((), dict(b=25, a=12)) func2 = getattr(my_module, 'func2') assert func2(25, a=12) == ((25,), dict(a=12)) if sys.version_info >= (3, 0): func3 = getattr(my_module, 'func3') assert func3(25, a=12) == ((), dict(c=25, a=12, d=None)) captured = capsys.readouterr() with capsys.disabled(): print(captured.out) if sys.version_info >= (3, 0): assert captured.out == """This is generic impl called by func1 called from func1 ! This is generic impl called by func2 This is generic impl called by func3 """ else: assert captured.out == """This is generic impl called by func2 """ def test_so_decorator(): """ Tests that solution at https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators/1594484#1594484 actually works """ # from functools import wraps def makebold(fn): @wraps(fn) def wrapped(): return "<b>" + fn() + "</b>" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(): return "<i>" + fn() + "</i>" return wrapped @makebold @makeitalic def hello(): """what?""" return "hello world" assert hello() == "<b><i>hello world</i></b>" assert hello.__name__ == "hello" help(hello) # the help and signature are preserved assert hasattr(hello, '__wrapped__') def test_so_facade(): def create_initiation_function(cls, gen_init): # (1) check which signature we want to create params = [Parameter('self', kind=Parameter.POSITIONAL_OR_KEYWORD)] for mandatory_arg_name in cls.__init_args__: params.append(Parameter(mandatory_arg_name, kind=Parameter.POSITIONAL_OR_KEYWORD)) for default_arg_name, default_arg_val in cls.__opt_init_args__.items(): params.append(Parameter(default_arg_name, kind=Parameter.POSITIONAL_OR_KEYWORD, default=default_arg_val)) sig = Signature(params) # (2) create the init function dynamically return create_function(sig, generic_init) # ----- let's use it def generic_init(self, *args, **kwargs): """Function to initiate a generic object""" assert len(args) == 0 for name, val in kwargs.items(): setattr(self, name, val) class my_class: __init_args__ = ["x", "y"] __opt_init_args__ = {"my_opt": None} my_class.__init__ = create_initiation_function(my_class, generic_init) # check o1 = my_class(1, 2) assert vars(o1) == {'y': 2, 'x': 1, 'my_opt': None} o2 = my_class(1, 2, 3) assert vars(o2) == {'y': 2, 'x': 1, 'my_opt': 3} o3 = my_class(my_opt='hello', y=3, x=2) assert vars(o3) == {'y': 3, 'x': 2, 'my_opt': 'hello'} def test_so_sig_preserving(capsys): """ Tests that the answer at https://stackoverflow.com/a/55163391/7262247 is correct """ def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) wrapper._decorator_name_ = 'my_decorator' return wrapper @my_decorator def my_func(x): """my function""" print('hello %s' % x) assert my_func._decorator_name_ == 'my_decorator' help(my_func) captured = capsys.readouterr() with capsys.disabled(): print(captured.out) assert captured.out == """Help on function my_func in module makefun.tests.test_so: my_func(x) my function """ def test_sig_preserving_2(capsys): """ Checks that answer at https://stackoverflow.com/a/55163816/7262247 works """ def args_as_ints(func): @wraps(func) def wrapper(*args, **kwargs): print("wrapper executes") # convert all to int. note that in a signature-preserving wrapper almost all args will come as kwargs args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return func(*args, **kwargs) return wrapper @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z print(funny_function("3", 4.0, z="5")) # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z with pytest.raises(TypeError): funny_function(0) # TypeError: funny_function() takes at least 2 arguments (1 given) captured = capsys.readouterr() with capsys.disabled(): print(captured.out) assert captured.out == """wrapper executes 22 Help on function funny_function in module makefun.tests.test_so: funny_function(x, y, z=3) Computes x*y + 2*z """ def test_so_partial(capsys): """ Tests that the answer at https://stackoverflow.com/a/55165541/7262247 is correct """ def foo(a, b, c=1): """Return (a+b)*c.""" return (a + b) * c bar10_p = partial(foo, b=10) assert bar10_p(0) == 10 assert bar10_p(0, c=2) == 20 help(bar10_p) captured = capsys.readouterr() with capsys.disabled(): print(captured.out) assert captured.out == """Help on function foo in module makefun.tests.test_so: foo(a, b=10, c=1) <This function is equivalent to 'foo(a, b=10, c=1)', see original 'foo' doc below.> Return (a+b)*c. """ def test_so_partial2(capsys): """ Tests that the solution at https://stackoverflow.com/a/55161579/7262247 works (the one using makefun only. for the other two, see test.so.py in decopatch project) """ @with_partial(a='hello', b='world') def test(a, b, x, y): print(a, b) print(x, y) test(1, 2) help(test) @with_partial(a='hello', b='world') def test(a, b, x, y): """Here is a doc""" print(a, b) print(x, y) help(test) captured = capsys.readouterr() with capsys.disabled(): print(captured.out) ref_str = """hello world 1 2 Help on function test in module makefun.tests.test_so: test(x, y, a='hello', b='world') <This function is equivalent to 'test(x, y, a='hello', b='world')'.> Help on function test in module makefun.tests.test_so: test(x, y, a='hello', b='world') <This function is equivalent to 'test(x, y, a='hello', b='world')', see original 'test' doc below.> Here is a doc """ if (3, 0) <= sys.version_info < (3, 6): # in older versions of python, the order of **kwargs is not guaranteed (see PEP 468) assert captured.out.replace('a=hello', 'b=world') == ref_str.replace('a=hello', 'b=world') else: assert captured.out == ref_str ����������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_create_from_string.py����������������������������������������������0000664�0003720�0003720�00000010112�13761735245�024103� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import pytest from pytest_cases import cases_data from makefun import create_function from makefun.tests import test_create_from_string_cases # Python 2 does not support function annotations; Python 3.0-3.4 do not support variable annotations. params_type_hints_allowed = sys.version_info.major >= 3 and sys.version_info.minor >= 5 if params_type_hints_allowed: type_hints_variants = [False, 1, 2] else: type_hints_variants = [False] @pytest.mark.parametrize('with_self_ref', [True, False], ids="self_ref={}".format) @pytest.mark.parametrize('params_type_hints', type_hints_variants, ids="type_hints={}".format) def test_basic(params_type_hints, with_self_ref): """ Tests that we can create a simple dynamic function from a signature string, redirected to a generic handler. """ if params_type_hints == 1: from typing import Any func_signature = "foo(b, # type: int\n" \ " a = 0, # type: float\n" \ " ):\n # type: (...) -> Any" elif params_type_hints == 2: from typing import Any func_signature = "foo(b: int, a: float=0) -> Any" else: func_signature = "foo(b, a=0)" # this handler will grab the inputs and return them if with_self_ref: def identity_handler(facade, *args, **kwargs): """test doc""" return facade, args, kwargs else: def identity_handler(*args, **kwargs): """test doc""" return args, kwargs # create the dynamic function dynamic_fun = create_function(func_signature, identity_handler, inject_as_first_arg=with_self_ref) # a few asserts on the signature assert dynamic_fun.__name__ == 'foo' assert dynamic_fun.__doc__ == 'test doc' assert dynamic_fun.__module__ == test_basic.__module__ if params_type_hints == 1: # unfortunately assert dynamic_fun.__annotations__ == {} elif params_type_hints == 2: assert dynamic_fun.__annotations__ == {'a': float, 'b': int, 'return': Any} else: assert dynamic_fun.__annotations__ == {} assert dynamic_fun.__defaults__ == (0,) assert dynamic_fun.__kwdefaults__ is None if params_type_hints != 1: func_signature = func_signature + ":" if with_self_ref: src = "def " + func_signature + '\n return _func_impl_(foo, b=b, a=a)\n' else: src = "def " + func_signature + '\n return _func_impl_(b=b, a=a)\n' dct = {'__source__': src, '__func_impl__': identity_handler} if not params_type_hints_allowed: dct['__annotations__'] = dict() dct['__kwdefaults__'] = None assert vars(dynamic_fun) == dct # try to call it ! if with_self_ref: f, args, kwargs = dynamic_fun(2) assert f is dynamic_fun else: args, kwargs = dynamic_fun(2) assert args == () assert kwargs == {'a': 0, 'b': 2} # def test_sig(): # b_param = Parameter() # parameters = OrderedDict( # ((b_param.name, b_param), # (a_param.name, a_param)) # ) # # s = Signature().replace(parameters=parameters) # # s = s.replace(return_annotation=return_annotation) @pytest.mark.skip("This test is known to fail because inspect.signature does not detect comment type hints") def test_type_comments(): """Tests that """ func_signature = """ foo(b, # type: int a = 0, # type: float ): # type: (...) -> str """ def dummy_handler(*args, **kwargs): return "hello" dynamic_fun = create_function(func_signature, dummy_handler) assert dynamic_fun.__annotations__ == {'a': float, 'b': int, 'return': str} @cases_data(module=test_create_from_string_cases) def test_arguments(case_data): """ Tests that the `PARAM_DEF` regexp works correctly """ def generic_handler(*args, **kwargs): return args, kwargs params_str, inputs, (eargs, ekwargs) = case_data.get() f = create_function("foo(%s)" % params_str, generic_handler) args, kwargs = eval("f(%s)" % inputs, globals(), locals()) assert args == eargs assert kwargs == ekwargs ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/__init__.py�������������������������������������������������������������0000664�0003720�0003720�00000000000�13761735245�020722� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/makefun/tests/test_issues.py����������������������������������������������������������0000664�0003720�0003720�00000004512�13761735245�021551� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import sys import pytest from makefun import wraps, with_signature, partial @pytest.mark.skip("known to fail") def test_wraps_varpositional(): """ test for https://github.com/smarie/python-makefun/issues/34 """ def f(a, *args): pass @wraps(f) def foo(*args, **kwargs): return f(*args, **kwargs) foo('hello', 12) def test_varpositional2(): """ test for https://github.com/smarie/python-makefun/issues/38 """ @with_signature("(a, *args)") def foo(a, *args): assert a == 'hello' assert args == (12, ) foo('hello', 12) def test_invalid_signature_str(): """Test for https://github.com/smarie/python-makefun/issues/36""" sig = "(a):" @with_signature(sig) def foo(a): pass @pytest.mark.skipif(sys.version_info < (3, 0), reason="type hints are not allowed with this syntax in python 2") def test_invalid_signature_str_py3(): """Test for https://github.com/smarie/python-makefun/issues/36""" sig = "(a) -> int:" @with_signature(sig) def foo(a): pass def test_return_annotation_in_py2(): """Test for https://github.com/smarie/python-makefun/issues/39""" def f(): pass f.__annotations__ = {'return': None} @wraps(f) def b(): pass b() def test_init_replaced(): class Foo(object): @with_signature("(self, a)") def __init__(self, *args, **kwargs): pass f = Foo(1) class Bar(Foo): def __init__(self, *args, **kwargs): super(Bar, self).__init__(*args, **kwargs) b = Bar(2) def test_issue_55(): """Tests that no syntax error appears when no arguments are provided in the signature (name change scenario)""" # full name change including stack trace @with_signature('bar()') def foo(): return 'a' assert "bar at" in repr(foo) assert foo.__name__ == 'bar' assert foo() == 'a' # only metadata change @with_signature(None, func_name='bar') def foo(): return 'a' if sys.version_info >= (3, 0): assert "foo at" in repr(foo) assert foo.__name__ == 'bar' assert foo() == 'a' def test_partial_noargs(): """ Fixes https://github.com/smarie/python-makefun/issues/59 """ def foo(): pass foo._mark = True g = partial(foo) assert g._mark is True ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/setup.cfg�����������������������������������������������������������������������������0000664�0003720�0003720�00000000302�13761735350�015644� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[egg_info] tag_build = tag_date = 0 [bdist_wheel] universal = 1 [metadata] description-file = README.md [aliases] test = pytest [tool:pytest] addopts = --verbose testpaths = makefun/tests ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/.travis.yml���������������������������������������������������������������������������0000664�0003720�0003720�00000011713�13761735245�016147� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������language: python cache: pip matrix: fast_finish: true include: # - python: 2.6 - python: 2.7 # - python: 3.2 # - python: 3.3 # - python: 3.4 - python: 3.5 - python: 3.6 - python: 3.7 dist: xenial sudo: true env: global: - GH_REF: git@github.com:smarie/python-makefun.git before_install: # (a) linux dependencies - sudo apt-get install ant - sudo apt-get install ant-optional install: - pip list # needs to be installed beforehand - pip install setuptools_scm - python ci_tools/py_install.py pip ci_tools/requirements-pip.txt # this does not work anymore on python 2 so lets only do it when needed - if [ "${TRAVIS_PYTHON_VERSION}" = "3.5" ]; then pip install mkdocs-material mkdocs; fi; # travis-specific installs - pip install PyGithub # for ci_tools/github_release.py - pip install codecov # See https://github.com/codecov/example-python. - pip list script: # - coverage run tests.py - pip install . - python -c "import os; os.chdir('..'); import makefun" # ***tests*** # - coverage run tests.py # - pytest --junitxml=reports/junit/junit.xml --html=reports/junit/report.html --cov-report term-missing --cov=./makefun -v makefun/tests/ # now done in a dedicated script to capture exit code 1 and transform it to 0 - chmod a+x ./ci_tools/run_tests.sh - sh ./ci_tools/run_tests.sh - python ci_tools/generate-junit-badge.py 100 # generates the badge for the test results and fail build if less than x% after_success: # ***reporting*** # - junit2html junit.xml testrun.html output is really not nice - ant -f ci_tools/generate-junit-html.xml # generates the html for the test results. Actually we dont use it anymore - codecov # - pylint makefun # note that at the moment the report is simply lost, we dont transform the result into anything # ***documentation*** - mkdocs build -f docs/mkdocs.yml - mv reports/junit docs/ # not in site/ anymore so that we do not need to use --dirty (it breaks client-side search) # mkdocs gh-deploy requires special care : # ---grant the possibility to push on the repo--- - openssl aes-256-cbc -K $encrypted_8056e8a9ebfc_key -iv $encrypted_8056e8a9ebfc_iv -in ci_tools/github_travis_rsa.enc -out ci_tools/github_travis_rsa -d # If the output file does not exist, that is because the secret is invalid. This can happen in forked repos so do not fail the build - | if [ -s "ci_tools/github_travis_rsa" ]; then chmod 600 ci_tools/github_travis_rsa eval `ssh-agent -s` # launch the authentication agent ssh-add ci_tools/github_travis_rsa # register the decrypted key git config user.name "Automatic Publish" git config user.email "sylvain.marie@schneider-electric.com" git remote add gh-remote "${GH_REF}"; git fetch gh-remote && git fetch gh-remote gh-pages:gh-pages; # make sure we have the latest gh-remote # push but only if this is not a build triggered by a pull request # note: do not use the --dirty flag as it breaks client-side search if [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_PYTHON_VERSION}" = "3.5" ]; then echo "Pushing to github"; PYTHONPATH=makefun/ mkdocs gh-deploy -v -f docs/mkdocs.yml --remote-name gh-remote; git push gh-remote gh-pages; fi; else echo "File 'ci_tools/github_travis_rsa' has not been created, please check your encrypted repo token in .travis.yml, on the line starting with 'openssl aes-256-cbc...'" fi # -- create the _version.py file # - python ci_tools/write_version.py ./makefun deploy: # Deploy on PyPI on tags - provider: pypi user: "smarie" password: secure: "FIVqZqP9kgOQ3/F2wyqEI88o6Ma2ec3o+6hh+Ry89Eqkkxt5zyzpsfxfoAxvLoSD1q2ERS0/aMYr4RHziIWtQXgIAAh7FcqeDj5UKqeDGVWOYHTpEhWlL7DBI2SMa7EM72uPncP9KaMiTE7oZkcIkISIpGU4kN5bvAjEZPMK7Nu9216HWVJyCjC34EkBiRWUX1MrFk8eqLbV7g4oRC8zp0SgQ89ihLAVL1XJRKIKERSHnkh4YCCZQkj3AfUK+6LVuTT+dptI90m5UYVvhAYKX/d6szb+0A94Mxp3u+HcEa93JbMXxMih+EnHD2rDtzGGaKtiCIOMBnTG1GQG7U6wt3Vp71GDqTH3TxxdBKjI8JkamIC72mmVrMGEsl37iWhOyex3uS71akQwgQm7H5fm5+sCqo8EXUmq24B48XL03a+A5EE13EfRSJGuOYdiLVcH9PadFCvjZYvw6HGsF4JgIWA23Sk9eQqiilyqqBEDxw0lJnpMwmv41KOlvxIjeVtBzkOkTslJkgFSzuFWhu2jdGhj+BRY6tqf9jQXi9WlQ2XVF0LgOQrC5sleR9QmlRXqLbRcsOd+wMvV0DExyMH9zuTqeUJhBNVxbTFDvXAyo+kkKK1qcefqG54J7jMrLjV2og2irOJAB5xObiA+0zVlgszKyYTIoq83IH6pU1rtMXM=" on: tags: true python: 3.5 #only one of the builds have to be deployed # condition: $PYTEST_VERSION = "<3" # server: https://test.pypi.org/legacy/ distributions: "sdist bdist_wheel" # Create a github release on tags - provider: script script: python ci_tools/github_release.py -s $GITHUB_TOKEN --repo-slug smarie/python-makefun -cf ./docs/changelog.md -d https://smarie.github.io/python-makefun/changelog/ $TRAVIS_TAG skip_cleanup: true on: tags: true python: 3.5 #only one of the builds have to be deployed # condition: $PYTEST_VERSION = "<3" notifications: email: on_success: never # options: [always|never|change] default: always �����������������������������������������������������makefun-1.9.5/LICENSE�������������������������������������������������������������������������������0000664�0003720�0003720�00000003016�13761735245�015040� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BSD 3-Clause License Copyright (c) 2019-2020, Sylvain MariĆ©, Schneider Electric Industries All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/pyproject.toml������������������������������������������������������������������������0000664�0003720�0003720�00000000133�13761735245�016744� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[build-system] requires = [ "pytest-runner", "setuptools", "setuptools_scm", ] �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������makefun-1.9.5/setup.py������������������������������������������������������������������������������0000664�0003720�0003720�00000013527�13761735245�015555� 0����������������������������������������������������������������������������������������������������ustar �travis��������������������������travis��������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""A setuptools based setup module. See: https://packaging.python.org/en/latest/distributing.html https://github.com/pypa/sampleproject """ from os import path import pkg_resources from setuptools import setup, find_packages pkg_resources.require("setuptools>=39.2") pkg_resources.require("setuptools_scm") from setuptools_scm import get_version # noqa: E402 # *************** Dependencies ********* INSTALL_REQUIRES = ['funcsigs;python_version<"3.3"'] DEPENDENCY_LINKS = [] SETUP_REQUIRES = ['pytest-runner', 'setuptools_scm'] TESTS_REQUIRE = ['pytest', 'pytest-logging', 'pytest-cases'] EXTRAS_REQUIRE = {} # ************** ID card ***************** DISTNAME = 'makefun' DESCRIPTION = 'Small library to dynamically create python functions.' AUTHOR = 'Sylvain MARIE' MAINTAINER = AUTHOR AUTHOR_EMAIL = 'sylvain.marie@se.com' MAINTAINER_EMAIL = AUTHOR_EMAIL URL = 'https://github.com/smarie/python-makefun' DOWNLOAD_URL = URL + '/tarball/' + get_version() LICENSE = 'BSD 3-Clause' LICENSE_LONG = 'License :: OSI Approved :: BSD License' KEYWORDS = 'decorate decorator compile make dynamic function generate generation define definition signature ' \ 'args wrapper' here = path.abspath(path.dirname(__file__)) with open(path.join(here, 'docs', 'long_description.md')) as f: LONG_DESCRIPTION = f.read() # OBSOLETES = [] setup( name=DISTNAME, description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/markdown', # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html # version=VERSION, NOW HANDLED BY GIT author=AUTHOR, author_email=AUTHOR_EMAIL, maintainer=MAINTAINER, maintainer_email=MAINTAINER_EMAIL, license=LICENSE, url=URL, download_url=DOWNLOAD_URL, # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ # How mature is this project? Common values are # 3 - Alpha # 4 - Beta # 5 - Production/Stable 'Development Status :: 5 - Production/Stable', # Indicate who your project is intended for 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries :: Python Modules', # Pick your license as you wish (should match "license" above) LICENSE_LONG, # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. # 'Programming Language :: Python :: 2', # 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', # 'Programming Language :: Python :: 3', # 'Programming Language :: Python :: 3.3', # 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', # 'Framework :: Pytest' ], # What does your project relate to? keywords=KEYWORDS, # You can just specify the packages manually here if your project is # simple. Or you can use find_packages(). packages=find_packages(exclude=['contrib', 'docs', '*tests*']), # Alternatively, if you want to distribute just a my_module.py, uncomment # this: # py_modules=["my_module"], # List run-time dependencies here. These will be installed by pip when # your project is installed. For an analysis of "install_requires" vs pip's # requirements files see: # https://packaging.python.org/en/latest/requirements.html install_requires=INSTALL_REQUIRES, dependency_links=DEPENDENCY_LINKS, # we're using git use_scm_version={'write_to': '%s/_version.py' % DISTNAME}, # this provides the version + adds the date if local non-commited changes. # use_scm_version={'local_scheme':'dirty-tag'}, # this provides the version + adds '+dirty' if local non-commited changes. setup_requires=SETUP_REQUIRES, # test # test_suite='nose.collector', tests_require=TESTS_REQUIRE, # List additional groups of dependencies here (e.g. development # dependencies). You can install these using the following syntax, # for example: # $ pip install -e .[dev,test] extras_require=EXTRAS_REQUIRE, # obsoletes=OBSOLETES # If there are data files included in your packages that need to be # installed, specify them here. If using Python 2.6 or less, then these # have to be included in MANIFEST.in as well. # Note: we use the empty string so that this also works with submodules package_data={"": ['py.typed', '*.pyi']}, # IMPORTANT: DO NOT set the `include_package_data` flag !! It triggers inclusion of all git-versioned files # see https://github.com/pypa/setuptools_scm/issues/190#issuecomment-351181286 # include_package_data=True, # Although 'package_data' is the preferred approach, in some case you may # need to place data files outside of your packages. See: # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa # In this case, 'data_file' will be installed into '<sys.prefix>/my_data' # data_files=[('my_data', ['data/data_file'])], # To provide executable scripts, use entry points in preference to the # "scripts" keyword. Entry points provide cross-platform support and allow # pip to create the appropriate form of executable for the target platform. # entry_points={ # 'console_scripts': [ # 'sample=sample:main', # ], # }, # explicitly setting the flag to avoid `ply` being downloaded # see https://github.com/smarie/python-getversion/pull/5 # and to make mypy happy # see https://mypy.readthedocs.io/en/latest/installed_packages.html zip_safe=False, ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������