diff --git a/README.md b/README.md index d0f006d..a440360 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,15 @@ Also it supports feature called «user dictionaries» — user can add his own w ### Config options You can change config of the service by changing the environment variables. Here is a list of them: * `SPELLCHECK_SENTRY_DSN` Sentry DSN for integration. Empty field disables integration. Default value is ``. -* `SPELLCHECK_API_KEY` define api key for users dictionaries mostly. Please, provide, if you want to enable user dictionaries API. Default value is ``. Restrictions is `[BeforeValidator(func=)]` +* `SPELLCHECK_API_KEY` define api key for users dictionaries mostly. Please, provide, if you want to enable user dictionaries API. Default value is ``. * `SPELLCHECK_ENABLE_CORS` enable CORS for all endpoints. In docker container this option is disabled. Default value is `True`. * `SPELLCHECK_STRUCTURED_LOGGING` enables structured (json) logging. Default value is `True`. -* `SPELLCHECK_WORKERS` define application server workers count. If you plan to use k8s and only scale with replica sets, you might want to reduce this value to `1`. Default value is `8`. Restrictions is `[typing.Annotated[int, None, Interval(gt=0, ge=None, lt=301, le=None), None]]` -* `SPELLCHECK_PORT` binding port. Default value is `10113`. Restrictions is `[typing.Annotated[int, None, Interval(gt=1023, ge=None, lt=65536, le=None), None]]` -* `SPELLCHECK_CACHE_SIZE` define LRU cache size for misspelled word/suggestions cache. Any value less than `1` makes the cache size unlimited, so be careful with this option. Default value is `10000`. Restrictions is `[BeforeValidator(func=)]` -* `SPELLCHECK_API_PREFIX` define all API's URL prefix. Default value is `/api/`. Restrictions is `[BeforeValidator(func= at 0x102ab4f70>)]` +* `SPELLCHECK_WORKERS` define application server workers count. If you plan to use k8s and only scale with replica sets, you might want to reduce this value to `1`. Default value is `8`. Restrictions: `Gt(gt=0)`, `Lt(lt=301)` +* `SPELLCHECK_PORT` binding port. Default value is `10113`. Restrictions: `Gt(gt=1023)`, `Lt(lt=65536)` +* `SPELLCHECK_CACHE_SIZE` define LRU cache size for misspelled word/suggestions cache. Any value less than `1` makes the cache size unlimited, so be careful with this option. Default value is `10000`. +* `SPELLCHECK_API_PREFIX` define all API's URL prefix. Default value is `/api/`. * `SPELLCHECK_DOCS_URL` define documentation (swagger) URL prefix. Default value is `/docs/`. -* `SPELLCHECK_MAX_SUGGESTIONS` defines how many maximum suggestions for each word will be available. `None` means unlimitied. Default value is `None`. Restrictions is `[typing.Optional[typing.Annotated[int, None, Interval(gt=0, ge=None, lt=None, le=None), None]]]` +* `SPELLCHECK_MAX_SUGGESTIONS` defines how many maximum suggestions for each word will be available. 0 means unlimitied. Default value is `0`. Restrictions: `Ge(ge=0)` * `SPELLCHECK_DICTIONARIES_PATH` define directory where user dicts is stored. This is inner directory in the docker image, please map it to volume as it shown in the quickstart part of this readme. Default value is `/data`. * `SPELLCHECK_DICTIONARIES_STORAGE_PROVIDER` define wich engine will store user dictionaries. Default value is `StorageProviders.FILE`. * `SPELLCHECK_DICTIONARIES_DISABLED` switches off user dictionaries API no matter what. Default value is `False`. diff --git a/scripts/__main__.py b/scripts/__main__.py index 84134e2..61434cd 100755 --- a/scripts/__main__.py +++ b/scripts/__main__.py @@ -39,7 +39,14 @@ def _update_readme() -> None: f"Default value is `{field_properties.default}`.", ] if field_properties.metadata: - one_row_parts.append(f"Restrictions is `{field_properties.metadata}`") + validators_buf: list[str] = [] + for one_obj in field_properties.metadata: + restriction_stringified: typing.Final = str(one_obj) + if any(("BeforeValidator" in restriction_stringified, "StringConstraints" in restriction_stringified)): + continue + validators_buf.append(f"`{restriction_stringified}`") + if validators_buf: + one_row_parts.append(f"Restrictions: {', '.join(validators_buf)}") pack_of_readme_lines.append(" ".join(one_row_parts)) automatic_config_readme: str = "* " + "\n* ".join(pack_of_readme_lines) new_content = re.sub( diff --git a/whole_app/settings.py b/whole_app/settings.py index 53815b8..eceb423 100644 --- a/whole_app/settings.py +++ b/whole_app/settings.py @@ -62,97 +62,138 @@ class StorageProviders(enum.Enum): class SettingsOfMicroservice(BaseSettings): - app_title: typing.Literal["Spellcheck API"] = "Spellcheck API" - service_name: typing.Literal["spellcheck-microservice"] = "spellcheck-microservice" - sentry_dsn: str = pydantic.Field( - "", - description="Sentry DSN for integration. Empty field disables integration", - ) + app_title: str = "Spellcheck API" + service_name: str = "spellcheck-microservice" + sentry_dsn: typing.Annotated[ + str, + pydantic.Field( + description="Sentry DSN for integration. Empty field disables integration", + ), + pydantic.StringConstraints( + strip_whitespace=True, + ), + ] = "" api_key: typing.Annotated[ str, pydantic.BeforeValidator(_warn_about_empty_api_key), - ] = pydantic.Field( - "", - description=( - "define api key for users dictionaries mostly. " - "Please, provide, if you want to enable user dictionaries API" - ), - ) - api_key_header_name: str = "Api-Key" - enable_cors: bool = pydantic.Field( - default=True, - description="enable CORS for all endpoints. In docker container this option is disabled", - ) - structured_logging: bool = pydantic.Field( - default=True, - description="enables structured (json) logging", - ) - workers: typing.Annotated[int, pydantic.conint(gt=0, lt=301)] = pydantic.Field( - 8, - description=( - "define application server workers count. " - "If you plan to use k8s and only scale with replica sets, you might want to reduce this value to `1`" - ), - ) - port: typing.Annotated[int, pydantic.conint(gt=1_023, lt=65_536)] = pydantic.Field( - 10_113, - description="binding port", - ) + pydantic.Field( + description=( + "define api key for users dictionaries mostly. " + "Please, provide, if you want to enable user dictionaries API" + ), + ), + ] = "" + api_key_header_name: typing.Annotated[ + str, + pydantic.StringConstraints( + strip_whitespace=True, + ), + ] = "Api-Key" + enable_cors: typing.Annotated[ + bool, + pydantic.Field( + description="enable CORS for all endpoints. In docker container this option is disabled", + ), + ] = True + structured_logging: typing.Annotated[ + bool, + pydantic.Field( + description="enables structured (json) logging", + ), + ] = True + workers: typing.Annotated[ + int, + pydantic.Field( + gt=0, + lt=301, + description=( + "define application server workers count. " + "If you plan to use k8s and only scale with replica sets, you might want to reduce this value to `1`" + ), + ), + ] = 8 + port: typing.Annotated[ + int, + pydantic.Field( + gt=1_023, + lt=65_536, + description="binding port", + ), + ] = 10_113 cache_size: typing.Annotated[ int, pydantic.BeforeValidator(_warn_about_poor_lru_cache_size), - ] = pydantic.Field( - 10_000, - description=( - "define LRU cache size for misspelled word/suggestions cache. " - "Any value less than `1` makes the cache size unlimited, so be careful with this option" + pydantic.Field( + description=( + "define LRU cache size for misspelled word/suggestions cache. " + "Any value less than `1` makes the cache size unlimited, so be careful with this option" + ), ), - ) + ] = 10_000 api_prefix: typing.Annotated[ str, + pydantic.StringConstraints( + strip_whitespace=True, + ), pydantic.BeforeValidator( lambda possible_value: f"/{possible_value.strip('/')}", ), - ] = pydantic.Field("/api/", description="define all API's URL prefix") - docs_url: str = pydantic.Field( - "/docs/", - description="define documentation (swagger) URL prefix", - ) + pydantic.Field(description="define all API's URL prefix"), + ] = "/api/" + docs_url: typing.Annotated[ + str, + pydantic.StringConstraints( + strip_whitespace=True, + ), + pydantic.Field( + description="define documentation (swagger) URL prefix", + ), + ] = "/docs/" max_suggestions: typing.Annotated[ - int | None, - pydantic.conint(gt=0) | None, - ] = pydantic.Field( - None, - description="defines how many maximum suggestions for each word will be available. `None` means unlimitied", - ) - dictionaries_path: pathlib.Path = pydantic.Field( - pathlib.Path("/data/"), - description=( - "define directory where user dicts is stored. " - "This is inner directory in the docker image, please map it to volume as it " - "shown in the quickstart part of this readme" - ), - ) - dictionaries_storage_provider: StorageProviders = pydantic.Field( - StorageProviders.FILE, - description="define wich engine will store user dictionaries", - ) - dictionaries_disabled: bool = pydantic.Field( - default=False, - description="switches off user dictionaries API no matter what", - ) + int, + pydantic.Field( + ge=0, + description="defines how many maximum suggestions for each word will be available. 0 means unlimitied", + ), + ] = 0 + dictionaries_path: typing.Annotated[ + pathlib.Path, + pydantic.Field( + description=( + "define directory where user dicts is stored. " + "This is inner directory in the docker image, please map it to volume as it " + "shown in the quickstart part of this readme" + ), + ), + ] = pathlib.Path("/data/") + dictionaries_storage_provider: typing.Annotated[ + StorageProviders, + pydantic.Field( + description="define wich engine will store user dictionaries", + ), + ] = StorageProviders.FILE + dictionaries_disabled: typing.Annotated[ + bool, + pydantic.Field( + description="switches off user dictionaries API no matter what", + ), + ] = False current_version: typing.Annotated[ str, pydantic.BeforeValidator(_parse_version_from_local_file), ] = "" - username_min_length: int = pydantic.Field( - 3, - description="minimum length of username", - ) - username_max_length: int = pydantic.Field( - 60, - description="maximum length of username", - ) + username_min_length: typing.Annotated[ + int, + pydantic.Field( + description="minimum length of username", + ), + ] = 3 + username_max_length: typing.Annotated[ + int, + pydantic.Field( + description="maximum length of username", + ), + ] = 60 username_regex: str = r"^[a-zA-Z0-9-_]*$" class Config: diff --git a/whole_app/spell.py b/whole_app/spell.py index ee66460..f9a8df1 100644 --- a/whole_app/spell.py +++ b/whole_app/spell.py @@ -38,7 +38,9 @@ def get_memorized_suggestions(word_spellcheck_result: SpellChecker) -> list[str] misspelled_suggestions = word_spellcheck_result.suggest() _MISSPELED_CACHE[word_spellcheck_result.word] = misspelled_suggestions return ( - misspelled_suggestions[: SETTINGS.max_suggestions] if SETTINGS.max_suggestions else misspelled_suggestions + misspelled_suggestions[: SETTINGS.max_suggestions] + if SETTINGS.max_suggestions > 0 + else misspelled_suggestions ) def run_check(self: "SpellCheckService") -> list[models.OneCorrection]: