schemathesis 4.3.8__py3-none-any.whl → 4.3.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of schemathesis might be problematic. Click here for more details.

@@ -1,38 +1,40 @@
1
- ALL_KEYWORDS = {
2
- "additionalItems",
3
- "additionalProperties",
4
- "allOf",
5
- "anyOf",
6
- "const",
7
- "contains",
8
- "contentEncoding",
9
- "contentMediaType",
10
- "dependencies",
11
- "enum",
12
- "else",
13
- "exclusiveMaximum",
14
- "exclusiveMinimum",
15
- "format",
16
- "if",
17
- "items",
18
- "maxItems",
19
- "maxLength",
20
- "maxProperties",
21
- "maximum",
22
- "minItems",
23
- "minLength",
24
- "minProperties",
25
- "minimum",
26
- "multipleOf",
27
- "not",
28
- "oneOf",
29
- "pattern",
30
- "patternProperties",
31
- "properties",
32
- "propertyNames",
33
- "$ref",
34
- "required",
35
- "then",
36
- "type",
37
- "uniqueItems",
38
- }
1
+ ALL_KEYWORDS = frozenset(
2
+ {
3
+ "additionalItems",
4
+ "additionalProperties",
5
+ "allOf",
6
+ "anyOf",
7
+ "const",
8
+ "contains",
9
+ "contentEncoding",
10
+ "contentMediaType",
11
+ "dependencies",
12
+ "enum",
13
+ "else",
14
+ "exclusiveMaximum",
15
+ "exclusiveMinimum",
16
+ "format",
17
+ "if",
18
+ "items",
19
+ "maxItems",
20
+ "maxLength",
21
+ "maxProperties",
22
+ "maximum",
23
+ "minItems",
24
+ "minLength",
25
+ "minProperties",
26
+ "minimum",
27
+ "multipleOf",
28
+ "not",
29
+ "oneOf",
30
+ "pattern",
31
+ "patternProperties",
32
+ "properties",
33
+ "propertyNames",
34
+ "$ref",
35
+ "required",
36
+ "then",
37
+ "type",
38
+ "uniqueItems",
39
+ }
40
+ )
@@ -7,6 +7,10 @@ from dataclasses import dataclass
7
7
  from functools import lru_cache, partial
8
8
  from itertools import combinations
9
9
 
10
+ from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
11
+ from schemathesis.core.jsonschema.keywords import ALL_KEYWORDS
12
+ from schemathesis.generation.hypothesis.strategies import combine
13
+
10
14
  try:
11
15
  from json.encoder import _make_iterencode # type: ignore[attr-defined]
12
16
  except ImportError:
@@ -72,6 +76,14 @@ STRATEGIES_FOR_TYPE = {
72
76
  "array": ARRAY_STRATEGY,
73
77
  "object": OBJECT_STRATEGY,
74
78
  }
79
+
80
+
81
+ def get_strategy_for_type(ty: str | list[str]) -> st.SearchStrategy:
82
+ if isinstance(ty, str):
83
+ return STRATEGIES_FOR_TYPE[ty]
84
+ return combine([STRATEGIES_FOR_TYPE[t] for t in ty if t in STRATEGIES_FOR_TYPE])
85
+
86
+
75
87
  FORMAT_STRATEGIES = {**BUILT_IN_STRING_FORMATS, **get_default_format_strategies(), **STRING_FORMATS}
76
88
 
77
89
  UNKNOWN_PROPERTY_KEY = "x-schemathesis-unknown-property"
@@ -267,11 +279,11 @@ class CoverageContext:
267
279
  if isinstance(schema, bool):
268
280
  return 0
269
281
  keys = sorted([k for k in schema if not k.startswith("x-") and k not in ["description", "example", "examples"]])
270
- if keys == ["type"] and isinstance(schema["type"], str) and schema["type"] in STRATEGIES_FOR_TYPE:
271
- return cached_draw(STRATEGIES_FOR_TYPE[schema["type"]])
282
+ if keys == ["type"]:
283
+ return cached_draw(get_strategy_for_type(schema["type"]))
272
284
  if keys == ["format", "type"]:
273
285
  if schema["type"] != "string":
274
- return cached_draw(STRATEGIES_FOR_TYPE[schema["type"]])
286
+ return cached_draw(get_strategy_for_type(schema["type"]))
275
287
  elif schema["format"] in FORMAT_STRATEGIES:
276
288
  return cached_draw(FORMAT_STRATEGIES[schema["format"]])
277
289
  if (keys == ["maxLength", "minLength", "type"] or keys == ["maxLength", "type"]) and schema["type"] == "string":
@@ -346,6 +358,19 @@ class CoverageContext:
346
358
  if isinstance(schema, dict) and "examples" in schema:
347
359
  # Examples may contain binary data which will fail the canonicalisation process in `hypothesis-jsonschema`
348
360
  schema = {key: value for key, value in schema.items() if key != "examples"}
361
+ # Prevent some hard to satisfy schemas
362
+ if isinstance(schema, dict) and schema.get("additionalProperties") is False and "required" in schema:
363
+ # Set required properties to any value to simplify generation
364
+ schema = dict(schema)
365
+ properties = schema.setdefault("properties", {})
366
+ for key in schema["required"]:
367
+ properties.setdefault(key, {})
368
+
369
+ # Add bundled schemas if any
370
+ if isinstance(schema, dict) and BUNDLE_STORAGE_KEY in self.root_schema:
371
+ schema = dict(schema)
372
+ schema[BUNDLE_STORAGE_KEY] = self.root_schema[BUNDLE_STORAGE_KEY]
373
+
349
374
  return self.generate_from(from_schema(schema, custom_formats=self.custom_formats))
350
375
 
351
376
 
@@ -490,12 +515,14 @@ def cover_schema_iter(
490
515
  # Can't resolve a reference - at this point, we can't generate anything useful as `$ref` is in the current schema root
491
516
  return
492
517
 
493
- if schema == {} or schema is True:
518
+ if schema is True:
494
519
  types = ["null", "boolean", "string", "number", "array", "object"]
495
520
  schema = {}
496
521
  elif schema is False:
497
522
  types = []
498
523
  schema = {"not": {}}
524
+ elif not any(k in ALL_KEYWORDS for k in schema):
525
+ types = ["null", "boolean", "string", "number", "array", "object"]
499
526
  else:
500
527
  types = schema.get("type", [])
501
528
  push_examples_to_properties(schema)
@@ -1305,7 +1332,7 @@ def _negative_type(
1305
1332
  }.get(ctx.location)
1306
1333
 
1307
1334
  if "number" in types:
1308
- del strategies["integer"]
1335
+ strategies.pop("integer", None)
1309
1336
  if "integer" in types:
1310
1337
  strategies["number"] = FLOAT_STRATEGY.filter(_is_non_integer_float)
1311
1338
  if ctx.location == ParameterLocation.QUERY:
@@ -529,7 +529,6 @@ def _iter_coverage_cases(
529
529
 
530
530
  instant = Instant()
531
531
  responses = list(operation.responses.iter_examples())
532
- # NOTE: The HEAD method is excluded
533
532
  custom_formats = _build_custom_formats(generation_config)
534
533
 
535
534
  seen_negative = coverage.HashSet()
@@ -2,16 +2,35 @@ from __future__ import annotations
2
2
 
3
3
 
4
4
  def from_parameter(parameter: str, path: str) -> str | None:
5
- # TODO: support other naming patterns
6
- # Named like "userId" -> look for "User" resource
7
- if parameter.endswith("Id") and len(parameter) > 2:
8
- return to_pascal_case(parameter[:-2])
9
- # Named like "user_id" -> look for "User" resource
10
- elif parameter.endswith("_id"):
11
- return to_pascal_case(parameter[:-3])
12
- # Just "id" -> infer from path context
13
- elif parameter == "id":
5
+ parameter = parameter.strip()
6
+ lower = parameter.lower()
7
+
8
+ if lower == "id":
14
9
  return from_path(path, parameter_name=parameter)
10
+
11
+ # Capital-sensitive
12
+ capital_suffixes = ("Id", "Uuid", "Guid")
13
+ for suffix in capital_suffixes:
14
+ if parameter.endswith(suffix):
15
+ prefix = parameter[: -len(suffix)]
16
+ if len(prefix) >= 2:
17
+ return to_pascal_case(prefix)
18
+
19
+ # Snake_case (case-insensitive is fine here)
20
+ snake_suffixes = ("_guid", "_uuid", "_id", "-guid", "-uuid", "-id")
21
+ for suffix in snake_suffixes:
22
+ if lower.endswith(suffix):
23
+ prefix = parameter[: -len(suffix)]
24
+ if len(prefix) >= 2:
25
+ return to_pascal_case(prefix)
26
+
27
+ # Special cases that need exact match
28
+ # Twilio-style, capital S
29
+ if parameter.endswith("Sid"):
30
+ prefix = parameter[:-3]
31
+ if len(prefix) >= 2:
32
+ return to_pascal_case(prefix)
33
+
15
34
  return None
16
35
 
17
36
 
@@ -335,8 +354,12 @@ def to_plural(word: str) -> str:
335
354
 
336
355
 
337
356
  def to_pascal_case(text: str) -> str:
338
- parts = text.replace("-", "_").split("_")
339
- return "".join(word.capitalize() for word in parts if word)
357
+ # snake_case/kebab-case - split and capitalize each word
358
+ if "_" in text or "-" in text:
359
+ parts = text.replace("-", "_").split("_")
360
+ return "".join(word.capitalize() for word in parts if word)
361
+ # camelCase - just uppercase first letter, preserve the rest
362
+ return text[0].upper() + text[1:] if text else text
340
363
 
341
364
 
342
365
  def to_snake_case(text: str) -> str:
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Callable, Mapping
5
5
 
6
6
  from schemathesis.core.jsonschema import ALL_KEYWORDS
7
7
  from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY, bundle
8
- from schemathesis.core.jsonschema.types import JsonSchema, JsonSchemaObject
8
+ from schemathesis.core.jsonschema.types import JsonSchema, JsonSchemaObject, get_type
9
9
  from schemathesis.core.transforms import encode_pointer
10
10
  from schemathesis.specs.openapi.adapter.parameters import resource_name_from_ref
11
11
  from schemathesis.specs.openapi.adapter.references import maybe_resolve
@@ -425,7 +425,7 @@ def _detect_externally_tagged_pattern(schema: Mapping[str, Any], path: str) -> s
425
425
  if name.lower() not in possible_names:
426
426
  continue
427
427
 
428
- if isinstance(subschema, dict):
428
+ if isinstance(subschema, dict) and "object" in get_type(subschema):
429
429
  return name
430
430
 
431
431
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.3.8
3
+ Version: 4.3.10
4
4
  Summary: Property-based testing framework for Open API and GraphQL based apps
5
5
  Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
6
6
  Project-URL: Changelog, https://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
@@ -70,7 +70,7 @@ schemathesis/core/validation.py,sha256=b0USkKzkWvdz3jOW1JXYc_TfYshfKZeP7xAUnMqcN
70
70
  schemathesis/core/version.py,sha256=dOBUWrY3-uA2NQXJp9z7EtZgkR6jYeLg8sMhQCL1mcI,205
71
71
  schemathesis/core/jsonschema/__init__.py,sha256=gBZGsXIpK2EFfcp8x0b69dqzWAm2OeZHepKImkkLvoE,320
72
72
  schemathesis/core/jsonschema/bundler.py,sha256=a8cQMJ7EQe3eWvS3haS2ZpxOUwzA4PY4OLI0Uyx045A,8472
73
- schemathesis/core/jsonschema/keywords.py,sha256=pjseXTfH9OItNs_Qq6ubkhNWQOrxTnwHmrP_jxrHeJU,631
73
+ schemathesis/core/jsonschema/keywords.py,sha256=ssys1lgo4T_-B0VjWTAp91cga1wxwufxlb3-MI75ioY,796
74
74
  schemathesis/core/jsonschema/references.py,sha256=c2Q4IKWUbwENNtkbFaqf8r3LLZu6GFE5YLnYQlg5tPg,6069
75
75
  schemathesis/core/jsonschema/types.py,sha256=C7f9g8yKFuoxC5_0YNIh8QAyGU0-tj8pzTMfMDjjjVM,1248
76
76
  schemathesis/core/output/__init__.py,sha256=SiHqONFskXl73AtP5dV29L14nZoKo7B-IeG52KZB32M,1446
@@ -93,13 +93,13 @@ schemathesis/engine/phases/unit/_executor.py,sha256=YDibV3lkC2UMHLvh1FSmnlaQ-SJS
93
93
  schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
94
94
  schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
95
95
  schemathesis/generation/case.py,sha256=SLMw6zkzmeiZdaIij8_0tjTF70BrMlRSWREaqWii0uM,12508
96
- schemathesis/generation/coverage.py,sha256=dZYX0gkHDHDenrubVQ58P3ww2xf91fEqk9s54AIokw4,59699
96
+ schemathesis/generation/coverage.py,sha256=uUUND4zF17hqs9L1VtNz41KtxcW-BB9JDsrNBA-w2-w,60813
97
97
  schemathesis/generation/meta.py,sha256=tXhUZBEdpQMn68uMx1SW8Vv59Uf6Wl6yzs-VB9lu_8o,2589
98
98
  schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz5-kt4,2836
99
99
  schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
100
100
  schemathesis/generation/overrides.py,sha256=xI2djHsa42fzP32xpxgxO52INixKagf5DjDAWJYswM8,3890
101
101
  schemathesis/generation/hypothesis/__init__.py,sha256=68BHULoXQC1WjFfw03ga5lvDGZ-c-J7H_fNEuUzFWRw,4976
102
- schemathesis/generation/hypothesis/builder.py,sha256=j7R_X9Z_50xjwIl_Z8DVBe6P1r8leVNvQME7mRLXM1A,38520
102
+ schemathesis/generation/hypothesis/builder.py,sha256=wOCjNAScpiN-wJ4EM0QnJ_5o9nczW0WILFNfIR1qeKQ,38480
103
103
  schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
104
104
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
105
105
  schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
@@ -167,10 +167,10 @@ schemathesis/specs/openapi/stateful/links.py,sha256=SSA66mU50FFBz7e6sA37CfL-Vt0O
167
167
  schemathesis/specs/openapi/stateful/dependencies/__init__.py,sha256=0JM-FrY6Awv6gl-qDHaaK7pXbt_GKutBKPyIaph8apA,7842
168
168
  schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=PSactImp4OqsYMHUl2gB2pgvUlZCCKJRJKeaalclFzU,11511
169
169
  schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=Kl482Hwq2M8lYAdqGmf_8Yje3voSj1WLDUIujRUDWDQ,12286
170
- schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=HfpkCB1GglX1BAKXer3llvPkQsk8wx0QZhZq7ANcdMM,12214
170
+ schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=NnXEFY1W3i18jEEYGgC_8oLoE7YOxdXgcMYtZvLj10w,12920
171
171
  schemathesis/specs/openapi/stateful/dependencies/outputs.py,sha256=zvVUfQWNIuhMkKDpz5hsVGkkvkefLt1EswpJAnHajOw,1186
172
172
  schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=p58XoADpMKFAun0Bx_rul-kiUlfA9PXjxHJ97dT2tBE,11202
173
- schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=snDmf2SWsvo9Oqk_X8xF8GB0Fbwx429UA6ZL2IokzDY,14101
173
+ schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=RaG1BJH4D7-o5Qs2rIRQvS8ERntMUEs2I5jXUFaKMRo,14147
174
174
  schemathesis/specs/openapi/types/__init__.py,sha256=VPsWtLJle__Kodw_QqtQ3OuvBzBcCIKsTOrXy3eA7OU,66
175
175
  schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzBXqerccLpo,1468
176
176
  schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
@@ -179,8 +179,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
179
179
  schemathesis/transport/requests.py,sha256=wriRI9fprTplE_qEZLEz1TerX6GwkE3pwr6ZnU2o6vQ,10648
180
180
  schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
181
181
  schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
182
- schemathesis-4.3.8.dist-info/METADATA,sha256=O5oMaXmmfDDPdYv49lHtjDoSl5pxboEfWCOyeeeYgTo,8540
183
- schemathesis-4.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
184
- schemathesis-4.3.8.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
185
- schemathesis-4.3.8.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
186
- schemathesis-4.3.8.dist-info/RECORD,,
182
+ schemathesis-4.3.10.dist-info/METADATA,sha256=BrfLLUJxe-ve_0xCRM77F5ksA27XYwnQaS_tf7pawQM,8541
183
+ schemathesis-4.3.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
184
+ schemathesis-4.3.10.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
185
+ schemathesis-4.3.10.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
186
+ schemathesis-4.3.10.dist-info/RECORD,,