Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ jobs:
- name: "Install dependencies"
if: steps.cache.outputs.cache-hit != 'true'
run: |
pip install hatch
pip install hatch virtualenv==20.39.1
- name: "Run linting checks"
run: "hatch run lint"
- name: "Run mypy"
- name: "Run ty"
run: "hatch run test:check_types"
- name: "Run tests"
env:
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ tasks:
cmds:
- hatch run format

mypy:
desc: Runs the type checker only, no formatting
ty:
desc: Runs the ty type checker only, no formatting
cmds:
- hatch run test:check_types

Expand Down
99 changes: 48 additions & 51 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ testing = [
"grpcio-tools>=1.71.0",
"litestar>=2.18.0",
"mongoz>=0.13.0",
"mypy==1.19.1",
"ty",
"pytest>=7.1.3,<10.0.0",
"pytest-cov>=4.1.0,<8.0.0",
"pytest-asyncio>=0.26.0",
Expand Down Expand Up @@ -275,7 +275,7 @@ features = ["all", "testing"]
test = "hatch test -- {args}"
test_man = "hatch test -- {args} -s -vv"
coverage = "hatch test -- --cov=ravyn --cov=tests --cov-report=term-missing:skip-covered --cov-report=html tests {args}"
check_types = "mypy -p ravyn"
check_types = "ty check --error-on-warning --python \"$VIRTUAL_ENV\" ravyn"

#[[tool.hatch.envs.hatch-test.matrix]]
#python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
Expand Down Expand Up @@ -308,30 +308,53 @@ combine-as-imports = true
profile = "black"
known_third_party = ["ravyn", "pydantic", "msgspec", "lilya"]

[tool.mypy]
strict = true
warn_unused_configs = true
warn_unreachable = true
warn_return_any = true
disallow_untyped_decorators = true
disallow_any_generics = false
implicit_reexport = false
show_error_codes = true
disallow_incomplete_defs = true
disable_error_code = [
"attr-defined",
"has-type",
"override",
"safe-super",
"misc",
"union-attr",
[tool.ty.analysis]
replace-imports-with-any = [
"multipart.**",
"sqlalchemy.**",
"sqlalchemy_utils.**",
"slugify.**",
"pytz",
"pyjwt.**",
"jwt.**",
"bcrypt.**",
"ravyn.contrib.auth.edgy.**",
"grpc.**",
"google.**",
]

[tool.ty.src]
exclude = [
"ravyn/conf",
"ravyn/utils",
]

[tool.ty.terminal]
error-on-warning = true

[tool.ty.rules]
unresolved-attribute = "ignore"
invalid-parameter-default = "ignore"
invalid-argument-type = "ignore"
unused-type-ignore-comment = "ignore"
invalid-assignment = "ignore"
unsupported-operator = "ignore"
not-iterable = "ignore"
possibly-missing-attribute = "ignore"
invalid-method-override = "ignore"
call-non-callable = "ignore"

[[tool.ty.overrides]]
include = [
"ravyn/injector.py",
"ravyn/openapi/params.py",
"ravyn/params.py",
]
exclude = "ravyn/conf,ravyn/utils"
warn_unused_ignores = true
warn_redundant_casts = true
no_implicit_optional = false
strict_equality = false
strict_optional = false
rules.subclass-of-final-class = "ignore"

[[tool.ty.overrides]]
include = ["ravyn/openapi/openapi.py"]
rules.unknown-argument = "ignore"

[tool.ruff]
line-length = 99
Expand Down Expand Up @@ -363,32 +386,6 @@ omit = [
"tests/cli/*",
]

[[tool.mypy.overrides]]
module = "tests.*"
ignore_missing_imports = true
check_untyped_defs = true
ignore_errors = true

[[tool.mypy.overrides]]
module = "docs_src.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = [
"multipart.*",
"sqlalchemy.*",
"sqlalchemy_utils.*",
"slugify.*",
"pytz",
"pyjwt.*",
"bcrypt.*",
"ravyn.contrib.auth.edgy.*",
"grpc.*",
"google.*",
]
ignore_missing_imports = true
ignore_errors = true

[tool.pytest.ini_options]
addopts = ["--strict-config", "--strict-markers"]
xfail_strict = true
Expand Down
4 changes: 2 additions & 2 deletions ravyn/core/config/openapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Sequence, Union, cast
from typing import Any, Optional, Sequence, Union

from pydantic import AnyUrl, BaseModel
from typing_extensions import Annotated, Doc
Expand Down Expand Up @@ -381,7 +381,7 @@ def openapi(self, app: Any) -> dict[str, Any]:
webhooks=self.webhooks,
)
app.openapi_schema = openapi_schema
return cast(dict[str, Any], app.openapi_schema)
return app.openapi_schema

def enable(self, app: Any) -> None:
"""Enables the OpenAPI documentation"""
Expand Down
5 changes: 3 additions & 2 deletions ravyn/core/config/session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Union

from pydantic import BaseModel, ConfigDict, constr, field_validator
from pydantic import BaseModel, ConfigDict, StringConstraints, field_validator
from typing_extensions import Annotated, Doc, Literal

from ravyn.core.datastructures import Secret
Expand Down Expand Up @@ -58,7 +58,8 @@ class SessionConfig(BaseModel):
),
] = "/"
session_cookie: Annotated[ # type: ignore
constr(min_length=1, max_length=256),
str,
StringConstraints(min_length=1, max_length=256),
Doc(
"""
The name for the session cookie.
Expand Down
5 changes: 3 additions & 2 deletions ravyn/core/config/static_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from lilya._internal._path import clean_path
from lilya.staticfiles import StaticFiles
from lilya.types import ASGIApp
from pydantic import BaseModel, DirectoryPath, constr, field_validator
from pydantic import BaseModel, DirectoryPath, StringConstraints, field_validator
from typing_extensions import Annotated, Doc

DirectoryType = Union[DirectoryPath, str, Path, Any]
Expand Down Expand Up @@ -35,7 +35,8 @@ class StaticFilesConfig(BaseModel):
"""

path: Annotated[ # type: ignore
constr(min_length=1),
str,
StringConstraints(min_length=1),
Doc(
"""
The path for the statics.
Expand Down
10 changes: 6 additions & 4 deletions ravyn/core/directives/operations/runserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,12 @@ def runserver(
"[pink]The [bold]Ravyn[/bold] object is imported using the following code:[/pink]",
tag="code",
)
toolkit.print(
f"[underline]from [bold]{env.module_info.module_import[0]}[/bold] import [bold]{env.module_info.module_import[1]}[/bold]",
tag=env.module_info.module_import[1],
)
module_import = env.module_info.module_import
if module_import:
toolkit.print(
f"[underline]from [bold]{module_import[0]}[/bold] import [bold]{module_import[1]}[/bold]",
tag=module_import[1],
)

# For the text access
url = f"http://{host}:{port}"
Expand Down
2 changes: 2 additions & 0 deletions ravyn/core/directives/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def handle(self, app_or_project: str, name: str, **options: Any) -> Any:
self.validate_name(name)
top_dir = os.path.join(self.location, name)
else:
if not self.deployment_folder_name:
raise DirectiveError("deployment_folder_name is required for project directives")
top_dir = os.path.join(self.location, self.deployment_folder_name)

try:
Expand Down
8 changes: 1 addition & 7 deletions ravyn/core/transformers/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@
get_origin,
)

try:
from typing import _GenericAlias # noqa
except ImportError:
from types import GenericAlias as _GenericAlias


from lilya.exceptions import HTTPException as LilyaHTTPException
from orjson import loads
from pydantic import ValidationError, create_model
Expand Down Expand Up @@ -517,7 +511,7 @@ def extract_arguments(
args = get_args(param)

for arg in args:
if isinstance(arg, _GenericAlias):
if get_origin(arg) is not None:
arguments.extend(self.extract_arguments(param=arg, argument_list=arguments))
else:
if arg not in arguments:
Expand Down
7 changes: 5 additions & 2 deletions ravyn/middleware/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Callable, Mapping, Optional, Type, Union
from typing import Any, Awaitable, Callable, Mapping, Optional, Type, Union, cast

from lilya import status
from lilya.compat import is_async_callable
Expand Down Expand Up @@ -100,7 +100,10 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
exception_handler = self.default_http_exception_handler # type: ignore
request = Request(scope, receive, send)
if is_async_callable(exception_handler):
response = await exception_handler(request, ex)
async_handler = cast(
Callable[[Request, Exception], Awaitable[LilyaResponse]], exception_handler
)
response = await async_handler(request, ex)
else:
response = await run_in_threadpool(exception_handler, request, ex)
await response(scope, receive, send)
Expand Down
8 changes: 2 additions & 6 deletions ravyn/openapi/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@
Sequence,
Set,
cast,
get_origin,
)

try:
from typing import _GenericAlias # noqa
except ImportError:
from types import GenericAlias as _GenericAlias

from lilya._internal._path import clean_path
from lilya.contrib.security.base import (
SecurityScheme as SecurityScheme,
Expand Down Expand Up @@ -120,7 +116,7 @@ def get_flat_params(route: router.HTTPHandler | Any, body_fields: list[str]) ->
query_params.append(param.field_info)

else:
if isinstance(param.field_info.annotation, _GenericAlias) and not is_base_requires(
if get_origin(param.field_info.annotation) is not None and not is_base_requires(
param.field_info.default
):
query_params.append(param.field_info)
Expand Down
2 changes: 1 addition & 1 deletion ravyn/permissions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def continue_or_raise_permission_exception(
message=getattr(permission, "message", None),
)
else:
is_permission = await has_permission(request, controller)
is_permission = await cast(Any, has_permission)(request, controller)
if not is_permission:
permission_denied(
request,
Expand Down
5 changes: 4 additions & 1 deletion ravyn/routing/controllers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ def get_route_handlers(
if self.exception_handlers:
route_handler.exception_handlers = self.get_exception_handlers(route_handler) # type: ignore

if self.tags or []: # pragma: no cover
if self.tags: # pragma: no cover
for tag in reversed(self.tags):
route_handler.tags.insert(0, tag)

Expand All @@ -474,6 +474,9 @@ def get_route_middleware(
Gets the list of extended middlewares for the handler starting from the last
to the first by reversing the list
"""
if self.middleware is None:
return

for middleware in reversed(self.middleware):
handler.middleware.insert(0, middleware)

Expand Down
7 changes: 1 addition & 6 deletions ravyn/routing/core/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
from pydantic import BaseModel, create_model
from pydantic.fields import FieldInfo

try:
from typing import _GenericAlias # noqa
except ImportError:
from types import GenericAlias as _GenericAlias

from ravyn.core.datastructures import UploadFile
from ravyn.encoders import LILYA_ENCODER_TYPES, is_body_encoder
from ravyn.openapi.params import ResponseParam
Expand Down Expand Up @@ -159,7 +154,7 @@ def convert_annotation_to_pydantic_model(field_annotation: Any) -> Any:

if is_structure or is_instance:
# If the encoder is a generic alias (e.g., a parameterized encoder class)
if isinstance(field_annotation, _GenericAlias):
if get_origin(field_annotation) is not None:
# Recursively convert the generic arguments
annotations: tuple[Any, ...] = tuple(
convert_annotation_to_pydantic_model(arg) for arg in args
Expand Down
6 changes: 3 additions & 3 deletions ravyn/routing/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,9 @@ def _get_default_status_code(self, data: Response) -> int:
int: The default status code for the response.
"""
default_response_status_code = status.HTTP_200_OK
if data.status_code != default_response_status_code:
if data.status_code is not None and data.status_code != default_response_status_code:
return data.status_code
return cast(int, self.status_code)
return self.status_code or default_response_status_code

def _get_response_container_handler(
self,
Expand Down Expand Up @@ -1005,7 +1005,7 @@ def get_security_schemes(self) -> list[SecurityScheme]:
security_schemes.extend(layer.security or [])
return security_schemes

def get_handler_tags(self) -> list[str]:
def get_handler_tags(self) -> list[str] | None:
"""
Returns all the tags associated with the handler by checking the parents as well.

Expand Down