Skip to content

Conversation

@eimrek
Copy link
Member

@eimrek eimrek commented Sep 22, 2025

Fixes #2307

This PR moves the global CONFIG singleton and ENTRY_COLLECTIONS to the Fastapi app.state. This required quite a large refactor, but the default functionality should be retained (so it's fully backwards compatible).

Now, there is the create_app function that allows to create multiple apps with different configs. So one could use something like

from fastapi import FastAPI

from optimade.server.config import ServerConfig
from optimade.server.create_app import create_app

app = FastAPI()


@app.get("/")
def read_main():
   return {"message": "Hello World from main app"}


base_url = "http://127.0.0.1:8000"

conf1 = ServerConfig()
conf1.base_url = f"{base_url}/app1"
app1 = create_app(conf1)
app.mount("/app1", app1)

conf2 = ServerConfig()
conf2.base_url = f"{base_url}/app2"
app2 = create_app(conf2)
app.mount("/app2", app2)

conf3 = ServerConfig()
conf3.base_url = f"{base_url}/ind"
app3 = create_app(conf3, index=True)
app.mount("/ind", app3)

which starts two different optimade APIs and an index metadb API.

@codecov
Copy link

codecov bot commented Sep 22, 2025

Codecov Report

❌ Patch coverage is 85.41667% with 63 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.65%. Comparing base (1d2add7) to head (287ef4e).

Files with missing lines Patch % Lines
optimade/server/routers/landing.py 13.33% 26 Missing ⚠️
optimade/server/create_app.py 90.29% 13 Missing ⚠️
optimade/server/entry_collections/mongo.py 75.60% 10 Missing ⚠️
optimade/server/logger.py 79.48% 8 Missing ⚠️
optimade/server/entry_collections/elasticsearch.py 91.30% 2 Missing ⚠️
optimade/server/mappers/entries.py 97.10% 2 Missing ⚠️
optimade/server/routers/utils.py 94.11% 1 Missing ⚠️
optimade/utils.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2313      +/-   ##
==========================================
- Coverage   90.66%   90.65%   -0.01%     
==========================================
  Files          75       75              
  Lines        4980     5021      +41     
==========================================
+ Hits         4515     4552      +37     
- Misses        465      469       +4     
Flag Coverage Δ
project 90.65% <85.41%> (-0.01%) ⬇️
validator 90.65% <85.41%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eimrek
Copy link
Member Author

eimrek commented Sep 22, 2025

@ml-evs i think this is ready for review. The only thing failing is the build of optimade-validator-action, which should not really be related to this PR. Initially it failed due to missing pyyaml, now it's not building because of something else. I wonder how it build in the previous commits to this repo. any ideas?

@ml-evs
Copy link
Member

ml-evs commented Sep 23, 2025

You should be able to ignore the validator action errors, I'll make an effort to merge some thing on that end today and we'll see if this turns green.

@ml-evs
Copy link
Member

ml-evs commented Sep 23, 2025

(Just rebased with the changes from #2308)

@ml-evs
Copy link
Member

ml-evs commented Sep 23, 2025

@ml-evs i think this is ready for review. The only thing failing is the build of optimade-validator-action, which should not really be related to this PR. Initially it failed due to missing pyyaml, now it's not building because of something else. I wonder how it build in the previous commits to this repo. any ideas?

Somehow the validator now touches the server config when called via the validator action, whereas before it didn't (and thus didnt need yaml). Might have to be careful when unpicking that one...

from typing import Any, Literal

from optimade.models.entries import EntryResource
from optimade.server.config import ServerConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's what was causing the validator failures

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks! i'm not fully understanding why but i guess it's not important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the config import was nested under the methods that needed it, so even if the validator-action imported the mappers, it never used them so it didn't import the config. Now just importing the config class triggers import yaml at the top of server.config so we hit issues. I'm not against just making yaml required for non-server stuff

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see. yep, i don't really feel strongly about this, so we can maybe keep it as it's now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think likely this can even be removed from the validator as part of a future separation, so yeah don't think there's anything to change here. Generally, I can't see anything obviously wrong with this PR but will need to test.

Would you able to try this out on your end with MCA on this before we merge?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, sure, yes, I can test it out a bit more for MC deployments.

Copy link
Member

@ml-evs ml-evs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @eimrek, got about halfway through a review.

Some general comments:

  • The mapper stuff is definitely a breaking change (at least for my usage in odbx etc.) and I'd want to double-check the effect on performance before merging (i.e., needing to create a mapper instance every request just to parse the query params). If its minimal (likely it is) then I'm happy to include this as it does simplify things.
  • This PR removes the functionality for CONFIG.VALIDATE_API_RESPONSE, which was added to allow non-compliant databases to still host their best guess at OPTIMADE data, for example NOMAD including a "D" in chemical formulae. I'm not sure if its possible to override this in a way that works with the new config (e.g., if there's a FastAPI-native setting we can use to disable validation on a per API basis) but I think this is still required.
  • Can you also write a little docs page about this under the "Deployment" section for running multiple APIs that use different dbs/collections?

LOGGER = logging.getLogger("optimade")
LOGGER.setLevel(logging.DEBUG)

CONFIG = ServerConfig()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this means you can't define per-API log config? I guess the import error stuff will never trigger either. I'm not against simplifying all this by not exposing LOGGER

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this probably should be improved. it would be great to have a per-api log. I am not fully sure how to do this the best way, i can look into it.

Comment on lines 60 to 62
if prefix in BaseResourceMapper().SUPPORTED_PREFIXES:
errors.append(param)
elif prefix not in BaseResourceMapper.KNOWN_PROVIDER_PREFIXES:
elif prefix not in BaseResourceMapper().KNOWN_PROVIDER_PREFIXES:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we have to initialize a class per request just to check query parameters, I'd probably prefer needing to pass the app config into this method instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, yes, this doesn't look correct either... i'll pass in the BaseResourceMapper instance perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ended up storing the base_resource_mapper in app.state. so that the cache is used as well.

ge=0,
),
] = CONFIG.page_limit,
] = 20,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine, as long as the configured default page limit is enforced elsewhere

Comment on lines 29 to 18
if CONFIG.validate_api_response
else dict[str, Any],
response_model=ReferenceResponseMany,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually quite important to keep, not sure how to approach it with the new config mechanism

Copy link
Member Author

@eimrek eimrek Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I could retain this by doing CONFIG = ServerConfig() at the start of this module. This basically means that you take the default config (as before) for these global settings. But allow to override (other things) per-api config, if needed.

@eimrek
Copy link
Member Author

eimrek commented Sep 29, 2025

@ml-evs I think now it's in a better shape. Would be great to have another review.

The idea is that the main way of using is still the same - config is read either from the ENV variables or the JSON file. However, now, with the create_app function, one can, if they want to, override config for any specific app, allowing to set different base urls and optimade db that at crucial for serving multiple APIs.

I also re-did logging such that you can specify a tag that allows one to differentiate logs from different apps.

Finally, I also updates a bit the docs and added some description on how to run multiple apps.

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

Successfully merging this pull request may close these issues.

Serving multiple APIs from a single python process

2 participants