[{"data":1,"prerenderedAt":1586},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fgenerating-custom-strategies-with-hypothesisstrategies\u002F":3},{"id":4,"title":5,"body":6,"description":16,"extension":1580,"meta":1581,"navigation":194,"path":1582,"seo":1583,"stem":1584,"__hash__":1585},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fgenerating-custom-strategies-with-hypothesisstrategies\u002Findex.md","Generating custom strategies with hypothesis.strategies",{"type":7,"value":8,"toc":1571},"minimark",[9,13,17,22,49,64,85,89,107,138,154,437,455,459,473,485,658,699,703,710,724,856,871,875,901,1076,1109,1113,1116,1278,1486,1496,1500,1513,1516,1567],[10,11,5],"h1",{"id":12},"generating-custom-strategies-with-hypothesisstrategies",[14,15,16],"p",{},"Property-based testing shifts the paradigm from example-driven assertions to invariant validation. While Hypothesis ships with a robust standard library of primitives, production-grade systems inevitably encounter domain models that require cross-field correlation, strict business invariants, or complex type hierarchies. Learning how to programmatically construct, register, and debug custom strategies is the definitive skill separating basic test coverage from resilient, self-healing test suites. This guide details the architectural patterns, diagnostic workflows, and CI optimization techniques required for generating custom strategies with hypothesis.strategies in modern Python codebases.",[18,19,21],"h2",{"id":20},"when-and-why-to-generate-custom-strategies","When and Why to Generate Custom Strategies",[14,23,24,25,29,30,33,34,37,38,29,41,44,45,48],{},"The architectural boundary between built-in strategies and custom implementations is defined by constraint density and generation efficiency. Built-in primitives like ",[26,27,28],"code",{},"st.integers()",", ",[26,31,32],{},"st.text()",", and ",[26,35,36],{},"st.dictionaries()"," operate on independent, uniformly distributed domains. They excel at fuzzing generic algorithms but degrade rapidly when applied to tightly coupled domain objects. When business rules dictate that ",[26,39,40],{},"start_date \u003C= end_date",[26,42,43],{},"currency_code"," must match ",[26,46,47],{},"locale",", or nested payloads must maintain referential integrity, naive composition triggers excessive rejection sampling. Hypothesis’s rejection sampler discards invalid examples and retries, but when rejection rates exceed 15%, generation throughput collapses, shrinking becomes non-deterministic, and CI pipelines stall.",[14,50,51,52,55,56,59,60,63],{},"Custom strategies solve this by shifting validation from post-generation filtering to pre-generation routing. Instead of generating arbitrary data and hoping it satisfies invariants, you construct strategies that natively produce valid states. This requires understanding strategy composition versus inheritance. Hypothesis strategies are immutable, lazy-evaluated generators; they do not support subclassing. Instead, you compose them functionally using combinators. The core API surface for this work includes ",[26,53,54],{},"@st.composite"," for imperative, stateful generation; ",[26,57,58],{},"st.builds"," for declarative constructor mapping; and ",[26,61,62],{},"st.register_type_strategy"," for automated type resolution.",[14,65,66,67,70,71,74,75,78,79,84],{},"When designing these abstractions, map business rules directly to Hypothesis primitives. If a field is an enum, use ",[26,68,69],{},"st.sampled_from()",". If a value is constant in certain contexts, use ",[26,72,73],{},"st.just()",". If multiple valid structural shapes exist, use ",[26,76,77],{},"st.one_of()",". By aligning your generation logic with the underlying constraint solver, you eliminate the performance bottlenecks that plague naive property tests. For foundational testing paradigms and broader architectural context, refer to ",[80,81,83],"a",{"href":82},"\u002Fproperty-based-fuzz-testing-strategies\u002F","Property-Based & Fuzz Testing Strategies"," before diving into advanced composition patterns.",[18,86,88],{"id":87},"composing-strategies-with-stcomposite-and-stbuilds","Composing Strategies with @st.composite and st.builds",[14,90,91,92,94,95,98,99,102,103,106],{},"The ",[26,93,54],{}," decorator transforms a standard Python function into a strategy factory. Inside a composite function, you call ",[26,96,97],{},"data.draw(strategy)"," to consume values from underlying strategies sequentially. This enables stateful generation, conditional branching, and cross-field validation without triggering the rejection sampler. Each ",[26,100,101],{},"draw()"," call advances Hypothesis’s internal ",[26,104,105],{},"DataTree",", allowing the shrinking engine to trace dependencies and minimize failures deterministically.",[14,108,109,110,113,114,117,118,120,121,124,125,128,129,131,132,29,135,137],{},"Contrast this with ",[26,111,112],{},"st.builds(target, **kwargs)",", which declaratively maps keyword arguments to strategies and invokes ",[26,115,116],{},"target"," with the generated values. ",[26,119,58],{}," is ideal for pure constructors where fields are independent. It automatically resolves type hints via ",[26,122,123],{},"st.from_type()"," and handles ",[26,126,127],{},"typing.Optional"," gracefully. However, ",[26,130,58],{}," cannot enforce cross-field constraints. When you need ",[26,133,134],{},"field_b > field_a",[26,136,54],{}," becomes mandatory.",[14,139,140,141,143,144,146,147,149,150,153],{},"Generator overhead and lazy evaluation are critical considerations. Strategies are not evaluated until ",[26,142,101],{}," is called. This means you can define complex conditional routing without incurring upfront costs. Use ",[26,145,77],{}," to branch generation paths based on domain probabilities, and ",[26,148,69],{}," to restrict categorical domains. Avoid placing ",[26,151,152],{},"assume()"," calls after expensive draws; instead, route generation early.",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-python shiki shiki-themes github-light github-dark","from datetime import date, timedelta\nfrom typing import Protocol\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings, Verbosity, Phase\n\nclass TimeRange(Protocol):\n start_date: date\n end_date: date\n duration_days: int\n\n@st.composite\ndef valid_time_ranges(draw: st.DrawFn) -> dict[str, date | int]:\n \"\"\"\n Generates time ranges where start_date \u003C= end_date.\n Uses conditional routing to avoid st.filter() rejection overhead.\n \"\"\"\n # Draw start date from a bounded domain for CI stability\n start = draw(st.dates(min_value=date(2020, 1, 1), max_value=date(2024, 12, 31)))\n \n # Conditionally route end_date generation\n # 80% chance of valid future date, 20% chance of same day\n end_strategy = st.one_of(\n st.dates(min_value=start, max_value=start + timedelta(days=365)),\n st.just(start)\n )\n end = draw(end_strategy)\n \n # Explicit assume() only for edge-case safety, not primary routing\n assume(end >= start)\n \n return {\n \"start_date\": start,\n \"end_date\": end,\n \"duration_days\": (end - start).days\n }\n\n@given(time_range=valid_time_ranges())\n@settings(\n max_examples=200,\n phases=[Phase.generate, Phase.shrink],\n verbosity=Verbosity.normal,\n database=None # Isolate for deterministic runs\n)\ndef test_time_range_invariants(time_range: dict[str, date | int]) -> None:\n assert time_range[\"start_date\"] \u003C= time_range[\"end_date\"]\n assert time_range[\"duration_days\"] >= 0\n","python","",[26,162,163,171,177,183,189,196,202,208,214,220,225,231,237,243,249,255,260,266,272,278,284,290,296,302,308,314,320,325,331,337,342,348,354,360,366,372,377,383,389,395,401,407,413,419,425,431],{"__ignoreMap":160},[164,165,168],"span",{"class":166,"line":167},"line",1,[164,169,170],{},"from datetime import date, timedelta\n",[164,172,174],{"class":166,"line":173},2,[164,175,176],{},"from typing import Protocol\n",[164,178,180],{"class":166,"line":179},3,[164,181,182],{},"import hypothesis.strategies as st\n",[164,184,186],{"class":166,"line":185},4,[164,187,188],{},"from hypothesis import given, settings, Verbosity, Phase\n",[164,190,192],{"class":166,"line":191},5,[164,193,195],{"emptyLinePlaceholder":194},true,"\n",[164,197,199],{"class":166,"line":198},6,[164,200,201],{},"class TimeRange(Protocol):\n",[164,203,205],{"class":166,"line":204},7,[164,206,207],{}," start_date: date\n",[164,209,211],{"class":166,"line":210},8,[164,212,213],{}," end_date: date\n",[164,215,217],{"class":166,"line":216},9,[164,218,219],{}," duration_days: int\n",[164,221,223],{"class":166,"line":222},10,[164,224,195],{"emptyLinePlaceholder":194},[164,226,228],{"class":166,"line":227},11,[164,229,230],{},"@st.composite\n",[164,232,234],{"class":166,"line":233},12,[164,235,236],{},"def valid_time_ranges(draw: st.DrawFn) -> dict[str, date | int]:\n",[164,238,240],{"class":166,"line":239},13,[164,241,242],{}," \"\"\"\n",[164,244,246],{"class":166,"line":245},14,[164,247,248],{}," Generates time ranges where start_date \u003C= end_date.\n",[164,250,252],{"class":166,"line":251},15,[164,253,254],{}," Uses conditional routing to avoid st.filter() rejection overhead.\n",[164,256,258],{"class":166,"line":257},16,[164,259,242],{},[164,261,263],{"class":166,"line":262},17,[164,264,265],{}," # Draw start date from a bounded domain for CI stability\n",[164,267,269],{"class":166,"line":268},18,[164,270,271],{}," start = draw(st.dates(min_value=date(2020, 1, 1), max_value=date(2024, 12, 31)))\n",[164,273,275],{"class":166,"line":274},19,[164,276,277],{}," \n",[164,279,281],{"class":166,"line":280},20,[164,282,283],{}," # Conditionally route end_date generation\n",[164,285,287],{"class":166,"line":286},21,[164,288,289],{}," # 80% chance of valid future date, 20% chance of same day\n",[164,291,293],{"class":166,"line":292},22,[164,294,295],{}," end_strategy = st.one_of(\n",[164,297,299],{"class":166,"line":298},23,[164,300,301],{}," st.dates(min_value=start, max_value=start + timedelta(days=365)),\n",[164,303,305],{"class":166,"line":304},24,[164,306,307],{}," st.just(start)\n",[164,309,311],{"class":166,"line":310},25,[164,312,313],{}," )\n",[164,315,317],{"class":166,"line":316},26,[164,318,319],{}," end = draw(end_strategy)\n",[164,321,323],{"class":166,"line":322},27,[164,324,277],{},[164,326,328],{"class":166,"line":327},28,[164,329,330],{}," # Explicit assume() only for edge-case safety, not primary routing\n",[164,332,334],{"class":166,"line":333},29,[164,335,336],{}," assume(end >= start)\n",[164,338,340],{"class":166,"line":339},30,[164,341,277],{},[164,343,345],{"class":166,"line":344},31,[164,346,347],{}," return {\n",[164,349,351],{"class":166,"line":350},32,[164,352,353],{}," \"start_date\": start,\n",[164,355,357],{"class":166,"line":356},33,[164,358,359],{}," \"end_date\": end,\n",[164,361,363],{"class":166,"line":362},34,[164,364,365],{}," \"duration_days\": (end - start).days\n",[164,367,369],{"class":166,"line":368},35,[164,370,371],{}," }\n",[164,373,375],{"class":166,"line":374},36,[164,376,195],{"emptyLinePlaceholder":194},[164,378,380],{"class":166,"line":379},37,[164,381,382],{},"@given(time_range=valid_time_ranges())\n",[164,384,386],{"class":166,"line":385},38,[164,387,388],{},"@settings(\n",[164,390,392],{"class":166,"line":391},39,[164,393,394],{}," max_examples=200,\n",[164,396,398],{"class":166,"line":397},40,[164,399,400],{}," phases=[Phase.generate, Phase.shrink],\n",[164,402,404],{"class":166,"line":403},41,[164,405,406],{}," verbosity=Verbosity.normal,\n",[164,408,410],{"class":166,"line":409},42,[164,411,412],{}," database=None # Isolate for deterministic runs\n",[164,414,416],{"class":166,"line":415},43,[164,417,418],{},")\n",[164,420,422],{"class":166,"line":421},44,[164,423,424],{},"def test_time_range_invariants(time_range: dict[str, date | int]) -> None:\n",[164,426,428],{"class":166,"line":427},45,[164,429,430],{}," assert time_range[\"start_date\"] \u003C= time_range[\"end_date\"]\n",[164,432,434],{"class":166,"line":433},46,[164,435,436],{}," assert time_range[\"duration_days\"] >= 0\n",[14,438,91,439,441,442,445,446,448,449,451,452,454],{},[26,440,101],{}," lifecycle isolates side effects. Each call to a composite function receives a fresh ",[26,443,444],{},"Data"," object, ensuring test isolation. When designing conditional routing, prefer ",[26,447,77],{}," over ",[26,450,152],{}," whenever possible. ",[26,453,152],{}," triggers Hypothesis’s rejection sampler, which increases generation latency and complicates shrinking. By pre-filtering branches, you guarantee that every generated example is structurally valid, allowing the shrinking engine to focus on semantic failures rather than constraint violations.",[18,456,458],{"id":457},"type-registration-and-automatic-resolution","Type Registration and Automatic Resolution",[14,460,461,462,464,465,468,469,472],{},"Hypothesis’s type resolution engine bridges static typing and runtime generation. ",[26,463,123],{}," automatically infers strategies from PEP 484 annotations, but it requires explicit registration for custom classes, Pydantic models, or ",[26,466,467],{},"attrs"," dataclasses. ",[26,470,471],{},"st.register_type_strategy(type_, strategy)"," binds a strategy to a type globally, enabling automatic resolution across your test suite.",[14,474,475,476,479,480,484],{},"Registration order matters. Hypothesis resolves types by walking the Method Resolution Order (MRO) and checking registered strategies before falling back to built-in inference. When registering strategies for complex type hierarchies, avoid namespace pollution by scoping registrations to test modules or using ",[26,477,478],{},"pytest"," fixtures that yield temporary registrations. For complex type hierarchies and automated strategy discovery, consult ",[80,481,483],{"href":482},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002F","Advanced Property-Based Testing",".",[155,486,488],{"className":157,"code":487,"language":159,"meta":160,"style":160},"from dataclasses import dataclass, field\nfrom typing import Optional, Literal\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings, Phase\n\n@dataclass\nclass UserConfig:\n username: str\n tier: Literal[\"free\", \"pro\", \"enterprise\"]\n max_requests: Optional[int] = None\n metadata: dict[str, str] = field(default_factory=dict)\n\n# Local registration strategy using context manager pattern\ndef register_user_config_strategy() -> st.SearchStrategy[UserConfig]:\n return st.builds(\n UserConfig,\n username=st.text(min_size=3, max_size=20).filter(lambda s: s.isalnum()),\n tier=st.sampled_from([\"free\", \"pro\", \"enterprise\"]),\n max_requests=st.one_of(st.none(), st.integers(min_value=100, max_value=10000)),\n metadata=st.dictionaries(\n keys=st.text(min_size=1, max_size=15),\n values=st.text(max_size=50)\n )\n )\n\n# Register globally for automatic st.from_type() resolution\nst.register_type_strategy(UserConfig, register_user_config_strategy())\n\n@given(config=st.from_type(UserConfig))\n@settings(max_examples=100, phases=[Phase.generate, Phase.shrink])\ndef test_user_config_type_resolution(config: UserConfig) -> None:\n assert config.username.isalnum()\n assert config.tier in {\"free\", \"pro\", \"enterprise\"}\n if config.max_requests is not None:\n assert config.max_requests >= 100\n",[26,489,490,495,500,504,509,513,518,523,528,533,538,543,547,552,557,562,567,572,577,582,587,592,597,601,605,609,614,619,623,628,633,638,643,648,653],{"__ignoreMap":160},[164,491,492],{"class":166,"line":167},[164,493,494],{},"from dataclasses import dataclass, field\n",[164,496,497],{"class":166,"line":173},[164,498,499],{},"from typing import Optional, Literal\n",[164,501,502],{"class":166,"line":179},[164,503,182],{},[164,505,506],{"class":166,"line":185},[164,507,508],{},"from hypothesis import given, settings, Phase\n",[164,510,511],{"class":166,"line":191},[164,512,195],{"emptyLinePlaceholder":194},[164,514,515],{"class":166,"line":198},[164,516,517],{},"@dataclass\n",[164,519,520],{"class":166,"line":204},[164,521,522],{},"class UserConfig:\n",[164,524,525],{"class":166,"line":210},[164,526,527],{}," username: str\n",[164,529,530],{"class":166,"line":216},[164,531,532],{}," tier: Literal[\"free\", \"pro\", \"enterprise\"]\n",[164,534,535],{"class":166,"line":222},[164,536,537],{}," max_requests: Optional[int] = None\n",[164,539,540],{"class":166,"line":227},[164,541,542],{}," metadata: dict[str, str] = field(default_factory=dict)\n",[164,544,545],{"class":166,"line":233},[164,546,195],{"emptyLinePlaceholder":194},[164,548,549],{"class":166,"line":239},[164,550,551],{},"# Local registration strategy using context manager pattern\n",[164,553,554],{"class":166,"line":245},[164,555,556],{},"def register_user_config_strategy() -> st.SearchStrategy[UserConfig]:\n",[164,558,559],{"class":166,"line":251},[164,560,561],{}," return st.builds(\n",[164,563,564],{"class":166,"line":257},[164,565,566],{}," UserConfig,\n",[164,568,569],{"class":166,"line":262},[164,570,571],{}," username=st.text(min_size=3, max_size=20).filter(lambda s: s.isalnum()),\n",[164,573,574],{"class":166,"line":268},[164,575,576],{}," tier=st.sampled_from([\"free\", \"pro\", \"enterprise\"]),\n",[164,578,579],{"class":166,"line":274},[164,580,581],{}," max_requests=st.one_of(st.none(), st.integers(min_value=100, max_value=10000)),\n",[164,583,584],{"class":166,"line":280},[164,585,586],{}," metadata=st.dictionaries(\n",[164,588,589],{"class":166,"line":286},[164,590,591],{}," keys=st.text(min_size=1, max_size=15),\n",[164,593,594],{"class":166,"line":292},[164,595,596],{}," values=st.text(max_size=50)\n",[164,598,599],{"class":166,"line":298},[164,600,313],{},[164,602,603],{"class":166,"line":304},[164,604,313],{},[164,606,607],{"class":166,"line":310},[164,608,195],{"emptyLinePlaceholder":194},[164,610,611],{"class":166,"line":316},[164,612,613],{},"# Register globally for automatic st.from_type() resolution\n",[164,615,616],{"class":166,"line":322},[164,617,618],{},"st.register_type_strategy(UserConfig, register_user_config_strategy())\n",[164,620,621],{"class":166,"line":327},[164,622,195],{"emptyLinePlaceholder":194},[164,624,625],{"class":166,"line":333},[164,626,627],{},"@given(config=st.from_type(UserConfig))\n",[164,629,630],{"class":166,"line":339},[164,631,632],{},"@settings(max_examples=100, phases=[Phase.generate, Phase.shrink])\n",[164,634,635],{"class":166,"line":344},[164,636,637],{},"def test_user_config_type_resolution(config: UserConfig) -> None:\n",[164,639,640],{"class":166,"line":350},[164,641,642],{}," assert config.username.isalnum()\n",[164,644,645],{"class":166,"line":356},[164,646,647],{}," assert config.tier in {\"free\", \"pro\", \"enterprise\"}\n",[164,649,650],{"class":166,"line":362},[164,651,652],{}," if config.max_requests is not None:\n",[164,654,655],{"class":166,"line":368},[164,656,657],{}," assert config.max_requests >= 100\n",[14,659,660,661,664,665,668,669,672,673,676,677,672,679,681,682,685,686,688,689,29,692,695,696,698],{},"When resolving ",[26,662,663],{},"typing.Annotated"," or ",[26,666,667],{},"typing.Literal",", Hypothesis extracts constraints automatically. However, circular type resolution occurs when ",[26,670,671],{},"A"," references ",[26,674,675],{},"B"," and ",[26,678,675],{},[26,680,671],{}," without explicit termination. Mitigate this by using ",[26,683,684],{},"st.recursive()"," with explicit depth limits or by breaking circular dependencies with ",[26,687,73],{}," placeholders during generation. Integration with static type checkers (",[26,690,691],{},"mypy",[26,693,694],{},"pyright",") remains seamless because ",[26,697,123],{}," respects runtime type hints without modifying source signatures. Always prefer local registration in test modules to avoid polluting the global strategy cache in large monorepos.",[18,700,702],{"id":701},"debugging-shrinking-failures-and-minimal-repros","Debugging Shrinking Failures and Minimal Repros",[14,704,705,706,709],{},"Shrinking is Hypothesis’s most powerful diagnostic tool, but it fails when predicates are non-monotonic, involve external state, or trigger excessive rejection. ",[26,707,708],{},"UnsatisfiedAssumption"," errors indicate that the rejection sampler exhausted its attempt budget without finding a valid example. Shrinking timeouts occur when the minimization algorithm traverses an excessively large search space.",[14,711,712,713,716,717,720,721,723],{},"To isolate minimal reproducible examples, use ",[26,714,715],{},"hypothesis.find(strategy, predicate)",". This bypasses the ",[26,718,719],{},"@given"," runner and directly minimizes a single input that satisfies your condition. It leverages the same ",[26,722,105],{}," and shrinking algorithms but returns the smallest valid example immediately.",[155,725,727],{"className":157,"code":726,"language":159,"meta":160,"style":160},"import hypothesis.strategies as st\nfrom hypothesis import find, settings, Verbosity, Phase\nfrom datetime import date\n\ndef is_invalid_range(r: dict[str, date | int]) -> bool:\n # Simulate a complex business rule violation\n return r[\"duration_days\"] > 300 and r[\"start_date\"].month == 12\n\n# Targeted minimal repro extraction\nminimal_failure = find(\n st.dates(min_value=date(2020, 1, 1), max_value=date(2024, 12, 31)).map(\n lambda d: {\n \"start_date\": d,\n \"end_date\": d,\n \"duration_days\": 0\n }\n ),\n is_invalid_range,\n settings=settings(\n max_examples=1000,\n verbosity=Verbosity.verbose,\n phases=[Phase.generate, Phase.shrink],\n database=None\n )\n)\n\nprint(f\"Minimal failing input: {minimal_failure}\")\n",[26,728,729,733,738,743,747,752,757,762,766,771,776,781,786,791,796,801,805,810,815,820,825,830,834,839,843,847,851],{"__ignoreMap":160},[164,730,731],{"class":166,"line":167},[164,732,182],{},[164,734,735],{"class":166,"line":173},[164,736,737],{},"from hypothesis import find, settings, Verbosity, Phase\n",[164,739,740],{"class":166,"line":179},[164,741,742],{},"from datetime import date\n",[164,744,745],{"class":166,"line":185},[164,746,195],{"emptyLinePlaceholder":194},[164,748,749],{"class":166,"line":191},[164,750,751],{},"def is_invalid_range(r: dict[str, date | int]) -> bool:\n",[164,753,754],{"class":166,"line":198},[164,755,756],{}," # Simulate a complex business rule violation\n",[164,758,759],{"class":166,"line":204},[164,760,761],{}," return r[\"duration_days\"] > 300 and r[\"start_date\"].month == 12\n",[164,763,764],{"class":166,"line":210},[164,765,195],{"emptyLinePlaceholder":194},[164,767,768],{"class":166,"line":216},[164,769,770],{},"# Targeted minimal repro extraction\n",[164,772,773],{"class":166,"line":222},[164,774,775],{},"minimal_failure = find(\n",[164,777,778],{"class":166,"line":227},[164,779,780],{}," st.dates(min_value=date(2020, 1, 1), max_value=date(2024, 12, 31)).map(\n",[164,782,783],{"class":166,"line":233},[164,784,785],{}," lambda d: {\n",[164,787,788],{"class":166,"line":239},[164,789,790],{}," \"start_date\": d,\n",[164,792,793],{"class":166,"line":245},[164,794,795],{}," \"end_date\": d,\n",[164,797,798],{"class":166,"line":251},[164,799,800],{}," \"duration_days\": 0\n",[164,802,803],{"class":166,"line":257},[164,804,371],{},[164,806,807],{"class":166,"line":262},[164,808,809],{}," ),\n",[164,811,812],{"class":166,"line":268},[164,813,814],{}," is_invalid_range,\n",[164,816,817],{"class":166,"line":274},[164,818,819],{}," settings=settings(\n",[164,821,822],{"class":166,"line":280},[164,823,824],{}," max_examples=1000,\n",[164,826,827],{"class":166,"line":286},[164,828,829],{}," verbosity=Verbosity.verbose,\n",[164,831,832],{"class":166,"line":292},[164,833,400],{},[164,835,836],{"class":166,"line":298},[164,837,838],{}," database=None\n",[164,840,841],{"class":166,"line":304},[164,842,313],{},[164,844,845],{"class":166,"line":310},[164,846,418],{},[164,848,849],{"class":166,"line":316},[164,850,195],{"emptyLinePlaceholder":194},[164,852,853],{"class":166,"line":322},[164,854,855],{},"print(f\"Minimal failing input: {minimal_failure}\")\n",[14,857,858,859,862,863,866,867,870],{},"Configure phase control to bypass shrinking when debugging known complex constraints: ",[26,860,861],{},"@settings(phases=[Phase.generate])",". This prevents CI timeouts during initial failure isolation. Use ",[26,864,865],{},"DirectoryExampleDatabase"," to cache minimal examples across runs, ensuring deterministic regression testing. Run ",[26,868,869],{},"pytest --hypothesis-show-statistics"," to inspect draw counts, rejection rates, and phase durations. When isolating flaky external dependencies, mock network calls, database connections, or time-dependent functions before strategy evaluation. Hypothesis assumes pure functions; impure predicates break shrinking guarantees.",[18,872,874],{"id":873},"profiling-generation-overhead-and-ci-optimization","Profiling Generation Overhead and CI Optimization",[14,876,877,878,29,881,884,885,888,889,891,892,894,895,897,898,900],{},"Generation overhead manifests as slow test execution, CI runner timeouts, or inconsistent flakiness. Profile strategy latency using ",[26,879,880],{},"cProfile",[26,882,883],{},"pytest-profiling",", or Hypothesis’s internal statistics. The primary bottleneck is almost always ",[26,886,887],{},"st.filter()"," rejection. Each filtered draw triggers a retry loop, consuming CPU cycles and fragmenting the ",[26,890,105],{},". Replace ",[26,893,887],{}," with ",[26,896,152],{}," placed immediately after cheap draws, or refactor to ",[26,899,54],{}," with conditional routing.",[155,902,904],{"className":157,"code":903,"language":159,"meta":160,"style":160},"import cProfile\nimport pstats\nimport io\nfrom hypothesis import given, settings, Verbosity, Phase\nimport hypothesis.strategies as st\n\n# Inefficient: High rejection rate via st.filter()\ndef inefficient_strategy() -> st.SearchStrategy[int]:\n return st.integers(min_value=1, max_value=10000).filter(lambda x: x % 17 == 0 and x > 5000)\n\n# Optimized: Pre-filtered domain via st.sampled_from\ndef efficient_strategy() -> st.SearchStrategy[int]:\n valid_values = [x for x in range(5001, 10001) if x % 17 == 0]\n return st.sampled_from(valid_values)\n\n@given(val=efficient_strategy())\n@settings(\n max_examples=500,\n deadline=None, # Disable per-example timeout for CI\n phases=[Phase.generate, Phase.shrink],\n database=None\n)\ndef test_optimized_generation(val: int) -> None:\n assert val % 17 == 0\n assert val > 5000\n\n# Profiling wrapper\ndef profile_strategy_overhead() -> None:\n pr = cProfile.Profile()\n pr.enable()\n test_optimized_generation()\n pr.disable()\n s = io.StringIO()\n ps = pstats.Stats(pr, stream=s).sort_stats(\"cumulative\")\n ps.print_stats(10)\n print(s.getvalue())\n",[26,905,906,911,916,921,925,929,933,938,943,948,952,957,962,967,972,976,981,985,990,995,999,1003,1007,1012,1017,1022,1026,1031,1036,1041,1046,1051,1056,1061,1066,1071],{"__ignoreMap":160},[164,907,908],{"class":166,"line":167},[164,909,910],{},"import cProfile\n",[164,912,913],{"class":166,"line":173},[164,914,915],{},"import pstats\n",[164,917,918],{"class":166,"line":179},[164,919,920],{},"import io\n",[164,922,923],{"class":166,"line":185},[164,924,188],{},[164,926,927],{"class":166,"line":191},[164,928,182],{},[164,930,931],{"class":166,"line":198},[164,932,195],{"emptyLinePlaceholder":194},[164,934,935],{"class":166,"line":204},[164,936,937],{},"# Inefficient: High rejection rate via st.filter()\n",[164,939,940],{"class":166,"line":210},[164,941,942],{},"def inefficient_strategy() -> st.SearchStrategy[int]:\n",[164,944,945],{"class":166,"line":216},[164,946,947],{}," return st.integers(min_value=1, max_value=10000).filter(lambda x: x % 17 == 0 and x > 5000)\n",[164,949,950],{"class":166,"line":222},[164,951,195],{"emptyLinePlaceholder":194},[164,953,954],{"class":166,"line":227},[164,955,956],{},"# Optimized: Pre-filtered domain via st.sampled_from\n",[164,958,959],{"class":166,"line":233},[164,960,961],{},"def efficient_strategy() -> st.SearchStrategy[int]:\n",[164,963,964],{"class":166,"line":239},[164,965,966],{}," valid_values = [x for x in range(5001, 10001) if x % 17 == 0]\n",[164,968,969],{"class":166,"line":245},[164,970,971],{}," return st.sampled_from(valid_values)\n",[164,973,974],{"class":166,"line":251},[164,975,195],{"emptyLinePlaceholder":194},[164,977,978],{"class":166,"line":257},[164,979,980],{},"@given(val=efficient_strategy())\n",[164,982,983],{"class":166,"line":262},[164,984,388],{},[164,986,987],{"class":166,"line":268},[164,988,989],{}," max_examples=500,\n",[164,991,992],{"class":166,"line":274},[164,993,994],{}," deadline=None, # Disable per-example timeout for CI\n",[164,996,997],{"class":166,"line":280},[164,998,400],{},[164,1000,1001],{"class":166,"line":286},[164,1002,838],{},[164,1004,1005],{"class":166,"line":292},[164,1006,418],{},[164,1008,1009],{"class":166,"line":298},[164,1010,1011],{},"def test_optimized_generation(val: int) -> None:\n",[164,1013,1014],{"class":166,"line":304},[164,1015,1016],{}," assert val % 17 == 0\n",[164,1018,1019],{"class":166,"line":310},[164,1020,1021],{}," assert val > 5000\n",[164,1023,1024],{"class":166,"line":316},[164,1025,195],{"emptyLinePlaceholder":194},[164,1027,1028],{"class":166,"line":322},[164,1029,1030],{},"# Profiling wrapper\n",[164,1032,1033],{"class":166,"line":327},[164,1034,1035],{},"def profile_strategy_overhead() -> None:\n",[164,1037,1038],{"class":166,"line":333},[164,1039,1040],{}," pr = cProfile.Profile()\n",[164,1042,1043],{"class":166,"line":339},[164,1044,1045],{}," pr.enable()\n",[164,1047,1048],{"class":166,"line":344},[164,1049,1050],{}," test_optimized_generation()\n",[164,1052,1053],{"class":166,"line":350},[164,1054,1055],{}," pr.disable()\n",[164,1057,1058],{"class":166,"line":356},[164,1059,1060],{}," s = io.StringIO()\n",[164,1062,1063],{"class":166,"line":362},[164,1064,1065],{}," ps = pstats.Stats(pr, stream=s).sort_stats(\"cumulative\")\n",[164,1067,1068],{"class":166,"line":368},[164,1069,1070],{}," ps.print_stats(10)\n",[164,1072,1073],{"class":166,"line":374},[164,1074,1075],{}," print(s.getvalue())\n",[14,1077,1078,1079,1082,1083,1086,1087,1089,1090,1092,1093,1096,1097,1100,1101,1104,1105,1108],{},"Implement caching for expensive draws using ",[26,1080,1081],{},"functools.lru_cache"," or Hypothesis’s ",[26,1084,1085],{},"st.shared()"," to reuse generated objects across related fields. Batch generation by yielding multiple values in a single ",[26,1088,54],{}," call rather than invoking separate ",[26,1091,719],{}," tests. Pin deterministic seeds in CI via ",[26,1094,1095],{},"@seed(12345)"," or the ",[26,1098,1099],{},"HYPOTHESIS_PROFILE"," environment variable to guarantee reproducible runs. Tune ",[26,1102,1103],{},"max_examples"," based on strategy complexity: 100 for complex nested structures, 500+ for simple primitives. Always set ",[26,1106,1107],{},"deadline=None"," in CI when testing I\u002FO-bound or computationally heavy strategies.",[18,1110,1112],{"id":1111},"edge-case-resolution-and-common-pitfalls","Edge-Case Resolution and Common Pitfalls",[14,1114,1115],{},"Custom strategies introduce subtle failure modes that bypass standard unit test coverage. The following diagnostic table addresses the most frequent production issues:",[1117,1118,1119,1135],"table",{},[1120,1121,1122],"thead",{},[1123,1124,1125,1129,1132],"tr",{},[1126,1127,1128],"th",{},"Issue",[1126,1130,1131],{},"Diagnosis",[1126,1133,1134],{},"Resolution",[1136,1137,1138,1167,1204,1235,1254],"tbody",{},[1123,1139,1140,1149,1155],{},[1141,1142,1143],"td",{},[1144,1145,1146,1147],"strong",{},"Excessive Rejection from ",[26,1148,887],{},[1141,1150,1151,1152,1154],{},"Shrinking stalls; ",[26,1153,708],{}," dominates output",[1141,1156,1157,1158,1160,1161,1164,1165,484],{},"Replace with conditional ",[26,1159,54],{}," routing or pre-filtered ",[26,1162,1163],{},"st.one_of",". Enforce \u003C15% rejection rate via ",[26,1166,869],{},[1123,1168,1169,1181,1187],{},[1141,1170,1171],{},[1144,1172,1173,1174,1177,1178],{},"Unhashable Types in ",[26,1175,1176],{},"st.dictionaries","\u002F",[26,1179,1180],{},"st.sets",[1141,1182,1183,1186],{},[26,1184,1185],{},"TypeError: unhashable type"," during strategy evaluation",[1141,1188,1189,1190,1177,1193,1196,1197,1199,1200,1203],{},"Convert mutable collections to ",[26,1191,1192],{},"tuple",[26,1194,1195],{},"frozenset"," before hashing. Use ",[26,1198,123],{}," with explicit ",[26,1201,1202],{},"Hashable"," constraints.",[1123,1205,1206,1211,1219],{},[1141,1207,1208],{},[1144,1209,1210],{},"Circular References in Recursive Strategies",[1141,1212,1213,1216,1217],{},[26,1214,1215],{},"RecursionError"," or infinite generation during ",[26,1218,101],{},[1141,1220,1221,1222,894,1224,1177,1227,1230,1231,664,1233,484],{},"Implement explicit depth counters. Use ",[26,1223,684],{},[26,1225,1226],{},"max_leaves",[26,1228,1229],{},"max_depth",". Terminate branches via ",[26,1232,73],{},[26,1234,69],{},[1123,1236,1237,1242,1245],{},[1141,1238,1239],{},[1144,1240,1241],{},"Mutable State Leakage Across Test Runs",[1141,1243,1244],{},"Flaky failures dependent on execution order or shared fixtures",[1141,1246,1247,1250,1251,484],{},[26,1248,1249],{},"copy.deepcopy()"," generated objects before mutation. Isolate databases via ",[26,1252,1253],{},"@settings(database=DirectoryExampleDatabase(...))",[1123,1255,1256,1261,1267],{},[1141,1257,1258],{},[1144,1259,1260],{},"CI Timeout Due to Shrinking Overhead",[1141,1262,1263,1264],{},"Tests exceed deadline or CI runner timeout during ",[26,1265,1266],{},"Phase.shrink",[1141,1268,1269,1270,1273,1274,1277],{},"Tune ",[26,1271,1272],{},"@settings(deadline=None, max_examples=100)",". Disable shrinking for known edge cases via ",[26,1275,1276],{},"phases=[Phase.generate]",". Cache minimal examples.",[155,1279,1281],{"className":157,"code":1280,"language":159,"meta":160,"style":160},"from typing import Any\nimport copy\nimport hypothesis.strategies as st\nfrom hypothesis import given, settings, Phase, Verbosity\n\n@st.composite\ndef safe_recursive_json(draw: st.DrawFn, max_depth: int = 3) -> Any:\n \"\"\"\n Generates nested JSON-like structures with explicit depth termination.\n Prevents infinite recursion and memory exhaustion in CI.\n \"\"\"\n if max_depth \u003C= 0:\n return draw(st.one_of(\n st.integers(min_value=-100, max_value=100),\n st.text(max_size=20),\n st.booleans(),\n st.none()\n ))\n \n # Recursive step with depth tracking\n return draw(st.one_of(\n st.lists(\n safe_recursive_json(max_depth=max_depth - 1),\n max_size=5\n ),\n st.dictionaries(\n keys=st.text(min_size=1, max_size=10),\n values=safe_recursive_json(max_depth=max_depth - 1),\n max_size=5\n )\n ))\n\n@given(data=safe_recursive_json())\n@settings(\n max_examples=150,\n phases=[Phase.generate, Phase.shrink],\n verbosity=Verbosity.normal,\n database=None\n)\ndef test_recursive_json_serialization(data: Any) -> None:\n # Isolate mutation via deepcopy\n snapshot = copy.deepcopy(data)\n # Validate invariants on copy\n assert isinstance(snapshot, (dict, list, int, str, bool, type(None)))\n",[26,1282,1283,1288,1293,1297,1302,1306,1310,1315,1319,1324,1329,1333,1338,1343,1348,1353,1358,1363,1368,1372,1377,1381,1386,1391,1396,1400,1405,1410,1415,1419,1423,1427,1431,1436,1440,1445,1449,1453,1457,1461,1466,1471,1476,1481],{"__ignoreMap":160},[164,1284,1285],{"class":166,"line":167},[164,1286,1287],{},"from typing import Any\n",[164,1289,1290],{"class":166,"line":173},[164,1291,1292],{},"import copy\n",[164,1294,1295],{"class":166,"line":179},[164,1296,182],{},[164,1298,1299],{"class":166,"line":185},[164,1300,1301],{},"from hypothesis import given, settings, Phase, Verbosity\n",[164,1303,1304],{"class":166,"line":191},[164,1305,195],{"emptyLinePlaceholder":194},[164,1307,1308],{"class":166,"line":198},[164,1309,230],{},[164,1311,1312],{"class":166,"line":204},[164,1313,1314],{},"def safe_recursive_json(draw: st.DrawFn, max_depth: int = 3) -> Any:\n",[164,1316,1317],{"class":166,"line":210},[164,1318,242],{},[164,1320,1321],{"class":166,"line":216},[164,1322,1323],{}," Generates nested JSON-like structures with explicit depth termination.\n",[164,1325,1326],{"class":166,"line":222},[164,1327,1328],{}," Prevents infinite recursion and memory exhaustion in CI.\n",[164,1330,1331],{"class":166,"line":227},[164,1332,242],{},[164,1334,1335],{"class":166,"line":233},[164,1336,1337],{}," if max_depth \u003C= 0:\n",[164,1339,1340],{"class":166,"line":239},[164,1341,1342],{}," return draw(st.one_of(\n",[164,1344,1345],{"class":166,"line":245},[164,1346,1347],{}," st.integers(min_value=-100, max_value=100),\n",[164,1349,1350],{"class":166,"line":251},[164,1351,1352],{}," st.text(max_size=20),\n",[164,1354,1355],{"class":166,"line":257},[164,1356,1357],{}," st.booleans(),\n",[164,1359,1360],{"class":166,"line":262},[164,1361,1362],{}," st.none()\n",[164,1364,1365],{"class":166,"line":268},[164,1366,1367],{}," ))\n",[164,1369,1370],{"class":166,"line":274},[164,1371,277],{},[164,1373,1374],{"class":166,"line":280},[164,1375,1376],{}," # Recursive step with depth tracking\n",[164,1378,1379],{"class":166,"line":286},[164,1380,1342],{},[164,1382,1383],{"class":166,"line":292},[164,1384,1385],{}," st.lists(\n",[164,1387,1388],{"class":166,"line":298},[164,1389,1390],{}," safe_recursive_json(max_depth=max_depth - 1),\n",[164,1392,1393],{"class":166,"line":304},[164,1394,1395],{}," max_size=5\n",[164,1397,1398],{"class":166,"line":310},[164,1399,809],{},[164,1401,1402],{"class":166,"line":316},[164,1403,1404],{}," st.dictionaries(\n",[164,1406,1407],{"class":166,"line":322},[164,1408,1409],{}," keys=st.text(min_size=1, max_size=10),\n",[164,1411,1412],{"class":166,"line":327},[164,1413,1414],{}," values=safe_recursive_json(max_depth=max_depth - 1),\n",[164,1416,1417],{"class":166,"line":333},[164,1418,1395],{},[164,1420,1421],{"class":166,"line":339},[164,1422,313],{},[164,1424,1425],{"class":166,"line":344},[164,1426,1367],{},[164,1428,1429],{"class":166,"line":350},[164,1430,195],{"emptyLinePlaceholder":194},[164,1432,1433],{"class":166,"line":356},[164,1434,1435],{},"@given(data=safe_recursive_json())\n",[164,1437,1438],{"class":166,"line":362},[164,1439,388],{},[164,1441,1442],{"class":166,"line":368},[164,1443,1444],{}," max_examples=150,\n",[164,1446,1447],{"class":166,"line":374},[164,1448,400],{},[164,1450,1451],{"class":166,"line":379},[164,1452,406],{},[164,1454,1455],{"class":166,"line":385},[164,1456,838],{},[164,1458,1459],{"class":166,"line":391},[164,1460,418],{},[164,1462,1463],{"class":166,"line":397},[164,1464,1465],{},"def test_recursive_json_serialization(data: Any) -> None:\n",[164,1467,1468],{"class":166,"line":403},[164,1469,1470],{}," # Isolate mutation via deepcopy\n",[164,1472,1473],{"class":166,"line":409},[164,1474,1475],{}," snapshot = copy.deepcopy(data)\n",[164,1477,1478],{"class":166,"line":415},[164,1479,1480],{}," # Validate invariants on copy\n",[164,1482,1483],{"class":166,"line":421},[164,1484,1485],{}," assert isinstance(snapshot, (dict, list, int, str, bool, type(None)))\n",[14,1487,1488,1489,1492,1493,1495],{},"Enforce ",[26,1490,1491],{},"__init__"," purity in generated objects. If constructors perform I\u002FO, network calls, or global state mutation, wrap them in ",[26,1494,54],{}," and mock dependencies before instantiation. Always validate constraints pre-generation rather than post-generation to maintain shrinking determinism.",[18,1497,1499],{"id":1498},"conclusion-and-production-readiness-checklist","Conclusion and Production Readiness Checklist",[14,1501,1502,1503,1505,1506,1508,1509,1512],{},"Generating custom strategies with hypothesis.strategies transforms property-based testing from a theoretical exercise into a production-grade validation engine. By composing strategies with ",[26,1504,54],{},", leveraging ",[26,1507,58],{}," for declarative mapping, and registering types for automatic resolution, you eliminate rejection bottlenecks and guarantee invariant compliance. Debugging workflows centered on ",[26,1510,1511],{},"hypothesis.find()",", phase control, and database isolation enable rapid diagnosis of shrinking failures. Profiling generation overhead and optimizing rejection rates ensure CI pipelines remain fast and deterministic.",[14,1514,1515],{},"Before merging custom strategies into main, validate against this production readiness checklist:",[1517,1518,1521,1534,1544,1556],"ul",{"className":1519},[1520],"contains-task-list",[1522,1523,1526,1530,1531,1533],"li",{"className":1524},[1525],"task-list-item",[1527,1528],"input",{"disabled":194,"type":1529},"checkbox"," Rejection rate consistently below 15% (verify via ",[26,1532,869],{},")",[1522,1535,1537,1539,1540,1543],{"className":1536},[1525],[1527,1538],{"disabled":194,"type":1529}," Deterministic CI seeds configured via ",[26,1541,1542],{},"@seed()"," or environment variables",[1522,1545,1547,1549,1550,664,1552,1555],{"className":1546},[1525],[1527,1548],{"disabled":194,"type":1529}," Test databases isolated using ",[26,1551,865],{},[26,1553,1554],{},"None"," for stateless runs",[1522,1557,1559,1561,1562,29,1564,1566],{"className":1558},[1525],[1527,1560],{"disabled":194,"type":1529}," Shrinking timeout guards applied (",[26,1563,1107],{},[26,1565,1276],{}," for known",[1568,1569,1570],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":160,"searchDepth":173,"depth":173,"links":1572},[1573,1574,1575,1576,1577,1578,1579],{"id":20,"depth":173,"text":21},{"id":87,"depth":173,"text":88},{"id":457,"depth":173,"text":458},{"id":701,"depth":173,"text":702},{"id":873,"depth":173,"text":874},{"id":1111,"depth":173,"text":1112},{"id":1498,"depth":173,"text":1499},"md",{},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fgenerating-custom-strategies-with-hypothesisstrategies",{"title":5,"description":16},"property-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002Fgenerating-custom-strategies-with-hypothesisstrategies\u002Findex","Vt0LX9nYS65ES2sdwPfdd9C64DLnzLHbJf0pnYRlvzQ",1778004578528]