Skip to content

Commit

Permalink
Added mypy integration plugin (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
francis-clairicia committed Dec 27, 2023
1 parent 1d83db5 commit 7b03657
Show file tree
Hide file tree
Showing 10 changed files with 987 additions and 743 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ repos:
require_serial: true
pass_filenames: false
- repo: https://github.com/PyCQA/isort
rev: '5.12.0' # Keep in sync with requirements-dev.txt
rev: '5.13.2' # Keep in sync with requirements-dev.txt
hooks:
- id: isort
args: ['--filter-files', '--settings-file', 'pyproject.toml']
- repo: https://github.com/psf/black
rev: '23.3.0' # Keep in sync with requirements-dev.txt
rev: '23.12.1' # Keep in sync with requirements-dev.txt
hooks:
- id: black
args: ['--config', 'pyproject.toml']
- repo: https://github.com/PyCQA/flake8
rev: '6.0.0' # Keep in sync with requirements-dev.txt
rev: '6.1.0' # Keep in sync with requirements-dev.txt
hooks:
- id: flake8
args: ['--config', '.flake8']
Expand All @@ -49,11 +49,11 @@ repos:
types: [] # Overwrite with empty in order to fallback to types_or
types_or: [python, pyi]
- repo: https://github.com/pdm-project/pdm
rev: '2.7.4'
rev: '2.11.1'
hooks:
- id: pdm-lock-check
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 'v4.4.0'
rev: 'v4.5.0'
hooks:
- id: mixed-line-ending
args: [--fix=lf]
Expand Down
77 changes: 67 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@

`async-object` let you write classes with `async def __init__`

## Installation
### From PyPI repository
```sh
pip install --user async-object
```

### From source
```sh
git clone https://github.com/francis-clairicia/async-object.git
cd async-object
pip install --user .
```

## Usage
It is simple, with `async-object` you can do this:
```py
Expand Down Expand Up @@ -68,8 +81,10 @@ Replace the `main` in the [Usage](#usage) example by this one and run it. You sh
Obviously, arguments can be given to `__init__` and `__new__`.
The inheritance logic with "normal" constructors is the same here:
```py
from typing_extensions import Self

class MyObjectOnlyNew(AsyncObject):
async def __new__(cls, *args: Any, **kwargs: Any) -> "MyObject":
async def __new__(cls, *args: Any, **kwargs: Any) -> Self:
self = await super().__new__(cls)

print(args)
Expand All @@ -80,14 +95,14 @@ class MyObjectOnlyNew(AsyncObject):

class MyObjectOnlyInit(AsyncObject):
async def __init__(self, *args: Any, **kwargs: Any) -> None:
await super().__init__()
# await super().__init__() # Optional if the base class is only AsyncObject (but useful in multiple inheritance context)

print(args)
print(kwargs)


class MyObjectBothNewAndInit(AsyncObject):
async def __new__(cls, *args: Any, **kwargs: Any) -> "MyObject":
async def __new__(cls, *args: Any, **kwargs: Any) -> Self:
self = await super().__new__(cls)

print(args)
Expand All @@ -96,7 +111,7 @@ class MyObjectBothNewAndInit(AsyncObject):
return self

async def __init__(self, *args: Any, **kwargs: Any) -> None:
await super().__init__()
# await super().__init__()

print(args)
print(kwargs)
Expand Down Expand Up @@ -128,7 +143,7 @@ class MyAbstractObject(AsyncObject, metaclass=AsyncABCMeta):

class MyObject(MyAbstractObject):
async def __init__(self) -> None:
await super().__init__()
pass

def method(self) -> None:
pass
Expand All @@ -154,16 +169,58 @@ class MyAbstractObject(AsyncABC):
raise NotImplementedError
```

## Troubleshoots
## Static type checking: mypy integration
`mypy` does not like having `async def` for `__new__` and `__init__`, and will not understand `await AsyncObject()`.

`async-object` embeds a plugin which helps `mypy` to understand asynchronous constructors.

### Installation
Firstly, install the needed dependencies:
```sh
pip install async-object[mypy]
```

### Static type checking
To register this plugin in your `mypy.ini`, `pyproject.toml`, or whatever, you must add `async_object.contrib.mypy.plugin` to the plugins list.

`mypy` does not like having `async def` for `__new__` and `__init__`. You can use `# type: ignore[misc]` comment to mask these errors when overriding these methods.
In `mypy.ini`:
```ini
[mypy]
plugins = async_object.contrib.mypy.plugin
```

In `pyproject.toml`:
```toml
[tool.mypy]
plugins = ["async_object.contrib.mypy.plugin"]
```

For more information, see [the mypy documentation](https://mypy.readthedocs.io/en/stable/extending_mypy.html#configuring-mypy-to-use-plugins).

### What is permitted then ?
#### `__init__` method returning a coroutine is accepted
The error `The return type of "__init__" must be None` is discarded.
```py
class MyObject(AsyncObject):
async def __init__(self, param: int) -> None:
await super().__init__()
```

#### The class instanciation introspection is fixed
```py
async def main() -> None:
coroutine = MyObject()
reveal_type(coroutine) # Revealed type is "typing.Coroutine[Any, Any, __main__.MyObject]"
instance = await coroutine
reveal_type(instance) # Revealed type is "__main__.MyObject"
```

### Caveat/Known issues
The errors triggered by `__new__` cannot be silenced yet. You can use `# type: ignore[misc]` comment to mask these errors.
```py
class MyObject(AsyncObject):
async def __new__(cls) -> "MyObject": # type: ignore[misc]
async def __new__(cls) -> Self: # type: ignore[misc]
return await super().__new__(cls)

async def __init__(self) -> None: # type: ignore[misc]
async def __init__(self) -> None:
await super().__init__()
```
Loading

0 comments on commit 7b03657

Please sign in to comment.