Skip to content

Commit

Permalink
feat: add entry point for returning Celery app (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Nov 10, 2022
1 parent 1223c64 commit a9b83ae
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 138 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,16 @@ jobs:
> FOCA and registers the tasks found in modules `my_task_1` and `my_task_2`.
>
> Cf. the [API model][docs-models-jobs] for further details.
>
> **WARNING:** Note that FOCA's support for asynchronous tasks is currently
> still experimental and thus hasn't been tested extensively. Please use with
> caution.
The `foca.Foca` class provides a method `.create_celery_app()` that you can
use in your Celery worker entry point to crate a Celery app, like so:

```py
from foca import Foca

foca = Foca(config="my_app/config.yaml")
my_celery_app = foca.create_celery_app()
```

### Configuring logging

Expand Down
157 changes: 87 additions & 70 deletions foca/foca.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from pathlib import Path
from typing import Optional

from celery import Celery
from connexion import App

from foca.security.access_control.register_access_control import (
register_access_control
register_access_control,
)
from foca.security.access_control.constants import (
DEFAULT_SPEC_CONTROLLER,
Expand All @@ -27,60 +28,52 @@


class Foca:

def __init__(
self,
config_file: Optional[Path] = 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.
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.
conf: App configuration. Instance of
:py:class:`foca.models.config.Config`.
"""
self.config_file: Optional[Path] = Path(
config_file
) if config_file is not None else None
self.config_file: Optional[Path] = (
Path(config_file) if config_file is not None else None
)
self.custom_config_model: Optional[str] = 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(
self.conf = ConfigParser(
config_file=self.config_file,
custom_config_model=self.custom_config_model,
format_logs=True,
Expand All @@ -91,80 +84,104 @@ def create_app(self) -> App:
else:
logger.info("Default app configuration used.")

def create_app(self) -> App:
"""Set up and initialize FOCA-based Connexion app.
Returns:
Connexion application instance.
"""
# Create Connexion app
cnx_app = create_connexion_app(conf)
cnx_app = create_connexion_app(self.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):
if self.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:
if self.conf.api.specs:
cnx_app = register_openapi(
app=cnx_app,
specs=conf.api.specs,
specs=self.conf.api.specs,
)
else:
logger.info("No OpenAPI specifications provided.")

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

# Register permission management and casbin enforcer
if conf.security.auth.required:
if self.conf.security.auth.required:
if (
conf.security.access_control.api_specs is None or
conf.security.access_control.api_controllers is None
self.conf.security.access_control.api_specs is None
or self.conf.security.access_control.api_controllers is None
):
conf.security.access_control.api_controllers = (
self.conf.security.access_control.api_controllers = (
DEFAULT_SPEC_CONTROLLER
)

if conf.security.access_control.db_name is None:
conf.security.access_control.db_name = (
if self.conf.security.access_control.db_name is None:
self.conf.security.access_control.db_name = (
DEFAULT_ACCESS_CONTROL_DB_NAME
)

if conf.security.access_control.collection_name is None:
conf.security.access_control.collection_name = (
if self.conf.security.access_control.collection_name is None:
self.conf.security.access_control.collection_name = (
DEFAULT_ACESS_CONTROL_COLLECTION_NAME
)

cnx_app = register_access_control(
cnx_app=cnx_app,
mongo_config=conf.db,
access_control_config=conf.security.access_control
mongo_config=self.conf.db,
access_control_config=self.conf.security.access_control,
)
else:
if (
conf.security.access_control.api_specs or
conf.security.access_control.api_controllers
self.conf.security.access_control.api_specs
or self.conf.security.access_control.api_controllers
):
logger.error(
"Please enable security config to register "
"access control."
"Please enable security config to register access control."
)

return cnx_app

def create_celery_app(self) -> Celery:
"""Set up and initialize FOCA-based Celery app.
Returns:
Celery application instance.
"""
# Create Connexion app
cnx_app = create_connexion_app(self.conf)
logger.info("Connexion app created.")

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

# Create Celery app
if conf.jobs:
create_celery_app(cnx_app.app)
if self.conf.jobs:
celery_app = create_celery_app(cnx_app.app)
logger.info("Support for background tasks set up.")
else:
logger.info("No support for background tasks configured.")
raise ValueError(
"No support for background tasks configured. Please use the "
"'jobs' keyword section in your configuration file."
)

return cnx_app
return celery_app
2 changes: 1 addition & 1 deletion foca/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Single source of truth for package version."""

__version__ = '0.10.0'
__version__ = '0.11.0'
62 changes: 62 additions & 0 deletions tests/test_files/conf_no_jobs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
server:
host: '0.0.0.0'
port: 8080
debug: True
environment: development
testing: False
use_reloader: True

api:
specs:
- path: my_specs.yaml
path_out: my_specs.modified.yaml
append: null
add_operation_fields: null
disable_auth: False
connexion: null

security:
auth:
add_key_to_claims: True
algorithms:
- RS256
allow_expired: False
audience: null
claim_identity: sub
claim_issuer: iss
validation_methods:
- userinfo
- public_key
validation_checks: all

db:
host: mongo
port: 27017
dbs:
my_db:
collections:
my-col-1:
indexes: null

custom:
my_custom_field_1: my_custom_value_1
my_custom_field_2: my_custom_value_2
my_custom_field_3: my_custom_value_3

log:
version: 1
disable_existing_loggers: False
formatters:
standard:
class: logging.Formatter
style: "{"
format: "[{asctime}: {levelname:<8}] {message} [{name}]"
handlers:
console:
class: logging.StreamHandler
level: 20
formatter: standard
stream: ext://sys.stderr
root:
level: 10
handlers: [console]

0 comments on commit a9b83ae

Please sign in to comment.