Skip to content

Commit

Permalink
feat: class-based FOCA entry point (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Jun 25, 2022
1 parent d5ce287 commit c33b7ef
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
run: flake8
- name: Calculate unit test coverage
run: |
coverage run --source foca -m pytest
coverage run --source foca -m pytest -W ignore::DeprecationWarning
coverage xml
- name: Submit Report to Codecov
uses: codecov/codecov-action@v2
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ pip install foca

(2) Create a [configuration file](#configuration-file).

(3) Import the FOCA main function `foca()` and pass your config:
(3) Import the FOCA class and pass your config file:

```python
from foca import foca
from foca import Foca

app = foca("path/to/my/app/config.yaml") # returns a Connexion app instance
app = Foca(config_file="path/to/my/app/config.yaml")
app = foca.create_app() # returns a Connexion app instance
```

(4) Start your [Flask][res-flask] app as usual.
Expand Down Expand Up @@ -376,15 +377,16 @@ custom:
```

You can then have FOCA validate your custom configuration against the
`CustomConfig` class by including it in the `foca()` call like so:
`CustomConfig` class by including it in the `Foca()` call like so:

```py
from foca.foca import foca
from foca import Foca

my_app = foca(
foca = Foca(
config="my_app/config.yaml",
custom_config_model="my_app.custom_config.CustomConfig",
)
my_app = foca.create_app()
```

We recommend that, when defining your `pydantic` model, that you supply
Expand All @@ -403,7 +405,7 @@ simply attach these to your application instance as described
[below](#accessing-configuration-parameters). Note, however, that any
such parameters need to be _manually_ validated. The same is true if you
include a `custom` section but do _not_ provide a validation model class via
the `custom_config_model` parameter when calling `foca()`.
the `custom_config_model` parameter when instantiating `Foca`.

_Example:_

Expand Down
10 changes: 7 additions & 3 deletions examples/petstore/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env python3
from foca.foca import foca
"""Entry point for petstore example app."""

from foca import Foca

if __name__ == '__main__':
app = foca("config.yaml")
foca = Foca(
config_file="config.yaml"
)
app = foca.create_app()
app.run()
2 changes: 1 addition & 1 deletion examples/petstore/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ services:
volumes:
- ./data/petstore/db:/data/db
ports:
- "27077:27017"
- "27017:27017"
2 changes: 2 additions & 0 deletions foca/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""FOCA root package."""

from foca.foca import Foca # noqa: F401

__version__ = '0.8.0'
185 changes: 106 additions & 79 deletions foca/foca.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Entry point for setting up and initializing a FOCA-based microservice."""
"""Class for setting up and initializing a FOCA-based microservice."""

import logging
from typing import Optional
Expand All @@ -17,81 +17,108 @@
logger = logging.getLogger(__name__)


def foca(
config: Optional[str] = None,
custom_config_model: Optional[str] = None,
) -> App:
"""Set up and initialize FOCA-based microservice.
Args:
config: Path to application configuration file in YAML format. Cf.
:py:class:`foca.models.config.Config` for required file structure.
custom_config_model: Path to model to be used for custom config
parameter validation, supplied in "dot notation", e.g.,
``myapp.config.models.CustomConfig`, where ``CustomConfig`` is the
actual importable name of a `pydantic` model for your custom
configuration, deriving from ``BaseModel``. FOCA will attempt to
instantiate the model with the values passed to the ``custom``
section in the application's configuration, if present. Wherever
possible, make sure that default values are supplied for each
config parameters, so as to make it easier for others to
write/modify their app configuration.
Returns:
Connexion application instance.
"""

# Parse config parameters and format logging
conf = ConfigParser(
config_file=config,
custom_config_model=custom_config_model,
format_logs=True,
).config
logger.info("Log formatting configured.")
if config is not None:
logger.info(f"Configuration file '{config}' parsed.")
else:
logger.info("Default app configuration used.")

# Create Connexion app
cnx_app = create_connexion_app(conf)
logger.info("Connexion app created.")

# Register error handlers
cnx_app = register_exception_handler(cnx_app)
logger.info("Error handler registered.")

# Enable cross-origin resource sharing
if(conf.security.cors.enabled is True):
enable_cors(cnx_app.app)
logger.info("CORS enabled.")
else:
logger.info("CORS not enabled.")

# Register OpenAPI specs
if conf.api.specs:
cnx_app = register_openapi(
app=cnx_app,
specs=conf.api.specs,
)
else:
logger.info("No OpenAPI specifications provided.")

# Register MongoDB
if conf.db:
cnx_app.app.config.foca.db = register_mongodb(
app=cnx_app.app,
conf=conf.db,
)
logger.info("Database registered.")
else:
logger.info("No database support configured.")

# Create Celery app
if conf.jobs:
create_celery_app(cnx_app.app)
logger.info("Support for background tasks set up.")
else:
logger.info("No support for background tasks configured.")

return cnx_app
class Foca:

def __init__(
self,
config_file: Optional[str] = None,
custom_config_model: Optional[str] = None,
) -> None:
"""Instantiate FOCA class.
Args:
config_file: Path to application configuration file in YAML
format. Cf. :py:class:`foca.models.config.Config` for
required file structure.
custom_config_model: Path to model to be used for custom config
parameter validation, supplied in "dot notation", e.g.,
``myapp.config.models.CustomConfig`, where ``CustomConfig``
is the actual importable name of a `pydantic` model for
your custom configuration, deriving from ``BaseModel``.
FOCA will attempt to instantiate the model with the values
passed to the ``custom`` section in the application's
configuration, if present. Wherever possible, make sure
that default values are supplied for each config
parameters, so as to make it easier for others to
write/modify their app configuration.
Attributes:
config_file: Path to application configuration file in YAML
format. Cf. :py:class:`foca.models.config.Config` for
required file structure.
custom_config_model: Path to model to be used for custom config
parameter validation, supplied in "dot notation", e.g.,
``myapp.config.models.CustomConfig`, where ``CustomConfig``
is the actual importable name of a `pydantic` model for
your custom configuration, deriving from ``BaseModel``.
FOCA will attempt to instantiate the model with the values
passed to the ``custom`` section in the application's
configuration, if present. Wherever possible, make sure
that default values are supplied for each config
parameters, so as to make it easier for others to
write/modify their app configuration.
"""
self.config_file = config_file
self.custom_config_model = custom_config_model

def create_app(self) -> App:
"""Set up and initialize FOCA-based microservice.
Returns:
Connexion application instance.
"""

# Parse config parameters and format logging
conf = ConfigParser(
config_file=self.config_file,
custom_config_model=self.custom_config_model,
format_logs=True,
).config
logger.info("Log formatting configured.")
if self.config_file is not None:
logger.info(f"Configuration file '{self.config_file}' parsed.")
else:
logger.info("Default app configuration used.")

# Create Connexion app
cnx_app = create_connexion_app(conf)
logger.info("Connexion app created.")

# Register error handlers
cnx_app = register_exception_handler(cnx_app)
logger.info("Error handler registered.")

# Enable cross-origin resource sharing
if(conf.security.cors.enabled is True):
enable_cors(cnx_app.app)
logger.info("CORS enabled.")
else:
logger.info("CORS not enabled.")

# Register OpenAPI specs
if conf.api.specs:
cnx_app = register_openapi(
app=cnx_app,
specs=conf.api.specs,
)
else:
logger.info("No OpenAPI specifications provided.")

# Register MongoDB
if conf.db:
cnx_app.app.config.foca.db = register_mongodb(
app=cnx_app.app,
conf=conf.db,
)
logger.info("Database registered.")
else:
logger.info("No database support configured.")

# Create Celery app
if conf.jobs:
create_celery_app(cnx_app.app)
logger.info("Support for background tasks set up.")
else:
logger.info("No support for background tasks configured.")

return cnx_app
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[metadata]
version = attr: foca.__version__

[flake8]
exclude = .git,.eggs,build,venv,env
max-line-length = 79
Expand Down
3 changes: 0 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import os
from setuptools import setup, find_packages

from foca import __version__

root_dir = os.path.dirname(os.path.abspath(__file__))

# Read long description from file
Expand All @@ -19,7 +17,6 @@

setup(
name="foca",
version=__version__,
description=(
"Archetype for OpenAPI microservices based on Flask and Connexion"
),
Expand Down
4 changes: 2 additions & 2 deletions templates/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ log:
# Available in app context as attributes of `current_app.config.foca`

# Can be validated by FOCA by passing a Pydantic model class to the
# `custom_config_model` parameter in `foca.foca.foca`
# `custom_config_model` parameter in the `foca.Foca()` constructor
custom:
my_param: 'some_value'

# Any other sections/parameters are *not* validated by FOCA; if desired,
# validate parameters in app
custom_params_not_validated:
Expand Down

0 comments on commit c33b7ef

Please sign in to comment.