Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LookupError in core.py after latest update (0.0.9) #36

Open
grandant opened this issue Mar 1, 2024 · 10 comments
Open

LookupError in core.py after latest update (0.0.9) #36

grandant opened this issue Mar 1, 2024 · 10 comments

Comments

@grandant
Copy link

grandant commented Mar 1, 2024

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'
    )
@Legopapurida
Copy link
Contributor

Legopapurida commented Mar 1, 2024

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")}

@grandant
Copy link
Author

grandant commented Mar 4, 2024

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?

@Legopapurida
Copy link
Contributor

Legopapurida commented Mar 4, 2024

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

@grandant
Copy link
Author

Thank you for your help.

@levente-murgas
Copy link

levente-murgas commented Jun 14, 2024

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...

@grandant
Copy link
Author

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.

@Legopapurida
Copy link
Contributor

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
Copy link
Contributor

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 Legopapurida reopened this Jun 21, 2024
@Gu-f
Copy link

Gu-f commented Jul 17, 2024

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?

@Legopapurida
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants