diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..bde7bcc --- /dev/null +++ b/docs/api.md @@ -0,0 +1,25 @@ +# API reference + +## Limiter class + +:::slowapi.extension.Limiter + :docstring: + :members: limit shared_limit + +## Wrappers around Limit objects + +These wrap the `RateLimitItem` from [alisaifee/limits](limits.readthedocs.io/). + +:::slowapi.wrappers.Limit + :docstring: + +:::slowapi.wrappers.LimitGroup + :docstring: + +## Utility functions + +:::slowapi.util.get_ipaddr + :docstring: + +:::slowapi.util.get_remote_address + :docstring: diff --git a/docs/css/custom.css b/docs/css/custom.css new file mode 100644 index 0000000..6e5d919 --- /dev/null +++ b/docs/css/custom.css @@ -0,0 +1,10 @@ +div.autodoc-docstring { + padding-left: 20px; + margin-bottom: 30px; + border-left: 5px solid rgba(230, 230, 230); +} + +div.autodoc-members { + padding-left: 20px; + margin-bottom: 15px; +} diff --git a/docs/index.md b/docs/index.md index 34eb88c..3a7697c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -65,7 +65,6 @@ Supported now: - support for sync and async HTTP endpoints - Support for shared limits across a set of routes - # Limitations and known issues * There is no support for default limits yet (in other words, the only default limit supported is "unlimited") @@ -92,6 +91,8 @@ and not: PRs are more than welcome! Please include tests for your changes :) +Please run [black](black.readthedocs.io/) on your code before committing, or your PR will not pass the tests. + The package uses [poetry](https://python-poetry.org) to manage dependencies. To setup your dev env: ```bash @@ -107,3 +108,5 @@ $ pytest Credits go to [flask-limiter](https://github.com/alisaifee/flask-limiter) of which SlowApi is a (still partial) adaptation to Starlette and FastAPI. It's also important to mention that the actual rate limiting work is done by [limits](https://github.com/alisaifee/limits/), `slowapi` is just a wrapper around it. + +The documentation is built using [mkDocs](https://www.mkdocs.org/) and the API documentation is generated using [mkautodoc](https://github.com/tomchristie/mkautodoc). diff --git a/mkdocs.yml b/mkdocs.yml index bee24bd..e18182f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1 +1,13 @@ site_name: SlowApi Documentation + +markdown_extensions: + - admonition + - codehilite + - mkautodoc + +extra_css: + - css/custom.css + +nav: +- SlowApi: index.md +- api.md diff --git a/poetry.lock b/poetry.lock index 9b8de82..ab16c33 100644 --- a/poetry.lock +++ b/poetry.lock @@ -274,6 +274,14 @@ optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" version = "1.1.1" +[[package]] +category = "dev" +description = "AutoDoc for MarkDown" +name = "mkautodoc" +optional = false +python-versions = ">=3.6" +version = "0.1.0" + [[package]] category = "dev" description = "Project documentation with Markdown." @@ -601,7 +609,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "362cd8689bc952a11b7030a218e1ab0b02cdb3be6c1a7aba3f8ff9ccd7830b71" +content-hash = "223a1bc633476a4cd28f2294d7cff6ede0b51afb3944f4ba680fbd0a0adf77c9" lock-version = "1.0" python-versions = "^3.6" @@ -747,6 +755,9 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] +mkautodoc = [ + {file = "mkautodoc-0.1.0.tar.gz", hash = "sha256:7c2595f40276b356e576ce7e343338f8b4fa1e02ea904edf33fadf82b68ca67c"}, +] mkdocs = [ {file = "mkdocs-1.1.2-py3-none-any.whl", hash = "sha256:096f52ff52c02c7e90332d2e53da862fde5c062086e1b5356a6e392d5d60f5e9"}, {file = "mkdocs-1.1.2.tar.gz", hash = "sha256:f0b61e5402b99d7789efa032c7a74c90a20220a9c81749da06dbfbcbd52ffb39"}, diff --git a/pyproject.toml b/pyproject.toml index e61aa21..d8afd1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ readme = "README.md" repository = "https://github.com/laurents/slowapi" homepage = "https://github.com/laurents/slowapi" +documentation = "https://slowapi.readthedocs.io/en/latest/" include = ["slowapi/py.typed"] @@ -29,6 +30,7 @@ hiro = "^0.5.1" requests = "^2.22.0" pytest = "^5.3.5" mkdocs = "^1.1.2" +mkautodoc = "^0.1.0" [tool.black] line-length = 88 diff --git a/slowapi/extension.py b/slowapi/extension.py index b99976d..c9b8e56 100644 --- a/slowapi/extension.py +++ b/slowapi/extension.py @@ -13,19 +13,8 @@ import time import warnings from email.utils import formatdate, parsedate_to_datetime from functools import wraps -from typing import ( - Any, - Awaitable, - Callable, - Dict, - List, - Optional, - Set, - Tuple, - Type, - TypeVar, - Union, -) +from typing import (Any, Awaitable, Callable, Dict, List, Optional, Set, Tuple, + Type, TypeVar, Union) from limits import RateLimitItem # type: ignore from limits.errors import ConfigurationError # type: ignore @@ -95,28 +84,39 @@ def _rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded) -> Re class Limiter: """ - :param app: :class:`Starlette/FastAPI` instance to initialize the extension + Initializes the slowapi rate limiter. + + ** parameter ** + + * **app**: `Starlette/FastAPI` instance to initialize the extension with. - :param list default_limits: a variable list of strings or callables returning strings denoting global - limits to apply to all routes. :ref:`ratelimit-string` for more details. - :param list application_limits: a variable list of strings or callables returning strings for limits that + + * **default_limits**: a variable list of strings or callables returning strings denoting global + limits to apply to all routes. `ratelimit-string` for more details. + + * **application_limits**: a variable list of strings or callables returning strings for limits that are applied to the entire application (i.e a shared limit for all routes) - :param function key_func: a callable that returns the domain to rate limit by. - :param bool headers_enabled: whether ``X-RateLimit`` response headers are written. - :param str strategy: the strategy to use. refer to :ref:`ratelimit-strategy` - :param str storage_uri: the storage location. refer to :ref:`ratelimit-conf` - :param dict storage_options: kwargs to pass to the storage implementation upon + + * **key_func**: a callable that returns the domain to rate limit by. + + * **headers_enabled**: whether ``X-RateLimit`` response headers are written. + + * **strategy:** the strategy to use. refer to `ratelimit-strategy` + + * **storage_uri**: the storage location. refer to `ratelimit-conf` + + * **storage_options**: kwargs to pass to the storage implementation upon instantiation. - :param bool auto_check: whether to automatically check the rate limit in the before_request + * **auto_check**: whether to automatically check the rate limit in the before_request chain of the application. default ``True`` - :param bool swallow_errors: whether to swallow errors when hitting a rate limit. + * **swallow_errors**: whether to swallow errors when hitting a rate limit. An exception will still be logged. default ``False`` - :param list in_memory_fallback: a variable list of strings or callables returning strings denoting fallback + * **in_memory_fallback**: a variable list of strings or callables returning strings denoting fallback limits to apply when the storage is down. - :param bool in_memory_fallback_enabled: simply falls back to in memory storage + * **in_memory_fallback_enabled**: simply falls back to in memory storage when the main storage is down and inherits the original limits. - :param str key_prefix: prefix prepended to rate limiter keys. - :param Optional[str] config_filename: name of the config file for Starlette from which to load settings + * **key_prefix**: prefix prepended to rate limiter keys. + * **config_filename**: name of the config file for Starlette from which to load settings for the rate limiter. Defaults to ".env". """ @@ -623,20 +623,20 @@ class Limiter: exempt_when: Optional[Callable[..., bool]] = None, ) -> Callable: """ - decorator to be used for rate limiting individual routes. + Decorator to be used for rate limiting individual routes. - :param limit_value: rate limit string or a callable that returns a string. + * **limit_value**: rate limit string or a callable that returns a string. :ref:`ratelimit-string` for more details. - :param function key_func: function/lambda to extract the unique identifier for + * **key_func**: function/lambda to extract the unique identifier for the rate limit. defaults to remote address of the request. - :param bool per_method: whether the limit is sub categorized into the http + * **per_method**: whether the limit is sub categorized into the http method of the request. - :param list methods: if specified, only the methods in this list will be rate + * **methods**: if specified, only the methods in this list will be rate limited (default: None). - :param error_message: string (or callable that returns one) to override the + * **error_message**: string (or callable that returns one) to override the error message used in the response. - :param exempt_when: - :return: + * **exempt_when**: function returning a boolean indicating whether to exempt + the route from the limit """ return self.__limit_decorator( limit_value, @@ -656,17 +656,22 @@ class Limiter: exempt_when: Optional[Callable[..., bool]] = None, ) -> Callable: """ - decorator to be applied to multiple routes sharing the same rate limit. + Decorator to be applied to multiple routes sharing the same rate limit. - :param limit_value: rate limit string or a callable that returns a string. + * **limit_value**: rate limit string or a callable that returns a string. :ref:`ratelimit-string` for more details. - :param scope: a string or callable that returns a string + * **scope**: a string or callable that returns a string for defining the rate limiting scope. - :param function key_func: function/lambda to extract the unique identifier for + * **key_func**: function/lambda to extract the unique identifier for the rate limit. defaults to remote address of the request. - :param error_message: string (or callable that returns one) to override the + * **per_method**: whether the limit is sub categorized into the http + method of the request. + * **methods**: if specified, only the methods in this list will be rate + limited (default: None). + * **error_message**: string (or callable that returns one) to override the error message used in the response. - :param exempt_when: + * **exempt_when**: function returning a boolean indicating whether to exempt + the route from the limit """ return self.__limit_decorator( limit_value, diff --git a/slowapi/util.py b/slowapi/util.py index 7a454ab..2a9269c 100644 --- a/slowapi/util.py +++ b/slowapi/util.py @@ -7,7 +7,7 @@ from starlette.requests import Request def get_ipaddr(request: Request) -> str: """ - :return: the ip address for the current request (or 127.0.0.1 if none found) + Returns the ip address for the current request (or 127.0.0.1 if none found) based on the X-Forwarded-For headers. Note that a more robust method for determining IP address of the client is provided by uvicorn's ProxyHeadersMiddleware. @@ -21,6 +21,6 @@ def get_ipaddr(request: Request) -> str: def get_remote_address(request: Request) -> str: """ - :return: the ip address for the current request (or 127.0.0.1 if none found) + Returns the ip address for the current request (or 127.0.0.1 if none found) """ return request.client.host or "127.0.0.1"