schemathesis 3.34.1__py3-none-any.whl → 3.34.3__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.
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from http.cookies import SimpleCookie
5
5
  from typing import TYPE_CHECKING, Any, Dict, Generator, NoReturn, cast
6
- from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
6
+ from urllib.parse import parse_qs, urlparse
7
7
 
8
8
  from ... import failures
9
9
  from ...exceptions import (
@@ -334,8 +334,6 @@ def ensure_resource_availability(response: GenericResponse, original: Case) -> b
334
334
 
335
335
  def ignored_auth(response: GenericResponse, case: Case) -> bool | None:
336
336
  """Check if an operation declares authentication as a requirement but does not actually enforce it."""
337
- from requests import Session
338
-
339
337
  from .schemas import BaseOpenAPISchema
340
338
 
341
339
  if not isinstance(case.operation.schema, BaseOpenAPISchema):
@@ -346,14 +344,15 @@ def ignored_auth(response: GenericResponse, case: Case) -> bool | None:
346
344
  if security_parameters and 200 <= response.status_code < 300:
347
345
  if _contains_auth(response.request, security_parameters):
348
346
  # If there is auth in the request, then drop it and retry the call
349
- request = _remove_auth_from_request(response.request, security_parameters)
350
- response.request = request
351
- new_response = Session().send(request)
352
- if new_response.ok:
347
+ _remove_auth_from_case(case, security_parameters)
348
+ new_response = case.operation.schema.transport.send(case)
349
+ if 200 <= new_response.status_code < 300:
353
350
  # Mutate the response object in place on the best effort basis
354
- for attribute in new_response.__attrs__:
355
- setattr(response, attribute, getattr(new_response, attribute))
356
- _remove_auth_from_case(case, security_parameters)
351
+ if hasattr(response, "__attrs__"):
352
+ for attribute in new_response.__attrs__:
353
+ setattr(response, attribute, getattr(new_response, attribute))
354
+ else:
355
+ response.__dict__.update(new_response.__dict__)
357
356
  _raise_auth_error(new_response, case.operation.verbose_name)
358
357
  else:
359
358
  # Successful response when there is no auth
@@ -417,40 +416,6 @@ def _contains_auth(request: PreparedRequest, security_parameters: list[SecurityP
417
416
  return False
418
417
 
419
418
 
420
- def _remove_auth_from_request(
421
- request: PreparedRequest, security_parameters: list[SecurityParameter]
422
- ) -> PreparedRequest:
423
- """Remove security parameters from a request."""
424
- from requests.cookies import get_cookie_header
425
-
426
- request = request.copy()
427
- parsed = urlparse(request.url)
428
- query = parse_qs(parsed.query) # type: ignore
429
- should_replace_url = False
430
-
431
- for parameter in security_parameters:
432
- name = parameter["name"]
433
- if parameter["in"] == "header":
434
- request.headers.pop(name, None)
435
- if parameter["in"] == "query":
436
- query.pop(name, None)
437
- should_replace_url = True
438
- if parameter["in"] == "cookie":
439
- del request._cookies[name] # type: ignore
440
-
441
- if should_replace_url:
442
- components = [parsed.scheme, parsed.netloc, parsed.path, parsed.params, urlencode(query), parsed.fragment]
443
- url = cast(str, urlunparse(components)) # type: ignore
444
- request.url = url
445
- # Re-generate the `Cookie` header if needed
446
- raw_cookie = request.headers.pop("Cookie", None)
447
- if raw_cookie is not None:
448
- new_cookie_header = get_cookie_header(request._cookies, request) # type: ignore
449
- if new_cookie_header:
450
- request.headers["Cookie"] = new_cookie_header
451
- return request
452
-
453
-
454
419
  def _remove_auth_from_case(case: Case, security_parameters: list[SecurityParameter]) -> None:
455
420
  """Remove security parameters from a generated case.
456
421
 
@@ -8,6 +8,7 @@ from hypothesis import strategies as st
8
8
  from hypothesis.stateful import Bundle, Rule, precondition, rule
9
9
 
10
10
  from ....constants import NOT_SET
11
+ from ....generation import DataGenerationMethod
11
12
  from ....internal.result import Ok
12
13
  from ....stateful.state_machine import APIStateMachine, Direction, StepResult
13
14
  from ....types import NotSet
@@ -43,6 +44,10 @@ class OpenAPIStateMachine(APIStateMachine):
43
44
  return "\n".join(item.line for item in cls._transition_stats_template.iter_with_format())
44
45
 
45
46
 
47
+ # The proportion of negative tests generated for "root" transitions
48
+ NEGATIVE_TEST_CASES_THRESHOLD = 20
49
+
50
+
46
51
  def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
47
52
  """Create a state machine class.
48
53
 
@@ -111,12 +116,24 @@ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
111
116
  # The source operation has no prerequisite, but we need to allow this rule to be executed
112
117
  # in order to reach other transitions
113
118
  name = _normalize_name(f"{target.verbose_name} -> X")
114
- case_strategy = combine_strategies(
115
- [
116
- target.as_strategy(data_generation_method=data_generation_method)
117
- for data_generation_method in schema.data_generation_methods
118
- ]
119
- )
119
+ if len(schema.data_generation_methods) == 1:
120
+ case_strategy = target.as_strategy(data_generation_method=schema.data_generation_methods[0])
121
+ else:
122
+ strategies = {
123
+ method: target.as_strategy(data_generation_method=method)
124
+ for method in schema.data_generation_methods
125
+ }
126
+
127
+ @st.composite # type: ignore[misc]
128
+ def case_strategy_factory(
129
+ draw: st.DrawFn, strategies: dict[DataGenerationMethod, st.SearchStrategy] = strategies
130
+ ) -> Case:
131
+ if draw(st.integers(min_value=0, max_value=99)) < NEGATIVE_TEST_CASES_THRESHOLD:
132
+ return draw(strategies[DataGenerationMethod.negative])
133
+ return draw(strategies[DataGenerationMethod.positive])
134
+
135
+ case_strategy = case_strategy_factory()
136
+
120
137
  rules[name] = precondition(ensure_links_followed)(
121
138
  transition(
122
139
  name=name,
@@ -192,6 +192,7 @@ class RequestsTransport:
192
192
  context=failures.RequestTimeout(message=message, timeout=timeout),
193
193
  ) from None
194
194
  response.verify = verify # type: ignore[attr-defined]
195
+ response._session = session # type: ignore[attr-defined]
195
196
  if close_session:
196
197
  session.close()
197
198
  return response
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: schemathesis
3
- Version: 3.34.1
3
+ Version: 3.34.3
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://schemathesis.readthedocs.io/en/stable/changelog.html
@@ -56,7 +56,26 @@ Provides-Extra: cov
56
56
  Requires-Dist: coverage-enable-subprocess; extra == 'cov'
57
57
  Requires-Dist: coverage[toml]>=5.3; extra == 'cov'
58
58
  Provides-Extra: dev
59
- Requires-Dist: schemathesis[bench,cov,docs,tests]; extra == 'dev'
59
+ Requires-Dist: aiohttp<4.0,>=3.9.1; extra == 'dev'
60
+ Requires-Dist: coverage-enable-subprocess; extra == 'dev'
61
+ Requires-Dist: coverage>=6; extra == 'dev'
62
+ Requires-Dist: coverage[toml]>=5.3; extra == 'dev'
63
+ Requires-Dist: fastapi>=0.86.0; extra == 'dev'
64
+ Requires-Dist: flask<3.0,>=2.1.1; extra == 'dev'
65
+ Requires-Dist: hypothesis-openapi<1,>=0.2; (python_version >= '3.10') and extra == 'dev'
66
+ Requires-Dist: pydantic>=1.10.2; extra == 'dev'
67
+ Requires-Dist: pytest-asyncio<1.0,>=0.18.0; extra == 'dev'
68
+ Requires-Dist: pytest-codspeed==2.2.1; extra == 'dev'
69
+ Requires-Dist: pytest-httpserver<2.0,>=1.0; extra == 'dev'
70
+ Requires-Dist: pytest-mock<4.0,>=3.7.0; extra == 'dev'
71
+ Requires-Dist: pytest-trio<1.0,>=0.8; extra == 'dev'
72
+ Requires-Dist: pytest-xdist<4.0,>=3; extra == 'dev'
73
+ Requires-Dist: sphinx; extra == 'dev'
74
+ Requires-Dist: sphinx-click; extra == 'dev'
75
+ Requires-Dist: sphinx-rtd-theme; extra == 'dev'
76
+ Requires-Dist: strawberry-graphql[fastapi]>=0.109.0; extra == 'dev'
77
+ Requires-Dist: syrupy<5.0,>=2; extra == 'dev'
78
+ Requires-Dist: trustme<1.0,>=0.9.0; extra == 'dev'
60
79
  Provides-Extra: docs
61
80
  Requires-Dist: sphinx; extra == 'docs'
62
81
  Requires-Dist: sphinx-click; extra == 'docs'
@@ -66,7 +85,7 @@ Requires-Dist: aiohttp<4.0,>=3.9.1; extra == 'tests'
66
85
  Requires-Dist: coverage>=6; extra == 'tests'
67
86
  Requires-Dist: fastapi>=0.86.0; extra == 'tests'
68
87
  Requires-Dist: flask<3.0,>=2.1.1; extra == 'tests'
69
- Requires-Dist: hypothesis-openapi<1,>=0.2; python_version >= '3.10' and extra == 'tests'
88
+ Requires-Dist: hypothesis-openapi<1,>=0.2; (python_version >= '3.10') and extra == 'tests'
70
89
  Requires-Dist: pydantic>=1.10.2; extra == 'tests'
71
90
  Requires-Dist: pytest-asyncio<1.0,>=0.18.0; extra == 'tests'
72
91
  Requires-Dist: pytest-httpserver<2.0,>=1.0; extra == 'tests'
@@ -78,10 +97,6 @@ Requires-Dist: syrupy<5.0,>=2; extra == 'tests'
78
97
  Requires-Dist: trustme<1.0,>=0.9.0; extra == 'tests'
79
98
  Description-Content-Type: text/markdown
80
99
 
81
- <p align="center">
82
- <em>Schemathesis: Supercharge your API testing, catch bugs, and ensure compliance</em>
83
- </p>
84
-
85
100
  <p align="center">
86
101
  <a href="https://github.com/schemathesis/schemathesis/actions" target="_blank">
87
102
  <img src="https://github.com/schemathesis/schemathesis/actions/workflows/build.yml/badge.svg" alt="Build">
@@ -98,22 +113,17 @@ Description-Content-Type: text/markdown
98
113
  <a href="https://discord.gg/R9ASRAmHnA" target="_blank">
99
114
  <img src="https://img.shields.io/discord/938139740912369755" alt="Discord">
100
115
  </a>
116
+ <a href="[https://discord.gg/R9ASRAmHnA](https://schemathesis.readthedocs.io/en/stable/)" target="_blank">
117
+ <img src="https://readthedocs.org/projects/schemathesis/badge/?version=stable" alt="Documentation">
118
+ </a>
101
119
  <a href="https://opensource.org/licenses/MIT" target="_blank">
102
120
  <img src="https://img.shields.io/pypi/l/schemathesis.svg" alt="License">
103
121
  </a>
104
122
  </p>
105
123
 
106
- ---
107
-
108
- **Documentation**: <a href="https://schemathesis.readthedocs.io/en/stable/" target="_blank">https://schemathesis.readthedocs.io/en/stable/ </a>
109
-
110
- **Chat**: <a href="https://discord.gg/R9ASRAmHnA" target="_blank">https://discord.gg/R9ASRAmHnA </a>
111
-
112
- ---
113
-
114
- ## What is Schemathesis?
124
+ ## Schemathesis
115
125
 
116
- Schemathesis is a tool that levels-up your API testing by automating the process of finding crashes, uncovering bugs, and validating spec compliance. With Schemathesis, you can:
126
+ Schemathesis is a tool that levels up your API testing by automating the process of finding crashes, uncovering bugs, and validating spec compliance. With Schemathesis, you can:
117
127
 
118
128
  🎯 **Catch Hard-to-Find Bugs**
119
129
 
@@ -322,11 +332,12 @@ Schemathesis is built on top of <a href="https://hypothesis.works/" target="_bla
322
332
 
323
333
  ## Who's Using Schemathesis?
324
334
 
325
- Schemathesis is used by a number of project and companies, including direct usage or integration into other tools:
335
+ Schemathesis is used by a number of projects and companies, including direct usage or integration into other tools:
326
336
 
327
337
  - Abstract Machines ([Magistrala](https://github.com/absmach/magistrala))
328
338
  - Bundesstelle für Open Data ([smard-api](https://github.com/bundesAPI/smard-api))
329
339
  - [CheckMK](https://github.com/Checkmk/checkmk)
340
+ - [Chronosphere.io](https://github.com/chronosphereio/calyptia-api)
330
341
  - HXSecurity ([DongTai](https://github.com/HXSecurity/DongTai))
331
342
  - Netflix ([Dispatch](https://github.com/Netflix/dispatch))
332
343
  - [Pixie](https://github.com/pixie-io/pixie)
@@ -100,7 +100,7 @@ schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzM
100
100
  schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
101
101
  schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
102
102
  schemathesis/specs/openapi/_hypothesis.py,sha256=XtC-rYiH-GHvWykdSSzPFVK7jHNNL7NtibjSEeIZDQw,24122
103
- schemathesis/specs/openapi/checks.py,sha256=CxDs3eXEXRN6c8xZNrEK60toImgirTP6VTBSoj5w7CU,21236
103
+ schemathesis/specs/openapi/checks.py,sha256=LiwoL5W_qK40j-JFSc9hfM8IGSszMBUWe71YZJ5FBzw,19931
104
104
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
105
105
  schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu3y_Vo3TJaQ,2800
106
106
  schemathesis/specs/openapi/definitions.py,sha256=Z186F0gNBSCmPg-Kk7Q-n6XxEZHIOzgUyeqixlC62XE,94058
@@ -127,7 +127,7 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=gw0w_9tVQf_MY5Df3_xTZFC4r
127
127
  schemathesis/specs/openapi/negative/mutations.py,sha256=lLEN0GLxvPmZBQ3tHCznDSjmZ4yQiQxspjv1UpO4Kx0,19019
128
128
  schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
129
129
  schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
130
- schemathesis/specs/openapi/stateful/__init__.py,sha256=WV0iPWE_BiSoTu2bt0IMbuk71TDJvLPv403thTSXcqc,8662
130
+ schemathesis/specs/openapi/stateful/__init__.py,sha256=wI9WCuHW0EBTUeBaClWi48I6J63TiczBX6UiErsmbB4,9470
131
131
  schemathesis/specs/openapi/stateful/statistic.py,sha256=EJK4NqeAYRYl1FtU9YEuTLyhGhPmks0bLoxUPuQlOvM,7443
132
132
  schemathesis/specs/openapi/stateful/types.py,sha256=UuGcCTFvaHsqeLN9ZeUNcbjsEwmthoT3UcHfDHchOYo,419
133
133
  schemathesis/stateful/__init__.py,sha256=gONzl3pgZ8DihjK52Wd3Ye1LeP6gnulmevHI_jEqHWI,5088
@@ -139,13 +139,13 @@ schemathesis/stateful/sink.py,sha256=xjsqJYH5WETKh5pDGlchYyjT3HcjzHEotUjvo1p0JsE
139
139
  schemathesis/stateful/state_machine.py,sha256=u7PkwCjxTZZcePC7GPiOUuu4uhqUees3UuorD_0Sx_c,12938
140
140
  schemathesis/stateful/statistic.py,sha256=xPLiCw61ofNXQicqcK_sZyLHiqiGcgQARpwd8AiRubM,487
141
141
  schemathesis/stateful/validation.py,sha256=JtqnRzl11ZbVR8Lcr0xBJKoOXzx8VUfRz92jOnB2Smg,3605
142
- schemathesis/transports/__init__.py,sha256=tIJm79N_pBHM5ccvez4Z4mW-VrG1LfN8yq7B0v9YDGk,12832
142
+ schemathesis/transports/__init__.py,sha256=L60_GCzJiX9jFYKVft4aeZb8EmyV6e72k8K73y2zvmE,12898
143
143
  schemathesis/transports/auth.py,sha256=yELjkEkfx4g74hNrd0Db9aFf0xDJDRIwhg2vzKOTZGg,1138
144
144
  schemathesis/transports/content_types.py,sha256=VrcRQvF5T_TUjrCyrZcYF2LOwKfs3IrLcMtkVSp1ImI,2189
145
145
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
146
146
  schemathesis/transports/responses.py,sha256=6-gvVcRK0Ho_lSydUysBNFWoJwZEiEgf6Iv-GWkQGd8,1675
147
- schemathesis-3.34.1.dist-info/METADATA,sha256=8nsSWlenwayD_fjRDqd_VqwH3zB3deT0zOEjq4JU7U8,18373
148
- schemathesis-3.34.1.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
149
- schemathesis-3.34.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
150
- schemathesis-3.34.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
- schemathesis-3.34.1.dist-info/RECORD,,
147
+ schemathesis-3.34.3.dist-info/METADATA,sha256=PsAgyQEs6iPW3VV5P_9r1zRAcw9WDnCsD4CbSieLWoM,19287
148
+ schemathesis-3.34.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
149
+ schemathesis-3.34.3.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
150
+ schemathesis-3.34.3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
+ schemathesis-3.34.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.12.2
2
+ Generator: hatchling 1.25.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any