Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mypy errors #9

Merged
merged 8 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,35 @@ jobs:
- name: Execute Pre-Commit
run: make lint-ci

test:
mypy:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.8"

- uses: pdm-project/setup-pdm@v4
name: Set up PDM
with:
python-version: ${{ matrix.python-version }}
allow-python-prereleases: false
prerelease: false
cache: true
cache-dependency-path: |
./pdm.lock

- name: Install dependencies
run: pdm install -G:all

- name: Execute mypy
run: make mypy

test:
needs: mypy
runs-on: ubuntu-latest

strategy:
fail-fast: true
Expand Down Expand Up @@ -54,7 +80,7 @@ jobs:
run: pdm install -G:all

- name: Test with coverage
run: make test
run: make test-ci

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ repos:
exclude: '.bumpversion.cfg'

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.5.4'
rev: v0.7.1
hooks:
- id: ruff
entry: ruff check src tests --fix --exit-non-zero-on-fix --show-fixes --config pyproject.toml
entry: ruff check src tests --fix --exit-non-zero-on-fix --show-fixes

- id: ruff-format
entry: ruff format src tests --config pyproject.toml
entry: ruff format src tests
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
test:
# pdm run hatch test --cover --all
pdm run pytest tests --cov=src --cov-report term-missing --cov-report=xml --asyncio-mode=auto
hatch test --cover --all --randomize

test-ci:
pdm run pytest -rA tests --cov=src --cov-report term-missing --cov-report=xml --asyncio-mode=auto

lint:
pdm run pre-commit install
Expand Down Expand Up @@ -37,3 +39,6 @@ tag:

push-tag:
git push origin ${v}

mypy:
pdm run mypy src
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[![Documentation Status](https://readthedocs.org/projects/injection/badge/?version=latest)](https://injection.readthedocs.io/en/latest/?badge=latest)

[![Tests And Linting](https://github.com/nightblure/injection/actions/workflows/ci.yml/badge.svg)](https://github.com/nightblure/injection/actions/workflows/ci.yml)
[![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration)
[![codecov](https://codecov.io/gh/nightblure/injection/graph/badge.svg?token=2ZTFBlJqTb)](https://codecov.io/gh/nightblure/injection)

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
Expand Down
1,790 changes: 792 additions & 998 deletions pdm.lock

Large diffs are not rendered by default.

37 changes: 26 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "deps-injection" # SHOULD BE SAME WITH PROJECT NAME IN PYPI PUBLISHER@
name = "deps-injection" # SHOULD BE SAME WITH PROJECT NAME IN PYPI PUBLISHER
requires-python = ">=3.8"
license = { text = "MIT" }
dynamic = ["version"]
Expand All @@ -16,8 +16,10 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython"
]
keywords = ["dependency", "injector", "di", "injection"]
description = "Easy dependency injection"
dependencies = []
description = "Easy dependency injection without wiring"
dependencies = [
"typing-extensions>=4.12.2",
]
readme = "README.md"

[project.urls]
Expand Down Expand Up @@ -92,27 +94,21 @@ filterwarnings = [
[tool.hatch.version]
path = "src/injection/__version__.py"

# FOR LOCAL TESTS (make test)
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]

[tool.hatch.envs.hatch-test]
randomize = true

[tool.hatch.build.targets.sdist]
only-include = ["src/injection"]

[tool.hatch.build.targets.wheel]
packages = ["src/injection"]


[tool.pdm]
distribution = false

[tool.pdm.dev-dependencies]
dev = [
"hatch==1.12.0",
"pre-commit",
"mypy>=1.13.0",
"setuptools>=75.2.0",
]
docs = [
"sphinx==7.*",
Expand All @@ -131,6 +127,20 @@ test = [
"flask",
]

[tool.hatch.envs.hatch-test]
extra-dependencies = [
"typing-extensions>=4.12.2",
"pytest-asyncio",
"pytest-cov",
"djangorestframework",
"fastapi",
"litestar",
"flask",
]

[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Expand All @@ -157,3 +167,8 @@ exclude_lines = [
"raise NotImplementedError",
"ImportError"
]

[tool.mypy]
python_version = "3.8" # oldest supported version at this moment
strict = true
ignore_missing_imports = true
14 changes: 7 additions & 7 deletions src/injection/base_container.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import inspect
from contextlib import contextmanager
from typing import Any, Callable, Dict, Iterator, List, TypeVar, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, TypeVar

from injection.providers import Singleton
from injection.providers.base import BaseProvider
Expand All @@ -9,8 +9,8 @@


class DeclarativeContainer:
__providers: Dict[str, BaseProvider] = None
__instance: Union["DeclarativeContainer", None] = None
__instance: Optional["DeclarativeContainer"] = None
__providers: Optional[Dict[str, BaseProvider[Any]]] = None

@classmethod
def instance(cls) -> "DeclarativeContainer":
Expand All @@ -19,7 +19,7 @@ def instance(cls) -> "DeclarativeContainer":
return cls.__instance

@classmethod
def __get_providers(cls) -> Dict[str, BaseProvider]:
def __get_providers(cls) -> Dict[str, BaseProvider[Any]]:
if cls.__providers is None:
cls.__providers = {
member_name: member
Expand All @@ -29,13 +29,13 @@ def __get_providers(cls) -> Dict[str, BaseProvider]:
return cls.__providers

@classmethod
def _get_providers_generator(cls) -> Iterator[BaseProvider]:
def _get_providers_generator(cls) -> Iterator[BaseProvider[Any]]:
for _, member in inspect.getmembers(cls):
if isinstance(member, BaseProvider):
yield member

@classmethod
def get_providers(cls) -> List[BaseProvider]:
def get_providers(cls) -> List[BaseProvider[Any]]:
return list(cls.__get_providers().values())

@classmethod
Expand All @@ -44,7 +44,7 @@ def override_providers_kwargs(
cls,
*,
reset_singletons: bool = False,
**providers_for_overriding,
**providers_for_overriding: Any,
) -> Iterator[None]:
with cls.override_providers(
providers_for_overriding,
Expand Down
34 changes: 21 additions & 13 deletions src/injection/inject.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import inspect
import sys
from functools import wraps
from typing import Any, Callable, Dict, TypeVar, Union
from typing import Any, Callable, Coroutine, Dict, TypeVar, Union, cast

from injection.provide import Provide
from injection.providers.base import BaseProvider

if sys.version_info < (3, 10):
from typing_extensions import ParamSpec
else:
from typing import ParamSpec

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

F = TypeVar("F", bound=Callable[..., Any])
T = TypeVar("T")
P = ParamSpec("P")
Markers = Dict[str, Provide]


def _is_fastapi_depends(param: Any) -> bool:
try:
import fastapi
except ImportError:
fastapi = None
fastapi = None # type: ignore
return fastapi is not None and isinstance(param, fastapi.params.Depends)


Expand All @@ -40,7 +46,7 @@ def _extract_marker(parameter: inspect.Parameter) -> Union[Any, Provide]:
return marker


def _get_markers_from_function(f: F) -> Markers:
def _get_markers_from_function(f: Callable[P, T]) -> Markers:
injections = {}
signature = inspect.signature(f)
parameters = signature.parameters
Expand All @@ -56,7 +62,7 @@ def _get_markers_from_function(f: F) -> Markers:
return injections


def _resolve_provide_marker(marker: Provide) -> BaseProvider:
def _resolve_provide_marker(marker: Provide) -> BaseProvider[Any]:
if not isinstance(marker, Provide):
msg = f"Incorrect marker type: {type(marker)!r}. Marker must be either Provide."
raise TypeError(msg)
Expand All @@ -81,33 +87,35 @@ def _extract_provider_values_from_markers(markers: Markers) -> Dict[str, Any]:
return providers


def _get_async_injected(f: F, markers: Markers) -> F:
def _get_async_injected(
f: Callable[P, Coroutine[Any, Any, T]],
markers: Markers,
) -> Callable[P, Coroutine[Any, Any, T]]:
@wraps(f)
async def wrapper(*args, **kwargs):
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
providers = _extract_provider_values_from_markers(markers)
kwargs.update(providers)
return await f(*args, **kwargs)

return wrapper


def _get_sync_injected(f: F, markers: Markers) -> F:
def _get_sync_injected(f: Callable[P, T], markers: Markers) -> Callable[P, T]:
@wraps(f)
def wrapper(*args, **kwargs):
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
providers = _extract_provider_values_from_markers(markers)
kwargs.update(providers)
return f(*args, **kwargs)

return wrapper


def inject(f: F) -> F:
def inject(f: Callable[P, T]) -> Callable[P, T]:
"""Decorate callable with injecting decorator"""
markers = _get_markers_from_function(f)

if inspect.iscoroutinefunction(f):
func_with_injected_params = _get_async_injected(f, markers)
else:
func_with_injected_params = _get_sync_injected(f, markers)
return cast(Callable[P, T], func_with_injected_params)

return func_with_injected_params
return _get_sync_injected(f, markers)
6 changes: 3 additions & 3 deletions src/injection/provide.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generic, TypeVar
from typing import Generic, TypeVar, cast

from injection.providers.base import BaseProvider

Expand All @@ -7,12 +7,12 @@

class ClassGetItemMeta(Generic[T], type):
def __getitem__(cls, item: BaseProvider[T]) -> T:
return cls(item)
return cast(T, cls(item))


class Provide(metaclass=ClassGetItemMeta):
def __init__(self, provider: BaseProvider[T]) -> None:
self.provider = provider

def __call__(self) -> T:
def __call__(self) -> "Provide":
return self
6 changes: 3 additions & 3 deletions src/injection/provided.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from operator import attrgetter
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, List

if TYPE_CHECKING:
from injection.providers.base import BaseProvider
Expand All @@ -13,9 +13,9 @@ def _get_value_from_object_by_dotted_path(obj: Any, path: str) -> Any:


class ProvidedInstance:
def __init__(self, provided: "BaseProvider"):
def __init__(self, provided: "BaseProvider[Any]"):
self._provided = provided
self._attrs = []
self._attrs: List[str] = []

def __getattr__(self, attr: str) -> "ProvidedInstance":
self._attrs.append(attr)
Expand Down
Loading