Skip to content

Commit

Permalink
feat: Add aliases decorator (#40)
Browse files Browse the repository at this point in the history
* Add aliases function for backward compatibility
  • Loading branch information
HCookie authored Nov 6, 2024
1 parent 2ad3640 commit ae8d22d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Keep it human-readable, your future self will thank you!
## [Unreleased](https://github.com/ecmwf/anemoi-utils/compare/0.4.1...HEAD)

### Added
- Add alias decorator [#40](https://github.com/ecmwf/anemoi-utils/pull/40)
- Add supporting_arrays to checkpoints
- Add factories registry
- Optional renaming of subcommands via `command` attribute [#34](https://github.com/ecmwf/anemoi-utils/pull/34)
Expand Down
76 changes: 76 additions & 0 deletions src/anemoi/utils/compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# (C) Copyright 2024 Anemoi contributors.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.

from __future__ import annotations

import functools
from typing import Any
from typing import Callable


def aliases(
aliases: dict[str, str | list[str]] | None = None, **kwargs: str | list[str]
) -> Callable[[Callable], Callable]:
"""Alias keyword arguments in a function call.
Allows for dynamically renaming keyword arguments in a function call.
Parameters
----------
aliases : dict[str, str | list[str]] | None, optional
Key, value pair of aliases, with keys being the true name, and value being a str or list of aliases,
by default None
**kwargs : str | list[str]
Kwargs form of aliases
Returns
-------
Callable
Decorator function that renames keyword arguments in a function call.
Raises
------
ValueError
If the aliasing would result in duplicate keys.
Examples
--------
```python
@aliases(a="b", c=["d", "e"])
def func(a, c):
return a, c
func(a=1, c=2) # (1, 2)
func(b=1, d=2) # (1, 2)
```
"""

if aliases is None:
aliases = {}
aliases.update(kwargs)

aliases = {v: k for k, vs in aliases.items() for v in (vs if isinstance(vs, list) else [vs])}

def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
keys = kwargs.keys()
for k in set(keys).intersection(set(aliases.keys())):
if aliases[k] in keys:
raise ValueError(
f"When aliasing {k} with {aliases[k]} duplicate keys were present. Cannot include both."
)
kwargs[aliases[k]] = kwargs.pop(k)

return func(*args, **kwargs)

return wrapper

return decorator
32 changes: 32 additions & 0 deletions tests/test_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# (C) Copyright 2024 Anemoi contributors.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.

import pytest

from anemoi.utils.compatibility import aliases


def test_aliases() -> None:

@aliases(a="b", c=["d", "e"])
def func(a, c):
return a, c

assert func(a=1, c=2) == (1, 2)
assert func(a=1, d=2) == (1, 2)
assert func(b=1, d=2) == (1, 2)


def test_duplicate_values() -> None:
@aliases(a="b", c=["d", "e"])
def func(a, c):
return a, c

with pytest.raises(ValueError):
func(a=1, b=2)

0 comments on commit ae8d22d

Please sign in to comment.