schemathesis 4.0.0a10__py3-none-any.whl → 4.0.0a12__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.
Files changed (111) hide show
  1. schemathesis/__init__.py +29 -30
  2. schemathesis/auths.py +65 -24
  3. schemathesis/checks.py +73 -39
  4. schemathesis/cli/commands/__init__.py +51 -3
  5. schemathesis/cli/commands/data.py +10 -0
  6. schemathesis/cli/commands/run/__init__.py +163 -274
  7. schemathesis/cli/commands/run/context.py +8 -4
  8. schemathesis/cli/commands/run/events.py +11 -1
  9. schemathesis/cli/commands/run/executor.py +70 -78
  10. schemathesis/cli/commands/run/filters.py +15 -165
  11. schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
  12. schemathesis/cli/commands/run/handlers/junitxml.py +5 -4
  13. schemathesis/cli/commands/run/handlers/output.py +195 -121
  14. schemathesis/cli/commands/run/loaders.py +35 -50
  15. schemathesis/cli/commands/run/validation.py +52 -162
  16. schemathesis/cli/core.py +5 -3
  17. schemathesis/cli/ext/fs.py +7 -5
  18. schemathesis/cli/ext/options.py +0 -21
  19. schemathesis/config/__init__.py +189 -0
  20. schemathesis/config/_auth.py +51 -0
  21. schemathesis/config/_checks.py +268 -0
  22. schemathesis/config/_diff_base.py +99 -0
  23. schemathesis/config/_env.py +21 -0
  24. schemathesis/config/_error.py +156 -0
  25. schemathesis/config/_generation.py +149 -0
  26. schemathesis/config/_health_check.py +24 -0
  27. schemathesis/config/_operations.py +327 -0
  28. schemathesis/config/_output.py +171 -0
  29. schemathesis/config/_parameters.py +19 -0
  30. schemathesis/config/_phases.py +187 -0
  31. schemathesis/config/_projects.py +523 -0
  32. schemathesis/config/_rate_limit.py +17 -0
  33. schemathesis/config/_report.py +120 -0
  34. schemathesis/config/_validator.py +9 -0
  35. schemathesis/config/_warnings.py +25 -0
  36. schemathesis/config/schema.json +885 -0
  37. schemathesis/core/__init__.py +2 -0
  38. schemathesis/core/compat.py +16 -9
  39. schemathesis/core/errors.py +24 -4
  40. schemathesis/core/failures.py +6 -7
  41. schemathesis/core/hooks.py +20 -0
  42. schemathesis/core/output/__init__.py +14 -37
  43. schemathesis/core/output/sanitization.py +3 -146
  44. schemathesis/core/transport.py +36 -1
  45. schemathesis/core/validation.py +16 -0
  46. schemathesis/engine/__init__.py +2 -4
  47. schemathesis/engine/context.py +42 -43
  48. schemathesis/engine/core.py +7 -5
  49. schemathesis/engine/errors.py +60 -1
  50. schemathesis/engine/events.py +10 -2
  51. schemathesis/engine/phases/__init__.py +10 -0
  52. schemathesis/engine/phases/probes.py +11 -8
  53. schemathesis/engine/phases/stateful/__init__.py +2 -1
  54. schemathesis/engine/phases/stateful/_executor.py +104 -46
  55. schemathesis/engine/phases/stateful/context.py +2 -2
  56. schemathesis/engine/phases/unit/__init__.py +23 -15
  57. schemathesis/engine/phases/unit/_executor.py +110 -21
  58. schemathesis/engine/phases/unit/_pool.py +1 -1
  59. schemathesis/errors.py +2 -0
  60. schemathesis/filters.py +2 -3
  61. schemathesis/generation/__init__.py +5 -33
  62. schemathesis/generation/case.py +6 -3
  63. schemathesis/generation/coverage.py +154 -124
  64. schemathesis/generation/hypothesis/builder.py +70 -20
  65. schemathesis/generation/meta.py +3 -3
  66. schemathesis/generation/metrics.py +93 -0
  67. schemathesis/generation/modes.py +0 -8
  68. schemathesis/generation/overrides.py +37 -1
  69. schemathesis/generation/stateful/__init__.py +4 -0
  70. schemathesis/generation/stateful/state_machine.py +9 -1
  71. schemathesis/graphql/loaders.py +159 -16
  72. schemathesis/hooks.py +62 -35
  73. schemathesis/openapi/checks.py +12 -8
  74. schemathesis/openapi/generation/filters.py +10 -8
  75. schemathesis/openapi/loaders.py +142 -17
  76. schemathesis/pytest/lazy.py +2 -5
  77. schemathesis/pytest/loaders.py +24 -0
  78. schemathesis/pytest/plugin.py +33 -2
  79. schemathesis/schemas.py +21 -66
  80. schemathesis/specs/graphql/scalars.py +37 -3
  81. schemathesis/specs/graphql/schemas.py +23 -18
  82. schemathesis/specs/openapi/_hypothesis.py +26 -28
  83. schemathesis/specs/openapi/checks.py +37 -36
  84. schemathesis/specs/openapi/examples.py +4 -3
  85. schemathesis/specs/openapi/formats.py +32 -5
  86. schemathesis/specs/openapi/media_types.py +44 -1
  87. schemathesis/specs/openapi/negative/__init__.py +2 -2
  88. schemathesis/specs/openapi/patterns.py +46 -16
  89. schemathesis/specs/openapi/references.py +2 -3
  90. schemathesis/specs/openapi/schemas.py +19 -22
  91. schemathesis/specs/openapi/stateful/__init__.py +12 -6
  92. schemathesis/transport/__init__.py +54 -16
  93. schemathesis/transport/prepare.py +38 -13
  94. schemathesis/transport/requests.py +12 -9
  95. schemathesis/transport/wsgi.py +11 -12
  96. {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/METADATA +50 -97
  97. schemathesis-4.0.0a12.dist-info/RECORD +164 -0
  98. schemathesis/cli/commands/run/checks.py +0 -79
  99. schemathesis/cli/commands/run/hypothesis.py +0 -78
  100. schemathesis/cli/commands/run/reports.py +0 -72
  101. schemathesis/cli/hooks.py +0 -36
  102. schemathesis/contrib/__init__.py +0 -9
  103. schemathesis/contrib/openapi/__init__.py +0 -9
  104. schemathesis/contrib/openapi/fill_missing_examples.py +0 -20
  105. schemathesis/engine/config.py +0 -59
  106. schemathesis/experimental/__init__.py +0 -72
  107. schemathesis/generation/targets.py +0 -69
  108. schemathesis-4.0.0a10.dist-info/RECORD +0 -153
  109. {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/WHEEL +0 -0
  110. {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/entry_points.txt +0 -0
  111. {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/licenses/LICENSE +0 -0
@@ -1,52 +1,48 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from random import Random
5
- from typing import Any, Sequence
4
+ from typing import Any, Callable
6
5
 
7
6
  import click
8
7
  from click.utils import LazyFile
9
8
 
10
- from schemathesis import contrib, experimental
11
9
  from schemathesis.checks import CHECKS
12
10
  from schemathesis.cli.commands.run import executor, validation
13
- from schemathesis.cli.commands.run.checks import CheckArguments
14
- from schemathesis.cli.commands.run.filters import FilterArguments, with_filters
15
- from schemathesis.cli.commands.run.hypothesis import (
16
- HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER,
17
- HealthCheck,
18
- prepare_health_checks,
19
- prepare_phases,
20
- prepare_settings,
21
- )
22
- from schemathesis.cli.commands.run.reports import DEFAULT_REPORT_DIRECTORY, ReportConfig, ReportFormat
11
+ from schemathesis.cli.commands.run.filters import with_filters
23
12
  from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
24
13
  from schemathesis.cli.core import ensure_color
25
14
  from schemathesis.cli.ext.groups import group, grouped_option
26
15
  from schemathesis.cli.ext.options import (
27
16
  CsvChoice,
28
17
  CsvEnumChoice,
29
- CsvListChoice,
30
18
  CustomHelpMessageChoice,
31
19
  RegistryChoice,
32
20
  )
33
- from schemathesis.core.output import OutputConfig
21
+ from schemathesis.config import (
22
+ DEFAULT_REPORT_DIRECTORY,
23
+ HealthCheck,
24
+ ReportFormat,
25
+ SchemathesisConfig,
26
+ SchemathesisWarning,
27
+ )
28
+ from schemathesis.core import HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER
34
29
  from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
35
- from schemathesis.engine.config import EngineConfig, ExecutionConfig, NetworkConfig
36
- from schemathesis.engine.phases import PhaseName
37
- from schemathesis.generation import DEFAULT_GENERATOR_MODES, GenerationConfig, GenerationMode
38
- from schemathesis.generation.overrides import Override
39
- from schemathesis.generation.targets import TARGETS
30
+ from schemathesis.generation import GenerationMode
31
+ from schemathesis.generation.metrics import METRICS, MetricFunction
40
32
 
41
33
  # NOTE: Need to explicitly import all registered checks
42
34
  from schemathesis.specs.openapi.checks import * # noqa: F401, F403
43
35
 
44
36
  COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
45
37
 
46
- DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
38
+ DEFAULT_PHASES = ["examples", "coverage", "fuzzing", "stateful"]
47
39
 
48
40
 
49
- @click.argument("schema", type=str) # type: ignore[misc]
41
+ @click.argument( # type: ignore[misc]
42
+ "location",
43
+ type=str,
44
+ callback=validation.validate_schema_location,
45
+ )
50
46
  @group("Options")
51
47
  @grouped_option(
52
48
  "--url",
@@ -61,7 +57,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
61
57
  @grouped_option(
62
58
  "--workers",
63
59
  "-w",
64
- "workers_num",
60
+ "workers",
65
61
  help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
66
62
  type=CustomHelpMessageChoice(
67
63
  ["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
@@ -92,6 +88,14 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
92
88
  default=None,
93
89
  envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
94
90
  )
91
+ @grouped_option(
92
+ "--warnings",
93
+ help="Control warning display: 'off' to disable all, or comma-separated list of warning types to enable",
94
+ type=str,
95
+ default=None,
96
+ callback=validation.validate_warnings,
97
+ metavar="WARNINGS",
98
+ )
95
99
  @group("API validation options")
96
100
  @grouped_option(
97
101
  "--checks",
@@ -100,7 +104,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
100
104
  multiple=True,
101
105
  help="Comma-separated list of checks to run against API responses",
102
106
  type=RegistryChoice(CHECKS, with_all=True),
103
- default=("not_a_server_error",),
107
+ default=None,
104
108
  callback=validation.reduce_list,
105
109
  show_default=True,
106
110
  metavar="",
@@ -111,7 +115,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
111
115
  multiple=True,
112
116
  help="Comma-separated list of checks to skip during testing",
113
117
  type=RegistryChoice(CHECKS, with_all=True),
114
- default=(),
118
+ default=None,
115
119
  callback=validation.reduce_list,
116
120
  show_default=True,
117
121
  metavar="",
@@ -153,12 +157,14 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
153
157
  "include_by",
154
158
  type=str,
155
159
  metavar="EXPR",
160
+ callback=validation.validate_filter_expression,
156
161
  help="Include using custom expression",
157
162
  )
158
163
  @grouped_option(
159
164
  "--exclude-by",
160
165
  "exclude_by",
161
166
  type=str,
167
+ callback=validation.validate_filter_expression,
162
168
  metavar="EXPR",
163
169
  help="Exclude using custom expression",
164
170
  )
@@ -246,6 +252,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
246
252
  )
247
253
  @grouped_option(
248
254
  "--report-dir",
255
+ "report_directory",
249
256
  help="Directory to store all report files",
250
257
  type=click.Path(file_okay=False, dir_okay=True),
251
258
  default=DEFAULT_REPORT_DIRECTORY,
@@ -295,53 +302,6 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
295
302
  metavar="BOOLEAN",
296
303
  callback=validation.convert_boolean_string,
297
304
  )
298
- @group("Experimental options")
299
- @grouped_option(
300
- "--experimental",
301
- "experiments",
302
- help="Enable experimental features",
303
- type=click.Choice(sorted([experiment.label for experiment in experimental.GLOBAL_EXPERIMENTS.available])),
304
- callback=validation.convert_experimental,
305
- multiple=True,
306
- metavar="FEATURES",
307
- )
308
- @grouped_option(
309
- "--experimental-coverage-unexpected-methods",
310
- "coverage_unexpected_methods",
311
- help="HTTP methods to use when generating test cases with methods not specified in the API during the coverage phase.",
312
- type=CsvChoice(["get", "put", "post", "delete", "options", "head", "patch", "trace"], case_sensitive=False),
313
- callback=validation.convert_http_methods,
314
- metavar="",
315
- default=None,
316
- envvar="SCHEMATHESIS_EXPERIMENTAL_COVERAGE_UNEXPECTED_METHODS",
317
- )
318
- @grouped_option(
319
- "--experimental-missing-required-header-allowed-statuses",
320
- "missing_required_header_allowed_statuses",
321
- help="Comma-separated list of status codes expected for test cases with a missing required header",
322
- type=CsvListChoice(),
323
- callback=validation.convert_status_codes,
324
- metavar="",
325
- envvar="SCHEMATHESIS_EXPERIMENTAL_MISSING_REQUIRED_HEADER_ALLOWED_STATUSES",
326
- )
327
- @grouped_option(
328
- "--experimental-positive-data-acceptance-allowed-statuses",
329
- "positive_data_acceptance_allowed_statuses",
330
- help="Comma-separated list of status codes considered as successful responses",
331
- type=CsvListChoice(),
332
- callback=validation.convert_status_codes,
333
- metavar="",
334
- envvar="SCHEMATHESIS_EXPERIMENTAL_POSITIVE_DATA_ACCEPTANCE_ALLOWED_STATUSES",
335
- )
336
- @grouped_option(
337
- "--experimental-negative-data-rejection-allowed-statuses",
338
- "negative_data_rejection_allowed_statuses",
339
- help="Comma-separated list of status codes expected for rejected negative data",
340
- type=CsvListChoice(),
341
- callback=validation.convert_status_codes,
342
- metavar="",
343
- envvar="SCHEMATHESIS_EXPERIMENTAL_NEGATIVE_DATA_REJECTION_ALLOWED_STATUSES",
344
- )
345
305
  @group("Data generation options")
346
306
  @grouped_option(
347
307
  "--mode",
@@ -349,7 +309,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
349
309
  "generation_modes",
350
310
  help="Test data generation mode",
351
311
  type=click.Choice([item.value for item in GenerationMode] + ["all"]),
352
- default=GenerationMode.default().value,
312
+ default="all",
353
313
  callback=validation.convert_generation_mode,
354
314
  show_default=True,
355
315
  metavar="",
@@ -378,7 +338,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
378
338
  help="Enables deterministic mode, which eliminates random variation between tests",
379
339
  is_flag=True,
380
340
  is_eager=True,
381
- default=None,
341
+ default=False,
382
342
  show_default=True,
383
343
  )
384
344
  @grouped_option(
@@ -402,9 +362,9 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
402
362
  "generation_maximize",
403
363
  multiple=True,
404
364
  help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
405
- type=RegistryChoice(TARGETS),
365
+ type=RegistryChoice(METRICS),
406
366
  default=None,
407
- callback=validation.reduce_list,
367
+ callback=validation.convert_maximize,
408
368
  show_default=True,
409
369
  metavar="METRIC",
410
370
  )
@@ -443,119 +403,69 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
443
403
  show_default=True,
444
404
  metavar="BOOLEAN",
445
405
  )
446
- @grouped_option(
447
- "--contrib-openapi-fill-missing-examples",
448
- "contrib_openapi_fill_missing_examples",
449
- help="Enable generation of random examples for API operations that do not have explicit examples",
450
- is_flag=True,
451
- default=False,
452
- show_default=True,
453
- metavar="BOOLEAN",
454
- )
455
- @group("Open API options")
456
- @grouped_option(
457
- "--set-query",
458
- "set_query",
459
- help=r"OpenAPI: Override a specific query parameter by specifying 'parameter=value'",
460
- multiple=True,
461
- type=str,
462
- callback=validation.validate_set_query,
463
- )
464
- @grouped_option(
465
- "--set-header",
466
- "set_header",
467
- help=r"OpenAPI: Override a specific header parameter by specifying 'parameter=value'",
468
- multiple=True,
469
- type=str,
470
- callback=validation.validate_set_header,
471
- )
472
- @grouped_option(
473
- "--set-cookie",
474
- "set_cookie",
475
- help=r"OpenAPI: Override a specific cookie parameter by specifying 'parameter=value'",
476
- multiple=True,
477
- type=str,
478
- callback=validation.validate_set_cookie,
479
- )
480
- @grouped_option(
481
- "--set-path",
482
- "set_path",
483
- help=r"OpenAPI: Override a specific path parameter by specifying 'parameter=value'",
484
- multiple=True,
485
- type=str,
486
- callback=validation.validate_set_path,
487
- )
488
406
  @group("Global options")
489
407
  @grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
490
408
  @grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
491
409
  @click.pass_context # type: ignore[misc]
492
410
  def run(
493
411
  ctx: click.Context,
494
- schema: str,
412
+ *,
413
+ location: str,
495
414
  auth: tuple[str, str] | None,
496
415
  headers: dict[str, str],
497
- set_query: dict[str, str],
498
- set_header: dict[str, str],
499
- set_cookie: dict[str, str],
500
- set_path: dict[str, str],
501
- experiments: list,
502
- coverage_unexpected_methods: set[str] | None,
503
- missing_required_header_allowed_statuses: list[str],
504
- positive_data_acceptance_allowed_statuses: list[str],
505
- negative_data_rejection_allowed_statuses: list[str],
506
- included_check_names: Sequence[str],
507
- excluded_check_names: Sequence[str],
416
+ included_check_names: list[str] | None,
417
+ excluded_check_names: list[str] | None,
508
418
  max_response_time: float | None = None,
509
- phases: Sequence[str] = DEFAULT_PHASES,
419
+ phases: list[str] = DEFAULT_PHASES,
510
420
  max_failures: int | None = None,
511
- continue_on_failure: bool = False,
512
- include_path: Sequence[str] = (),
513
- include_path_regex: str | None = None,
514
- include_method: Sequence[str] = (),
515
- include_method_regex: str | None = None,
516
- include_name: Sequence[str] = (),
517
- include_name_regex: str | None = None,
518
- include_tag: Sequence[str] = (),
519
- include_tag_regex: str | None = None,
520
- include_operation_id: Sequence[str] = (),
521
- include_operation_id_regex: str | None = None,
522
- exclude_path: Sequence[str] = (),
523
- exclude_path_regex: str | None = None,
524
- exclude_method: Sequence[str] = (),
525
- exclude_method_regex: str | None = None,
526
- exclude_name: Sequence[str] = (),
527
- exclude_name_regex: str | None = None,
528
- exclude_tag: Sequence[str] = (),
529
- exclude_tag_regex: str | None = None,
530
- exclude_operation_id: Sequence[str] = (),
531
- exclude_operation_id_regex: str | None = None,
532
- include_by: str | None = None,
533
- exclude_by: str | None = None,
421
+ continue_on_failure: bool | None = None,
422
+ include_path: tuple[str, ...],
423
+ include_path_regex: str | None,
424
+ include_method: tuple[str, ...],
425
+ include_method_regex: str | None,
426
+ include_name: tuple[str, ...],
427
+ include_name_regex: str | None,
428
+ include_tag: tuple[str, ...],
429
+ include_tag_regex: str | None,
430
+ include_operation_id: tuple[str, ...],
431
+ include_operation_id_regex: str | None,
432
+ exclude_path: tuple[str, ...],
433
+ exclude_path_regex: str | None,
434
+ exclude_method: tuple[str, ...],
435
+ exclude_method_regex: str | None,
436
+ exclude_name: tuple[str, ...],
437
+ exclude_name_regex: str | None,
438
+ exclude_tag: tuple[str, ...],
439
+ exclude_tag_regex: str | None,
440
+ exclude_operation_id: tuple[str, ...],
441
+ exclude_operation_id_regex: str | None,
442
+ include_by: Callable | None = None,
443
+ exclude_by: Callable | None = None,
534
444
  exclude_deprecated: bool = False,
535
- workers_num: int = DEFAULT_WORKERS,
536
- base_url: str | None = None,
445
+ workers: int = DEFAULT_WORKERS,
446
+ base_url: str | None,
537
447
  wait_for_schema: float | None = None,
448
+ suppress_health_check: list[HealthCheck] | None,
449
+ warnings: bool | list[SchemathesisWarning] | None,
538
450
  rate_limit: str | None = None,
539
- suppress_health_check: list[HealthCheck] | None = None,
540
451
  request_timeout: int | None = None,
541
452
  request_tls_verify: bool = True,
542
453
  request_cert: str | None = None,
543
454
  request_cert_key: str | None = None,
544
455
  request_proxy: str | None = None,
545
- report_formats: list[ReportFormat] | None = None,
546
- report_dir: Path = DEFAULT_REPORT_DIRECTORY,
456
+ report_formats: list[ReportFormat] | None,
457
+ report_directory: Path | str = DEFAULT_REPORT_DIRECTORY,
547
458
  report_junit_path: LazyFile | None = None,
548
459
  report_vcr_path: LazyFile | None = None,
549
460
  report_har_path: LazyFile | None = None,
550
461
  report_preserve_bytes: bool = False,
551
462
  output_sanitize: bool = True,
552
463
  output_truncate: bool = True,
553
- contrib_openapi_fill_missing_examples: bool = False,
554
- generation_modes: tuple[GenerationMode, ...] = DEFAULT_GENERATOR_MODES,
464
+ generation_modes: list[GenerationMode],
555
465
  generation_seed: int | None = None,
556
466
  generation_max_examples: int | None = None,
557
- generation_maximize: Sequence[str] | None = None,
558
- generation_deterministic: bool | None = None,
467
+ generation_maximize: list[MetricFunction] | None,
468
+ generation_deterministic: bool = False,
559
469
  generation_database: str | None = None,
560
470
  generation_unique_inputs: bool = False,
561
471
  generation_allow_x00: bool = True,
@@ -573,126 +483,105 @@ def run(
573
483
  """
574
484
  if no_color and force_color:
575
485
  raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
576
- ensure_color(ctx, no_color, force_color)
577
-
578
- validation.validate_schema(schema, base_url)
579
486
 
580
- _hypothesis_phases = prepare_phases(generation_no_shrink)
581
- _hypothesis_suppress_health_check = prepare_health_checks(suppress_health_check)
487
+ config: SchemathesisConfig = ctx.obj.config
582
488
 
583
- for experiment in experiments:
584
- experiment.enable()
585
- if contrib_openapi_fill_missing_examples:
586
- contrib.openapi.fill_missing_examples.install()
587
-
588
- override = Override(query=set_query, headers=set_header, cookies=set_cookie, path_parameters=set_path)
589
-
590
- validation.validate_auth_overlap(auth, headers, override)
489
+ # First, set the right color
490
+ color: bool | None
491
+ if force_color:
492
+ color = True
493
+ elif no_color:
494
+ color = False
495
+ else:
496
+ color = config.color
497
+ ensure_color(ctx, color)
591
498
 
592
- filter_set = FilterArguments(
593
- include_path=include_path,
594
- include_method=include_method,
595
- include_name=include_name,
596
- include_tag=include_tag,
597
- include_operation_id=include_operation_id,
598
- include_path_regex=include_path_regex,
599
- include_method_regex=include_method_regex,
600
- include_name_regex=include_name_regex,
601
- include_tag_regex=include_tag_regex,
602
- include_operation_id_regex=include_operation_id_regex,
603
- exclude_path=exclude_path,
604
- exclude_method=exclude_method,
605
- exclude_name=exclude_name,
606
- exclude_tag=exclude_tag,
607
- exclude_operation_id=exclude_operation_id,
608
- exclude_path_regex=exclude_path_regex,
609
- exclude_method_regex=exclude_method_regex,
610
- exclude_name_regex=exclude_name_regex,
611
- exclude_tag_regex=exclude_tag_regex,
612
- exclude_operation_id_regex=exclude_operation_id_regex,
613
- include_by=include_by,
614
- exclude_by=exclude_by,
615
- exclude_deprecated=exclude_deprecated,
616
- ).into()
499
+ validation.validate_auth_overlap(auth, headers)
617
500
 
618
- selected_checks, checks_config = CheckArguments(
501
+ # Then override the global config from CLI options
502
+ config.update(
503
+ color=color,
504
+ suppress_health_check=suppress_health_check,
505
+ seed=generation_seed,
506
+ wait_for_schema=wait_for_schema,
507
+ max_failures=max_failures,
508
+ )
509
+ config.output.sanitization.update(enabled=output_sanitize)
510
+ config.output.truncation.update(enabled=output_truncate)
511
+ config.reports.update(
512
+ formats=report_formats,
513
+ junit_path=report_junit_path.name if report_junit_path else None,
514
+ vcr_path=report_vcr_path.name if report_vcr_path else None,
515
+ har_path=report_har_path.name if report_har_path else None,
516
+ directory=Path(report_directory),
517
+ preserve_bytes=report_preserve_bytes,
518
+ )
519
+ # Other CLI options work as an override for all defined projects
520
+ config.projects.override.update(
521
+ base_url=base_url,
522
+ headers=headers if headers else None,
523
+ basic_auth=auth,
524
+ workers=workers,
525
+ continue_on_failure=continue_on_failure,
526
+ rate_limit=rate_limit,
527
+ request_timeout=request_timeout,
528
+ tls_verify=request_tls_verify,
529
+ request_cert=request_cert,
530
+ request_cert_key=request_cert_key,
531
+ proxy=request_proxy,
532
+ warnings=warnings,
533
+ )
534
+ # These are filters for what API operations should be tested
535
+ filter_set = {
536
+ "include_path": include_path,
537
+ "include_method": include_method,
538
+ "include_name": include_name,
539
+ "include_tag": include_tag,
540
+ "include_operation_id": include_operation_id,
541
+ "include_path_regex": include_path_regex,
542
+ "include_method_regex": include_method_regex,
543
+ "include_name_regex": include_name_regex,
544
+ "include_tag_regex": include_tag_regex,
545
+ "include_operation_id_regex": include_operation_id_regex,
546
+ "exclude_path": exclude_path,
547
+ "exclude_method": exclude_method,
548
+ "exclude_name": exclude_name,
549
+ "exclude_tag": exclude_tag,
550
+ "exclude_operation_id": exclude_operation_id,
551
+ "exclude_path_regex": exclude_path_regex,
552
+ "exclude_method_regex": exclude_method_regex,
553
+ "exclude_name_regex": exclude_name_regex,
554
+ "exclude_tag_regex": exclude_tag_regex,
555
+ "exclude_operation_id_regex": exclude_operation_id_regex,
556
+ "include_by": include_by,
557
+ "exclude_by": exclude_by,
558
+ "exclude_deprecated": exclude_deprecated,
559
+ }
560
+ config.projects.override.phases.update(phases=phases)
561
+ config.projects.override.checks.update(
619
562
  included_check_names=included_check_names,
620
563
  excluded_check_names=excluded_check_names,
621
- positive_data_acceptance_allowed_statuses=positive_data_acceptance_allowed_statuses,
622
- missing_required_header_allowed_statuses=missing_required_header_allowed_statuses,
623
- negative_data_rejection_allowed_statuses=negative_data_rejection_allowed_statuses,
624
564
  max_response_time=max_response_time,
625
- ).into()
626
-
627
- report_config = None
628
- if report_formats or report_junit_path or report_vcr_path or report_har_path:
629
- report_config = ReportConfig(
630
- formats=report_formats,
631
- directory=Path(report_dir),
632
- junit_path=report_junit_path if report_junit_path else None,
633
- vcr_path=report_vcr_path if report_vcr_path else None,
634
- har_path=report_har_path if report_har_path else None,
635
- preserve_bytes=report_preserve_bytes,
636
- sanitize_output=output_sanitize,
637
- )
638
-
639
- # Use the same seed for all tests unless `derandomize=True` is used
640
- seed: int | None
641
- if generation_seed is None and not generation_deterministic:
642
- seed = Random().getrandbits(128)
643
- else:
644
- seed = generation_seed
645
-
646
- phases_ = [PhaseName.PROBING] + [PhaseName.from_str(phase) for phase in phases]
565
+ )
566
+ config.projects.override.generation.update(
567
+ modes=generation_modes,
568
+ max_examples=generation_max_examples,
569
+ no_shrink=generation_no_shrink,
570
+ maximize=generation_maximize,
571
+ deterministic=generation_deterministic,
572
+ database=generation_database,
573
+ unique_inputs=generation_unique_inputs,
574
+ allow_x00=generation_allow_x00,
575
+ graphql_allow_null=generation_graphql_allow_null,
576
+ with_security_parameters=generation_with_security_parameters,
577
+ codec=generation_codec,
578
+ )
647
579
 
648
- config = executor.RunConfig(
649
- location=schema,
650
- base_url=base_url,
651
- engine=EngineConfig(
652
- execution=ExecutionConfig(
653
- phases=phases_,
654
- checks=selected_checks,
655
- targets=TARGETS.get_by_names(generation_maximize or []),
656
- hypothesis_settings=prepare_settings(
657
- database=generation_database,
658
- derandomize=generation_deterministic,
659
- max_examples=generation_max_examples,
660
- phases=_hypothesis_phases,
661
- suppress_health_check=_hypothesis_suppress_health_check,
662
- ),
663
- generation=GenerationConfig(
664
- modes=list(generation_modes),
665
- allow_x00=generation_allow_x00,
666
- graphql_allow_null=generation_graphql_allow_null,
667
- codec=generation_codec,
668
- with_security_parameters=generation_with_security_parameters,
669
- unexpected_methods=coverage_unexpected_methods,
670
- ),
671
- max_failures=max_failures,
672
- continue_on_failure=continue_on_failure,
673
- unique_inputs=generation_unique_inputs,
674
- seed=seed,
675
- workers_num=workers_num,
676
- ),
677
- network=NetworkConfig(
678
- auth=auth,
679
- headers=headers,
680
- timeout=request_timeout,
681
- tls_verify=request_tls_verify,
682
- proxy=request_proxy,
683
- cert=(request_cert, request_cert_key)
684
- if request_cert is not None and request_cert_key is not None
685
- else request_cert,
686
- ),
687
- override=override,
688
- checks_config=checks_config,
689
- ),
580
+ executor.execute(
581
+ location=location,
690
582
  filter_set=filter_set,
691
- wait_for_schema=wait_for_schema,
692
- rate_limit=rate_limit,
693
- output=OutputConfig(sanitize=output_sanitize, truncate=output_truncate),
694
- report=report_config,
583
+ # We don't the project yet, so pass the default config
584
+ config=config.projects.get_default(),
695
585
  args=ctx.args,
696
586
  params=ctx.params,
697
587
  )
698
- executor.execute(config)
@@ -1,16 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Generator
4
+ from typing import TYPE_CHECKING, Callable, Generator
5
5
 
6
+ from schemathesis.cli.commands.run.events import LoadingFinished
7
+ from schemathesis.config import ProjectConfig
6
8
  from schemathesis.core.failures import Failure
7
- from schemathesis.core.output import OutputConfig
8
9
  from schemathesis.core.result import Err, Ok
9
10
  from schemathesis.core.transforms import UNRESOLVABLE
10
11
  from schemathesis.core.transport import Response
11
12
  from schemathesis.engine import Status, events
12
13
  from schemathesis.engine.recorder import CaseNode, ScenarioRecorder
13
14
  from schemathesis.generation.case import Case
15
+ from schemathesis.schemas import APIOperation
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from schemathesis.generation.stateful.state_machine import ExtractionFailure
@@ -176,12 +178,12 @@ class GroupedFailures:
176
178
  class ExecutionContext:
177
179
  """Storage for the current context of the execution."""
178
180
 
181
+ config: ProjectConfig
182
+ find_operation_by_label: Callable[[str], APIOperation | None] | None = None
179
183
  statistic: Statistic = field(default_factory=Statistic)
180
184
  exit_code: int = 0
181
- output_config: OutputConfig = field(default_factory=OutputConfig)
182
185
  initialization_lines: list[str | Generator[str, None, None]] = field(default_factory=list)
183
186
  summary_lines: list[str | Generator[str, None, None]] = field(default_factory=list)
184
- seed: int | None = None
185
187
 
186
188
  def add_initialization_line(self, line: str | Generator[str, None, None]) -> None:
187
189
  self.initialization_lines.append(line)
@@ -190,6 +192,8 @@ class ExecutionContext:
190
192
  self.summary_lines.append(line)
191
193
 
192
194
  def on_event(self, event: events.EngineEvent) -> None:
195
+ if isinstance(event, LoadingFinished):
196
+ self.find_operation_by_label = event.find_operation_by_label
193
197
  if isinstance(event, events.ScenarioFinished):
194
198
  self.statistic.on_scenario_finished(event.recorder)
195
199
  elif isinstance(event, events.NonFatalError) or (
@@ -1,9 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import time
2
4
  import uuid
5
+ from typing import Callable
3
6
 
7
+ from schemathesis.config import ProjectConfig
4
8
  from schemathesis.core import Specification
5
9
  from schemathesis.engine import events
6
- from schemathesis.schemas import ApiStatistic
10
+ from schemathesis.schemas import APIOperation, ApiStatistic
7
11
 
8
12
 
9
13
  class LoadingStarted(events.EngineEvent):
@@ -26,6 +30,8 @@ class LoadingFinished(events.EngineEvent):
26
30
  "specification",
27
31
  "statistic",
28
32
  "schema",
33
+ "config",
34
+ "find_operation_by_label",
29
35
  )
30
36
 
31
37
  def __init__(
@@ -38,6 +44,8 @@ class LoadingFinished(events.EngineEvent):
38
44
  specification: Specification,
39
45
  statistic: ApiStatistic,
40
46
  schema: dict,
47
+ config: ProjectConfig,
48
+ find_operation_by_label: Callable[[str], APIOperation | None],
41
49
  ) -> None:
42
50
  self.id = uuid.uuid4()
43
51
  self.timestamp = time.time()
@@ -48,3 +56,5 @@ class LoadingFinished(events.EngineEvent):
48
56
  self.statistic = statistic
49
57
  self.schema = schema
50
58
  self.base_path = base_path
59
+ self.config = config
60
+ self.find_operation_by_label = find_operation_by_label