def _get_strategy_for_field(f): # type: (Type[dm.Field]) -> st.SearchStrategy[Any] if f.choices: choices = [] # type: list for value, name_or_optgroup in f.choices: if isinstance(name_or_optgroup, (list, tuple)): choices.extend(key for key, _ in name_or_optgroup) else: choices.append(value) if isinstance(f, (dm.CharField, dm.TextField)) and f.blank: choices.insert(0, u'') strategy = st.sampled_from(choices) elif type(f) == dm.SlugField: strategy = st.text(alphabet=string.ascii_letters + string.digits, min_size=(None if f.blank else 1), max_size=f.max_length) elif type(f) == dm.GenericIPAddressField: lookup = {'both': ip4_addr_strings() | ip6_addr_strings(), 'ipv4': ip4_addr_strings(), 'ipv6': ip6_addr_strings()} strategy = lookup[f.protocol.lower()] elif type(f) in (dm.TextField, dm.CharField): strategy = st.text( alphabet=st.characters(blacklist_characters=u'\x00', blacklist_categories=('Cs',)), min_size=(None if f.blank else 1), max_size=f.max_length, ) # We can infer a vastly more precise strategy by considering the # validators as well as the field type. This is a minimal proof of # concept, but we intend to leverage the idea much more heavily soon. # See https://github.com/HypothesisWorks/hypothesis-python/issues/1116 re_validators = [ v for v in f.validators if isinstance(v, validators.RegexValidator) and not v.inverse_match ] if re_validators: regexes = [re.compile(v.regex, v.flags) if isinstance(v.regex, str) else v.regex for v in re_validators] # This strategy generates according to one of the regexes, and # filters using the others. It can therefore learn to generate # from the most restrictive and filter with permissive patterns. # Not maximally efficient, but it makes pathological cases rarer. # If you want a challenge: extend https://qntm.org/greenery to # compute intersections of the full Python regex language. strategy = st.one_of(*[st.from_regex(r) for r in regexes]) elif type(f) == dm.DecimalField: bound = Decimal(10 ** f.max_digits - 1) / (10 ** f.decimal_places) strategy = st.decimals(min_value=-bound, max_value=bound, places=f.decimal_places) else: strategy = field_mappings().get(type(f), st.nothing()) if f.validators: strategy = strategy.filter(validator_to_filter(f)) if f.null: strategy = st.one_of(st.none(), strategy) return strategy
def _for_form_ip(field): # the IP address form fields have no direct indication of which type # of address they want, so direct comparison with the validator # function has to be used instead. Sorry for the potato logic here if validate_ipv46_address in field.default_validators: return ip4_addr_strings() | ip6_addr_strings() if validate_ipv4_address in field.default_validators: return ip4_addr_strings() if validate_ipv6_address in field.default_validators: return ip6_addr_strings() raise InvalidArgument("No IP version validator on field=%r" % field)
class TestIPAddress: @given(value=ip4_addr_strings()) def test_init__valid(self, value): core.IPAddress(*value.split(".")) @pytest.mark.parametrize("value", ("127.0.0", "localhost", "")) def test_init__invalid(self, value): with pytest.raises(ValueError, match="IPAddress must consist of 4 items"): core.IPAddress(*value.split(".")) @given(value=ip4_addr_strings()) def test_str(self, value): target = core.IPAddress(*value.split(".")) actual = str(target) assert actual == value
def _get_strategy_for_field(f): if f.choices: choices = [] for value, name_or_optgroup in f.choices: if isinstance(name_or_optgroup, (list, tuple)): choices.extend(key for key, _ in name_or_optgroup) else: choices.append(value) if isinstance(f, (dm.CharField, dm.TextField)) and f.blank: choices.insert(0, u'') strategy = st.sampled_from(choices) elif type(f) == dm.SlugField: strategy = st.text(alphabet=string.ascii_letters + string.digits, min_size=(None if f.blank else 1), max_size=f.max_length) elif type(f) == dm.GenericIPAddressField: lookup = { 'both': ip4_addr_strings() | ip6_addr_strings(), 'ipv4': ip4_addr_strings(), 'ipv6': ip6_addr_strings() } strategy = lookup[f.protocol.lower()] elif type(f) in (dm.TextField, dm.CharField): strategy = st.text( alphabet=st.characters(blacklist_characters=u'\x00', blacklist_categories=('Cs', )), min_size=(None if f.blank else 1), max_size=f.max_length, ) elif type(f) == dm.DecimalField: bound = Decimal(10**f.max_digits - 1) / (10**f.decimal_places) strategy = st.decimals(min_value=-bound, max_value=bound, places=f.decimal_places) else: strategy = field_mappings().get(type(f), st.nothing()) if f.validators: strategy = strategy.filter(validator_to_filter(f)) if f.null: strategy = st.one_of(st.none(), strategy) return strategy
def string_schema(schema: dict) -> st.SearchStrategy[str]: """Handle schemata for strings.""" # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7 min_size = schema.get("minLength", 0) max_size = schema.get("maxLength", float("inf")) strategy = st.text(min_size=min_size, max_size=schema.get("maxLength")) if "format" in schema: url_synonyms = [ "uri", "uri-reference", "iri", "iri-reference", "uri-template" ] domains = prov.domains() # type: ignore formats = { # A value of None indicates a known but unsupported format. **{name: rfc3339(name) for name in RFC3339_FORMATS}, "date": rfc3339("full-date"), "time": rfc3339("full-time"), # Hypothesis' provisional strategies are not type-annotated. # We should get a principled plan for them at some point I guess... "email": st.emails(), # type: ignore "idn-email": st.emails(), # type: ignore "hostname": domains, "idn-hostname": domains, "ipv4": prov.ip4_addr_strings(), # type: ignore "ipv6": prov.ip6_addr_strings(), # type: ignore **{ name: domains.map("https://{}".format) for name in url_synonyms }, "json-pointer": st.just(""), "relative-json-pointer": st.just(""), "regex": REGEX_PATTERNS, } if schema["format"] not in formats: raise InvalidArgument( f"Unsupported string format={schema['format']}") strategy = formats[schema["format"]] if "pattern" in schema: # pragma: no cover # This isn't really supported, but we'll do our best. strategy = strategy.filter( lambda s: re.search(schema["pattern"], string=s) is not None) elif "pattern" in schema: try: re.compile(schema["pattern"]) strategy = st.from_regex(schema["pattern"]) except re.error: # Patterns that are invalid in Python, or just malformed return st.nothing() # TODO: mypy should be able to tell that the lambda is returning a bool # without the explicit cast, but can't as of v 0.720 - report upstream. return strategy.filter(lambda s: bool(min_size <= len(s) <= max_size))
def string_schema(schema: dict) -> st.SearchStrategy[str]: """Handle schemata for strings.""" # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7 min_size = schema.get("minLength", 0) max_size = schema.get("maxLength", float("inf")) strategy: str = st.text(min_size=min_size, max_size=schema.get("maxLength")) if "format" in schema: url_synonyms = [ "uri", "uri-reference", "iri", "iri-reference", "uri-template" ] domains = prov.domains() strategy = { # A value of None indicates a known but unsupported format. **{name: rfc3339(name) for name in RFC3339_FORMATS}, "date": rfc3339("full-date"), "time": rfc3339("full-time"), "email": st.emails(), "idn-email": st.emails(), "hostname": domains, "idn-hostname": domains, "ipv4": prov.ip4_addr_strings(), "ipv6": prov.ip6_addr_strings(), **{ name: domains.map("https://{}".format) for name in url_synonyms }, "json-pointer": st.just(""), "relative-json-pointer": st.just(""), "regex": REGEX_PATTERNS, }.get(schema["format"]) if strategy is None: raise InvalidArgument( f"Unsupported string format={schema['format']}") if "pattern" in schema: # pragma: no cover # This isn't really supported, but we'll do our best. strategy = strategy.filter( lambda s: re.search(schema["pattern"], string=s) is not None) elif "pattern" in schema: try: re.compile(schema["pattern"]) strategy = st.from_regex(schema["pattern"]) except re.error: # Patterns that are invalid in Python, or just malformed strategy = st.nothing() return strategy.filter(lambda s: min_size <= len(s) <= max_size)
def string_schema(schema: dict) -> st.SearchStrategy[str]: """Handle schemata for strings.""" # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7 min_size = schema.get("minLength", 0) max_size = schema.get("maxLength", float("inf")) strategy: Any = st.text(min_size=min_size, max_size=schema.get("maxLength")) assert not ( "format" in schema and "pattern" in schema ), "format and regex constraints are supported, but not both at once." if "pattern" in schema: strategy = st.from_regex(schema["pattern"]) elif "format" in schema: url_synonyms = [ "uri", "uri-reference", "iri", "iri-reference", "uri-template" ] domains = prov.domains() # type: ignore strategy = { # A value of None indicates a known but unsupported format. **{name: rfc3339(name) for name in RFC3339_FORMATS}, "date": rfc3339("full-date"), "time": rfc3339("full-time"), "email": st.emails(), # type: ignore "idn-email": st.emails(), # type: ignore "hostname": domains, "idn-hostname": domains, "ipv4": prov.ip4_addr_strings(), # type: ignore "ipv6": prov.ip6_addr_strings(), # type: ignore **{ name: domains.map("https://{}".format) for name in url_synonyms }, "json-pointer": st.just(""), "relative-json-pointer": st.just(""), "regex": REGEX_PATTERNS, }.get(schema["format"]) if strategy is None: raise InvalidArgument( f"Unsupported string format={schema['format']}") return strategy.filter( lambda s: min_size <= len(s) <= max_size) # type: ignore
# # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at http://mozilla.org/MPL/2.0/. # # END HEADER from __future__ import division, print_function, absolute_import from binascii import unhexlify from hypothesis import given from hypothesis.provisional import ip4_addr_strings, ip6_addr_strings @given(ip4_addr_strings()) def test_is_IP4_addr(address): as_num = [int(n) for n in address.split('.')] assert len(as_num) == 4 assert all(0 <= n <= 255 for n in as_num) @given(ip6_addr_strings()) def test_is_IP6_addr(address): # Works for non-normalised addresses produced by this strategy, but not # a particularly general test assert address == address.upper() as_hex = address.split(':') assert len(as_hex) == 8 assert all(len(part) == 4 for part in as_hex) raw = unhexlify(address.replace(u':', u'').encode('ascii'))
from hypothesis.provisional import domains, ip4_addr_strings, ip6_addr_strings, urls from tests.common.debug import find_any @given(urls()) def test_is_URL(url): allowed_chars = set(string.ascii_letters + string.digits + "$-_.+!*'(),%/") url_schemeless = url.split("://", 1)[1] path = url_schemeless.split("/", 1)[1] if "/" in url_schemeless else "" assert all(c in allowed_chars for c in path) assert all( re.match("^[0-9A-Fa-f]{2}", after_perc) for after_perc in path.split("%")[1:] ) @given(ip4_addr_strings()) def test_is_IP4_addr(address): as_num = [int(n) for n in address.split(".")] assert len(as_num) == 4 assert all(0 <= n <= 255 for n in as_num) @given(ip6_addr_strings()) def test_is_IP6_addr(address): # Works for non-normalised addresses produced by this strategy, but not # a particularly general test assert address == address.upper() as_hex = address.split(":") assert len(as_hex) == 8 assert all(len(part) == 4 for part in as_hex) raw = unhexlify(address.replace(u":", u"").encode("ascii"))
def _for_model_ip(field): return { "ipv4": ip4_addr_strings(), "ipv6": ip6_addr_strings(), "both": ip4_addr_strings() | ip6_addr_strings(), }[field.protocol.lower()]
def _for_model_ip(field): return dict( ipv4=ip4_addr_strings(), ipv6=ip6_addr_strings(), both=ip4_addr_strings() | ip6_addr_strings(), )[field.protocol.lower()]
def test_cpp_string_escape(string, expected): actual = helpers.cpp_string_escape(string) assert actual == expected @pytest.mark.parametrize("host", ( "127.0.0", "localhost", "127.0.0.b", )) def test_is_ip_address__invalid(host): actual = helpers.is_ip_address(host) assert actual is False @given(value=ip4_addr_strings()) def test_is_ip_address__valid(value): actual = helpers.is_ip_address(value) assert actual is True @pytest.mark.parametrize("var, value, default, expected", ( ("FOO", None, False, False), ("FOO", None, True, True), ("FOO", "", False, False), ("FOO", "Yes", False, True), ("FOO", "123", False, True), )) def test_get_bool_env(monkeypatch, var, value, default, expected): if value is None:
REGEX_PATTERNS = regex_patterns() STRING_FORMATS = { # A value of None indicates a known but unsupported format. **{name: rfc3339(name) for name in RFC3339_FORMATS}, "date": rfc3339("full-date"), "time": rfc3339("full-time"), # Hypothesis' provisional strategies are not type-annotated. # We should get a principled plan for them at some point I guess... "email": st.emails(), # type: ignore "idn-email": st.emails(), # type: ignore "hostname": prov.domains(), "idn-hostname": prov.domains(), "ipv4": prov.ip4_addr_strings(), "ipv6": prov.ip6_addr_strings(), **{ name: prov.domains().map("https://{}".format) for name in [ "uri", "uri-reference", "iri", "iri-reference", "uri-template" ] }, "json-pointer": st.just(""), "relative-json-pointer": st.just(""), "regex": REGEX_PATTERNS, } def string_schema(schema: dict) -> st.SearchStrategy[str]: """Handle schemata for strings."""
REGEX_PATTERNS = regex_patterns() _domains = prov.domains() # type: ignore STRING_FORMATS = { # A value of None indicates a known but unsupported format. **{name: rfc3339(name) for name in RFC3339_FORMATS}, "date": rfc3339("full-date"), "time": rfc3339("full-time"), # Hypothesis' provisional strategies are not type-annotated. # We should get a principled plan for them at some point I guess... "email": st.emails(), # type: ignore "idn-email": st.emails(), # type: ignore "hostname": _domains, "idn-hostname": _domains, "ipv4": prov.ip4_addr_strings(), # type: ignore "ipv6": prov.ip6_addr_strings(), # type: ignore **{ name: _domains.map("https://{}".format) for name in [ "uri", "uri-reference", "iri", "iri-reference", "uri-template" ] }, "json-pointer": st.just(""), "relative-json-pointer": st.just(""), "regex": REGEX_PATTERNS, } def string_schema(schema: dict) -> st.SearchStrategy[str]: """Handle schemata for strings."""
class InvalidFormatError(ValueError): def __init__(self, fmt: str): super().__init__(f"{fmt} is not currently supported.") FORMATS = { "uuid": st.uuids().map(str), "email": st.emails(), "idn-email": st.emails(), "date-time": st.datetimes().map(lambda x: x.isoformat()), "date": st.dates().map(lambda x: x.isoformat()), "time": st.times().map(lambda x: x.isoformat()), "ipv4": pst.ip4_addr_strings(), "ipv6": pst.ip6_addr_strings(), } def format_strings(fmt: str) -> Strategy: try: return FORMATS[fmt] except KeyError: raise InvalidFormatError(fmt) def pattern_strings(pattern: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> Strategy: if pattern[-1] == "$": pattern = rf"{pattern[:-1]}\Z" schema = st.from_regex(pattern)