Skip to content

Commit

Permalink
feat: access FOCA config as attribute in app (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
uniqueg committed Jun 25, 2022
1 parent 402173a commit d5ce287
Show file tree
Hide file tree
Showing 13 changed files with 51 additions and 44 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ db:
> `mySecondCollection` and `myThirdCollection`, respectively). FOCA will
> automatically register and initialize these databases and collections for you
> and add convenient clients to the app instance (accessible as children of
> `current_app.config['FOCA']` in an [application
> `current_app.config.foca` in an [application
> context][res-flask-app-context]). The collections would be indexed by keys
> `id`, `other_id` and `third_id`, respectively. Out of these, only `id`
> will be required to be unique.
Expand Down Expand Up @@ -422,12 +422,12 @@ my_custom_param_section:

Once the application is created using `foca()`, one can easily access any
configuration parameters from within the [application
context][res-flask-app-context] through `current_app.config['FOCA'] like so:
context][res-flask-app-context] through `current_app.config.foca like so:

```python
from flask import current_app

app_config = current_app.config['FOCA']
app_config = current_app.config.foca

db = app_config.db
api = app_config.api
Expand All @@ -440,7 +440,7 @@ app_specific_param = current_app.config['app_specific_param']
```

_Outside of the application context_, configuration parameters are available
via `app.config['FOCA']` in a similar way.
via `app.config.foca` in a similar way.

### More examples

Expand Down
8 changes: 4 additions & 4 deletions examples/petstore/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
def findPets(limit=None, tags=None):
try:
db_collection = (
current_app.config['FOCA'].db.dbs['petstore']
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
filter_dict = {} if tags is None else {'tag': {'$in': tags}}
Expand All @@ -33,7 +33,7 @@ def findPets(limit=None, tags=None):
def addPet(pet):
try:
db_collection = (
current_app.config['FOCA'].db.dbs['petstore']
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
counter = 0
Expand All @@ -56,7 +56,7 @@ def addPet(pet):
def findPetById(id):
try:
db_collection = (
current_app.config['FOCA'].db.dbs['petstore']
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
Expand All @@ -74,7 +74,7 @@ def findPetById(id):
def deletePet(id):
try:
db_collection = (
current_app.config['FOCA'].db.dbs['petstore']
current_app.config.foca.db.dbs['petstore']
.collections['pets'].client
)
record = db_collection.find_one(
Expand Down
2 changes: 1 addition & 1 deletion foca/errors/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def _problem_handler_json(exception: Exception) -> Response:
JSON-formatted error response.
"""
# Look up exception & get status code
conf = current_app.config['FOCA'].exceptions
conf = current_app.config.foca.exceptions
exc = type(exception)
if exc not in conf.mapping:
exc = Exception
Expand Down
4 changes: 2 additions & 2 deletions foca/factories/celery_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def create_celery_app(app: Flask) -> Celery:
Returns:
Celery application instance.
"""
conf = app.config['FOCA'].jobs
conf = app.config.foca.jobs

# Instantiate Celery app
celery = Celery(
Expand All @@ -32,7 +32,7 @@ def create_celery_app(app: Flask) -> Celery:
logger.debug(f"Celery app created from '{calling_module}'.")

# Update Celery app configuration with Flask app configuration
celery.conf['FOCA'] = app.config['FOCA']
setattr(celery.conf, 'foca', app.config.foca)
logger.debug('Celery app configured.')

class ContextTask(celery.Task): # type: ignore
Expand Down
2 changes: 1 addition & 1 deletion foca/factories/connexion_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def __add_config_to_connexion_app(
logger.debug('* {}: {}'.format(key, value))

# Add user configuration to Flask app config
app.app.config['FOCA'] = config
setattr(app.app.config, 'foca', config)

logger.debug('Connexion app configured.')
return app
4 changes: 2 additions & 2 deletions foca/foca.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def foca(
format_logs=True,
).config
logger.info("Log formatting configured.")
if config:
if config is not None:
logger.info(f"Configuration file '{config}' parsed.")
else:
logger.info("Default app configuration used.")
Expand Down Expand Up @@ -79,7 +79,7 @@ def foca(

# Register MongoDB
if conf.db:
cnx_app.app.config['FOCA'].db = register_mongodb(
cnx_app.app.config.foca.db = register_mongodb(
app=cnx_app.app,
conf=conf.db,
)
Expand Down
2 changes: 1 addition & 1 deletion foca/security/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def validate_token(token: str) -> Dict:
oidc_config_claim_public_keys: str = 'jwks_uri'

# Fetch security parameters
conf = current_app.config['FOCA'].security.auth
conf = current_app.config.foca.security.auth
add_key_to_claims: bool = conf.add_key_to_claims
allow_expired: bool = conf.allow_expired
audience: Optional[Iterable[str]] = conf.audience
Expand Down
15 changes: 11 additions & 4 deletions templates/config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FOCA CONFIGURATION

# Available in app context as attributes of `current_app.config['FOCA']`
# Available in app context as attributes of `current_app.config.foca`
# Automatically validated via FOCA
# Cf. https://foca.readthedocs.io/en/latest/modules/foca.models.html

Expand Down Expand Up @@ -100,7 +100,14 @@ log:


# CUSTOM APP CONFIGURATION
# Available in app context as attributes of `current_app.config.foca`

# Available in app context as attributes of `current_app.config['FOCA']`
# NOT VALIDATED VIA FOCA; if desired, validate parameters in app
my_custom_param: 'some_value'
# Can be validated by FOCA by passing a Pydantic model class to the
# `custom_config_model` parameter in `foca.foca.foca`
custom:
my_param: 'some_value'

# Any other sections/parameters are *not* validated by FOCA; if desired,
# validate parameters in app
custom_params_not_validated:
my_other_param: 'some_other_value'
16 changes: 8 additions & 8 deletions tests/errors/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def test__exclude_key_nested_dict():
def test__problem_handler_json():
"""Test problem handler with instance of custom, unlisted error."""
app = Flask(__name__)
app.config['FOCA'] = Config()
EXPECTED_RESPONSE = app.config['FOCA'].exceptions.mapping[Exception]
setattr(app.config, 'foca', Config())
EXPECTED_RESPONSE = app.config.foca.exceptions.mapping[Exception]
with app.app_context():
res = _problem_handler_json(UnknownException())
assert isinstance(res, Response)
Expand All @@ -117,8 +117,8 @@ def test__problem_handler_json():
def test__problem_handler_json_no_fallback_exception():
"""Test problem handler; unlisted error without fallback."""
app = Flask(__name__)
app.config['FOCA'] = Config()
del app.config['FOCA'].exceptions.mapping[Exception]
setattr(app.config, 'foca', Config())
del app.config.foca.exceptions.mapping[Exception]
with app.app_context():
res = _problem_handler_json(UnknownException())
assert isinstance(res, Response)
Expand All @@ -131,8 +131,8 @@ def test__problem_handler_json_no_fallback_exception():
def test__problem_handler_json_with_public_members():
"""Test problem handler with public members."""
app = Flask(__name__)
app.config['FOCA'] = Config()
app.config['FOCA'].exceptions.public_members = PUBLIC_MEMBERS
setattr(app.config, 'foca', Config())
app.config.foca.exceptions.public_members = PUBLIC_MEMBERS
with app.app_context():
res = _problem_handler_json(UnknownException())
assert isinstance(res, Response)
Expand All @@ -143,8 +143,8 @@ def test__problem_handler_json_with_public_members():
def test__problem_handler_json_with_private_members():
"""Test problem handler with private members."""
app = Flask(__name__)
app.config['FOCA'] = Config()
app.config['FOCA'].exceptions.private_members = PRIVATE_MEMBERS
setattr(app.config, 'foca', Config())
app.config.foca.exceptions.private_members = PRIVATE_MEMBERS
with app.app_context():
res = _problem_handler_json(UnknownException())
assert isinstance(res, Response)
Expand Down
2 changes: 1 addition & 1 deletion tests/factories/test_celery_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def test_create_celery_app():
cnx_app = create_connexion_app(CONFIG)
cel_app = create_celery_app(cnx_app.app)
assert isinstance(cel_app, Celery)
assert cel_app.conf['FOCA'] == CONFIG
assert cel_app.conf.foca == CONFIG
2 changes: 1 addition & 1 deletion tests/factories/test_connexion_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_add_config_to_connexion_app():
cnx_app = App(__name__)
cnx_app = __add_config_to_connexion_app(cnx_app, CONFIG)
assert isinstance(cnx_app, App)
assert cnx_app.app.config['FOCA'] == CONFIG
assert cnx_app.app.config.foca == CONFIG


def test_create_connexion_app_without_config():
Expand Down
24 changes: 12 additions & 12 deletions tests/security/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class TestValidateToken:
def test_success_all_validation_checks(self, monkeypatch):
"""Test for validating token successfully via all methods."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
request = MagicMock(name='requests')
request.status_code = 200
request.return_value.json.return_value = {
Expand All @@ -150,8 +150,8 @@ def test_success_all_validation_checks(self, monkeypatch):
def test_success_any_validation_check(self, monkeypatch):
"""Test for validating token successfully via any method."""
app = Flask(__name__)
app.config['FOCA'] = Config()
app.config['FOCA'].security.auth.\
setattr(app.config, 'foca', Config())
app.config.foca.security.auth.\
validation_checks = ValidationChecksEnum.any
request = MagicMock(name='requests')
request.status_code = 200
Expand All @@ -171,24 +171,24 @@ def test_success_any_validation_check(self, monkeypatch):
def test_no_validation_methods(self):
"""Test for failed validation due to missing validation methods."""
app = Flask(__name__)
app.config['FOCA'] = Config()
app.config['FOCA'].security.auth.validation_methods = []
setattr(app.config, 'foca', Config())
app.config.foca.security.auth.validation_methods = []
with app.app_context():
with pytest.raises(Unauthorized):
validate_token(token=MOCK_TOKEN)

def test_invalid_token(self):
"""Test for failed validation due to invalid token."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
with app.app_context():
with pytest.raises(Unauthorized):
validate_token(token=MOCK_TOKEN_INVALID)

def test_no_claims(self, monkeypatch):
"""Test for token with no issuer claim."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
monkeypatch.setattr(
'jwt.decode',
lambda *args, **kwargs: {},
Expand All @@ -200,7 +200,7 @@ def test_no_claims(self, monkeypatch):
def test_oidc_config_unavailable(self, monkeypatch):
"""Test for mocking an unavailable OIDC configuration server."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
monkeypatch.setattr(
'requests.get',
lambda **kwargs: _raise(ConnectionError)
Expand All @@ -212,7 +212,7 @@ def test_oidc_config_unavailable(self, monkeypatch):
def test_success_no_subject_claim(self, monkeypatch):
"""Test for validating token without subject claim."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
monkeypatch.setattr(
'jwt.decode',
lambda *args, **kwargs: MOCK_CLAIMS_NO_SUB,
Expand Down Expand Up @@ -240,7 +240,7 @@ def test_fail_all_validation_checks_all_required(self, monkeypatch):
"""Test for all token validation methods failing when all methods
are required to pass."""
app = Flask(__name__)
app.config['FOCA'] = Config()
setattr(app.config, 'foca', Config())
request = MagicMock(name='requests')
request.status_code = 200
request.return_value.json.return_value = {
Expand All @@ -264,8 +264,8 @@ def test_fail_all_validation_checks_any_required(self, monkeypatch):
"""Test for all token validation methods failing when any method
is required to pass."""
app = Flask(__name__)
app.config['FOCA'] = Config()
app.config['FOCA'].security.auth.\
setattr(app.config, 'foca', Config())
app.config.foca.security.auth.\
validation_checks = ValidationChecksEnum.any
request = MagicMock(name='requests')
request.status_code = 200
Expand Down
6 changes: 3 additions & 3 deletions tests/test_foca.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def test_foca_api():
def test_foca_db():
"""Ensure foca() returns a Connexion app instance; valid db field"""
app = foca(VALID_DB_CONF)
my_db = app.app.config['FOCA'].db.dbs['my-db']
my_db = app.app.config.foca.db.dbs['my-db']
my_coll = my_db.collections['my-col-1']
assert isinstance(my_db.client, Database)
assert isinstance(my_coll.client, Collection)
Expand All @@ -114,10 +114,10 @@ def test_foca_invalid_db():
def test_foca_CORS_enabled():
"""Ensures CORS is enabled for Microservice"""
app = foca(VALID_CORS_ENA_CONF)
assert app.app.config['FOCA'].security.cors.enabled is True
assert app.app.config.foca.security.cors.enabled is True


def test_foca_CORS_disabled():
"""Ensures CORS is disabled if user explicitly mentions"""
app = foca(VALID_CORS_DIS_CONF)
assert app.app.config['FOCA'].security.cors.enabled is False
assert app.app.config.foca.security.cors.enabled is False

0 comments on commit d5ce287

Please sign in to comment.