diff --git a/.all-contributorsrc b/.all-contributorsrc index ddbacfcd34..e0ca6b8c09 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1688,6 +1688,15 @@ "contributions": [ "bug" ] + }, + { + "login": "sherbang", + "name": "sherbang", + "avatar_url": "https://avatars.githubusercontent.com/u/275015?v=4", + "profile": "https://github.com/sherbang", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbf0986b56..766279dcb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,31 @@ jobs: - name: Run pyright run: pdm run pyright + slotscheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.8" + allow-prereleases: false + + - uses: pdm-project/setup-pdm@v4 + name: Set up PDM + with: + python-version: "3.8" + allow-python-prereleases: false + cache: true + cache-dependency-path: | + ./pdm.lock + + - name: Install dependencies + run: pdm install -G:all + + - name: Run slotscheck + run: pdm run slotscheck litestar + test: name: "test (${{ matrix.python-version }})" strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abdb4aac6d..ecf00f31c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,11 +41,6 @@ repos: - id: ensure-dunder-all exclude: "test*|examples*|tools" args: ["--use-tuple"] - - repo: https://github.com/ariebovenberg/slotscheck - rev: v0.19.0 - hooks: - - id: slotscheck - exclude: "test_*|docs|.github" - repo: https://github.com/sphinx-contrib/sphinx-lint rev: "v0.9.1" hooks: diff --git a/Makefile b/Makefile index 4407d391da..b17df23549 100644 --- a/Makefile +++ b/Makefile @@ -102,8 +102,14 @@ pre-commit: ## Runs pre-commit hooks; includes ruff formatting and lin @$(PDM) run pre-commit run --all-files @echo "=> Pre-commit complete" +.PHONY: slots-check +slots-check: ## Check for slots usage in classes + @echo "=> Checking for slots usage in classes" + @$(PDM) run slotscheck litestar + @echo "=> Slots check complete" + .PHONY: lint -lint: pre-commit type-check ## Run all linting +lint: pre-commit type-check slots-check ## Run all linting .PHONY: coverage coverage: ## Run the tests and generate coverage report diff --git a/README.md b/README.md index fc277654d9..2469b993e5 100644 --- a/README.md +++ b/README.md @@ -129,10 +129,10 @@ A **huge** thanks to our sponsors: Check out our sponsors in the docs -If you would like to support the work that we do please consider [becoming a sponsor][sponsor-github] -on [GitHub][sponsor-github] or [Open Collective][sponsor-oc]. +If you would like to support the work that we do please consider [becoming a sponsor][sponsor-polar] +via [Polar.sh][sponsor-polar] (preferred), [GitHub][sponsor-github] or [Open Collective][sponsor-oc]. -We also participate in pledge-based sponsorship with [Polar][sponsor-polar]. +Also, exclusively with [Polar][sponsor-polar], you can engage in pledge-based sponsorships. [sponsor-github]: https://github.com/sponsors/litestar-org [sponsor-oc]: https://opencollective.com/litestar @@ -554,6 +554,7 @@ see [the contribution guide](CONTRIBUTING.rst). James Bennett
James Bennett

🐛 + sherbang
sherbang

📖 diff --git a/docs/PYPI_README.md b/docs/PYPI_README.md index 4ee50f7aca..ddef0ed1ed 100644 --- a/docs/PYPI_README.md +++ b/docs/PYPI_README.md @@ -126,10 +126,10 @@ A **huge** thanks to our sponsors: Check out our sponsors in the docs -If you would like to support the work that we do please consider [becoming a sponsor][sponsor-github] -on [GitHub][sponsor-github] or [Open Collective][sponsor-oc]. +If you would like to support the work that we do please consider [becoming a sponsor][sponsor-polar] +via [Polar.sh][sponsor-polar] (preferred), [GitHub][sponsor-github] or [Open Collective][sponsor-oc]. -We also participate in pledge-based sponsorship with [Polar][sponsor-polar]. +Also, exclusively with [Polar][sponsor-polar], you can engage in pledge-based sponsorships. [sponsor-github]: https://github.com/sponsors/litestar-org [sponsor-oc]: https://opencollective.com/litestar diff --git a/docs/index.rst b/docs/index.rst index f526e32898..3fb006fa79 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -164,11 +164,10 @@ A huge thank you to our current sponsors: We invite organizations and individuals to join our sponsorship program. -By becoming a sponsor on platforms like `GitHub `_ +By becoming a sponsor on `Polar `_ (preferred), or other platforms like `GitHub `_ and `Open Collective `_, you can play a pivotal role in our project's growth. -Additionally, we engage in pledge-based sponsorship opportunities through `Polar `_. - +Also, exclusively with `Polar `_, you can engage in pledge-based sponsorships. .. _sponsor-github: https://github.com/sponsors/litestar-org .. _sponsor-oc: https://opencollective.com/litestar diff --git a/docs/release-notes/changelog.rst b/docs/release-notes/changelog.rst index 75caa3a854..5bbc846f09 100644 --- a/docs/release-notes/changelog.rst +++ b/docs/release-notes/changelog.rst @@ -6,12 +6,6 @@ .. changelog:: 2.7.1 :date: 2024-03-22 - .. change:: add default encoders for `Enums` and `EnumMeta` - :type: bugfix - :pr: 3193 - - This addresses an issue when serializing ``Enums`` that was reported in discord. - .. change:: replace TestClient.__enter__ return type with Self :type: bugfix :pr: 3194 diff --git a/litestar/app.py b/litestar/app.py index eaa796c468..3dbdbf676f 100644 --- a/litestar/app.py +++ b/litestar/app.py @@ -151,21 +151,17 @@ class Litestar(Router): "csrf_config", "event_emitter", "get_logger", - "include_in_schema", "logger", "logging_config", "multipart_form_part_limit", "on_shutdown", "on_startup", "openapi_config", - "request_class", "response_cache_config", "route_map", - "signature_namespace", "state", "stores", "template_engine", - "websocket_class", "pdb_on_exception", "experimental_features", ) diff --git a/litestar/cli/__init__.py b/litestar/cli/__init__.py index f6c366e495..7dabefaae9 100644 --- a/litestar/cli/__init__.py +++ b/litestar/cli/__init__.py @@ -14,8 +14,6 @@ click.rich_click.USE_MARKDOWN = False click.rich_click.SHOW_ARGUMENTS = True click.rich_click.GROUP_ARGUMENTS_OPTIONS = True - click.rich_click.SHOW_ARGUMENTS = True - click.rich_click.GROUP_ARGUMENTS_OPTIONS = True click.rich_click.STYLE_ERRORS_SUGGESTION = "magenta italic" click.rich_click.ERRORS_SUGGESTION = "" click.rich_click.ERRORS_EPILOGUE = "" diff --git a/litestar/contrib/opentelemetry/middleware.py b/litestar/contrib/opentelemetry/middleware.py index 762bae9125..59ea4dce06 100644 --- a/litestar/contrib/opentelemetry/middleware.py +++ b/litestar/contrib/opentelemetry/middleware.py @@ -24,8 +24,6 @@ class OpenTelemetryInstrumentationMiddleware(AbstractMiddleware): """OpenTelemetry Middleware.""" - __slots__ = ("open_telemetry_middleware",) - def __init__(self, app: ASGIApp, config: OpenTelemetryConfig) -> None: """Middleware that adds OpenTelemetry instrumentation to the application. diff --git a/litestar/dto/_types.py b/litestar/dto/_types.py index 24e99b793b..b0863b2593 100644 --- a/litestar/dto/_types.py +++ b/litestar/dto/_types.py @@ -96,9 +96,6 @@ class MappingType(CompositeType): @dataclass(frozen=True) class TransferDTOFieldDefinition(DTOFieldDefinition): __slots__ = ( - "default_factory", - "dto_field", - "model_name", "is_excluded", "is_partial", "serialization_name", diff --git a/litestar/handlers/websocket_handlers/listener.py b/litestar/handlers/websocket_handlers/listener.py index 86fefc913a..8e702ea1aa 100644 --- a/litestar/handlers/websocket_handlers/listener.py +++ b/litestar/handlers/websocket_handlers/listener.py @@ -62,11 +62,10 @@ class WebsocketListenerRouteHandler(WebsocketRouteHandler): "connection_accept_handler": "Callback to accept a WebSocket connection. By default, calls WebSocket.accept", "on_accept": "Callback invoked after a WebSocket connection has been accepted", "on_disconnect": "Callback invoked after a WebSocket connection has been closed", - "weboscket_class": "WebSocket class", "_connection_lifespan": None, - "_handle_receive": None, - "_handle_send": None, + "_receive_handler": None, "_receive_mode": None, + "_send_handler": None, "_send_mode": None, } diff --git a/litestar/handlers/websocket_handlers/route_handler.py b/litestar/handlers/websocket_handlers/route_handler.py index edb49c3030..4b8953ee26 100644 --- a/litestar/handlers/websocket_handlers/route_handler.py +++ b/litestar/handlers/websocket_handlers/route_handler.py @@ -18,6 +18,8 @@ class WebsocketRouteHandler(BaseRouteHandler): Use this decorator to decorate websocket handler functions. """ + __slots__ = ("websocket_class",) + def __init__( self, path: str | list[str] | None = None, diff --git a/litestar/middleware/compression/facade.py b/litestar/middleware/compression/facade.py index 0074b57419..a1a62728ce 100644 --- a/litestar/middleware/compression/facade.py +++ b/litestar/middleware/compression/facade.py @@ -12,6 +12,8 @@ class CompressionFacade(Protocol): """A unified facade offering a uniform interface for different compression libraries.""" + __slots__ = () + encoding: ClassVar[str] """The encoding of the compression.""" diff --git a/litestar/middleware/cors.py b/litestar/middleware/cors.py index 6c4de31f8f..010576aa6a 100644 --- a/litestar/middleware/cors.py +++ b/litestar/middleware/cors.py @@ -17,8 +17,6 @@ class CORSMiddleware(AbstractMiddleware): """CORS Middleware.""" - __slots__ = ("config",) - def __init__(self, app: ASGIApp, config: CORSConfig) -> None: """Middleware that adds CORS validation to the application. diff --git a/litestar/middleware/logging.py b/litestar/middleware/logging.py index 0094f10cfa..c986eb6433 100644 --- a/litestar/middleware/logging.py +++ b/litestar/middleware/logging.py @@ -48,8 +48,6 @@ class LoggingMiddleware(AbstractMiddleware): """Logging middleware.""" - __slots__ = ("config", "logger", "request_extractor", "response_extractor", "is_struct_logger") - logger: Logger def __init__(self, app: ASGIApp, config: LoggingMiddlewareConfig) -> None: diff --git a/litestar/middleware/rate_limit.py b/litestar/middleware/rate_limit.py index cd767ba4a2..0c3de7f6e5 100644 --- a/litestar/middleware/rate_limit.py +++ b/litestar/middleware/rate_limit.py @@ -41,8 +41,6 @@ class CacheObject: class RateLimitMiddleware(AbstractMiddleware): """Rate-limiting middleware.""" - __slots__ = ("app", "check_throttle_handler", "max_requests", "unit", "request_quota", "config") - def __init__(self, app: ASGIApp, config: RateLimitConfig) -> None: """Initialize ``RateLimitMiddleware``. diff --git a/litestar/plugins/base.py b/litestar/plugins/base.py index afc571efe7..65710c9fc0 100644 --- a/litestar/plugins/base.py +++ b/litestar/plugins/base.py @@ -212,6 +212,8 @@ def to_openapi_schema(self, field_definition: FieldDefinition, schema_creator: S class OpenAPISchemaPlugin(OpenAPISchemaPluginProtocol): """Plugin to extend the support of OpenAPI schema generation for non-library types.""" + __slots__ = () + @staticmethod def is_plugin_supported_type(value: Any) -> bool: """Given a value of indeterminate type, determine if this value is supported by the plugin. diff --git a/litestar/stores/base.py b/litestar/stores/base.py index 34aa514fca..69a63663e5 100644 --- a/litestar/stores/base.py +++ b/litestar/stores/base.py @@ -20,6 +20,8 @@ class Store(ABC): """Thread and process safe asynchronous key/value store.""" + __slots__ = () + @abstractmethod async def set(self, key: str, value: str | bytes, expires_in: int | timedelta | None = None) -> None: """Set a value. @@ -97,6 +99,8 @@ class NamespacedStore(Store): should be isolated. """ + __slots__ = ("namespace",) + @abstractmethod def with_namespace(self, namespace: str) -> Self: """Return a new instance of :class:`NamespacedStore`, which exists in a child namespace of the current namespace. diff --git a/litestar/stores/redis.py b/litestar/stores/redis.py index 6697962fab..4b46097199 100644 --- a/litestar/stores/redis.py +++ b/litestar/stores/redis.py @@ -21,7 +21,12 @@ class RedisStore(NamespacedStore): """Redis based, thread and process safe asynchronous key/value store.""" - __slots__ = ("_redis",) + __slots__ = ( + "_delete_all_script", + "_get_and_renew_script", + "_redis", + "handle_client_shutdown", + ) def __init__( self, redis: Redis, namespace: str | None | EmptyType = Empty, handle_client_shutdown: bool = False diff --git a/litestar/typing.py b/litestar/typing.py index 3a275573f3..14a92dc44f 100644 --- a/litestar/typing.py +++ b/litestar/typing.py @@ -96,7 +96,7 @@ def _parse_metadata(value: Any, is_sequence_container: bool, extra: dict[str, An example_list: list[Any] | None if example := extra.pop("example", None): example_list = [Example(value=example)] - elif examples := getattr(value, "examples", None): + elif examples := (extra.pop("examples", None) or getattr(value, "examples", None)): example_list = [Example(value=example) for example in cast("list[str]", examples)] else: example_list = None diff --git a/pyproject.toml b/pyproject.toml index 1dfc87b68b..3eb1d2489d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -285,6 +285,12 @@ reportUnnecessaryTypeIgnoreComments = true [tool.slotscheck] strict-imports = false +exclude-classes = """ +( + # github.com/python/cpython/pull/106771 + (^litestar.events.emitter:BaseEventEmitterBackend) +) +""" [tool.ruff] lint.select = [ diff --git a/tests/unit/test_contrib/test_pydantic/test_openapi.py b/tests/unit/test_contrib/test_pydantic/test_openapi.py index aa84def09b..4407b79980 100644 --- a/tests/unit/test_contrib/test_pydantic/test_openapi.py +++ b/tests/unit/test_contrib/test_pydantic/test_openapi.py @@ -553,6 +553,26 @@ class Model(pydantic_v2.BaseModel): assert value.examples == ["example"] +def test_create_schema_for_field_v2__examples() -> None: + class Model(pydantic_v2.BaseModel): + value: str = pydantic_v2.Field( + title="title", description="description", max_length=16, json_schema_extra={"examples": ["example"]} + ) + + schema = get_schema_for_field_definition( + FieldDefinition.from_kwarg(name="Model", annotation=Model), plugins=[PydanticSchemaPlugin()] + ) + + assert schema.properties + + value = schema.properties["value"] + + assert isinstance(value, Schema) + assert value.description == "description" + assert value.title == "title" + assert value.examples == ["example"] + + @pytest.mark.parametrize("with_future_annotations", [True, False]) def test_create_schema_for_pydantic_model_with_annotated_model_attribute( with_future_annotations: bool, create_module: "Callable[[str], ModuleType]", pydantic_version: PydanticVersion