Skip to content

Commit

Permalink
Test custom validators support and improve configuration of them
Browse files Browse the repository at this point in the history
  • Loading branch information
amol- committed Mar 25, 2024
1 parent 0ae3579 commit 76ef4a7
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 12 deletions.
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[aliases]
# A handy alias to make a release to pypi
release = egg_info -RDb "" sdist bdist_egg register upload

[tool:pytest]
addopts = --cov=tg --cov-report lcov --cov-report term --cov-append
119 changes: 119 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import tests

import tg
from tg.configuration.utils import TGConfigError
from tg.configurator.fullstack import FullStackApplicationConfigurator
from tg.controllers import TGController, DecoratedController, abort
from tg.controllers.util import validation_errors_response
from tg.decorators import expose, validate, before_render, before_call, Decoration
Expand All @@ -21,6 +23,8 @@
from tg.validation import TGValidationError, _ValidationStatus, Convert, RequireValue
from tg.i18n import lazy_ugettext as l_

from webtest import TestApp


def setup_module():
setup_session_dir()
Expand Down Expand Up @@ -551,3 +555,118 @@ def test_double_chain_validation(self):
def test_last_chain_validation(self):
res = self.app.get('/chain_validation_begin', params={'val': 0}, status=412)
assert res.json == json.loads('{"errors":{"val":"Invalid"},"values":{"val":"0"}}')


class TestValidationConfiguration:
def create_app(self, root_controller, options=None):
cfg = FullStackApplicationConfigurator()
cfg.update_blueprint({'root_controller': root_controller})
cfg.update_blueprint(options or {})

app = TestApp(cfg.make_wsgi_app({
'debug': False,
'errorpage.enabled': False,
'trace_errors.enable': False
}, {}))
return app

def test_no_validation_function(self):
class FakeSchema:
pass

class RootController(TGController):
@validate(FakeSchema())
@expose("text/plain")
def test(self, **kwargs):
return "HI"

app = self.create_app(RootController())

with pytest.raises(TGConfigError) as exc_info:
app.get("/test", {"value": 5})
assert "No validation validator function found for" in str(exc_info.value)
assert "FakeSchema" in str(exc_info.value)

def test_custom_validation(self):
class FakeSchema:
pass

def validate_fake_schema(schema, params):
if params.get("fail"):
raise TGValidationError("Invalid params", value=params,
error_dict={"fail": "Fail is true"})
return params

class RootController(TGController):
@validate(FakeSchema())
@expose("text/plain")
def test(self, **kwargs):
if tg.request.validation.errors:
return str(tg.request.validation.errors)
return "HI"

app = self.create_app(RootController(), {
"validation.validators": {FakeSchema: validate_fake_schema}
})

resp = app.get("/test", {"value": 5})
assert resp.text == "HI"

resp = app.get("/test", {"fail": 1})
assert "Fail is true" in resp.text

def test_custom_validation_error(self):
class FakeSchema:
pass

def validate_fake_schema(schema, params):
if params.get("fail"):
raise FakeError()
return params

class FakeError(Exception):
pass

def explode_fake_error(error):
return {"values": {"fail": True}, "errors": "fail was true"}

class RootController(TGController):
@validate(FakeSchema())
@expose("text/plain")
def test(self, **kwargs):
if tg.request.validation.errors:
return str(tg.request.validation.errors)
return "HI"

app = self.create_app(RootController(), {
"validation.validators": {FakeSchema: validate_fake_schema},
"validation.exceptions": [FakeError],
"validation.explode": {FakeError: explode_fake_error}
})

resp = app.get("/test", {"value": 5})
assert resp.text == "HI"

resp = app.get("/test", {"fail": 1})
assert "fail was true" in resp.text

# Do not provide explode function
app = self.create_app(RootController(), {
"validation.validators": {FakeSchema: validate_fake_schema},
"validation.exceptions": [FakeError]
})
with pytest.raises(TGConfigError) as exc_info:
resp = app.get("/test", {"fail": 1})
assert "No validation explode function found for" in str(exc_info.value)
assert "FakeError" in str(exc_info.value)

# Do not provide missing explode function
app = self.create_app(RootController(), {
"validation.validators": {FakeSchema: validate_fake_schema},
"validation.exceptions": [FakeError],
"validation.explode": {FakeError: None}
})
with pytest.raises(TGConfigError) as exc_info:
resp = app.get("/test", {"fail": 1})
assert "No validation explode function found for" in str(exc_info.value)
assert "FakeError" in str(exc_info.value)
2 changes: 1 addition & 1 deletion tg/controllers/decoratedcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def _process_validation_errors(cls, controller, remainder, params, exception, co
validation_status.errors = exploded_validation['errors']
validation_status.values = exploded_validation['values']
else:
raise TGConfigError(f"No validation explode function found for: {exception}")
raise TGConfigError(f"No validation explode function found for: {exception.__class__}")

# Get the error handler associated to the current validation status.
error_handler = validation_status.error_handler
Expand Down
18 changes: 7 additions & 11 deletions tg/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,13 @@ def check(self, config, method, params):
raise TGValidationError(TGValidationError.make_compound_message(errors),
value=params,
error_dict=errors)

elif isinstance(validators, tuple(validation_validators.keys())):
elif hasattr(validators, 'validate') and getattr(self, 'needs_controller', False):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(method, params)
elif hasattr(validators, 'validate'):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(params)
else:
schema_class = validators.__class__
validation_function = None
for supported_class in validation_validators:
Expand All @@ -94,15 +99,6 @@ def check(self, config, method, params):

validated_params = validation_function(validators, params)

elif hasattr(validators, 'validate') and getattr(self, 'needs_controller', False):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(method, params)

elif hasattr(validators, 'validate'):
# An object with a "validate" method - call it with the parameters
validated_params = validators.validate(params)


return validated_params


Expand Down

0 comments on commit 76ef4a7

Please sign in to comment.