Skip to content

Commit

Permalink
Run API type checks on Pyright, warn against Annotated & PEP 695
Browse files Browse the repository at this point in the history
The support for `type` is gonna be more complicated it seem, since it
returns a TypeAliasType which is different from an type.

Fixes #74
  • Loading branch information
hynek committed Jun 27, 2024
1 parent 908141a commit d10779b
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 16 deletions.
25 changes: 17 additions & 8 deletions docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ But the types must be *hashable* because they're used as keys in a lookup dictio
Sometimes, it makes sense to have multiple instances of the same type.
For example, you might have multiple HTTP client pools or more than one database connection.

You can achieve this by using either {any}`typing.Annotated` (Python 3.9+, or in [*typing-extensions*](https://pypi.org/project/typing-extensions/)) or by using {keyword}`type` (Python 3.12+, use {any}`typing.NewType` on older versions).
You can also mix and match the two.
You can achieve this by creating new types using {any}`typing.NewType`.

On Mypy, you can also use {any}`typing.Annotated` to add metadata to the type to the same effect.
However, it doesn't work with Pyright and [it's unclear if it's even _supposed_ to work](https://github.com/hynek/svcs/discussions/74), so we recommend the first approach.

You can mix and match those two approaches, as long your type checker supports both.
For instance, if you need a primary and a secondary database connection:

% invisible-code-block: python
Expand All @@ -57,18 +61,23 @@ from sqlalchemy import Connection, create_engine
primary_engine = create_engine(primary_url)
secondary_engine = create_engine(secondary_url)

# Create unique types for both with two different approaches
PrimaryConnection = Annotated[Connection, "primary"]
SecondaryConnection = NewType("SecondaryConnection", Connection)
# Or on Python 3.12:
# type SecondaryConnection = Connection
# Clunky, but works universally.
PrimaryConnection = NewType("PrimaryConnection", Connection)

# This works with Mypy, but NOT with Pyright:
SecondaryConnection = Annotated[Connection, "secondary"]

# Register the factories to the aliases
registry.register_factory(PrimaryConnection, primary_engine.connect)
registry.register_factory(SecondaryConnection, secondary_engine.connect)
```

The type and content of the annotated metadata ("primary") are not important to *svcs*, as long as the whole type is hashable.
The type and content of the annotated metadata ("secondary") are not important to *svcs*, as long as the whole type is hashable.

::: {note}
The {pep}`695` {keyword}`type` keyword is currently not widely supported and therefore not supported by *svcs*.
This will hopefully change in the future.
:::


### Cleanup
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ whitelist-regex = ["test_.*"]

[tool.mypy]
strict = true
pretty = true

show_error_codes = true
enable_error_code = ["ignore-without-code"]
Expand Down Expand Up @@ -194,7 +195,9 @@ ignore = [
"conftest.py",
"docs",
"src/svcs/pyramid.py",
"tests",
# Excluding tests and allowing tests/typing (like in Mypy) doesn't work.
"tests/*.py",
"tests/integrations",
"tests/typing/pyramid.py",
]

Expand Down
8 changes: 1 addition & 7 deletions tests/typing/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@
import svcs


if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated


reg = svcs.Registry()
con = svcs.Container(reg)

Expand Down Expand Up @@ -142,7 +136,7 @@ async def ctx() -> None:


# Multiple factories for same type:
S1 = Annotated[str, "s1"]
S1 = NewType("S1", str)
S2 = NewType("S2", str)

reg.register_value(S1, "foo")
Expand Down

0 comments on commit d10779b

Please sign in to comment.