Skip to content

Commit

Permalink
Merge pull request #105 from Stranger6667/dd/fix-alphabet
Browse files Browse the repository at this point in the history
fix: not respecting allow_x00 and codec arguments for values in some schemas
  • Loading branch information
Zac-HD authored Feb 28, 2024
2 parents 34e6f53 + a75fb16 commit e8e6dd0
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

- Fix not respecting `allow_x00` and `codec` arguments for values in some schemas

#### 0.23.0 - 2023-09-24
- Add new `allow_x00=` and `codec=` arguments to `from_schema()`, so that you can
control generated strings more precisely.
Expand Down
44 changes: 33 additions & 11 deletions src/hypothesis_jsonschema/_from_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,16 @@ def from_js_regex(pattern: str, alphabet: CharStrategy) -> st.SearchStrategy[str


def merged_as_strategies(
schemas: List[Schema], custom_formats: Optional[Dict[str, st.SearchStrategy[str]]]
schemas: List[Schema],
*,
alphabet: CharStrategy,
custom_formats: Optional[Dict[str, st.SearchStrategy[str]]],
) -> st.SearchStrategy[JSONType]:
assert schemas, "internal error: must pass at least one schema to merge"
if len(schemas) == 1:
return from_schema(schemas[0], custom_formats=custom_formats)
return __from_schema(
schemas[0], alphabet=alphabet, custom_formats=custom_formats
)
# Try to merge combinations of strategies.
strats = []
combined: Set[str] = set()
Expand All @@ -96,7 +101,9 @@ def merged_as_strategies(
s = merged([inputs[g] for g in group])
if s is not None and s != FALSEY:
strats.append(
from_schema(s, custom_formats=custom_formats).filter(
__from_schema(
s, alphabet=alphabet, custom_formats=custom_formats
).filter(
lambda obj, validators=tuple(
make_validator(s).is_valid for s in schemas
): all(v(obj) for v in validators)
Expand Down Expand Up @@ -165,7 +172,7 @@ def __from_schema(
schema: Union[bool, Schema],
*,
alphabet: CharStrategy,
custom_formats: Optional[Dict[str, st.SearchStrategy[str]]] = None,
custom_formats: Optional[Dict[str, st.SearchStrategy[str]]],
) -> st.SearchStrategy[JSONType]:
try:
schema = resolve_all_refs(schema)
Expand Down Expand Up @@ -217,27 +224,36 @@ def __from_schema(
not_ = schema.pop("not")
assert isinstance(not_, dict)
validator = make_validator(not_).is_valid
return from_schema(schema, custom_formats=custom_formats).filter(
lambda v: not validator(v)
)
return __from_schema(
schema, alphabet=alphabet, custom_formats=custom_formats
).filter(lambda v: not validator(v))
if "anyOf" in schema:
tmp = schema.copy()
ao = tmp.pop("anyOf")
assert isinstance(ao, list)
return st.one_of([merged_as_strategies([tmp, s], custom_formats) for s in ao])
return st.one_of(
[
merged_as_strategies(
[tmp, s], alphabet=alphabet, custom_formats=custom_formats
)
for s in ao
]
)
if "allOf" in schema:
tmp = schema.copy()
ao = tmp.pop("allOf")
assert isinstance(ao, list)
return merged_as_strategies([tmp, *ao], custom_formats)
return merged_as_strategies(
[tmp, *ao], alphabet=alphabet, custom_formats=custom_formats
)
if "oneOf" in schema:
tmp = schema.copy()
oo = tmp.pop("oneOf")
assert isinstance(oo, list)
schemas = [merged([tmp, s]) for s in oo]
return st.one_of(
[
from_schema(s, custom_formats=custom_formats)
__from_schema(s, alphabet=alphabet, custom_formats=custom_formats)
for s in schemas
if s is not None
]
Expand Down Expand Up @@ -692,7 +708,13 @@ def from_object_schema(draw: Any) -> Any:
pattern_schemas.insert(0, properties[key])

if pattern_schemas:
out[key] = draw(merged_as_strategies(pattern_schemas, custom_formats))
out[key] = draw(
merged_as_strategies(
pattern_schemas,
alphabet=alphabet,
custom_formats=custom_formats,
)
)
else:
out[key] = draw(
__from_schema(
Expand Down
24 changes: 24 additions & 0 deletions tests/test_from_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,27 @@ def test_errors_on_unencodable_property_name(data):
data.draw(from_schema(non_ascii_schema, codec=None))
with pytest.raises(InvalidArgument, match=r"'é' cannot be encoded as 'ascii'"):
data.draw(from_schema(non_ascii_schema, codec="ascii"))


@settings(deadline=None)
@given(data=st.data())
def test_no_null_bytes(data):
schema = {
"type": "object",
"properties": {
"p1": {"type": "string"},
"p2": {
"type": "object",
"properties": {"pp1": {"type": "string"}},
"required": ["pp1"],
"additionalProperties": False,
},
"p3": {"type": "array", "items": {"type": "string"}},
},
"required": ["p1", "p2", "p3"],
"additionalProperties": False,
}
example = data.draw(from_schema(schema, allow_x00=False))
assert "\x00" not in example["p1"]
assert "\x00" not in example["p2"]["pp1"]
assert all("\x00" not in item for item in example["p3"])

0 comments on commit e8e6dd0

Please sign in to comment.