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)
```

Pull request:
Added `type` parameter to `attrs.field()` function
# Summary

<!-- Please tell us what your pull request is about here. -->
This resolves #1082 

Could somebody help me with the tests? I'm not sure they're right where they are. 

# Pull Request Check List

<!--
This is just a friendly reminder about the most common mistakes.
Please make sure that you tick all boxes.
But please read our [contribution guide](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) at least once, it will save you unnecessary review cycles!

If an item doesn't apply to your pull request, **check it anyway** to make it apparent that there's nothing left to do.
If your pull request is a documentation fix or a trivial typo, feel free to delete the whole thing.
-->

- [x] Added **tests** for changed code.
  Our CI fails if coverage is not 100%.
- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py).
- [x] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``).
    - [ ] ...and used in the stub test file `tests/typing_example.py`.
    - [ ] If they've been added to `attr/__init__.pyi`, they've *also* been re-imported in `attrs/__init__.pyi`.
- [x] Updated **documentation** for changed code.
    - [ ] New functions/classes have to be added to `docs/api.rst` by hand.
    - [ ] Changes to the signature of `@attr.s()` have to be added by hand too.
    - [x] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
          The next version is the second number in the current release + 1.
          So if the current version on PyPI is 23.1.0, the next version is gonna be 23.2.0.
- [ ] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
- [x] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d).
- [x] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you.

<!--
If you have *any* questions to *any* of the points above, just **submit and ask**!
This checklist is here to *help* you, not to deter you from contributing!
-->


Patch:
diff --git a/changelog.d/1107.change.md b/changelog.d/1107.change.md
new file mode 100644
index 0000000..7606494
--- /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 465dc39..6329efc 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 003dac4..ced5a3f 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 1ce7b3d..7c4d5db 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 cb6ee2a..7908a41 100644
--- a/tests/test_next_gen.py
+++ b/tests/test_next_gen.py
@@ -28,6 +28,16 @@ class TestNextGen:
         """
         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.