# i2.wrapper

A wrapper object and tools to work with it

How the `Wrap` class works:

```default
      *outer_args, **outer_kwargs
                 │
                 ▼
┌───────────────────────────────────┐
│              ingress              │
└───────────────────────────────────┘
                 │
                 ▼
      *inner_args, **inner_kwargs
                 │
                 ▼
┌───────────────────────────────────┐
│               func                │
└───────────────────────────────────┘
                 │
                 ▼
             func_output
                 │
                 ▼
┌───────────────────────────────────┐
│              egress               │
└───────────────────────────────────┘
                 │
                 ▼
            final_output
```

How the `Ingress` class (ingress templated function maker) works:

```default
      *outer_args, **outer_kwargs
                 │
                 ▼
┌───────────────────────────────────┐
│          outer_sig_bind           │
└───────────────────────────────────┘
                 │
                 ▼
          outer_all_kwargs
                 │
                 ▼
┌───────────────────────────────────┐
│            kwargs_trans           │
└───────────────────────────────────┘
                 │
                 ▼
          inner_all_kwargs
                 │
                 ▼
┌───────────────────────────────────┐
│          inner_sig_bind           │
└───────────────────────────────────┘
                 │
                 ▼
      *inner_args, **inner_kwargs
```

### Module Attributes

| [`AUTO_PRESERVE_SIGNATURE`](#i2.wrapper.AUTO_PRESERVE_SIGNATURE)   | `preserve_signature` value meaning "decide per ingress" (see `_should_preserve_signature()`).   |
|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|

### Functions

| `add_smart_defaults`([func, ...])                                                                   | Add smart defaults to function.                                                                                                           |
|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| [`append_empty_args`](#i2.wrapper.append_empty_args)(func)                            | To use to transform an ingress function that only returns kwargs to one that returns the normal form of ingress functions: ((), kwargs)   |
| [`apply_func_on_cond`](#i2.wrapper.apply_func_on_cond)(func, cond, k, v)               | Return `func(v)` if `cond(k, v)` is true, else `v` unchanged.                                                                             |
| [`arg_val_converter`](#i2.wrapper.arg_val_converter)(func, \*\*conversion_for_arg)    | Wrap `func` so that the given arguments are converted (`name=converter`) before the call.                                                 |
| [`arg_val_converter_ingress`](#i2.wrapper.arg_val_converter_ingress)(func[, \_\_strict])      | Function form of `ArgValConverterIngress`: an ingress converting the named arguments.                                                     |
| [`bind_funcs_object_attrs`](#i2.wrapper.bind_funcs_object_attrs)(funcs[, ...])              | Transform one or several functions into a class that contains them as methods sourcing specific arguments from the instance's attributes. |
| [`bind_funcs_object_attrs_old`](#i2.wrapper.bind_funcs_object_attrs_old)(funcs[, ...])          | Transform one or several functions into a class that contains them as methods sourcing specific arguments from the instance's attributes. |
| [`camelize`](#i2.wrapper.camelize)(s)                                        |                                                                                                                                           |
| `ch_names`([func])                                                                                  | Change the argument names of a function.                                                                                                  |
| [`complete_dict_applying_functions`](#i2.wrapper.complete_dict_applying_functions)(d, /[, ...])      | Complete dict `d` by applying function to variables in `d`, sequentially.                                                                 |
| [`convert_VK_to_KO`](#i2.wrapper.convert_VK_to_KO)(kinds)                            | In a `{name: kind}` dict, replace VAR_KEYWORD kinds with KEYWORD_ONLY.                                                                    |
| [`convert_dict_values`](#i2.wrapper.convert_dict_values)(to_convert, ...)               | Yield `(key, value)` pairs of `to_convert`, converting the values whose key has a function.                                               |
| [`func_to_method_func`](#i2.wrapper.func_to_method_func)(func[, instance_params, ...])  | Get a 'method function' from a 'normal function'.                                                                                         |
| [`identity`](#i2.wrapper.identity)(x)                                        | Return the input unchanged.                                                                                                               |
| `include_exclude`([func, include, exclude])                                                         | Reorder and/or remove parameters.                                                                                                         |
| [`include_exclude_ingress_factory`](#i2.wrapper.include_exclude_ingress_factory)(func[, ...])       | A pattern underlying any ingress that takes a subset of parameters (possibly reordering them).                                            |
| [`invert_map`](#i2.wrapper.invert_map)(d)                                      | Swap keys and values of a mapping, raising `ValueError` if values are not unique.                                                         |
| [`items_with_mapped_keys`](#i2.wrapper.items_with_mapped_keys)(d, key_mapper)              | Transform dict keys.                                                                                                                      |
| [`kwargs_trans`](#i2.wrapper.kwargs_trans)([kwargs, \_recursive, \_inplace])     | Transform a kwargs dict or build a transformer.                                                                                           |
| [`kwargs_trans_to_extract_args_from_attrs`](#i2.wrapper.kwargs_trans_to_extract_args_from_attrs)(...)       | Pop the `obj_param` object out of `outer_kwargs` and source `attr_names` from its attributes.                                             |
| `map_names`([func])                                                                                 | Change the argument names of a function.                                                                                                  |
| [`mk_ingress_from_name_mapper`](#i2.wrapper.mk_ingress_from_name_mapper)(func, name_mapper, \*) | Make an ingress that renames `func`'s parameters (`{inner_name: outer_name}`).                                                            |
| [`modify_dict_on_cond`](#i2.wrapper.modify_dict_on_cond)(d, cond, func)                 | Copy `d`, applying `func` to the values whose `(key, value)` satisfy `cond`.                                                              |
| [`move_names_to_the_end`](#i2.wrapper.move_names_to_the_end)(names, ...)                  | Remove the items of `names_to_move_to_the_end` from `names` and append to the right of names                                              |
| [`move_params_to_the_end`](#i2.wrapper.move_params_to_the_end)(func, names_to_move)        | Choose args from func, according to choice_args_func and move them to the right                                                           |
| [`param_to_dataclass_field_tuple`](#i2.wrapper.param_to_dataclass_field_tuple)(param)              | The `(name, annotation, default)` tuple `dataclasses.make_dataclass` expects for a field.                                                 |
| [`parameters_to_dict`](#i2.wrapper.parameters_to_dict)(parameters)                     | Map each parameter name to its `parameter_to_dict` (name, kind, default, annotation) dict.                                                |
| [`params_used_in_funcs`](#i2.wrapper.params_used_in_funcs)(funcs)                        | The set of parameter names of all the given functions.                                                                                    |
| [`partialx`](#i2.wrapper.partialx)(func, \*args[, \_\_name_\_, ...])         | Extends the functionality of builtin `functools.partial` with the ability to                                                              |
| [`required_params_used_in_funcs`](#i2.wrapper.required_params_used_in_funcs)(funcs)               | The set of names that are required (no default) in at least one of the given functions.                                                   |
| `rm_params`([func, params_to_remove, ...])                                                          | Get a function with some parameters removed.                                                                                              |
| [`transparent_egress`](#i2.wrapper.transparent_egress)(output)                         |                                                                                                                                           |
| [`transparent_ingress`](#i2.wrapper.transparent_ingress)(\*args, \*\*kwargs)            |                                                                                                                                           |
| `wrap`([func, ingress, egress, caller, name, ...])                                                  | Wrap a function, optionally transforming interface, input and output.                                                                     |
| [`wrap_from_sig`](#i2.wrapper.wrap_from_sig)(func, new_sig)                       | Give `func` the signature `new_sig`, calling it with only the arguments it takes.                                                         |

### Classes

| [`ArgNameMappingIngress`](#i2.wrapper.ArgNameMappingIngress)(inner_sig, \*[, ...])     | Ingress that renames parameters: called with outer names, returns inner (args, kwargs).                 |
|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| [`ArgValConverterIngress`](#i2.wrapper.ArgValConverterIngress)(func[, ...])             | Ingress with `func`'s signature that applies `name=converter` functions to arguments.                   |
| [`Ingress`](#i2.wrapper.Ingress)(inner_sig[, kwargs_trans, ...])         | The Ingress class offers a template for creating ingress classes.                                       |
| [`InnerMapIngress`](#i2.wrapper.InnerMapIngress)(inner_sig[, kwargs_trans, ...]) | Build an ingress from the wrapped function's signature and a spec of changes to it.                     |
| [`MakeFromFunc`](#i2.wrapper.MakeFromFunc)(func_to_obj)                       | Used to indicate that an object should be made as a function of an input func                           |
| [`PickleHelperMixin`](#i2.wrapper.PickleHelperMixin)()                             | Mixin whose `__reduce__` pickles an instance by its `__name__` (a global reference).                    |
| [`SmartDefault`](#i2.wrapper.SmartDefault)(func_computing_default, ...)       | Placeholder default for a parameter whose value `add_smart_defaults` computes from the other arguments. |
| [`Wrap`](#i2.wrapper.Wrap)(func[, ingress, egress, name, ...])        | A function wrapper with interface modifiers.                                                            |
| [`Wrapx`](#i2.wrapper.Wrapx)(func[, ingress, egress, caller, name])    | An extended wrapping object that allows more complex wrapping mechanisms.                               |

### Exceptions

| [`CallerValidationError`](#i2.wrapper.CallerValidationError)   | Raised when a caller is not valid                          |
|--------------------------------------------------------------------------|------------------------------------------------------------|
| [`EgressValidationError`](#i2.wrapper.EgressValidationError)   | Raised when a egress is not valid                          |
| [`IngressValidationError`](#i2.wrapper.IngressValidationError)  | Raised when a ingress is not valid                         |
| [`WrapperValidationError`](#i2.wrapper.WrapperValidationError)  | Raised when wrapper some construction params are not valid |

### i2.wrapper.AUTO_PRESERVE_SIGNATURE *= 'auto'*

`preserve_signature` value meaning “decide per ingress” (see
`_should_preserve_signature()`). Named rather than spelled `'auto'` at each
use so the sentinel has exactly one definition.

### *class* i2.wrapper.ArgNameMappingIngress(inner_sig, , conserve_kind=False, \*\*outer_name_for_inner_name)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Ingress that renames parameters: called with outer names, returns inner (args, kwargs).

Unless `conserve_kind=True`, all parameter kinds of the outer signature become
POSITIONAL_OR_KEYWORD. `mk_ingress_from_name_mapper` is the function form.

### *class* i2.wrapper.ArgValConverterIngress(func, \_ArgValConverterIngress_\_strict=True, \*\*conversion_for_arg)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Ingress with `func`’s signature that applies `name=converter` functions to arguments.

Names that are not parameters of `func` are rejected with an `AssertionError`
at construction (the `__strict` parameter cannot be passed by keyword from outside
the class, since the name is mangled; use `arg_val_converter_ingress` to switch it
off).

### *exception* i2.wrapper.CallerValidationError

Bases: [`WrapperValidationError`](#i2.wrapper.WrapperValidationError)

Raised when a caller is not valid

### *exception* i2.wrapper.EgressValidationError

Bases: [`WrapperValidationError`](#i2.wrapper.WrapperValidationError)

Raised when a egress is not valid

### *class* i2.wrapper.Ingress(inner_sig, kwargs_trans=None, outer_sig=None, , allow_excess=True, apply_defaults=True, allow_partial=False)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

The Ingress class offers a template for creating ingress classes.

Note that when writing a decorator with `i2.wrapper`, you’re usually better off
writing an ingress function for the purpose. As a result, your code will usually
be less complex, easier to read, and more efficient than using the Ingress class.

So why use the `Ingress` class at all? For one, because it’ll take care of some
common mechanics for you, so once you understand how to use it, you’ll probably
create a correct wrapper faster.

Further, if you’re writing a general wrapping tool (e.g. your own currying machine,
some rule-based input casting function, etc.) then you’ll find that using
Ingres will usually with on the complexity, readability and/or efficiency front.

Consider the following function:

```pycon
>>> def f(w, /, x: float, y=2, *, z: int = 3):
...     return f"(w:={w}) + (x:={x}) * (y:={y}) ** (z:={z}) == {w + x * y ** z}"
>>>
>>> f(0, 1)
'(w:=0) + (x:=1) * (y:=2) ** (z:=3) == 8'
```

Let’s say you wanted to dispatch this function to a command line interface,
or a webservice where all arguments are taken from the url.
The problem here is that this means that all incoming values will be strings
in that case.
Say you wanted all input values to be cast to ints. In that case you could do:

```pycon
>>> from i2.wrapper import Ingress, wrap
>>> from inspect import signature
>>>
>>> trans_all_vals_to_ints = lambda d: {k: int(v) for k, v in d.items()}
>>>
>>> cli_f = wrap(
...     f,
...     ingress=Ingress(signature(f), kwargs_trans=trans_all_vals_to_ints)
... )
>>>
>>> cli_f("2", "3", "4")
'(w:=2) + (x:=3) * (y:=4) ** (z:=3) == 194'
```

In a more realistic situation, you’d want to have more control over this value
transformation.

Say you wanted to convert to int if it’s possible, try float if not,
and just leave the string alone otherwise.

```pycon
>>> def _try_casting_to_numeric(x):
...     try:
...         return int(x)
...     except ValueError:
...         try:
...             return float(x)
...         except ValueError:
...             return x
...
>>> def cast_numbers(d: dict):
...     return {k: _try_casting_to_numeric(v) for k, v in d.items()}
>>>
>>> cli_f = wrap(f, ingress=Ingress(signature(f), kwargs_trans=cast_numbers))
>>>
>>> cli_f("2", "3.14", "4")
'(w:=2) + (x:=3.14) * (y:=4) ** (z:=3) == 202.96'
```

Let’s say that our values transformations are not all 1-to-1 as in the examples
above.
Instead, they can be

- `1-to-many` (e.g. the outer `w` is used to compute the inner `w` and `x`)
- `many-to-1` (e.g. the outer `x` and `y` are used to compute inner `y`)

```default
  w   x   y   z
 / \   \ /    |
w   x   y     z
```

```pycon
>>> def kwargs_trans(outer_kw):
...     return dict(
...         # e.g. 1-to-many: one outer arg (w) producing two inner args (w, and y)
...         w=outer_kw['w'] * 2,
...         x=outer_kw['w'] * 3,
...         # e.g. many-to-1: two outer args (x and y) producing one inner arg (y)
...         y=outer_kw['x'] + outer_kw['y'],
...         # Note that no z is mentioned: This means we're just leaving it alone
...     )
...
>>>
>>> ingress = Ingress(signature(f), kwargs_trans=kwargs_trans)
>>> assert ingress(2, x=3, y=4) == ((4,), {'x': 6, 'y': 7, 'z': 3})
>>>
>>> wrapped_f = wrap(f, ingress=ingress)
>>> assert wrapped_f(2, x=3, y=4) == '(w:=4) + (x:=6) * (y:=7) ** (z:=3) == 2062'
```

The following is an example that involves several aspects of the `Ingress` class.

```pycon
>>> from i2 import Sig
>>> def kwargs_trans(outer_kw):
...     return dict(
...         w=outer_kw['w'] * 2,
...         x=outer_kw['w'] * 3,
...         # need to pop you (inner func has no you argument)
...         y=outer_kw['x'] + outer_kw.pop('you'),
...         # Note that no z is mentioned: This means we're just leaving it alone
...     )
>>>
>>> ingress = Ingress(
...     inner_sig=signature(f),
...     kwargs_trans=kwargs_trans,
...     outer_sig=Sig(f).ch_names(y='you')  # need to give the outer sig a you
...     # You could also express it this way (though you'd lose the annotations)
...     # outer_sig=lambda w, /, x, you=2, *, z=3: None
... )
>>> assert ingress(2, x=3, you=4) == ((4,), {'x': 6, 'y': 7, 'z': 3})
>>>
>>> wrapped_f = wrap(f, ingress=ingress)
>>> assert wrapped_f(2, x=3, you=4) == '(w:=4) + (x:=6) * (y:=7) ** (z:=3) == 2062'
```

A convenience method allows to do the same with the ingress instance itself:

```pycon
>>> wrapped_f = ingress.wrap(f)
>>> assert wrapped_f(2, x=3, you=4) == '(w:=4) + (x:=6) * (y:=7) ** (z:=3) == 2062'
```

#### *classmethod* name_map(wrapped, \*\*old_to_new_name)

Change argument names.

```pycon
>>> def f(w, /, x: float, y=2, *, z: int = 3):
...     return f"(w:={w}) + (x:={x}) * (y:={y}) ** (z:={z}) == {w + x * y ** z}"
>>> ingress = Ingress.name_map(f, w='DoubleYou', z='Zee')
>>> ingress
Ingress signature: (DoubleYou, /, x: float, y=2, *, Zee: int = 3)
>>> wrapped_f = ingress.wrap(f)
>>> wrapped_f(1, 2, y=3, Zee=4)
'(w:=1) + (x:=2) * (y:=3) ** (z:=4) == 163'
```

#### wrap(func, egress=None, , name=None)

Convenience method to wrap a function with the instance ingress.
`ingress.wrap(func,...)` equivalent to `Wrap(func, ingress, ...)`

* **Return type:**
  [`Wrap`](#i2.wrapper.Wrap)

### *exception* i2.wrapper.IngressValidationError

Bases: [`WrapperValidationError`](#i2.wrapper.WrapperValidationError)

Raised when a ingress is not valid

### *class* i2.wrapper.InnerMapIngress(inner_sig, kwargs_trans=None, , \_allow_reordering=False, \*\*changes_for_name)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Build an ingress from the wrapped function’s signature and a spec of changes to it.

*Systematically*, i.e. “according to a fixed plan/system” is what it’s about here.
As we’ll see below, if you need to write a particular adapter for a specific case,
you probably should do by writing an actual ingress function directly.
In cases where you might want to apply a same logic to wrap many functions,
you may want to fix that wrapping logic: `InnerMapIngress` provides one
way to do this.

* **Parameters:**
  * **inner_sig** – The signature of the wrapped function.
  * **kwargs_trans** ([`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)[[[`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)], [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)] | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – A dict-to-dict transformation of the outer kwargs to
    the kwargs that should be input to the inner function.
    That is `kwargs_trans` is `outer_kwargs -> inner_kwargs`.
    Note that though both outer and inner signatures could have those annoying
    position-only kinds, you don’t have to think of that.
    The parameter kind restrictions are taken care of automatically.
  * **\_allow_reordering** – Whether we want to allow reordering of variables
  * **in_to_out_sig_changes** – The `inner_name=dict_of_changes_for_that_name`
    pairs, the `dict_of_changes_for_that_name` is a `dict` with keys being valid
    `inspect.Parameter`

Consider the following function that has a position only, a keyword only,
two arguments with annotations, and three with a default.

```pycon
>>> def f(w, /, x: float = 1, y=2, *, z: int = 3):
...     return w + x * y ** z
```

Say we wanted a version of this function

- that didn’t have the argument kind restrinctions (all POSITION_OR_KEYWORD),
- where the annotation of `x` was changed `int` and the default removed
- where `y` was named `you` instead, and has an annotation (`int`).
- where the default of `z` was `10` instead of `3`, and doesn’t have an
  annotation.

In order to get a version of this function we wanted (more lenient kinds,
with some annotations and a default change), we can use the ingress function:

```pycon
>>> def directly_defined_ingress(w, x: int, you: int=2, z = 10):
...     return (w,), dict(x=x, y=you, z=z)
```

When we need to wrap a specific function in a specific way, defining an
ingress function  this way is usually the simplest way.
But in some cases we need to build the ingress function using some predefined
rule/protocol to make applying the rule/protocol systematic.

For those cases, `InnerMapIngress` comes in handy.

With `InnerMapIngress` we’d build our ingress function like this:

```pycon
>>> from inspect import Parameter, signature
>>> PK = Parameter.POSITIONAL_OR_KEYWORD
>>> empty = Parameter.empty
>>> ingress = InnerMapIngress(
...     f,
...     # change kind to PK:
...     w=dict(kind=PK),
...     # change annotation of x from float to int and remove default
...     x=dict(annotation=int, default=empty),
...     # rename y to you and add annotation int:
...     y=dict(name='you', annotation=int),
...     # change kind to PK, default to 10, and remove annotation:
...     z=dict(kind=PK, default=10, annotation=empty),
... )
```

**Note**

- Only the changes we wish to make to the parameters are mentioned.
  You could also define the parameters explicitly by simply listing all three
  of the dimensions (kind, annotation, and default)
- Three? But a `Parameter` object has four; what about the name?
  Indeed, you can use name as well, more on that later.
- Note that in order to specify that you want no default, or no annotation,
  you cannot use `None` since `None` is both a valid default and a valid
  annotation; So instead you need to use `Parameter.empty` (conveniently
  assigned to a constant named `empty` in the `wrapping` module.

Now see that all arguments are `POSITIONAL_OR_KEYWORD`, `x` and `y` are
`int`, and default of `z` is 10:

```pycon
>>> assert (
...     str(signature(ingress))
...     == str(signature(directly_defined_ingress))
...     == '(w, x: int, you: int = 2, z=10)'
... )
```

Additionally, `ingress` function does it’s job of dispatching the right args
and kwargs to the target function:

```pycon
>>> assert (
...     ingress(0,1,2,3)
...     == directly_defined_ingress(0,1,2,3)
...     == ((0,), {'x': 1, 'y': 2, 'z': 3})
... )
```

#### *classmethod* from_signature(inner_sig, outer_sig, \_allow_reordering=False)

A convienience ingress constructor to specify wrappings that affect arguments
independently.

* **Parameters:**
  * **inner_sig** – The signature of wrapped, inner function (or the inner
    function itself)
  * **outer_sig** – The desired outer signature. Can also use a function (will
    only take it’s signature though).
  * **\_allow_reordering** – Whether to allow `outer_sig` to reorder arguments.
* **Returns:**
  An ingress that will allow one to use a function having the
  `inner_sig` signature to

Say we wanted to get a version of the function:

```pycon
>>> def f(w, /, x: float = 1, y=2, *, z: int = 3):
...     return w + x * y ** z
```

That was equivalent to (note the kind, default and annotation differences):

```pycon
>>> def g(w, x=1, y: float = 2.0, z=10):
...     return w + x * y ** z
```

```pycon
>>> h = wrap(f, ingress=InnerMapIngress.from_signature(f, g))
>>> Sig(h)
<Sig (w, x=1, y: float = 2.0, z=10)>
```

Note we could have used `...from_signature(Sig(f), Sig(g))` as well,
since the method doesn’t use the actual functions, just their signatures.

So we’ve seen that `h` takes on the signature (kind, defaults,
and annotations) of `g`.
Let’s see now that `h` actually computes, uses the defaults of `g` and
can doesn’t have the position only restriction on `w`.

```pycon
>>> assert h(0) == g(0) == 1024 == 0 + 1 * 2 ** 10
>>> assert h(1,2) == g(1,2) == 2049 == 1 + 2 * 2 ** 10
>>> assert h(1,2,3,4) == g(1,2,3,4) == 1 + 2 * 3 ** 4
>>>
>>> assert h(w=1,x=2,y=3,z=4) == g(1,2,3,4) == 1 + 2 * 3 ** 4  # w keyword arg!
```

### *class* i2.wrapper.MakeFromFunc(func_to_obj)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Used to indicate that an object should be made as a function of an input func

### *class* i2.wrapper.PickleHelperMixin

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Mixin whose `__reduce__` pickles an instance by its `__name__` (a global reference).

### *class* i2.wrapper.SmartDefault(func_computing_default, original_default)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Placeholder default for a parameter whose value `add_smart_defaults` computes from the other arguments.

Holds the function that computes the value and, if the parameter had one, its
original default (shown in the repr).

#### original_default

alias of `_empty`

### *class* i2.wrapper.Wrap(func, ingress=None, egress=None, , name=None, preserve_signature='auto')

Bases: `_Wrap`

A function wrapper with interface modifiers.

* **Parameters:**
  * **func** – The wrapped function
  * **ingress** – The incoming data transformer. It determines the argument properties
    (name, kind, default and annotation) as well as the actual input of the
    wrapped function.
  * **egress** – The outgoing data transformer. It also takes precedence over the
    wrapped function to determine the return annotation of the `Wrap` instance
  * **name** – Name to give the wrapper (will use wrapped func name by default)
  * **preserve_signature** – 

    Controls signature preservation from the wrapped function.
    - ’auto’ (default): Automatically preserve if ingress has `(*args, **kwargs)` signature
    - True: Always preserve signature from func (copies \_\_signature_\_)
    - False: Don’t preserve (use ingress’s natural signature)

    When signature is preserved, both \_\_signature_\_ and \_\_annotations_\_ are
    copied from func to the wrapper, ensuring type checkers and IDEs see
    the original signature.
* **Returns:**
  A callable instance wrapping `func`

Some examples:

```pycon
>>> from inspect import signature
>>> from i2 import Sig
```

```pycon
>>> def func(a, b):
...     return a * b
```

```pycon
>>> wrapped_func = wrap(func)  # no transformations: wrapped_func is the same as func
>>> assert wrapped_func(2, 'Hi') == func(2, 'Hi') == 'HiHi'
```

Modifying the first argument

```pycon
>>> def ingress(a, b):
...   return (2 * a, b), dict()
>>> wrapped_func = wrap(func, ingress=ingress)  # first variable is now multiplied by 2
>>> wrapped_func(2, 'Hi')
'HiHiHiHi'
```

Same using keyword args, we need to use tuple to represent an empty tuple

```pycon
>>> def ingress(a, b):
...   return tuple(), dict(a=2 * a, b=b) # Note that b MUST be present as well, or an error will be raised
>>> wrapped_func = wrap(func, ingress=ingress)  # first variable is now multiplied by 2
>>> wrapped_func(2, 'Hi')
'HiHiHiHi'
```

Using both args and kwargs

```pycon
>>> def ingress(a, b):
...   return (2 * a, ), dict(b=b)
>>> wrapped_func = wrap(func, ingress=ingress)  # first variable is now multiplied by 2
>>> wrapped_func(2, 'Hi')
'HiHiHiHi'
```

We can use ingress to ADD parameters to func

```pycon
>>> def ingress(a, b, c):
...   return (a, b + c), dict()
>>> wrapped_func = wrap(func, ingress=ingress)
>>> # now wrapped_func takes three arguments
>>> wrapped_func(2, 'Hi', 'world!')
'Hiworld!Hiworld!'
```

Egress is a bit more straightforward, it simply applies to the output of the
wrapped function. We can use ingress to ADD parameters to func

```pycon
>>> def egress(output):
...   return output + ' ITSME!!!'
>>> wrapped_func = wrap(func, ingress=ingress, egress=egress)
>>> # now wrapped_func takes three arguments
>>> wrapped_func(2, 'Hi', 'world!')
'Hiworld!Hiworld! ITSME!!!'
```

A more involved example:

```pycon
>>> def ingress(a, b: str, c="hi"):
...     return (a + len(b) % 2,), dict(string=f"{c} {b}")
...
>>> def func(times, string):
...     return times * string
...
>>> wrapped_func = wrap(func, ingress=ingress)
>>> assert wrapped_func(2, "world! ", "Hi") == "Hi world! Hi world! Hi world! "
>>>
>>> wrapped_func = wrap(func, egress=len)
>>> assert wrapped_func(2, "co") == 4 == len("coco") == len(func(2, "co"))
>>>
>>> wrapped_func = wrap(func, ingress=ingress, egress=len)
>>> assert (
...     wrapped_func(2, "world! ", "Hi")
...     == 30
...     == len("Hi world! Hi world! Hi world! ")
... )
```

An `ingress` function links the interface of the wrapper to the interface of the
wrapped func; therefore it’s definition often depends on information of both,
and for that reason, we provide the ability to specify the ingress not only
explicitly (as in the examples above), but through a factory – a function that
will be called on `func` to produce the ingress that should be used to wrap it.

**Common Patterns and Best Practices**

**Pattern 1: Transform inputs while preserving signature**

By default (with preserve_signature=’auto’), Wrap automatically preserves
signatures when your ingress uses `(*args, **kwargs)`:

```pycon
>>> def uppercase_args(func):
...     def ingress(*args, **kwargs):
...         args = tuple(str(a).upper() if isinstance(a, str) else a for a in args)
...         return args, kwargs
...     return Wrap(func, ingress=ingress)
>>>
>>> @uppercase_args
... def greet(name: str, greeting: str = "Hello") -> str:
...     return f"{greeting}, {name}!"
>>>
>>> greet("alice")  # Signature preserved, input transformed
'Hello, ALICE!'
```

**Pattern 2: Keep return annotation with transparent egress**

When using an egress that doesn’t transform the type, return annotations
are automatically preserved:

```pycon
>>> def add_logging(func):
...     def egress(output):
...         # print(f"Result: {output}")  # Commented out for doctest
...         return output  # Type unchanged
...     return Wrap(func, egress=egress)
>>>
>>> @add_logging
... def calculate(x: int) -> int:
...     return x * 2
>>>
>>> result = calculate(5)  # Return type preserved as int
>>> result
10
```

**Pattern 3: Validation without transformation**

Use ingress for validation without modifying arguments:

```pycon
>>> def validate_positive(func):
...     def ingress(*args, **kwargs):
...         if any(a <= 0 for a in args if isinstance(a, (int, float))):
...             raise ValueError("All numeric arguments must be positive")
...         return args, kwargs
...     return Wrap(func, ingress=ingress)
>>>
>>> @validate_positive
... def multiply(x: int, y: int) -> int:
...     return x * y
>>>
>>> multiply(2, 3)
6
```

**Pattern 4: Error handling and logging**

Wrap both ends for comprehensive error handling:

```pycon
>>> def safe_call(func):
...     def ingress(*args, **kwargs):
...         # print(f"Calling {func.__name__}")  # Commented out for doctest
...         return args, kwargs
...
...     def egress(output):
...         # print(f"Success: {output}")  # Commented out for doctest
...         return output
...
...     return Wrap(func, ingress=ingress, egress=egress)
```

## Common Pitfalls and Solutions

**Pitfall 1: Losing signatures with explicit non-generic ingress**

If your ingress doesn’t use `(*args, **kwargs)`, the auto mode won’t preserve
the signature. Use preserve_signature=True explicitly:

```pycon
>>> # WRONG: Signature lost with non-generic ingress
>>> def my_func(x: int, y: int = 5) -> int:
...     return x + y
>>>
>>> def ingress(x, y):  # Specific signature
...     return (x,), {'y': y}
>>>
>>> # Without explicit preservation, signature won't match original
>>> wrapped = Wrap(my_func, ingress=ingress, preserve_signature=False)
>>> # Signature is now (x, y) instead of (x: int, y: int = 5) -> int
>>>
>>> # RIGHT: Explicit preservation
>>> wrapped = Wrap(my_func, ingress=ingress, preserve_signature=True)
>>> # Now signature is correctly (x: int, y: int = 5) -> int
```

**Pitfall 2: Type-changing egress without annotation**

If your egress changes the output type, annotate it. Otherwise, the function’s
original return type will be preserved, creating incorrect type hints:

```pycon
>>> # RIGHT: Egress annotated with correct return type
>>> def stringify(func):
...     def egress(output) -> str:  # Annotated!
...         return str(output)
...     return Wrap(func, egress=egress)
>>>
>>> @stringify
... def calc(x: int) -> str:  # Note: return annotation updated
...     return x * 2
>>>
>>> isinstance(calc(5), str)
True
```

**Pitfall 3: Forgetting to return (args, kwargs) from ingress**

Ingress MUST return a tuple of (args, kwargs) for the wrapped function:

```pycon
>>> # WRONG: ingress doesn't return (args, kwargs)
>>> # def broken_ingress(*args, **kwargs):
>>> #     print("called")
>>> #     return None  # WRONG! Must return (args, kwargs)
>>>
>>> # RIGHT: Always return (args, kwargs)
>>> def correct_ingress(*args, **kwargs):
...     # Do any processing here
...     return args, kwargs  # CORRECT
```

**Pitfall 4: Modifying mutable arguments in place**

Be careful when modifying arguments - changes affect the original objects:

```pycon
>>> # RIGHT: Create new objects
>>> def fixed(func):
...     def ingress(*args, **kwargs):
...         if args and isinstance(args[0], list):
...             args = ([*args[0], 999],) + args[1:]  # New list
...         return args, kwargs
...     return Wrap(func, ingress=ingress)
```

## Backward Compatibility Notes

**Signature Preservation (v3.0):**

Currently, preserve_signature defaults to ‘auto’ which only preserves
signatures for generic `(*args, **kwargs)` ingress functions. In v3.0,
we may change the default to True to always preserve signatures unless
explicitly disabled. This matches user expectations that decorators
should preserve signatures by default.

To prepare for this change:

- If you want current behavior: explicitly set preserve_signature=’auto’
- If you want v3.0 behavior: explicitly set preserve_signature=True
- If you never want preservation: explicitly set preserve_signature=False

#### SEE ALSO
`wrap` function.

### *exception* i2.wrapper.WrapperValidationError

Bases: [`ValueError`](https://docs.python.org/3/builtins/exceptions.html#ValueError)

Raised when wrapper some construction params are not valid

### *class* i2.wrapper.Wrapx(func, ingress=None, egress=None, , caller=None, name=None)

Bases: `_Wrap`

An extended wrapping object that allows more complex wrapping mechanisms.

* **Parameters:**
  * **func** – The wrapped function
  * **ingress** – The incoming data transformer. It determines the argument properties
    (name, kind, default and annotation) as well as the actual input of the
    wrapped function.
  * **egress** – The outgoing data transformer. It also takes precedence over the
    wrapped function to determine the return annotation of the `Wrap` instance
  * **caller** – A caller defines what it means to call the `func` on the
    arguments it is given. It should be of the form
    `caller(func, args, kwargs, *, ...extra_keyword_only_params)`.
    By default, the caller will simply return `func(*args, **kwargs)`.
  * **name** – Name to give the wrapper (will use wrapped func name by default)
* **Returns:**
  A callable instance wrapping `func`

```pycon
>>> from inspect import signature
>>>
>>> def func(x, y):
...     return x + y
...
>>> def save_on_output_egress(v, *, k, s):
...     s[k] = v
...     return v
...
>>> save_on_output = Wrapx(func, egress=save_on_output_egress)
>>> # TODO: should be `(x, y, *, k, s)` --> Need to work on the merge for this.
>>> str(signature(save_on_output))
'(x, y, k, s)'
>>>
>>> store = dict()
>>> save_on_output(1, 2, k='save_here', s=store)
3
>>> assert save_on_output(1, 2, k='save_here', s=store) == 3 == func(1, 2)
>>> store  # see what's in the store now!
{'save_here': 3}
```

A caller is meant to control the way the function is called.
It is given the `func` and the `func_args` and `func_kwargs`
(whatever the ingress function gives it, if present) and possibly additional
params and will return… well, what ever you tell it to.

This can be used, for example, to call the function in a subprocess,
or on a remote system, differ computation (command pattern, for example, using
`functools.partial`, or do what ever needs to have a view both on the function
and its inputs.

Here, we will wrap the function so it will apply to an iterable of inputs
(of the first argument), returning a list of results

```pycon
>>> def func(x, y=2):
...     return x + y
...
>>> def iterize(func, args, kwargs):
...     first_arg_val = next(iter(kwargs.values()))
...     return list(map(func, first_arg_val))
...
>>> iterized_func = Wrapx(func, caller=iterize)
>>> iterized_func([1, 2, 3, 4])
[3, 4, 5, 6]
```

Let’s do the same as above, but allow other variables (here `y`) to be input as
well. This takes a bit more work…

```pycon
>>> from functools import partial
>>> def _iterize_first_arg(func, args, kwargs):
...     first_arg_name = next(iter(kwargs))
...     remaining_kwargs = {
...         k: v for k, v in kwargs.items() if k != first_arg_name
...     }
...     return list(
...         map(partial(func, **remaining_kwargs), kwargs[first_arg_name])
...     )
```

Let’s demo a different way of using Wrapx: Making a wrapper to apply at
function definition time

```pycon
>>> iterize_first_arg = partial(Wrapx, caller=_iterize_first_arg)
>>> @iterize_first_arg
... def func(x, y):
...     return x + y
>>>
>>> func([1, 2, 3, 4], 10)
[11, 12, 13, 14]
```

### i2.wrapper.append_empty_args(func)

To use to transform an ingress function that only returns kwargs to one that
returns the normal form of ingress functions: ((), kwargs)

### i2.wrapper.apply_func_on_cond(func, cond, k, v)

Return `func(v)` if `cond(k, v)` is true, else `v` unchanged.

### i2.wrapper.arg_val_converter(func, \*\*conversion_for_arg)

Wrap `func` so that the given arguments are converted (`name=converter`) before the call.

```pycon
>>> def f(x, y=1):
...     return x + y
>>> g = arg_val_converter(f, x=int)
>>> g('2', 3)
5
```

### i2.wrapper.arg_val_converter_ingress(func, \_\_strict=True, \*\*conversion_for_arg)

Function form of `ArgValConverterIngress`: an ingress converting the named arguments.

### i2.wrapper.bind_funcs_object_attrs(funcs, init_params=(), , cls=None, module=None, \*\*extra_attrs)

Transform one or several functions into a class that contains them as methods
sourcing specific arguments from the instance’s attributes.

```pycon
>>> from inspect import signature
>>> from dataclasses import dataclass
>>>
>>> def foo(a, b, c=2, *, d='bar'):
...     return f"{d}: {(a + b) * c}"
>>> foo(1, 2)
'bar: 6'
>>> Klass = bind_funcs_object_attrs(foo, init_params='a c')
>>> Klass.__name__
'Foo'
>>> instance = Klass(a=1, c=3)
>>> assert instance.foo(2, d='hello') == 'hello: 9' == foo(
...     a=1, b=2, c=3, d='hello')
>>> str(signature(Klass))
'(a, c=2) -> None'
>>>
>>> instance = Klass(a=1, c=3)
>>> str(instance)
'Foo(a=1, c=3)'
>>> str(signature(instance.foo))
"(b, *, d='bar')"
>>> instance.foo(2, d='hello')
'hello: 9'
>>> instance.foo(10, d='goodbye')
'goodbye: 33'
```

```pycon
>>> def foo(a, b, c):
...     return a + b * c
...
>>> def bar(d, e):
...     return f"{d=}, {e=}"
...
>>> @dataclass
... class K:
...     a: int
...     e: int
...
>>> C = bind_funcs_object_attrs([foo, bar], 'a e', cls=K)
>>> str(signature(C))
'(a: int, e: int) -> None'
>>> c = C(1,2)
>>> assert str(signature(c.foo)) == '(b, c)'
>>> c.foo(3,4)
13
>>> assert str(signature(c.bar)) == '(d)'
>>> c.bar(5)
'd=5, e=2'
```

### i2.wrapper.bind_funcs_object_attrs_old(funcs, init_params=(), , cls=None)

Transform one or several functions into a class that contains them as methods
sourcing specific arguments from the instance’s attributes.

```pycon
>>> from inspect import signature
>>> from dataclasses import dataclass
>>>
>>> def foo(a, b, c=2, *, d='bar'):
...     return f"{d}: {(a + b) * c}"
>>> foo(1, 2)
'bar: 6'
>>> Klass = bind_funcs_object_attrs_old(foo, init_params='a c')
>>> Klass.__name__
'Foo'
>>> instance = Klass(a=1, c=3)
>>> assert instance.foo(2, d='hello') == 'hello: 9' == foo(
...     a=1, b=2, c=3, d='hello')
>>> str(signature(Klass))
'(a, c=2) -> None'
>>>
>>> instance = Klass(a=1, c=3)
>>> str(instance)
'Foo(a=1, c=3)'
>>> str(signature(instance.foo))
"(b, *, d='bar')"
>>> instance.foo(2, d='hello')
'hello: 9'
>>> instance.foo(10, d='goodbye')
'goodbye: 33'
>>> def foo(a, b, c):
...     return a + b * c
...
>>> def bar(d, e):
...     return f"{d=}, {e=}"
...
>>> @dataclass
... class K:
...     a: int
...     e: int
...
>>> C = bind_funcs_object_attrs([foo, bar], 'a e', cls=K)
>>> str(signature(C))
'(a: int, e: int) -> None'
>>> c = C(1,2)
>>> assert str(signature(c.foo)) == '(b, c)'
>>> c.foo(3,4)
13
>>> assert str(signature(c.bar)) == '(d)'
>>> c.bar(5)
'd=5, e=2'
```

### i2.wrapper.camelize(s)

```pycon
>>> camelize('camel_case')
'CamelCase'
```

### i2.wrapper.complete_dict_applying_functions(d, , \_only_if_name_missing=True, \_allow_overwrites=False, \*\*func_for_name)

Complete dict `d` by applying function to variables in `d`, sequentially.

That is, doing `d[name] = func(**d)` for all `name, func in d.items()`.

Set `_allow_overwrites=True` to allow overwrites.

Set `_only_if_name_missing=False` to apply all functions of `func_for_name`
regardless if the `name` already exists in `d` or not.

```pycon
>>> func_for_name = dict(
...     b=lambda a: a * 10, c=lambda a, b: a + b, d=lambda c: c * 2
... )
>>> complete_dict_applying_functions(dict(a=1), **func_for_name)
{'a': 1, 'b': 10, 'c': 11, 'd': 22}
```

Notice that when `b` is present in input `dict`, it’s value is conserved.
That is, the `b` of `func_for_name` isn’t applied to compute it.

```pycon
>>> complete_dict_applying_functions(dict(a=1, b=2), **func_for_name)
{'a': 1, 'b': 2, 'c': 3, 'd': 6}
```

If you specify `_only_if_name_missing=False`,
`complete_dict_applying_functions` will try to compute everything
`func_for_name` tells it too, regardless if the input dictionary contains the
key or not, resulting in an error:

```pycon
>>> complete_dict_applying_functions(
...     dict(a=1, b=2), **func_for_name, _only_if_name_missing=False
... )
Traceback (most recent call last):
  ...
i2.errors.OverwritesNotAllowed: You're not allowed to overwrite to the values of b
```

If you want, on the other hand, to allow overwrites, you can do so specifying
`_allow_overwrites=True`:

```pycon
>>> complete_dict_applying_functions(
...     dict(a=1, b=2), **func_for_name,
...     _only_if_name_missing=False, _allow_overwrites=True
... )
{'a': 1, 'b': 10, 'c': 11, 'd': 22}
```

### i2.wrapper.convert_VK_to_KO(kinds)

In a `{name: kind}` dict, replace VAR_KEYWORD kinds with KEYWORD_ONLY.

### i2.wrapper.convert_dict_values(to_convert, key_to_conversion_function)

Yield `(key, value)` pairs of `to_convert`, converting the values whose key has a function.

```pycon
>>> dict(convert_dict_values({'x': '2', 'y': 3}, {'x': int}))
{'x': 2, 'y': 3}
```

### i2.wrapper.func_to_method_func(func, instance_params=(), , method_name=None, method_params=None, instance_arg_name='self')

Get a ‘method function’ from a ‘normal function’. Also known as “methodize”.

That is, get a function that gives the same outputs as the ‘normal function’,
except that some of the arguments are sourced from the attributes of the first
argument.

The intended use case is when you want to inject one or several methods in a class
or instance, sourcing some of the arguments of the underlying function from a
common pool: The attributes of the instance.

Consider the following function involving four parameters: `a, b, c` and `d`.

* **Return type:**
  [`Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)

```pycon
>>> def func(a, b: int, c=2, *, d='bar'):
...     return f"{d}: {(a + b) * c}"
>>> func(1, 2, c=3, d='hello')
'hello: 9'
```

If we wanted to make an equivalent “method function” that would source it’s `a`
and it’s `c` from the first argument’s (in practice this first argument will be
and instance of the class the method will be bound to), we can do so like so:

```pycon
>>> method_func = func_to_method_func(func, 'a c')
>>> from inspect import signature
>>> str(signature(method_func))
"(self, b: int, *, d='bar')"
```

Note that the first argument is `self` (default name for an “instance”),
that `a` and `c` are not there, but that the two remaining parameters,
`b` and `d` are present, in the same order, and with the same annotations and
parameter kind (the `d` is still keyword-only).

Now let’s make a dummy object that has attributes `a` and a `c`, and use it to
call `method_func`:

```pycon
>>> from collections import namedtuple
>>> instance = namedtuple('FakeInstance', 'a c')(1, 3)
>>> method_func(instance, 2, d='hello')
'hello: 9'
```

Which is:

```pycon
>>> assert method_func(instance, 2, d='hello') == func(1, 2, c=3, d='hello')
```

Often, though, what you’ll want is to include this method function directly in
a class, as you’re making that class “normally”. That works too:

```pycon
>>> from dataclasses import dataclass
>>> @dataclass
... class Klass:
...     a : int = 1
...     c : int = 3
...     method_func = func_to_method_func(func, 'a c')
>>> instance = Klass(1, 3)
>>> instance.method_func(2, d='hello')
'hello: 9'
```

What if your function has argument names that don’t correspond to the names you
have, or want, as attributes of the class? Or even, you have several functions that
share an argument name that need to be bound to a different attribute?

For that, just use `map_names` to wrap the function, giving it the names that
you need to give it to have the effect you want (the binding of those arguments
to attributes of the instance):

```pycon
>>> from i2.wrapper import ch_names
>>> def func(x, y: int, z=2, *, d='bar'):
...     return f"{d}: {(x + y) * z}"
>>> from dataclasses import dataclass
>>> @dataclass
... class Klass:
...     a : int = 1
...     c : int = 3
...     method_func = func_to_method_func(ch_names(func, x='a', z='c'), 'a c')
>>> instance = Klass(1, 3)
>>> instance.method_func(2, d='hello')
'hello: 9'
```

### i2.wrapper.identity(x)

Return the input unchanged.

```pycon
>>> identity('x')
'x'
```

### i2.wrapper.include_exclude_ingress_factory(func, include=None, exclude=None, , allow_partial=False)

A pattern underlying any ingress that takes a subset of parameters (possibly
reordering them).

For example: Keep only required arguments, or reorder params to be able to
partialize #3 (without having to partialize #1 and #2)

### i2.wrapper.invert_map(d)

Swap keys and values of a mapping, raising `ValueError` if values are not unique.

```pycon
>>> invert_map({'a': 1, 'b': 2})
{1: 'a', 2: 'b'}
```

### i2.wrapper.items_with_mapped_keys(d, key_mapper)

Transform dict keys. More precisely yield (new_k,v) pairs from a key mapper dict.

* **Parameters:**
  * **d** ([`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)) – src dict
  * **key_mapper** – {old_name: new_name, …} mapping
* **Returns:**
  generator of (new_name, value) pairs

Often used in conjunction with dict:

```pycon
>>> dict(items_with_mapped_keys(
...     {'a': 1, 'b': 2, 'c': 3, 'd': 4},
...     {'a': 'Ay', 'd': 'Dee'})
... )
{'Ay': 1, 'b': 2, 'c': 3, 'Dee': 4}
```

### i2.wrapper.kwargs_trans(kwargs=None, , \_recursive=False, \_inplace=False, \*\*key_and_val_func)

Transform a kwargs dict or build a transformer.

* **Parameters:**
  * **kwargs** ([`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)) – The dict containing the input kwargs that we will transform
  * **\_recursive** – Whether the transformations listed in `key_and_val_func` should
    be applied “recursively”. When set to `False` (default), each transformation
    function applies to the original `kwargs`, not the one that was transformed,
    so far.
  * **\_inplace** – If set to `False` will make a shallow copy of the `kwargs`
    before transforming it (only relevant if `_recursive=True`
  * **key_and_val_func** – The `key=val_func` pairs that indicate that a
    `val_func` should be applied to the `kwargs`, maching the argument names of
    the `val_func` to the keys of `kwargs` and extracting the values found
    therein to use for the corresponding inputs of that `val_func`.
* **Returns:**
  The transformed kwargs.

```pycon
>>> d = dict(a=1, b=2, c=3)
>>> kwargs_trans(
...     d,
...     a=lambda a: a * 10,
...     b=lambda a, b: a + b
... )
{'a': 10, 'b': 3, 'c': 3}
```

See that `d` is unchanged here (transformation is not in place).

```pycon
>>> d
{'a': 1, 'b': 2, 'c': 3}
```

Typically you’ll use `kwargs_trans` as a factory:

```pycon
>>> trans = kwargs_trans(a=lambda a: a * 10, b=lambda a, b: a + b)
>>> trans(d)
{'a': 10, 'b': 3, 'c': 3}
```

Here we’ll demo what the `_recursive` and `_inplace` arguments do.

```pycon
>>> from functools import partial
>>> re_kwargs_trans = partial(kwargs_trans, _recursive=True, _inplace=True)
>>> d = dict(a=1, b=2, c=3)
>>>
>>> re_kwargs_trans(
...     d,
...     a=lambda a: a * 10,
...     b=lambda a, b: a + b
...     # since _recursive=True, the a that is used is the new a = 10, not a = 1:
... )
{'a': 10, 'b': 12, 'c': 3}
```

Since `_inplace=True`, `d` itself has changed:

```pycon
>>> d
{'a': 10, 'b': 12, 'c': 3}
```

Sometimes you’ll pipe several transformers together:

```pycon
>>> from i2 import Pipe
>>> trans = Pipe(
...     re_kwargs_trans(a=lambda a: a / 10),
...     re_kwargs_trans(c=lambda a,b,c: a * b * c),
...     # and then compute a new value of a using a and c:
...     re_kwargs_trans(a=lambda a, c: c - 1),
... )
>>> trans(d)
{'a': 35.0, 'b': 12, 'c': 36.0}
```

### i2.wrapper.kwargs_trans_to_extract_args_from_attrs(outer_kwargs, attr_names=(), obj_param='self')

Pop the `obj_param` object out of `outer_kwargs` and source `attr_names` from its attributes.

Mutates `outer_kwargs` (the object is popped). Explicit kwargs win over attributes.

```pycon
>>> class O: pass
>>> o = O(); o.a = 1; o.b = 2
>>> kwargs_trans_to_extract_args_from_attrs({'self': o, 'c': 3}, attr_names=('a', 'b'))
{'a': 1, 'b': 2, 'c': 3}
```

### i2.wrapper.mk_ingress_from_name_mapper(func, name_mapper, , conserve_kind=False)

Make an ingress that renames `func`’s parameters (`{inner_name: outer_name}`).

```pycon
>>> def foo(a, b: int, c=7):
...     return (a, b, c)
>>> ingress = mk_ingress_from_name_mapper(foo, dict(a='aa', c='cc'))
>>> Sig(ingress)
<Sig (aa, b: int, cc=7)>
>>> ingress(1, b=2, cc=3)
((), {'a': 1, 'b': 2, 'c': 3})
>>> wrap(foo, ingress=ingress)(1, 2, cc=3)
(1, 2, 3)
```

By default the outer signature loses positional-only and keyword-only kinds;
`conserve_kind=True` keeps them.

### i2.wrapper.modify_dict_on_cond(d, cond, func)

Copy `d`, applying `func` to the values whose `(key, value)` satisfy `cond`.

### i2.wrapper.move_names_to_the_end(names, names_to_move_to_the_end)

Remove the items of `names_to_move_to_the_end` from `names`
and append to the right of names

```pycon
>>> names = ['a','c','d','e']
>>> names_to_move_to_the_end = ['c','e']
>>> move_names_to_the_end(names, names_to_move_to_the_end)
['a', 'd', 'c', 'e']
>>> names_to_move_to_the_end = 'c e'
>>> move_names_to_the_end(names, names_to_move_to_the_end)
['a', 'd', 'c', 'e']
```

### i2.wrapper.move_params_to_the_end(func, names_to_move)

Choose args from func, according to choice_args_func and move them
to the right

```pycon
>>> from functools import partial
>>> from i2 import Sig
>>> def foo(a, b, c):
...     return a + b + c
>>> g = partial(foo, b=4)  # fixing a, which is before b
>>> h = move_params_to_the_end(g, Sig(g).defaults)
>>> assert str(Sig(g)) == '(a, *, b=4, c)'
>>> assert str(Sig(h)) == '(a, *, c, b=4)'
```

### i2.wrapper.param_to_dataclass_field_tuple(param)

The `(name, annotation, default)` tuple `dataclasses.make_dataclass` expects for a field.

### i2.wrapper.parameters_to_dict(parameters)

Map each parameter name to its `parameter_to_dict` (name, kind, default, annotation) dict.

### i2.wrapper.params_used_in_funcs(funcs)

The set of parameter names of all the given functions.

### i2.wrapper.partialx(func, \*args, \_\_name_\_=None, \_rm_partialize=False, \_allow_reordering=False, \*\*kwargs)

Extends the functionality of builtin `functools.partial` with the ability to

- set `__name__`
- remove partialized arguments from signature
- reorder params (so that defaults are at the end)

```pycon
>>> def f(a, b=2, c=3):
...     return a + b * c
>>> curried_f = partialx(f, c=10, _rm_partialize=True)
>>> curried_f.__name__
'f'
>>> from inspect import signature
>>> str(signature(curried_f))
'(a, b=2)'
```

```pycon
>>> def f(a, b, c=3):
...     return a + b * c
```

Note that `a` gets a default, but `b` does not, yet is after `a`.
This is allowed because these parameters all became KEYWORD_ONLY.

```pycon
>>> g = partialx(f, a=1)
>>> str(Sig(g))
'(*, a=1, b, c=3)'
```

If you wanted to reorder the parameters to have all defaulted kinds be at the end,
as usual, you can do so using `_allow_reordering=True`

```pycon
>>> g = partialx(f, a=1, _allow_reordering=True)
>>> str(Sig(g))
'(*, b, a=1, c=3)'
```

### i2.wrapper.required_params_used_in_funcs(funcs)

The set of names that are required (no default) in at least one of the given functions.

### i2.wrapper.transparent_egress(output)

```pycon
>>> transparent_egress('unnecessary_doctest')
'unnecessary_doctest'
```

### i2.wrapper.transparent_ingress(\*args, \*\*kwargs)

```pycon
>>> transparent_ingress(1, 2, test=1)
((1, 2), {'test': 1})
```

### i2.wrapper.wrap_from_sig(func, new_sig)

Give `func` the signature `new_sig`, calling it with only the arguments it takes.

```pycon
>>> def f(x, y=1):
...     return x + y
>>> h = wrap_from_sig(f, Sig('(x, y=1, z=0)'))
>>> Sig(h)
<Sig (x, y=1, z=0)>
>>> h(1, 2, 3), h(1, z=5)
(3, 2)
```
