From d9119e5727c9566bc0b00ed7178df17b6fc50e86 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:36:39 -0500 Subject: [PATCH 1/7] feat(make_class): add optional namespace arguments to pass through to `make_class` Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/attr/__init__.pyi | 1 + src/attr/_make.py | 8 +++++++- tests/test_make.py | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index 0f6415012..e41774396 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -490,6 +490,7 @@ def make_class( name: str, attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], bases: Tuple[type, ...] = ..., + namespaces: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., cmp: Optional[_EqOrderType] = ..., diff --git a/src/attr/_make.py b/src/attr/_make.py index 9dd0e2e37..548007628 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -2867,7 +2867,9 @@ def __setstate__(self, state): Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) -def make_class(name, attrs, bases=(object,), **attributes_arguments): +def make_class( + name, attrs, bases=(object,), namespaces=None, **attributes_arguments +): r""" A quick way to create a new class called *name* with *attrs*. @@ -2883,6 +2885,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): :param tuple bases: Classes that the new class will subclass. + :param dict namespaces: An optional dictionary of namespaces for the new class. + :param attributes_arguments: Passed unmodified to `attr.s`. :return: A new class with *attrs*. @@ -2904,6 +2908,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): user_init = cls_dict.pop("__init__", None) body = {} + if namespaces is not None: + body.update(namespaces) if pre_init is not None: body["__attrs_pre_init__"] = pre_init if post_init is not None: diff --git a/tests/test_make.py b/tests/test_make.py index 45c35df6a..f5b2f78bf 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -1106,6 +1106,18 @@ class D: assert D in cls.__mro__ assert isinstance(cls(), D) + def test_additional_namespace(self): + """ + Additional namespace is added to the class dict. + """ + + def echo_func(cls, *args): + return args + + cls = make_class("C", {}, namespaces={"echo": classmethod(echo_func)}) + + assert ("a", "b") == cls.echo("a", "b") + def test_clean_class(self, slots): """ Attribute definitions do not appear on the class body. From 83325397c266c4a6ce24bac8e7eb9bfe31107667 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:46:38 -0500 Subject: [PATCH 2/7] chore: add changelog Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- changelog.d/1203.feature.md | 17 +++++++++++++++++ src/attr/_make.py | 1 + 2 files changed, 18 insertions(+) create mode 100644 changelog.d/1203.feature.md diff --git a/changelog.d/1203.feature.md b/changelog.d/1203.feature.md new file mode 100644 index 000000000..0f9e44e88 --- /dev/null +++ b/changelog.d/1203.feature.md @@ -0,0 +1,17 @@ +Added `namespaces` to `attr.make_class` to provide additional attributes +for newly created class + +```python +import attr + +@attr.define +class A: + pass + +def new_echo(cls, *something: int) -> tuple[int, ...]: + return something + +B = attr.make_class("B", bases=(A,), namespaces={"echo": classmethod(new_echo)}) + +B.echo(1, 2, 3) +``` diff --git a/src/attr/_make.py b/src/attr/_make.py index 548007628..a856bb8f5 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -2894,6 +2894,7 @@ def make_class( .. versionadded:: 17.1.0 *bases* .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 If *attrs* is ordered, the order is retained. """ if isinstance(attrs, dict): cls_dict = attrs From 984a59285e55ec6052bb2835fa69c86d5b8e8b20 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Fri, 17 Nov 2023 07:08:26 -0500 Subject: [PATCH 3/7] Rename `namespaces` to `class_body` Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/attr/__init__.pyi | 2 +- src/attr/_make.py | 8 ++++---- tests/test_make.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index e41774396..37a208732 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -490,7 +490,7 @@ def make_class( name: str, attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], bases: Tuple[type, ...] = ..., - namespaces: Optional[Dict[str, Any]] = ..., + class_body: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., cmp: Optional[_EqOrderType] = ..., diff --git a/src/attr/_make.py b/src/attr/_make.py index a856bb8f5..46003358a 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -2868,7 +2868,7 @@ def __setstate__(self, state): def make_class( - name, attrs, bases=(object,), namespaces=None, **attributes_arguments + name, attrs, bases=(object,), class_body=None, **attributes_arguments ): r""" A quick way to create a new class called *name* with *attrs*. @@ -2885,7 +2885,7 @@ def make_class( :param tuple bases: Classes that the new class will subclass. - :param dict namespaces: An optional dictionary of namespaces for the new class. + :param dict class_body: An optional dictionary of class attributes for the new class. :param attributes_arguments: Passed unmodified to `attr.s`. @@ -2909,8 +2909,8 @@ def make_class( user_init = cls_dict.pop("__init__", None) body = {} - if namespaces is not None: - body.update(namespaces) + if class_body is not None: + body.update(class_body) if pre_init is not None: body["__attrs_pre_init__"] = pre_init if post_init is not None: diff --git a/tests/test_make.py b/tests/test_make.py index f5b2f78bf..3541ee50e 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -1114,7 +1114,7 @@ def test_additional_namespace(self): def echo_func(cls, *args): return args - cls = make_class("C", {}, namespaces={"echo": classmethod(echo_func)}) + cls = make_class("C", {}, class_body={"echo": classmethod(echo_func)}) assert ("a", "b") == cls.echo("a", "b") From e4ebeb30d4b95bcda16095698ebcffda58a5974d Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Fri, 17 Nov 2023 07:09:22 -0500 Subject: [PATCH 4/7] Fix versionchanged for correct features Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/attr/_make.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attr/_make.py b/src/attr/_make.py index 46003358a..8a4e6581f 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -2894,7 +2894,7 @@ def make_class( .. versionadded:: 17.1.0 *bases* .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. - .. versionchanged:: 23.2.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* """ if isinstance(attrs, dict): cls_dict = attrs From 9c8c95446cd6cfe07860336dde53480d8c3615c7 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Fri, 17 Nov 2023 07:10:16 -0500 Subject: [PATCH 5/7] Correctly name test case Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- tests/test_make.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_make.py b/tests/test_make.py index 3541ee50e..19f7a4cd4 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -1106,9 +1106,9 @@ class D: assert D in cls.__mro__ assert isinstance(cls(), D) - def test_additional_namespace(self): + def test_additional_class_body(self): """ - Additional namespace is added to the class dict. + Additional class_body is added to newly created class. """ def echo_func(cls, *args): From 6e67abe1427ee087641beb4089abcaba0da58f29 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Fri, 17 Nov 2023 07:26:37 -0500 Subject: [PATCH 6/7] Update changelog Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- changelog.d/1203.feature.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.d/1203.feature.md b/changelog.d/1203.feature.md index 0f9e44e88..5c95df596 100644 --- a/changelog.d/1203.feature.md +++ b/changelog.d/1203.feature.md @@ -1,4 +1,4 @@ -Added `namespaces` to `attr.make_class` to provide additional attributes +Added `class_body` to `attr.make_class` to provide additional attributes for newly created class ```python @@ -11,7 +11,7 @@ class A: def new_echo(cls, *something: int) -> tuple[int, ...]: return something -B = attr.make_class("B", bases=(A,), namespaces={"echo": classmethod(new_echo)}) +B = attr.make_class("B", bases=(A,), class_body={"echo": classmethod(new_echo)}) B.echo(1, 2, 3) ``` From b478d6722a151d38cc535079ba17cae09b87c2e9 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Tue, 28 Nov 2023 13:02:01 +0100 Subject: [PATCH 7/7] Adjust changelog --- changelog.d/1203.change.md | 2 ++ changelog.d/1203.feature.md | 17 ----------------- 2 files changed, 2 insertions(+), 17 deletions(-) create mode 100644 changelog.d/1203.change.md delete mode 100644 changelog.d/1203.feature.md diff --git a/changelog.d/1203.change.md b/changelog.d/1203.change.md new file mode 100644 index 000000000..38c5c9f8a --- /dev/null +++ b/changelog.d/1203.change.md @@ -0,0 +1,2 @@ +Added *class_body* argument to `attrs.make_class()` to provide additional attributes for newly created classes. +It is, for example, now possible to attach methods. diff --git a/changelog.d/1203.feature.md b/changelog.d/1203.feature.md deleted file mode 100644 index 5c95df596..000000000 --- a/changelog.d/1203.feature.md +++ /dev/null @@ -1,17 +0,0 @@ -Added `class_body` to `attr.make_class` to provide additional attributes -for newly created class - -```python -import attr - -@attr.define -class A: - pass - -def new_echo(cls, *something: int) -> tuple[int, ...]: - return something - -B = attr.make_class("B", bases=(A,), class_body={"echo": classmethod(new_echo)}) - -B.echo(1, 2, 3) -```