Name | pydantic-i18n JSON |
Version |
0.4.5
JSON |
| download |
home_page | None |
Summary | pydantic-i18n is an extension to support an i18n for the pydantic error messages. |
upload_time | 2024-09-22 15:29:39 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8.1 |
license | None |
keywords |
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
|
<p align="center">
<a href="https://pydantic-i18n.boardpack.org/"><img src="https://pydantic-i18n.boardpack.org/img/logo-white.png" alt="pydantic-i18n"></a>
</p>
<p align="center">
<em>pydantic-i18n is an extension to support an i18n for the pydantic error messages.</em>
</p>
<p align="center">
<a href="https://github.com/boardpack/pydantic-i18n/actions?query=workflow%3ATest" target="_blank">
<img src="https://github.com/boardpack/pydantic-i18n/workflows/Test/badge.svg" alt="Test">
</a>
<a href="https://codecov.io/gh/boardpack/pydantic-i18n" target="_blank">
<img src="https://img.shields.io/codecov/c/github/boardpack/pydantic-i18n?color=%2334D058" alt="Coverage">
</a>
<a href="https://pypi.org/project/pydantic-i18n" target="_blank">
<img src="https://img.shields.io/pypi/v/pydantic-i18n?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://camo.githubusercontent.com/d91ed7ac7abbd5a6102cbe988dd8e9ac21bde0a73d97be7603b891ad08ce3479/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64652532307374796c652d626c61636b2d3030303030302e737667" data-canonical-src="https://img.shields.io/badge/code%20style-black-000000.svg" style="max-width:100%;"></a>
<a href="https://pycqa.github.io/isort/" rel="nofollow"><img src="https://camo.githubusercontent.com/fe4a658dd745f746410f961ae45d44355db1cc0e4c09c7877d265c1380248943/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f253230696d706f7274732d69736f72742d2532333136373462313f7374796c653d666c6174266c6162656c436f6c6f723d656638333336" alt="Imports: isort" data-canonical-src="https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336" style="max-width:100%;"></a>
</p>
---
**Documentation**: <a href="https://pydantic-i18n.boardpack.org" target="_blank">https://pydantic-i18n.boardpack.org</a>
**Source Code**: <a href="https://github.com/boardpack/pydantic-i18n" target="_blank">https://github.com/boardpack/pydantic-i18n</a>
---
## Requirements
Python 3.8+
pydantic-i18n has the next dependencies:
* <a href="https://pydantic-docs.helpmanual.io/" class="external-link" target="_blank">Pydantic</a>
* <a href="http://babel.pocoo.org/en/latest/index.html" class="external-link" target="_blank">Babel</a>
## Installation
<div class="termy">
```console
$ pip install pydantic-i18n
---> 100%
```
</div>
## First steps
To start to work with pydantic-i18n, you can just create a dictionary (or
create any needed translations storage and then convert it into dictionary)
and pass to the main `PydanticI18n` class.
To translate messages, you need to pass result of `exception.errors()` call to
the `translate` method:
```Python hl_lines="14 24"
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n
translations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}
tr = PydanticI18n(translations)
class User(BaseModel):
name: str
try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")
print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_
In the next chapters, you will see current available loaders and how to
implement your own loader.
## Usage with FastAPI
Here is a simple example usage with FastAPI.
### Create it
Let's create a `tr.py` file:
```Python linenums="1" hl_lines="13-22 25-26 32 35"
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from starlette.responses import JSONResponse
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
from pydantic_i18n import PydanticI18n
__all__ = ["get_locale", "validation_exception_handler"]
DEFAULT_LOCALE = "en_US"
translations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}
tr = PydanticI18n(translations)
def get_locale(locale: str = DEFAULT_LOCALE) -> str:
return locale
async def validation_exception_handler(
request: Request, exc: RequestValidationError
) -> JSONResponse:
current_locale = request.query_params.get("locale", DEFAULT_LOCALE)
return JSONResponse(
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": tr.translate(exc.errors(), current_locale)},
)
```
`11-20`: As you see, we selected the simplest variant to store translations,
you can use any that you need.
`23-24`: To not include `locale` query parameter into every handler, we
created a simple function `get_locale`, which we will include as a global
dependency with `Depends`.
`29-36`: An example of overridden function to return translated messages of the
validation exception.
Now we are ready to create a FastAPI application:
```Python linenums="1" hl_lines="8 10"
from fastapi import Depends, FastAPI, Request
from fastapi.exceptions import RequestValidationError
from pydantic import BaseModel
import tr
app = FastAPI(dependencies=[Depends(tr.get_locale)])
app.add_exception_handler(RequestValidationError, tr.validation_exception_handler)
class User(BaseModel):
name: str
@app.post("/user", response_model=User)
def create_user(request: Request, user: User):
pass
```
`8`: Add `get_locale` function as a global dependency.
!!! note
If you need to use i18n only for specific part of your
application, you can add this `get_locale` function to the specific
`APIRouter`. More information about `APIRouter` you can find
[here](https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter).
`10`: Override default request validation error handler with
`validation_exception_handler`.
### Run it
Run the server with:
<div class="termy">
```console
$ uvicorn main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
</div>
<details markdown="1">
<summary>About the command <code>uvicorn main:app --reload</code>...</summary>
The command `uvicorn main:app` refers to:
* `main`: the file `main.py` (the Python "module").
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--reload`: make the server restart after code changes. Only do this for development.
</details>
### Send it
Open your browser at <a href="http://127.0.0.1:8000/docs#/default/create_user_user_post" class="external-link" target="_blank">http://127.0.0.1:8000/docs#/default/create_user_user_post</a>.
Send POST-request with empty body and `de_DE` locale query param via swagger UI
or `curl`:
```bash
$ curl -X 'POST' \
'http://127.0.0.1:8000/user?locale=de_DE' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
}'
```
### Check it
As a result, you will get the next response body:
```json hl_lines="8"
{
"detail": [
{
"loc": [
"body",
"name"
],
"msg": "Feld erforderlich",
"type": "value_error.missing"
}
]
}
```
If you don't mention the `locale` param, English locale will be used by
default.
## Use placeholder in error strings
You can use placeholders in error strings, but you **must mark** every placeholder with `{}`.
```Python
from decimal import Decimal
from pydantic import BaseModel, ValidationError, Field
from pydantic_i18n import PydanticI18n
translations = {
"en_US": {
"Decimal input should have no more than {} in total":
"Decimal input should have no more than {} in total",
},
"es_AR": {
"Decimal input should have no more than {} in total":
"La entrada decimal no debe tener más de {} en total",
},
}
tr = PydanticI18n(translations)
class CoolSchema(BaseModel):
my_field: Decimal = Field(max_digits=3)
try:
CoolSchema(my_field=1111)
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="es_AR")
print(translated_errors)
# [
# {
# 'type': 'decimal_max_digits',
# 'loc': ('my_field',),
# 'msg': 'La entrada decimal no debe tener más de 3 digits en total',
# 'input': 1111,
# 'ctx': {
# 'max_digits': 3
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/decimal_max_digits'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/placeholder/tutorial001.py))_
## Get current error strings from Pydantic
pydantic-i18n doesn't provide prepared translations of all current error
messages from pydantic, but you can use a special class method
`PydanticI18n.get_pydantic_messages` to load original messages in English. By
default, it returns a `dict` object:
```Python
from pydantic_i18n import PydanticI18n
print(PydanticI18n.get_pydantic_messages())
# {
# "Object has no attribute '{}'": "Object has no attribute '{}'",
# "Invalid JSON: {}": "Invalid JSON: {}",
# "JSON input should be string, bytes or bytearray": "JSON input should be string, bytes or bytearray",
# "Recursion error - cyclic reference detected": "Recursion error - cyclic reference detected",
# "Field required": "Field required",
# "Field is frozen": "Field is frozen",
# .....
# }
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial001.py))_
You can also choose JSON string or Babel format with `output` parameter values
`"json"` and `"babel"`:
```Python
from pydantic_i18n import PydanticI18n
print(PydanticI18n.get_pydantic_messages(output="json"))
# {
# "Field required": "Field required",
# "Field is frozen": "Field is frozen",
# "Error extracting attribute: {}": "Error extracting attribute: {}",
# .....
# }
print(PydanticI18n.get_pydantic_messages(output="babel"))
# msgid "Field required"
# msgstr "Field required"
#
# msgid "Field is frozen"
# msgstr "Field is frozen"
#
# msgid "Error extracting attribute: {}"
# msgstr "Error extracting attribute: {}"
# ....
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial002.py))_
## Loaders
pydantic-i18n provides a list of loaders to use translations.
### DictLoader
DictLoader is the simplest loader and default in PydanticI18n. So you can
just pass your translations dictionary without any other preparation steps.
```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n
translations = {
"en_US": {
"Field required": "field required",
},
"de_DE": {
"Field required": "Feld erforderlich",
},
}
tr = PydanticI18n(translations)
class User(BaseModel):
name: str
try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")
print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_
### JsonLoader
JsonLoader needs to get the path to some directory with the next structure:
```text
|-- translations
|-- en_US.json
|-- de_DE.json
|-- ...
```
where e.g. `en_US.json` looks like:
```json
{
"Field required": "Field required"
}
```
and `de_DE.json`:
```json
{
"Field required": "Feld erforderlich"
}
```
Then we can use `JsonLoader` to load our translations:
```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, JsonLoader
loader = JsonLoader("./translations")
tr = PydanticI18n(loader)
class User(BaseModel):
name: str
try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")
print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',
# ),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/json-loader/tutorial001.py))_
### BabelLoader
BabelLoader works in the similar way as JsonLoader. It also needs a
translations directory with the next structure:
```text
|-- translations
|-- en_US
|-- LC_MESSAGES
|-- messages.mo
|-- messages.po
|-- de_DE
|-- LC_MESSAGES
|-- messages.mo
|-- messages.po
|-- ...
```
Information about translations preparation you can find on the
[Babel docs pages](http://babel.pocoo.org/en/latest/cmdline.html){:target="_blank"} and e.g.
from [this article](https://phrase.com/blog/posts/i18n-advantages-babel-python/#Message_Extraction){:target="_blank"}.
Here is an example of the `BabelLoader` usage:
```Python
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BabelLoader
loader = BabelLoader("./translations")
tr = PydanticI18n(loader)
class User(BaseModel):
name: str
try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")
print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/babel-loader/tutorial001.py))_
### Write your own loader
If current loaders aren't suitable for you, it's possible to write your own
loader and use it with pydantic-i18n. To do it, you need to import
`BaseLoader` and implement the next items:
- property `locales` to get a list of locales;
- method `get_translations` to get content for the specific locale.
In some cases you will also need to change implementation of the `gettext`
method.
Here is an example of the loader to get translations from CSV files:
```text
|-- translations
|-- en_US.csv
|-- de_DE.csv
|-- ...
```
`en_US.csv` content:
```csv
Field required,Field required
```
`de_DE.csv` content:
```csv
Field required,Feld erforderlich
```
```Python
import os
from typing import List, Dict
from pydantic import BaseModel, ValidationError
from pydantic_i18n import PydanticI18n, BaseLoader
class CsvLoader(BaseLoader):
def __init__(self, directory: str):
self.directory = directory
@property
def locales(self) -> List[str]:
return [
filename[:-4]
for filename in os.listdir(self.directory)
if filename.endswith(".csv")
]
def get_translations(self, locale: str) -> Dict[str, str]:
with open(os.path.join(self.directory, f"{locale}.csv")) as fp:
data = dict(line.strip().split(",") for line in fp)
return data
class User(BaseModel):
name: str
if __name__ == '__main__':
loader = CsvLoader("./translations")
tr = PydanticI18n(loader)
try:
User()
except ValidationError as e:
translated_errors = tr.translate(e.errors(), locale="de_DE")
print(translated_errors)
# [
# {
# 'type': 'missing',
# 'loc': ('name',),
# 'msg': 'Feld erforderlich',
# 'input': {
#
# },
# 'url': 'https://errors.pydantic.dev/2.6/v/missing'
# }
# ]
```
_(This script is complete, it should run "as is" for Pydantic 2+, an example for Pydantic 1 is
[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/own-loader/tutorial001.py))_
## Acknowledgments
Thanks to [Samuel Colvin](https://github.com/samuelcolvin) and his
[pydantic](https://github.com/samuelcolvin/pydantic) library.
Also, thanks to [Sebastián Ramírez](https://github.com/tiangolo) and his
[FastAPI](https://github.com/tiangolo/fastapi) project, some scripts and
documentation structure and parts were used from there.
## License
This project is licensed under the terms of the MIT license.
Raw data
{
"_id": null,
"home_page": null,
"name": "pydantic-i18n",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8.1",
"maintainer_email": null,
"keywords": null,
"author": null,
"author_email": "Roman Sadzhenytsia <urchin.dukkee@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/58/70/c21ed1ce36a947c5a7fee04c5d3926db4907f00bc29b193759d675554329/pydantic_i18n-0.4.5.tar.gz",
"platform": null,
"description": "<p align=\"center\">\n <a href=\"https://pydantic-i18n.boardpack.org/\"><img src=\"https://pydantic-i18n.boardpack.org/img/logo-white.png\" alt=\"pydantic-i18n\"></a>\n</p>\n<p align=\"center\">\n <em>pydantic-i18n is an extension to support an i18n for the pydantic error messages.</em>\n</p>\n<p align=\"center\">\n <a href=\"https://github.com/boardpack/pydantic-i18n/actions?query=workflow%3ATest\" target=\"_blank\">\n <img src=\"https://github.com/boardpack/pydantic-i18n/workflows/Test/badge.svg\" alt=\"Test\">\n </a>\n <a href=\"https://codecov.io/gh/boardpack/pydantic-i18n\" target=\"_blank\">\n <img src=\"https://img.shields.io/codecov/c/github/boardpack/pydantic-i18n?color=%2334D058\" alt=\"Coverage\">\n </a>\n <a href=\"https://pypi.org/project/pydantic-i18n\" target=\"_blank\">\n <img src=\"https://img.shields.io/pypi/v/pydantic-i18n?color=%2334D058&label=pypi%20package\" alt=\"Package version\">\n </a>\n <a href=\"https://github.com/psf/black\"><img alt=\"Code style: black\" src=\"https://camo.githubusercontent.com/d91ed7ac7abbd5a6102cbe988dd8e9ac21bde0a73d97be7603b891ad08ce3479/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64652532307374796c652d626c61636b2d3030303030302e737667\" data-canonical-src=\"https://img.shields.io/badge/code%20style-black-000000.svg\" style=\"max-width:100%;\"></a>\n <a href=\"https://pycqa.github.io/isort/\" rel=\"nofollow\"><img src=\"https://camo.githubusercontent.com/fe4a658dd745f746410f961ae45d44355db1cc0e4c09c7877d265c1380248943/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f253230696d706f7274732d69736f72742d2532333136373462313f7374796c653d666c6174266c6162656c436f6c6f723d656638333336\" alt=\"Imports: isort\" data-canonical-src=\"https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336\" style=\"max-width:100%;\"></a>\n</p>\n\n---\n\n**Documentation**: <a href=\"https://pydantic-i18n.boardpack.org\" target=\"_blank\">https://pydantic-i18n.boardpack.org</a>\n\n**Source Code**: <a href=\"https://github.com/boardpack/pydantic-i18n\" target=\"_blank\">https://github.com/boardpack/pydantic-i18n</a>\n\n---\n\n## Requirements\n\nPython 3.8+\n\npydantic-i18n has the next dependencies:\n\n* <a href=\"https://pydantic-docs.helpmanual.io/\" class=\"external-link\" target=\"_blank\">Pydantic</a>\n* <a href=\"http://babel.pocoo.org/en/latest/index.html\" class=\"external-link\" target=\"_blank\">Babel</a>\n\n\n## Installation\n\n<div class=\"termy\">\n\n```console\n$ pip install pydantic-i18n\n\n---> 100%\n```\n\n</div>\n\n## First steps\n\nTo start to work with pydantic-i18n, you can just create a dictionary (or\ncreate any needed translations storage and then convert it into dictionary)\nand pass to the main `PydanticI18n` class.\n\nTo translate messages, you need to pass result of `exception.errors()` call to\nthe `translate` method:\n\n```Python hl_lines=\"14 24\"\nfrom pydantic import BaseModel, ValidationError\nfrom pydantic_i18n import PydanticI18n\n\n\ntranslations = {\n \"en_US\": {\n \"Field required\": \"field required\",\n },\n \"de_DE\": {\n \"Field required\": \"Feld erforderlich\",\n },\n}\n\ntr = PydanticI18n(translations)\n\n\nclass User(BaseModel):\n name: str\n\n\ntry:\n User()\nexcept ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"de_DE\")\n\nprint(translated_errors)\n# [\n# {\n# 'type': 'missing',\n# 'loc': ('name',),\n# 'msg': 'Feld erforderlich',\n# 'input': {\n#\n# },\n# 'url': 'https://errors.pydantic.dev/2.6/v/missing'\n# }\n# ]\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_\n\nIn the next chapters, you will see current available loaders and how to\nimplement your own loader.\n\n## Usage with FastAPI\n\nHere is a simple example usage with FastAPI.\n\n### Create it\n\nLet's create a `tr.py` file:\n\n```Python linenums=\"1\" hl_lines=\"13-22 25-26 32 35\"\nfrom fastapi import Request\nfrom fastapi.exceptions import RequestValidationError\nfrom starlette.responses import JSONResponse\nfrom starlette.status import HTTP_422_UNPROCESSABLE_ENTITY\n\nfrom pydantic_i18n import PydanticI18n\n\n__all__ = [\"get_locale\", \"validation_exception_handler\"]\n\n\nDEFAULT_LOCALE = \"en_US\"\n\ntranslations = {\n \"en_US\": {\n \"Field required\": \"field required\",\n },\n \"de_DE\": {\n \"Field required\": \"Feld erforderlich\",\n },\n}\n\ntr = PydanticI18n(translations)\n\n\ndef get_locale(locale: str = DEFAULT_LOCALE) -> str:\n return locale\n\n\nasync def validation_exception_handler(\n request: Request, exc: RequestValidationError\n) -> JSONResponse:\n current_locale = request.query_params.get(\"locale\", DEFAULT_LOCALE)\n return JSONResponse(\n status_code=HTTP_422_UNPROCESSABLE_ENTITY,\n content={\"detail\": tr.translate(exc.errors(), current_locale)},\n )\n```\n\n`11-20`: As you see, we selected the simplest variant to store translations,\nyou can use any that you need.\n\n`23-24`: To not include `locale` query parameter into every handler, we\ncreated a simple function `get_locale`, which we will include as a global\ndependency with `Depends`.\n\n`29-36`: An example of overridden function to return translated messages of the\nvalidation exception.\n\nNow we are ready to create a FastAPI application:\n\n```Python linenums=\"1\" hl_lines=\"8 10\"\nfrom fastapi import Depends, FastAPI, Request\nfrom fastapi.exceptions import RequestValidationError\n\nfrom pydantic import BaseModel\n\nimport tr\n\napp = FastAPI(dependencies=[Depends(tr.get_locale)])\n\napp.add_exception_handler(RequestValidationError, tr.validation_exception_handler)\n\n\nclass User(BaseModel):\n name: str\n\n\n@app.post(\"/user\", response_model=User)\ndef create_user(request: Request, user: User):\n pass\n```\n\n`8`: Add `get_locale` function as a global dependency.\n\n!!! note\n If you need to use i18n only for specific part of your\n application, you can add this `get_locale` function to the specific\n `APIRouter`. More information about `APIRouter` you can find\n [here](https://fastapi.tiangolo.com/tutorial/bigger-applications/#apirouter).\n\n`10`: Override default request validation error handler with\n`validation_exception_handler`.\n\n### Run it\n\nRun the server with:\n\n<div class=\"termy\">\n\n```console\n$ uvicorn main:app --reload\n\nINFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\nINFO: Started reloader process [28720]\nINFO: Started server process [28722]\nINFO: Waiting for application startup.\nINFO: Application startup complete.\n```\n\n</div>\n\n<details markdown=\"1\">\n<summary>About the command <code>uvicorn main:app --reload</code>...</summary>\n\nThe command `uvicorn main:app` refers to:\n\n* `main`: the file `main.py` (the Python \"module\").\n* `app`: the object created inside of `main.py` with the line `app = FastAPI()`.\n* `--reload`: make the server restart after code changes. Only do this for development.\n\n</details>\n\n### Send it\n\nOpen your browser at <a href=\"http://127.0.0.1:8000/docs#/default/create_user_user_post\" class=\"external-link\" target=\"_blank\">http://127.0.0.1:8000/docs#/default/create_user_user_post</a>.\n\nSend POST-request with empty body and `de_DE` locale query param via swagger UI\n or `curl`:\n\n```bash\n$ curl -X 'POST' \\\n 'http://127.0.0.1:8000/user?locale=de_DE' \\\n -H 'accept: application/json' \\\n -H 'Content-Type: application/json' \\\n -d '{\n}'\n```\n\n### Check it\n\nAs a result, you will get the next response body:\n\n```json hl_lines=\"8\"\n{\n \"detail\": [\n {\n \"loc\": [\n \"body\",\n \"name\"\n ],\n \"msg\": \"Feld erforderlich\",\n \"type\": \"value_error.missing\"\n }\n ]\n}\n```\n\nIf you don't mention the `locale` param, English locale will be used by\ndefault.\n\n## Use placeholder in error strings\n\nYou can use placeholders in error strings, but you **must mark** every placeholder with `{}`.\n\n```Python\nfrom decimal import Decimal\n\nfrom pydantic import BaseModel, ValidationError, Field\nfrom pydantic_i18n import PydanticI18n\n\n\ntranslations = {\n \"en_US\": {\n \"Decimal input should have no more than {} in total\":\n \"Decimal input should have no more than {} in total\",\n },\n \"es_AR\": {\n \"Decimal input should have no more than {} in total\":\n \"La entrada decimal no debe tener m\u00e1s de {} en total\",\n },\n}\n\ntr = PydanticI18n(translations)\n\n\nclass CoolSchema(BaseModel):\n my_field: Decimal = Field(max_digits=3)\n\n\ntry:\n CoolSchema(my_field=1111)\nexcept ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"es_AR\")\n\nprint(translated_errors)\n# [\n# {\n# 'type': 'decimal_max_digits',\n# 'loc': ('my_field',),\n# 'msg': 'La entrada decimal no debe tener m\u00e1s de 3 digits en total',\n# 'input': 1111,\n# 'ctx': {\n# 'max_digits': 3\n# },\n# 'url': 'https://errors.pydantic.dev/2.6/v/decimal_max_digits'\n# }\n# ]\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/placeholder/tutorial001.py))_\n\n## Get current error strings from Pydantic\n\npydantic-i18n doesn't provide prepared translations of all current error\nmessages from pydantic, but you can use a special class method\n`PydanticI18n.get_pydantic_messages` to load original messages in English. By\ndefault, it returns a `dict` object:\n\n```Python\nfrom pydantic_i18n import PydanticI18n\n\nprint(PydanticI18n.get_pydantic_messages())\n# {\n# \"Object has no attribute '{}'\": \"Object has no attribute '{}'\",\n# \"Invalid JSON: {}\": \"Invalid JSON: {}\",\n# \"JSON input should be string, bytes or bytearray\": \"JSON input should be string, bytes or bytearray\",\n# \"Recursion error - cyclic reference detected\": \"Recursion error - cyclic reference detected\",\n# \"Field required\": \"Field required\",\n# \"Field is frozen\": \"Field is frozen\",\n# .....\n# }\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial001.py))_\n\nYou can also choose JSON string or Babel format with `output` parameter values\n`\"json\"` and `\"babel\"`:\n\n```Python\nfrom pydantic_i18n import PydanticI18n\n\nprint(PydanticI18n.get_pydantic_messages(output=\"json\"))\n# {\n# \"Field required\": \"Field required\",\n# \"Field is frozen\": \"Field is frozen\",\n# \"Error extracting attribute: {}\": \"Error extracting attribute: {}\",\n# .....\n# }\n\nprint(PydanticI18n.get_pydantic_messages(output=\"babel\"))\n# msgid \"Field required\"\n# msgstr \"Field required\"\n#\n# msgid \"Field is frozen\"\n# msgstr \"Field is frozen\"\n#\n# msgid \"Error extracting attribute: {}\"\n# msgstr \"Error extracting attribute: {}\"\n# ....\n\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/pydantic-messages/tutorial002.py))_\n\n\n## Loaders\n\npydantic-i18n provides a list of loaders to use translations.\n\n### DictLoader\n\nDictLoader is the simplest loader and default in PydanticI18n. So you can\njust pass your translations dictionary without any other preparation steps.\n\n```Python\nfrom pydantic import BaseModel, ValidationError\nfrom pydantic_i18n import PydanticI18n\n\n\ntranslations = {\n \"en_US\": {\n \"Field required\": \"field required\",\n },\n \"de_DE\": {\n \"Field required\": \"Feld erforderlich\",\n },\n}\n\ntr = PydanticI18n(translations)\n\n\nclass User(BaseModel):\n name: str\n\n\ntry:\n User()\nexcept ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"de_DE\")\n\nprint(translated_errors)\n# [\n# {\n# 'type': 'missing',\n# 'loc': ('name',),\n# 'msg': 'Feld erforderlich',\n# 'input': {\n#\n# },\n# 'url': 'https://errors.pydantic.dev/2.6/v/missing'\n# }\n# ]\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/dict-loader/tutorial001.py))_\n\n### JsonLoader\n\nJsonLoader needs to get the path to some directory with the next structure:\n\n```text\n\n|-- translations\n |-- en_US.json\n |-- de_DE.json\n |-- ...\n```\n\nwhere e.g. `en_US.json` looks like:\n\n```json\n{\n \"Field required\": \"Field required\"\n}\n```\n\nand `de_DE.json`:\n\n```json\n{\n \"Field required\": \"Feld erforderlich\"\n}\n```\n\nThen we can use `JsonLoader` to load our translations:\n\n```Python\nfrom pydantic import BaseModel, ValidationError\nfrom pydantic_i18n import PydanticI18n, JsonLoader\n\nloader = JsonLoader(\"./translations\")\ntr = PydanticI18n(loader)\n\n\nclass User(BaseModel):\n name: str\n\n\ntry:\n User()\nexcept ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"de_DE\")\n\nprint(translated_errors)\n# [\n# {\n# 'type': 'missing',\n# 'loc': ('name',\n# ),\n# 'msg': 'Feld erforderlich',\n# 'input': {\n#\n# },\n# 'url': 'https://errors.pydantic.dev/2.6/v/missing'\n# }\n# ]\n\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/json-loader/tutorial001.py))_\n\n### BabelLoader\n\nBabelLoader works in the similar way as JsonLoader. It also needs a\ntranslations directory with the next structure:\n\n```text\n\n|-- translations\n |-- en_US\n |-- LC_MESSAGES\n |-- messages.mo\n |-- messages.po\n |-- de_DE\n |-- LC_MESSAGES\n |-- messages.mo\n |-- messages.po\n |-- ...\n```\n\nInformation about translations preparation you can find on the\n[Babel docs pages](http://babel.pocoo.org/en/latest/cmdline.html){:target=\"_blank\"} and e.g.\nfrom [this article](https://phrase.com/blog/posts/i18n-advantages-babel-python/#Message_Extraction){:target=\"_blank\"}.\n\nHere is an example of the `BabelLoader` usage:\n\n```Python\nfrom pydantic import BaseModel, ValidationError\nfrom pydantic_i18n import PydanticI18n, BabelLoader\n\nloader = BabelLoader(\"./translations\")\ntr = PydanticI18n(loader)\n\n\nclass User(BaseModel):\n name: str\n\n\ntry:\n User()\nexcept ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"de_DE\")\n\nprint(translated_errors)\n# [\n# {\n# 'type': 'missing',\n# 'loc': ('name',),\n# 'msg': 'Feld erforderlich',\n# 'input': {\n#\n# },\n# 'url': 'https://errors.pydantic.dev/2.6/v/missing'\n# }\n# ]\n\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/babel-loader/tutorial001.py))_\n\n### Write your own loader\n\nIf current loaders aren't suitable for you, it's possible to write your own\nloader and use it with pydantic-i18n. To do it, you need to import\n`BaseLoader` and implement the next items:\n\n - property `locales` to get a list of locales;\n - method `get_translations` to get content for the specific locale.\n\nIn some cases you will also need to change implementation of the `gettext`\nmethod.\n\nHere is an example of the loader to get translations from CSV files:\n\n```text\n|-- translations\n |-- en_US.csv\n |-- de_DE.csv\n |-- ...\n```\n\n`en_US.csv` content:\n\n```csv\nField required,Field required\n```\n\n`de_DE.csv` content:\n\n```csv\nField required,Feld erforderlich\n```\n\n```Python\nimport os\nfrom typing import List, Dict\n\nfrom pydantic import BaseModel, ValidationError\nfrom pydantic_i18n import PydanticI18n, BaseLoader\n\n\nclass CsvLoader(BaseLoader):\n def __init__(self, directory: str):\n self.directory = directory\n\n @property\n def locales(self) -> List[str]:\n return [\n filename[:-4]\n for filename in os.listdir(self.directory)\n if filename.endswith(\".csv\")\n ]\n\n def get_translations(self, locale: str) -> Dict[str, str]:\n with open(os.path.join(self.directory, f\"{locale}.csv\")) as fp:\n data = dict(line.strip().split(\",\") for line in fp)\n\n return data\n\n\nclass User(BaseModel):\n name: str\n\n\nif __name__ == '__main__':\n loader = CsvLoader(\"./translations\")\n tr = PydanticI18n(loader)\n\n try:\n User()\n except ValidationError as e:\n translated_errors = tr.translate(e.errors(), locale=\"de_DE\")\n\n print(translated_errors)\n # [\n # {\n # 'type': 'missing',\n # 'loc': ('name',),\n # 'msg': 'Feld erforderlich',\n # 'input': {\n #\n # },\n # 'url': 'https://errors.pydantic.dev/2.6/v/missing'\n # }\n # ]\n\n```\n_(This script is complete, it should run \"as is\" for Pydantic 2+, an example for Pydantic 1 is\n[here](https://github.com/boardpack/pydantic-i18n/blob/master/docs_src/pydantic_v1/own-loader/tutorial001.py))_\n\n## Acknowledgments\n\nThanks to [Samuel Colvin](https://github.com/samuelcolvin) and his\n[pydantic](https://github.com/samuelcolvin/pydantic) library.\n\nAlso, thanks to [Sebasti\u00e1n Ram\u00edrez](https://github.com/tiangolo) and his\n[FastAPI](https://github.com/tiangolo/fastapi) project, some scripts and\ndocumentation structure and parts were used from there.\n\n## License\n\nThis project is licensed under the terms of the MIT license.\n",
"bugtrack_url": null,
"license": null,
"summary": "pydantic-i18n is an extension to support an i18n for the pydantic error messages.",
"version": "0.4.5",
"project_urls": {
"Documentation": "https://pydantic-i18n.boardpack.org",
"Source": "https://github.com/boardpack/pydantic-i18n"
},
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "7e3b4d2630503016cedef1751bc9ddea85b437fbfc9ca65d6af87285d76b7c2c",
"md5": "621968ea2ee28550a3b7111909cc5c99",
"sha256": "592ae6b4fee13eb0193dc0c7bdc1e629d2ab1d732d5508368412a338b16cfece"
},
"downloads": -1,
"filename": "pydantic_i18n-0.4.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "621968ea2ee28550a3b7111909cc5c99",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8.1",
"size": 10436,
"upload_time": "2024-09-22T15:29:38",
"upload_time_iso_8601": "2024-09-22T15:29:38.397315Z",
"url": "https://files.pythonhosted.org/packages/7e/3b/4d2630503016cedef1751bc9ddea85b437fbfc9ca65d6af87285d76b7c2c/pydantic_i18n-0.4.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5870c21ed1ce36a947c5a7fee04c5d3926db4907f00bc29b193759d675554329",
"md5": "3afaaaa8908cf4792655c80d0d21072a",
"sha256": "37c3b40df31713dba27c436d15a8d894d6022f3da5b78a40805e6b64edde34a3"
},
"downloads": -1,
"filename": "pydantic_i18n-0.4.5.tar.gz",
"has_sig": false,
"md5_digest": "3afaaaa8908cf4792655c80d0d21072a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8.1",
"size": 78725,
"upload_time": "2024-09-22T15:29:39",
"upload_time_iso_8601": "2024-09-22T15:29:39.828419Z",
"url": "https://files.pythonhosted.org/packages/58/70/c21ed1ce36a947c5a7fee04c5d3926db4907f00bc29b193759d675554329/pydantic_i18n-0.4.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-22 15:29:39",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "boardpack",
"github_project": "pydantic-i18n",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "pydantic-i18n"
}