Repository: python-attrs/attrs (Python)

Issue:
Add `type` back to `attrs.field()` for use in `attrs.make_class()`
I'm using `attrs.make_class()` to dynamically construct a class based on other attrs classes, and it would be nice to be able to specify the `type` of the fields on the generated class using `attrs.field()` instead of `attr.ib()`. In other words, this works:

```python
fields = {
    field.name: attr.ib(type=field.type)
    for field in itertools.chain.from_iterable(
        attrs.fields(t) for t in SOME_ATTRS_CLASSES
    )
    if not field.name.startswith("_")
}

MyClass = attrs.make_class("MyClass", fields)
```

but this does not:

```python
fields = {
    field.name: attrs.field(type=field.type)
    for field in itertools.chain.from_iterable(
        attrs.fields(t) for t in SOME_ATTRS_CLASSES
    )
    if not field.name.startswith("_")
}

MyClass = attrs.make_class("MyClass", fields)
```


Discussion:
Aw man yeah when I celebrated to get rid of that, I forgot about `make_class`. 🙈
I'm working on the implementation. 
Given that no type checker cares about `make_class()` as far as I can tell (I tried to get it to fail by passing wrong arguments to a make_class-borne class), would you mind sharing the utility you're getting out of this?
I ended up not needing `make_class()`, but I wanted `type` for our in-house structuring library, similar to cattrs.
So metadata, gotcha!

I'm asking because I want to put a fat warning next to the argument.
This would also be nice for [Typed Settings](https://gitlab.com/sscherfke/typed-settings/-/blob/main/src/typed_settings/attrs/__init__.py#L496-501) :-)
@sscherfke well good news in #1107 ;)
woop woop \o/



Patch:
diff --git a/changelog.d/1107.change.md b/changelog.d/1107.change.md
new file mode 100644
index 000000000..760649432
--- /dev/null
+++ b/changelog.d/1107.change.md
@@ -0,0 +1,3 @@
+Added *type* parameter to `attrs.field()` function for use with `attrs.make_class().
+
+Please note that type checkers ignore type metadata passed into `make_class()`, but it can be useful if you're wrapping _attrs_.
diff --git a/docs/examples.md b/docs/examples.md
index 465dc3952..6329efc35 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -471,7 +471,7 @@ If you're the author of a third-party library with *attrs* integration, please s
 
 ## Types
 
-*attrs* also allows you to associate a type with an attribute using either the *type* argument to {func}`attr.ib` or using {pep}`526`-annotations:
+*attrs* also allows you to associate a type with an attribute using either the *type* argument to {func}`attr.ib` and {func}`attr.field` or using {pep}`526`-annotations:
 
 ```{doctest}
 >>> @define
@@ -626,11 +626,13 @@ Sometimes you may want to create a class programmatically.
 >>> from attrs import make_class
 >>> @define
 ... class C1:
-...     x = field()
+...     x = field(type=int)
 ...     y = field()
->>> C2 = make_class("C2", ["x", "y"])
+>>> C2 = make_class("C2", {"x": field(type=int), "y": field()})
 >>> fields(C1) == fields(C2)
 True
+>>> fields(C1).x.type
+<class 'int'>
 ```
 
 You can still have power over the attributes if you pass a dictionary of name: {func}`~attrs.field` mappings and can pass arguments to `@attr.s`:
diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi
index 003dac4b4..ced5a3fd4 100644
--- a/src/attr/__init__.pyi
+++ b/src/attr/__init__.pyi
@@ -259,6 +259,7 @@ def field(
     order: Optional[bool] = ...,
     on_setattr: Optional[_OnSetAttrArgType] = ...,
     alias: Optional[str] = ...,
+    type: Optional[type] = ...,
 ) -> Any: ...
 
 # This form catches an explicit None or no default and infers the type from the
@@ -279,6 +280,7 @@ def field(
     order: Optional[_EqOrderType] = ...,
     on_setattr: Optional[_OnSetAttrArgType] = ...,
     alias: Optional[str] = ...,
+    type: Optional[type] = ...,
 ) -> _T: ...
 
 # This form catches an explicit default argument.
@@ -298,6 +300,7 @@ def field(
     order: Optional[_EqOrderType] = ...,
     on_setattr: Optional[_OnSetAttrArgType] = ...,
     alias: Optional[str] = ...,
+    type: Optional[type] = ...,
 ) -> _T: ...
 
 # This form covers type=non-Type: e.g. forward references (str), Any
@@ -317,6 +320,7 @@ def field(
     order: Optional[_EqOrderType] = ...,
     on_setattr: Optional[_OnSetAttrArgType] = ...,
     alias: Optional[str] = ...,
+    type: Optional[type] = ...,
 ) -> Any: ...
 @overload
 @__dataclass_transform__(order_default=True, field_descriptors=(attrib, field))
diff --git a/src/attr/_next_gen.py b/src/attr/_next_gen.py
index 1ce7b3d7e..7c4d5db0c 100644
--- a/src/attr/_next_gen.py
+++ b/src/attr/_next_gen.py
@@ -167,6 +167,7 @@ def field(
     hash=None,
     init=True,
     metadata=None,
+    type=None,
     converter=None,
     factory=None,
     kw_only=False,
@@ -179,6 +180,10 @@ def field(
     Identical to `attr.ib`, except keyword-only and with some arguments
     removed.
 
+    .. versionadded:: 22.3.0
+       The *type* parameter has been re-added; mostly for
+       {func}`attrs.make_class`. Please note that type checkers ignore this
+       metadata.
     .. versionadded:: 20.1.0
     """
     return attrib(
@@ -188,6 +193,7 @@ def field(
         hash=hash,
         init=init,
         metadata=metadata,
+        type=type,
         converter=converter,
         factory=factory,
         kw_only=kw_only,


Test patch:
diff --git a/tests/test_next_gen.py b/tests/test_next_gen.py
index cb6ee2a78..7908a418c 100644
--- a/tests/test_next_gen.py
+++ b/tests/test_next_gen.py
@@ -28,6 +28,16 @@ def test_simple(self):
         """
         C("1", 2)
 
+    def test_field_type(self):
+        """
+        Make class with attrs.field and type parameter.
+        """
+        classFields = {"testint": attrs.field(type=int)}
+
+        A = attrs.make_class("A", classFields)
+
+        assert int == attrs.fields(A).testint.type
+
     def test_no_slots(self):
         """
         slots can be deactivated.