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

build: add support for Connexion 3+ #204

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
build: add support for Connexion 3+
Rahuljagwani committed Feb 16, 2024
commit cbe73346753465ce2e67433cb4809db1997e1572
2 changes: 1 addition & 1 deletion examples/petstore-access-control/app.py
Original file line number Diff line number Diff line change
@@ -7,4 +7,4 @@
config_file="config.yaml"
)
app = foca.create_app()
app.run()
app.run(port=app.app.config.get('port'), host=app.app.config.get('host'))
Original file line number Diff line number Diff line change
@@ -68,8 +68,8 @@ paths:
content:
application/json:
schema:
x-body-name: pet
$ref: '#/components/schemas/NewPet'
x-body-name: pet
responses:
'200':
description: pet response
2 changes: 1 addition & 1 deletion examples/petstore/app.py
Original file line number Diff line number Diff line change
@@ -7,4 +7,4 @@
config_file="config.yaml"
)
app = foca.create_app()
app.run()
app.run(port=app.app.config.get('port'), host=app.app.config.get('host'))
2 changes: 1 addition & 1 deletion examples/petstore/petstore.yaml
Original file line number Diff line number Diff line change
@@ -56,8 +56,8 @@ paths:
content:
application/json:
schema:
x-body-name: pet
$ref: '#/components/schemas/NewPet'
x-body-name: pet
responses:
'200':
description: pet response
6 changes: 3 additions & 3 deletions foca/api/register_openapi.py
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
from pathlib import Path
from typing import Dict, List

from connexion import App
import connexion

from foca.models.config import SpecConfig
from foca.config.config_parser import ConfigParser
@@ -14,9 +14,9 @@


def register_openapi(
app: App,
app: connexion.FlaskApp,
specs: List[SpecConfig],
) -> App:
) -> connexion.FlaskApp:
"""
Register OpenAPI specifications with Connexion application instance.

4 changes: 2 additions & 2 deletions foca/errors/exceptions.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from traceback import format_exception
from typing import (Dict, List)

from connexion import App
import connexion
from connexion.exceptions import (
ExtraParameterProblem,
Forbidden,
@@ -77,7 +77,7 @@
}


def register_exception_handler(app: App) -> App:
def register_exception_handler(app: connexion.FlaskApp) -> connexion.FlaskApp:
"""Register generic JSON problem handler with Connexion application
instance.

27 changes: 13 additions & 14 deletions foca/factories/connexion_app.py
Original file line number Diff line number Diff line change
@@ -3,16 +3,16 @@
from inspect import stack
import logging
from typing import Optional

from connexion import App

import connexion
from foca.models.config import Config

# Get logger instance
logger = logging.getLogger(__name__)


def create_connexion_app(config: Optional[Config] = None) -> App:
def create_connexion_app(
config: Optional[Config] = None
) -> connexion.FlaskApp:
"""Create and configure Connexion application instance.

Args:
@@ -22,9 +22,9 @@ def create_connexion_app(config: Optional[Config] = None) -> App:
Connexion application instance.
"""
# Instantiate Connexion app
app = App(
app = connexion.FlaskApp(
__name__,
skip_error_handlers=True,
arguments=["port: 6000"]
)

calling_module = ':'.join([stack()[1].filename, stack()[1].function])
@@ -41,9 +41,9 @@ def create_connexion_app(config: Optional[Config] = None) -> App:


def __add_config_to_connexion_app(
app: App,
app: connexion.FlaskApp,
config: Config,
) -> App:
) -> connexion.FlaskApp:
"""Replace default Flask and Connexion settings with FOCA configuration
parameters.

@@ -57,14 +57,13 @@ def __add_config_to_connexion_app(
conf = config.server

# replace Connexion app settings
app.host = conf.host
app.port = conf.port
app.debug = conf.debug
app.app.config["port"] = conf.port
app.app.config["host"] = conf.host

# replace Flask app settings
app.app.config['DEBUG'] = conf.debug
app.app.config['ENV'] = conf.environment
app.app.config['TESTING'] = conf.testing
app.app.env = conf.environment
app.app.debug = conf.debug
app.app.testing = conf.testing

logger.debug('Flask app settings:')
for (key, value) in app.app.config.items():
4 changes: 2 additions & 2 deletions foca/foca.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from typing import Optional

from celery import Celery
from connexion import App
import connexion

from foca.security.access_control.register_access_control import (
register_access_control,
@@ -84,7 +84,7 @@ def __init__(
else:
logger.info("Default app configuration used.")

def create_app(self) -> App:
def create_app(self) -> connexion.FlaskApp:
"""Set up and initialize FOCA-based Connexion app.

Returns:
14 changes: 7 additions & 7 deletions foca/security/access_control/register_access_control.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Methods to manage permission management configuration"""

import logging
from connexion import App
import connexion
from connexion.exceptions import Forbidden
from flask_authz import CasbinEnforcer
from pkg_resources import resource_filename
@@ -28,10 +28,10 @@


def register_access_control(
cnx_app: App,
cnx_app: connexion.FlaskApp,
mongo_config: Optional[MongoConfig],
access_control_config: AccessControlConfig
) -> App:
) -> connexion.FlaskApp:
"""Register access control configuration with flask app.

Args:
@@ -90,7 +90,7 @@ def register_access_control(


def register_permission_specs(
app: App,
app: connexion.FlaskApp,
access_control_config: AccessControlConfig
):
"""Register open api specs for permission management.
@@ -137,10 +137,10 @@ def register_permission_specs(


def register_casbin_enforcer(
app: App,
app: connexion.FlaskApp,
access_control_config: AccessControlConfig,
mongo_config: MongoConfig
) -> App:
) -> connexion.FlaskApp:
"""Method to add casbin permission enforcer.

Args:
@@ -191,7 +191,7 @@ def check_permissions(
"""

def _decorator_check_permissions(fn):
"""User access decorator. Used to facilitate optional decorator arguments.
"""User access decorator. Used to facilitate optional decorator arguments. # noqa

Args:
fn: The function to be decorated.
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
addict==2.2.1
celery==5.2.2
connexion>=2.11.2,<3.0.0
connexion[flask]
connexion[uvicorn]
cryptography==41.0.3
Flask==2.2.5
flask-authz==2.5.1
40 changes: 20 additions & 20 deletions tests/api/test_register_openapi.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from copy import deepcopy
from pathlib import Path

from connexion import App
import connexion
from connexion.exceptions import InvalidSpecification
import pytest
from yaml import YAMLError
@@ -80,54 +80,54 @@ class TestRegisterOpenAPI:

def test_openapi_2_yaml(self):
"""Successfully register OpenAPI 2 YAML specs with Connexion app."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_2)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_3_yaml(self):
"""Successfully register OpenAPI 3 YAML specs with Connexion app."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_3)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_2_json(self):
"""Successfully register OpenAPI 2 JSON specs with Connexion app."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_2_JSON)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_2_json_and_3_yaml(self):
"""Successfully register both OpenAPI2 JSON and OpenAPI3 YAML specs
with Connexion app.
"""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [
SpecConfig(**SPEC_CONFIG_2_JSON),
SpecConfig(**SPEC_CONFIG_3),
]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_2_invalid(self):
"""Registration failing because of invalid OpenAPI 2 spec file."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(path=PATH_SPECS_INVALID_YAML)]
with pytest.raises(InvalidSpecification):
register_openapi(app=app, specs=spec_configs)

def test_openapi_2_json_invalid(self):
"""Registration failing because of invalid JSON spec file."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(path=PATH_SPECS_INVALID_JSON)]
with pytest.raises(ValueError):
register_openapi(app=app, specs=spec_configs)

def test_openapi_not_found(self):
"""Registration failing because spec file is unavailable."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(path=PATH_NOT_FOUND)]
with pytest.raises(OSError):
register_openapi(app=app, specs=spec_configs)
@@ -136,34 +136,34 @@ def test_openapi_2_list(self):
"""Successfully register OpenAPI 2 JSON specs with Connexion app;
specs provided as list.
"""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_2_LIST)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_2_fragments(self):
"""Successfully register OpenAPI 2 JSON specs with Connexion app;
specs provided as multiple fragments.
"""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_2_MULTI)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_2_yaml_no_auth(self):
"""Successfully register OpenAPI 2 YAML specs with Connexion app;
no security definitions/fields.
"""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_2_DISABLE_AUTH)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)

def test_openapi_3_yaml_no_auth(self):
"""Successfully register OpenAPI 3 YAML specs with Connexion app;
no security schemes/fields.
"""
app = App(__name__)
app = connexion.FlaskApp(__name__)
spec_configs = [SpecConfig(**SPEC_CONFIG_3_DISABLE_AUTH)]
res = register_openapi(app=app, specs=spec_configs)
assert isinstance(res, App)
assert isinstance(res, connexion.FlaskApp)
2 changes: 1 addition & 1 deletion tests/database/test_register_mongodb.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
from foca.models.config import MongoConfig

MONGO_DICT_MIN = {
'host': 'mongodb',
'host': '127.0.0.1',
'port': 27017,
}
DB_DICT_NO_COLL = {
6 changes: 3 additions & 3 deletions tests/errors/test_errors.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
import json

from flask import (Flask, Response)
from connexion import App
import connexion
import pytest

from foca.errors.exceptions import (
@@ -52,9 +52,9 @@ class UnknownException(Exception):

def test_register_exception_handler():
"""Test exception handler registration with Connexion app."""
app = App(__name__)
app = connexion.FlaskApp(__name__)
ret = register_exception_handler(app)
assert isinstance(ret, App)
assert isinstance(ret, connexion.FlaskApp)


def test__exc_to_str():
10 changes: 5 additions & 5 deletions tests/factories/test_connexion_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for foca.factories.connexion_app."""

from connexion import App
import connexion

from foca.models.config import Config
from foca.factories.connexion_app import (
@@ -22,19 +22,19 @@

def test_add_config_to_connexion_app():
"""Test if app config is updated."""
cnx_app = App(__name__)
cnx_app = connexion.FlaskApp(__name__)
cnx_app = __add_config_to_connexion_app(cnx_app, CONFIG)
assert isinstance(cnx_app, App)
assert isinstance(cnx_app, connexion.FlaskApp)
assert cnx_app.app.config.foca == CONFIG


def test_create_connexion_app_without_config():
"""Test Connexion app creation without config."""
cnx_app = create_connexion_app()
assert isinstance(cnx_app, App)
assert isinstance(cnx_app, connexion.FlaskApp)


def test_create_connexion_app_with_config():
"""Test Connexion app creation with config."""
cnx_app = create_connexion_app(CONFIG)
assert isinstance(cnx_app, App)
assert isinstance(cnx_app, connexion.FlaskApp)
12 changes: 6 additions & 6 deletions tests/test_foca.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
import shutil

from celery import Celery
from connexion import App
import connexion
from pydantic import ValidationError
from pymongo.collection import Collection
from pymongo.database import Database
@@ -82,14 +82,14 @@ def test_foca_create_app_output_defaults():
"""Ensure a Connexion app instance is returned; defaults only."""
foca = Foca()
app = foca.create_app()
assert isinstance(app, App)
assert isinstance(app, connexion.FlaskApp)


def test_foca_create_app_jobs():
"""Ensure a Connexion app instance is returned; valid 'jobs' field."""
foca = Foca(config_file=JOBS_CONF)
app = foca.create_app()
assert isinstance(app, App)
assert isinstance(app, connexion.FlaskApp)


def test_foca_create_app_api(tmpdir):
@@ -102,7 +102,7 @@ def test_foca_create_app_api(tmpdir):
)
foca = Foca(config_file=temp_file)
app = foca.create_app()
assert isinstance(app, App)
assert isinstance(app, connexion.FlaskApp)


def test_foca_db():
@@ -113,7 +113,7 @@ def test_foca_db():
my_coll = my_db.collections["my-col-1"]
assert isinstance(my_db.client, Database)
assert isinstance(my_coll.client, Collection)
assert isinstance(app, App)
assert isinstance(app, connexion.FlaskApp)


def test_foca_cors_config_flag_enabled():
@@ -145,7 +145,7 @@ def test_foca_valid_access_control():
my_coll = my_db.collections["test_collection"]
assert isinstance(my_db.client, Database)
assert isinstance(my_coll.client, Collection)
assert isinstance(app, App)
assert isinstance(app, connexion.FlaskApp)


def test_foca_create_celery_app():