Using FOCA without Docker
Problem
When trying to use FOCA without using docker container, the below error is arising.
Steps to recreate
- create a virtualenv using
virtualenv venvand activate it usingvenv\scripts\activate. - Install foca package using
pip install foca. - Create app.py file and add the following code in it.
from foca import Foca
if __name__ == '__main__':
foca = Foca(
config_file="config.yaml"
)
app = foca.create_app()
app.run()
- create a config.yaml file and add the following code in it.
server:
host: '0.0.0.0'
port: 8080
debug: True
environment: development
testing: False
use_reloader: True```
- Run
python app.py
Workaround
- Create a requirements.txt file in the folder where your app.py is present.
- Copy all the packages from this file and paste it into your requirements.txt file (just copy all and paste).
- Run
python install -r requirements.txt. - Now start again
python app.pyand it will work.
Thanks for the detailed report, @paras41617 :pray: We need to do some maintenance work first because currently our tests are failing on the head commit. We will look into it afterwards.
Currently blocked by #188
TL;DR: relative path causing issues, check this fix.
@uniqueg
The issue (well not really an issue), is that configs use relative paths.
api
- app.py
- models.py
- ...
- ...
- config.yaml
Lets say you have the above str, if you run app.py from inside the api dir and outside with ``python3 api/app.pyboth will yield diff results, as most probably config written wrt$(pwd)/api/` (because we decided to pu config in api dir).
All you need to do is be consistent with how you envoke app.py in Dockerfile and in local machine.
There could be a 2 better way to deal with this:
- If
focarestricts config to only absolute path (which I don't think is ideal). - Consumers of foca should use jinja templating to create a temporary config files. This IMO solves another imp problem of setting up a dev env. Lets say I wanted to test my API with my mongo atlas, but my prod should be a
podor acontainer. This would make more sense and be less pain to inject values injinja. Take a look at below.
# config.yaml
database:
host: {% if environment == "prod" %}prod.db.example.com{% else %}dev.db.example.com{% endif %}
username: {% if environment == "prod" %}prod_user{% else %}dev_user{% endif %}
password: {% if environment == "prod" %}prod_password{% else %}dev_password{% endif %}
port: {{ db_port }}
from jinja2 import Environment, FileSystemLoader
import os
from pathlib import Path
import tempfile
def main():
config_path = Path(__file__).parent / "config.yaml"
if not config_path.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
environment = os.getenv('ENV', 'dev') # Default to 'dev' if ENV is not set
db_port = os.getenv('DB_PORT', '5432') # Default to '5432' if DB_PORT is not set
env = Environment(loader=FileSystemLoader(config_path.parent))
template = env.get_template(config_path.name)
rendered_config = template.render(environment=environment, db_port=db_port)
# Create a temporary config file with the rendered template
with tempfile.NamedTemporaryFile(delete=False, suffix='.yaml') as tmp_config_file:
tmp_config_file.write(rendered_config.encode())
tmp_config_path = tmp_config_file.name
# Create app object using the temporary config file
foca = Foca(
config_file=tmp_config_path,
custom_config_model="service_models.custom_config.CustomConfig",
)
foca.config_file
app = foca.create_app()
# Optionally delete the temporary file after creating the app
os.remove(tmp_config_path)
if __name__ == "__main__":
main()
Thanks @JaeAeich.
I think there are other options as well, e.g., anchoring the relative path not to the caller's current working directory, but to some other reference, e.g., the repository root path. I will take care of that when refactoring FOCA.
I will think about the templating suggestion as well (another good point), though I feel that that is another issue. We should probably take the config.yaml files out of the code package and put them in deployment/, together with the docker-compose.yml files. We could then also think about creating a config map in Kubernetes, so that params can be adjusted on running services (another long-standing open issue).