fastapi-babel icon indicating copy to clipboard operation
fastapi-babel copied to clipboard

LookupError in core.py after latest update (0.0.9)

Open grandant opened this issue 1 year ago • 10 comments

Hi,

I just want to say thank you for your work. I use fastapi-babel and it has been working fine so far.

After the latest update I get this:

.../fastapi_babel/core.py", line 123, in _
    gettext = _context_var.get()
              ^^^^^^^^^^^^^^^^^^
LookupError: <ContextVar name='gettext' at 0x7f4b6dc20e00>

Here is my config file:

from pathlib import Path
from fastapi_babel import Babel, BabelConfigs

LANGUAGES = ['en', 'bg']

translations_dir = Path(__file__).parent.resolve() / 'translations'
configs = BabelConfigs(
    ROOT_DIR=__file__,
    BABEL_DEFAULT_LOCALE="en",
    BABEL_TRANSLATION_DIRECTORY=str(translations_dir),
)

babel = Babel(configs=configs)

if __name__ == "__main__":
    babel.run_cli()

Please note that I made a dependency to set the locale globally because using fastapi-babel as middleware was breaking my app in other ways.

async def get_locale(
        accept_language: Annotated[str | None, Header()] = None,
        x_user_locale: Annotated[str | None, Header()] = None):

    babel.locale = (
            x_user_locale or
            LanguageAccept(parse_accept_header(accept_language)).best_match(LANGUAGES) or
            'bg'
    )

grandant avatar Mar 01 '24 09:03 grandant

Hello I think you have missed adding the babel to the Fastapi middleware. please have a look at the examples.

from fastapi_babel import Babel, BabelConfigs, BabelMiddleware
from fastapi_babel.core import _

app = FastAPI()
babel_configs = BabelConfigs(
    ROOT_DIR=__file__,
    BABEL_DEFAULT_LOCALE="en",
    BABEL_TRANSLATION_DIRECTORY="lang",
)

app.add_middleware(
    BabelMiddleware, babel_configs=babel_configs
)

@app.get("/")
async def index():
    return {"text": _("Hello World")}

also, you can use the depends

from fastapi_babel import Babel, BabelConfigs, BabelMiddleware
from fastapi_babel.core import make_gettext

app = FastAPI()
babel_configs = BabelConfigs(
    ROOT_DIR=__file__,
    BABEL_DEFAULT_LOCALE="en",
    BABEL_TRANSLATION_DIRECTORY="lang",
)

app.add_middleware(
    BabelMiddleware, babel_configs=babel_configs
)

@app.get("/")
async def index(_: Annotated[Callable[[str], str], Depends(make_gettext)]):
    return {"text": _("Hello World")}

Legopapurida avatar Mar 01 '24 10:03 Legopapurida

It seems that a minimal app works as expected as shown in your examples. However, in order to get strings from my API router, I had to use both gettext and make_gettext:

from typing import Annotated, Callable
from fastapi import APIRouter, Depends
from fastapi_babel.core import gettext as _
from fastapi_babel.core import make_gettext

router = APIRouter(
    prefix='/translations',
    tags=['translations'],
    dependencies=[],
    responses={}
)


@router.get('/')
async def fetch_common_strings(_: Annotated[Callable[[str], str], Depends(make_gettext)]):
    translated_strings = {k: _(v) for k, v in common_strings.items()}
    return translated_strings

common_strings = {
    'title': _('My Page Title'),
}

This was not the case in v0.0.8. Is this a reasonable approach?

grandant avatar Mar 04 '24 06:03 grandant

If you want to use _('WORD') outside of the request context (API scope) you have to use the lazy_gettext.

The best solution is: create the file messages.py and write the code below.

...
from fastapi_babel.core import lazy_gettext as _

common_strings = {
    'title': _('My Page Title'),
}

and use the from fastapi_babel import _ in other files.

Why you should use the lazy_gettext instead of simple _ outside of the request context? because _ depends on the request object and the income header to change the babel.locale. when you use that outside of the specific scope you must get the Context error.

so the ideal solution is to use the lazy_gettext instead of that.

Note You cannot import the lazy_gettext side by side of the simple gettext. I'd like to refer you to the Django documentation about this topic to get my statements. also, lazy_gettext never translates any text so we use it only for extracting messages to use throughout the files and APIs using real gettext _().

In this example, I've demonstrated the usage of the lazy_gettext for translating wtform input labels. https://github.com/Anbarryprojects/fastapi-babel/blob/main/examples/wtforms/forms.py

Legopapurida avatar Mar 04 '24 14:03 Legopapurida

Thank you for your help.

grandant avatar Mar 11 '24 08:03 grandant

Hi!

First of all, thank you for this amazing library! I'd like to reopen this issue because I have a similar problem and I wasn't able to solve it. I'm using Version: 0.0.9.

In my application I use the _() inside a worker function using ThreadPoolExecutor. The error I'm receiving is the same as @StikyFingaz 's:

\fastapi_babel\core.py", line 123, in _
    gettext = _context_var.get()
LookupError: <ContextVar name='gettext' at 0x0000016824CC2B30>

Here's a simplified version of my issue, which still illustrates the problem

from fastapi_babel import Babel, BabelConfigs, BabelMiddleware
from fastapi_babel.core import _
from fastapi import FastAPI, Header
from typing_extensions import Annotated
from typing import Union, Callable

from concurrent.futures import ThreadPoolExecutor, as_completed


app = FastAPI()
babel_configs = BabelConfigs(
    ROOT_DIR=__file__,
    BABEL_DEFAULT_LOCALE="en",
    BABEL_TRANSLATION_DIRECTORY="lang",
)

app.add_middleware(
    BabelMiddleware, babel_configs=babel_configs
)

# Passes, no problemo
@app.get("/test0")
async def func_0(accept_language: Annotated[Union[str, None], Header()] = None):
    return {"text": _("Hello, World!")}

def getHW():
    return _("Hello, World!")

# Still passes, no problemo
@app.get("/test1")
async def func_1(accept_language: Annotated[Union[str, None], Header()] = None):
    return {"text": getHW()}

# LookupError: <ContextVar name='gettext' at 0x0000016824CC2B30> :(
@app.get("/test2")
async def func_2(accept_language: Annotated[Union[str, None], Header()] = None
    ):
    future_to_index = {}
    index_to_result = {}
    with ThreadPoolExecutor() as executor:
        future_to_index = {executor.submit(getHW): i for i in range(10)}
        for future in as_completed(future_to_index):
            index = future_to_index[future]
            res = future.result()
            index_to_result[index] = res
    return {"text": index_to_result}

Do you have any suggestions on how to resolve this issue @Legopapurida ? I read your comment mentioning lazy_gettext, but I was not able to apply it to my case...

levente-murgas avatar Jun 14 '24 15:06 levente-murgas

Updating to 0.0.9 breaks my app. This happened again with 0.0.8. I think @Legopapurida might be going in circles on this one. I don't have the time to try to find a workaround so I downgraded to 0.0.8. If you don't have problems with that version you might stick with it for now. It works perfectly fine for me.

grandant avatar Jun 18 '24 03:06 grandant

Hi!

First of all, thank you for this amazing library! I'd like to reopen this issue because I have a similar problem and I wasn't able to solve it. I'm using Version: 0.0.9.

In my application I use the _() inside a worker function using ThreadPoolExecutor. The error I'm receiving is the same as @StikyFingaz 's:

\fastapi_babel\core.py", line 123, in _
    gettext = _context_var.get()
LookupError: <ContextVar name='gettext' at 0x0000016824CC2B30>

Here's a simplified version of my issue, which still illustrates the problem

from fastapi_babel import Babel, BabelConfigs, BabelMiddleware
from fastapi_babel.core import _
from fastapi import FastAPI, Header
from typing_extensions import Annotated
from typing import Union, Callable

from concurrent.futures import ThreadPoolExecutor, as_completed


app = FastAPI()
babel_configs = BabelConfigs(
    ROOT_DIR=__file__,
    BABEL_DEFAULT_LOCALE="en",
    BABEL_TRANSLATION_DIRECTORY="lang",
)

app.add_middleware(
    BabelMiddleware, babel_configs=babel_configs
)

# Passes, no problemo
@app.get("/test0")
async def func_0(accept_language: Annotated[Union[str, None], Header()] = None):
    return {"text": _("Hello, World!")}

def getHW():
    return _("Hello, World!")

# Still passes, no problemo
@app.get("/test1")
async def func_1(accept_language: Annotated[Union[str, None], Header()] = None):
    return {"text": getHW()}

# LookupError: <ContextVar name='gettext' at 0x0000016824CC2B30> :(
@app.get("/test2")
async def func_2(accept_language: Annotated[Union[str, None], Header()] = None
    ):
    future_to_index = {}
    index_to_result = {}
    with ThreadPoolExecutor() as executor:
        future_to_index = {executor.submit(getHW): i for i in range(10)}
        for future in as_completed(future_to_index):
            index = future_to_index[future]
            res = future.result()
            index_to_result[index] = res
    return {"text": index_to_result}

Do you have any suggestions on how to resolve this issue @Legopapurida ? I read your comment mentioning lazy_gettext, but I was not able to apply it to my case...

Hello dear friend I've figured out your problem domain.

I should create a context invoker for resolving this problem.

Legopapurida avatar Jun 18 '24 18:06 Legopapurida

You must use the FastAPI background task and make_gettext as dependency injection. you need to merge these two approaches to achieve the best result.

If you want to handle this issue without my idea, you may need to use the queue and thread shared memory concept.

please try those solutions, If you can't get good output please let me know to find a better way to handle this issue. Otherwise, I have to write a PushContext API to resolve the problem.

Legopapurida avatar Jun 21 '24 17:06 Legopapurida

You must use the FastAPI background task and make_gettext as dependency injection. you need to merge these two approaches to achieve the best result.

If you want to handle this issue without my idea, you may need to use the queue and thread shared memory concept.

please try those solutions, If you can't get good output please let me know to find a better way to handle this issue. Otherwise, I have to write a PushContext API to resolve the problem.

I had the same problem. I'm using alembic, It doesn't have a fastapi context, and it doesn't seem to need a context for revision. So is there a better solution?

Gu-f avatar Jul 17 '24 12:07 Gu-f

I will check this out and resolve the issues immediately @Gu-f

Legopapurida avatar Jul 17 '24 18:07 Legopapurida

@Gu-f @levente-murgas @grandant
I have been many issues and please upgrade your babel

Legopapurida avatar Dec 05 '24 21:12 Legopapurida

You must use the FastAPI background task and make_gettext as dependency injection. you need to merge these two approaches to achieve the best result.

If you want to handle this issue without my idea, you may need to use the queue and thread shared memory concept.

please try those solutions, If you can't get good output please let me know to find a better way to handle this issue. Otherwise, I have to write a PushContext API to resolve the problem.

@Legopapurida the same problem: error=<ContextVar name='gettext' at 0x10dd9c090> How exactly should I make gettext as dependency injection? Can you provide code snippet please?

mixartemev avatar Dec 27 '24 07:12 mixartemev