invar-tools 1.7.1__py3-none-any.whl → 1.10.0__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.
- invar/__init__.py +8 -0
- invar/core/language.py +88 -0
- invar/core/models.py +106 -0
- invar/core/patterns/detector.py +6 -1
- invar/core/patterns/p0_exhaustive.py +15 -3
- invar/core/patterns/p0_literal.py +15 -3
- invar/core/patterns/p0_newtype.py +15 -3
- invar/core/patterns/p0_nonempty.py +15 -3
- invar/core/patterns/p0_validation.py +15 -3
- invar/core/patterns/registry.py +5 -1
- invar/core/patterns/types.py +5 -1
- invar/core/property_gen.py +4 -0
- invar/core/rules.py +84 -18
- invar/core/sync_helpers.py +27 -1
- invar/core/template_helpers.py +32 -0
- invar/core/ts_parsers.py +286 -0
- invar/core/ts_sig_parser.py +307 -0
- invar/node_tools/MANIFEST +7 -0
- invar/node_tools/__init__.py +51 -0
- invar/node_tools/fc-runner/cli.js +77 -0
- invar/node_tools/quick-check/cli.js +28 -0
- invar/node_tools/ts-analyzer/cli.js +480 -0
- invar/shell/claude_hooks.py +35 -12
- invar/shell/commands/guard.py +36 -1
- invar/shell/commands/init.py +133 -7
- invar/shell/commands/perception.py +157 -33
- invar/shell/commands/skill.py +187 -0
- invar/shell/commands/template_sync.py +65 -13
- invar/shell/commands/uninstall.py +77 -12
- invar/shell/commands/update.py +6 -14
- invar/shell/contract_coverage.py +1 -0
- invar/shell/fs.py +66 -13
- invar/shell/pi_hooks.py +213 -0
- invar/shell/prove/guard_ts.py +899 -0
- invar/shell/skill_manager.py +353 -0
- invar/shell/template_engine.py +28 -4
- invar/shell/templates.py +4 -4
- invar/templates/claude-md/python/critical-rules.md +33 -0
- invar/templates/claude-md/python/quick-reference.md +24 -0
- invar/templates/claude-md/typescript/critical-rules.md +40 -0
- invar/templates/claude-md/typescript/quick-reference.md +24 -0
- invar/templates/claude-md/universal/check-in.md +25 -0
- invar/templates/claude-md/universal/skills.md +73 -0
- invar/templates/claude-md/universal/workflow.md +55 -0
- invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
- invar/templates/config/AGENT.md.jinja +256 -0
- invar/templates/config/CLAUDE.md.jinja +16 -209
- invar/templates/config/context.md.jinja +19 -0
- invar/templates/examples/{README.md → python/README.md} +2 -0
- invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
- invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
- invar/templates/examples/python/core_shell.py +227 -0
- invar/templates/examples/python/functional.py +613 -0
- invar/templates/examples/typescript/README.md +31 -0
- invar/templates/examples/typescript/contracts.ts +163 -0
- invar/templates/examples/typescript/core_shell.ts +374 -0
- invar/templates/examples/typescript/functional.ts +601 -0
- invar/templates/examples/typescript/workflow.md +95 -0
- invar/templates/hooks/PostToolUse.sh.jinja +10 -1
- invar/templates/hooks/PreToolUse.sh.jinja +38 -0
- invar/templates/hooks/Stop.sh.jinja +1 -1
- invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
- invar/templates/hooks/pi/invar.ts.jinja +82 -0
- invar/templates/manifest.toml +8 -6
- invar/templates/onboard/assessment.md.jinja +214 -0
- invar/templates/onboard/patterns/python.md +347 -0
- invar/templates/onboard/patterns/typescript.md +452 -0
- invar/templates/onboard/roadmap.md.jinja +168 -0
- invar/templates/protocol/INVAR.md.jinja +51 -0
- invar/templates/protocol/python/architecture-examples.md +41 -0
- invar/templates/protocol/python/contracts-syntax.md +56 -0
- invar/templates/protocol/python/markers.md +44 -0
- invar/templates/protocol/python/tools.md +24 -0
- invar/templates/protocol/python/troubleshooting.md +38 -0
- invar/templates/protocol/typescript/architecture-examples.md +52 -0
- invar/templates/protocol/typescript/contracts-syntax.md +73 -0
- invar/templates/protocol/typescript/markers.md +48 -0
- invar/templates/protocol/typescript/tools.md +65 -0
- invar/templates/protocol/typescript/troubleshooting.md +104 -0
- invar/templates/protocol/universal/architecture.md +36 -0
- invar/templates/protocol/universal/completion.md +14 -0
- invar/templates/protocol/universal/contracts-concept.md +37 -0
- invar/templates/protocol/universal/header.md +17 -0
- invar/templates/protocol/universal/session.md +17 -0
- invar/templates/protocol/universal/six-laws.md +10 -0
- invar/templates/protocol/universal/usbv.md +14 -0
- invar/templates/protocol/universal/visible-workflow.md +25 -0
- invar/templates/skills/develop/SKILL.md.jinja +98 -3
- invar/templates/skills/extensions/_registry.yaml +93 -0
- invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
- invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
- invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
- invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
- invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
- invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
- invar/templates/skills/extensions/security/SKILL.md +382 -0
- invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
- invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
- invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
- invar/templates/skills/investigate/SKILL.md.jinja +15 -0
- invar/templates/skills/propose/SKILL.md.jinja +33 -0
- invar/templates/skills/review/SKILL.md.jinja +346 -71
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/METADATA +326 -19
- invar_tools-1.10.0.dist-info/RECORD +173 -0
- invar/templates/examples/core_shell.py +0 -127
- invar/templates/protocol/INVAR.md +0 -310
- invar_tools-1.7.1.dist-info/RECORD +0 -112
- /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.7.1.dist-info → invar_tools-1.10.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
# ruff: noqa: ERA001
|
|
1
2
|
"""
|
|
2
3
|
Invar Contract Examples
|
|
3
4
|
|
|
4
5
|
Reference patterns for @pre/@post contracts and doctests.
|
|
5
6
|
Managed by Invar - do not edit directly.
|
|
6
7
|
"""
|
|
8
|
+
# @invar:allow partial_contract: Educational file showing multiple @pre pattern
|
|
7
9
|
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
from deal import post, pre
|
|
10
|
+
# invar_runtime supports both lambda and Contract objects
|
|
11
|
+
from invar_runtime import post, pre
|
|
11
12
|
|
|
12
13
|
# =============================================================================
|
|
13
14
|
# GOOD: Complete Contract
|
|
@@ -80,7 +81,7 @@ def normalize_keys(data: dict[str, int]) -> dict[str, int]:
|
|
|
80
81
|
# DON'T: Empty contract tells nothing
|
|
81
82
|
# @pre(lambda: True)
|
|
82
83
|
# @post(lambda result: True)
|
|
83
|
-
# def process(x): ...
|
|
84
|
+
# def process(x): ...
|
|
84
85
|
|
|
85
86
|
# DON'T: Missing edge cases in doctests
|
|
86
87
|
# def divide(a, b):
|
|
@@ -111,3 +112,79 @@ def range_size(start: int, end: int) -> int:
|
|
|
111
112
|
1
|
|
112
113
|
"""
|
|
113
114
|
return end - start
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# =============================================================================
|
|
118
|
+
# CRITICAL: Default Parameters in Contracts
|
|
119
|
+
# =============================================================================
|
|
120
|
+
# Lambda signatures MUST include ALL parameters, including defaults!
|
|
121
|
+
# This is a common mistake that causes silent contract failures.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# DON'T: Missing default parameter in lambda
|
|
125
|
+
# @pre(lambda x: x >= 0) # BAD: 'y' is missing!
|
|
126
|
+
# def calc_bad(x: int, y: int = 0) -> int:
|
|
127
|
+
# return x + y
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# DO: Include all parameters with their defaults
|
|
131
|
+
@pre(lambda x, y=0: x >= 0 and y >= 0)
|
|
132
|
+
@post(lambda result: result >= 0)
|
|
133
|
+
def calculate_with_default(x: int, y: int = 0) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Calculate sum with optional y.
|
|
136
|
+
|
|
137
|
+
The lambda MUST include y=0 to match the function signature.
|
|
138
|
+
|
|
139
|
+
>>> calculate_with_default(5)
|
|
140
|
+
5
|
|
141
|
+
>>> calculate_with_default(5, 3)
|
|
142
|
+
8
|
|
143
|
+
>>> calculate_with_default(0, 0) # Edge: both zero
|
|
144
|
+
0
|
|
145
|
+
"""
|
|
146
|
+
return x + y
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# =============================================================================
|
|
150
|
+
# CRITICAL: @post Cannot Access Parameters
|
|
151
|
+
# =============================================================================
|
|
152
|
+
# @post only receives the return value, not the original parameters!
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# DON'T: Reference parameters in @post
|
|
156
|
+
# @post(lambda result: result > x) # BAD: 'x' is not available!
|
|
157
|
+
# def double_bad(x: int) -> int:
|
|
158
|
+
# return x * 2
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# DO: @post only validates the result itself
|
|
162
|
+
@pre(lambda x: x >= 0)
|
|
163
|
+
@post(lambda result: result >= 0) # GOOD: only uses 'result'
|
|
164
|
+
def double_positive(x: int) -> int:
|
|
165
|
+
"""
|
|
166
|
+
Double a positive number.
|
|
167
|
+
|
|
168
|
+
@post can only validate result properties, not relationships to inputs.
|
|
169
|
+
|
|
170
|
+
>>> double_positive(5)
|
|
171
|
+
10
|
|
172
|
+
>>> double_positive(0) # Edge: zero
|
|
173
|
+
0
|
|
174
|
+
"""
|
|
175
|
+
return x * 2
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# =============================================================================
|
|
179
|
+
# Decorator Order with @pre/@post
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# When combining with other decorators, @pre/@post should be closest to function.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# DO: @pre/@post closest to function
|
|
185
|
+
# @other_decorator
|
|
186
|
+
# @pre(lambda x: x > 0)
|
|
187
|
+
# @post(lambda result: result > 0)
|
|
188
|
+
# def my_func(x: int) -> int: ...
|
|
189
|
+
|
|
190
|
+
# This ensures contracts run BEFORE other decorators modify behavior.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# ruff: noqa: ERA001
|
|
2
|
+
"""
|
|
3
|
+
Invar Core/Shell Separation Examples
|
|
4
|
+
|
|
5
|
+
Reference patterns for Core vs Shell architecture.
|
|
6
|
+
Managed by Invar - do not edit directly.
|
|
7
|
+
"""
|
|
8
|
+
# @invar:allow forbidden_import: Example file demonstrates Shell pattern with pathlib
|
|
9
|
+
# @invar:allow missing_contract: Shell functions intentionally without contracts to show pattern
|
|
10
|
+
# @invar:allow contract_quality_ratio: Educational file with Shell examples
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# invar_runtime supports both lambda and Contract objects
|
|
15
|
+
from invar_runtime import post, pre
|
|
16
|
+
from returns.result import Failure, Result, Success
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# CORE: Pure Logic (no I/O)
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Location: src/*/core/
|
|
22
|
+
# Requirements: @pre/@post, doctests, no I/O imports
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
27
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
28
|
+
@pre(lambda content: content is not None) # Accepts any string including empty
|
|
29
|
+
@post(lambda result: all(line.strip() == line and line for line in result)) # No whitespace, non-empty
|
|
30
|
+
def parse_lines(content: str) -> list[str]:
|
|
31
|
+
"""
|
|
32
|
+
Parse content into non-empty lines.
|
|
33
|
+
|
|
34
|
+
>>> parse_lines("a\\nb\\nc")
|
|
35
|
+
['a', 'b', 'c']
|
|
36
|
+
>>> parse_lines("")
|
|
37
|
+
[]
|
|
38
|
+
>>> parse_lines(" \\n ") # Edge: whitespace only
|
|
39
|
+
[]
|
|
40
|
+
"""
|
|
41
|
+
return [line.strip() for line in content.split("\n") if line.strip()]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
45
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
46
|
+
@pre(lambda items: all(isinstance(i, str) for i in items)) # All items must be strings
|
|
47
|
+
@post(lambda result: all(v > 0 for v in result.values())) # All counts are positive
|
|
48
|
+
def count_items(items: list[str]) -> dict[str, int]:
|
|
49
|
+
"""
|
|
50
|
+
Count occurrences of each item.
|
|
51
|
+
|
|
52
|
+
>>> sorted(count_items(['a', 'b', 'a']).items())
|
|
53
|
+
[('a', 2), ('b', 1)]
|
|
54
|
+
>>> count_items([])
|
|
55
|
+
{}
|
|
56
|
+
"""
|
|
57
|
+
counts: dict[str, int] = {}
|
|
58
|
+
for item in items:
|
|
59
|
+
counts[item] = counts.get(item, 0) + 1
|
|
60
|
+
return counts
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# =============================================================================
|
|
64
|
+
# SHELL: I/O Operations
|
|
65
|
+
# =============================================================================
|
|
66
|
+
# Location: src/*/shell/
|
|
67
|
+
# Requirements: Result[T, E] return type, calls Core for logic
|
|
68
|
+
# =============================================================================
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def read_file(path: Path) -> Result[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
Read file content.
|
|
74
|
+
|
|
75
|
+
Shell handles I/O, returns Result for error handling.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
return Success(path.read_text())
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
return Failure(f"File not found: {path}")
|
|
81
|
+
except PermissionError:
|
|
82
|
+
return Failure(f"Permission denied: {path}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def count_lines_in_file(path: Path) -> Result[dict[str, int], str]:
|
|
86
|
+
"""
|
|
87
|
+
Count lines in file - demonstrates Core/Shell integration.
|
|
88
|
+
|
|
89
|
+
Shell reads file → Core parses content → Shell returns result.
|
|
90
|
+
"""
|
|
91
|
+
# Shell: I/O operation
|
|
92
|
+
content_result = read_file(path)
|
|
93
|
+
|
|
94
|
+
if isinstance(content_result, Failure):
|
|
95
|
+
return content_result
|
|
96
|
+
|
|
97
|
+
content = content_result.unwrap()
|
|
98
|
+
|
|
99
|
+
# Core: Pure logic (no I/O)
|
|
100
|
+
lines = parse_lines(content)
|
|
101
|
+
counts = count_items(lines)
|
|
102
|
+
|
|
103
|
+
# Shell: Return result
|
|
104
|
+
return Success(counts)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# ANTI-PATTERNS
|
|
109
|
+
# =============================================================================
|
|
110
|
+
|
|
111
|
+
# DON'T: I/O in Core
|
|
112
|
+
# def parse_file(path: Path): # BAD: Path in Core
|
|
113
|
+
# content = path.read_text() # BAD: I/O in Core
|
|
114
|
+
# return parse_lines(content)
|
|
115
|
+
|
|
116
|
+
# DO: Core receives content, not paths
|
|
117
|
+
# def parse_content(content: str): # GOOD: receives data
|
|
118
|
+
# return parse_lines(content)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# DON'T: Missing Result in Shell
|
|
122
|
+
# def load_config(path: Path) -> dict: # BAD: no Result type
|
|
123
|
+
# return json.loads(path.read_text()) # Exceptions not handled
|
|
124
|
+
|
|
125
|
+
# DO: Return Result[T, E]
|
|
126
|
+
# def load_config(path: Path) -> Result[dict, str]: # GOOD
|
|
127
|
+
# try:
|
|
128
|
+
# return Success(json.loads(path.read_text()))
|
|
129
|
+
# except Exception as e:
|
|
130
|
+
# return Failure(str(e))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# FastAPI Integration Pattern
|
|
135
|
+
# =============================================================================
|
|
136
|
+
# Demonstrates how to use Result with FastAPI endpoints.
|
|
137
|
+
# Shell (API handler) → Core (business logic) → Shell (HTTP response)
|
|
138
|
+
|
|
139
|
+
# NOTE: This is pseudocode - requires FastAPI to be installed
|
|
140
|
+
# from fastapi import FastAPI, HTTPException
|
|
141
|
+
# from pydantic import BaseModel
|
|
142
|
+
|
|
143
|
+
# class UserResponse(BaseModel):
|
|
144
|
+
# id: str
|
|
145
|
+
# name: str
|
|
146
|
+
|
|
147
|
+
# app = FastAPI()
|
|
148
|
+
|
|
149
|
+
# CORE: Pure business logic
|
|
150
|
+
# @pre(lambda user_id: len(user_id) > 0)
|
|
151
|
+
# @post(lambda result: result.get("name") is not None)
|
|
152
|
+
# def get_user_data(user_id: str) -> dict:
|
|
153
|
+
# """Core function - pure logic, receives string ID."""
|
|
154
|
+
# # Business logic here (no HTTP, no database I/O)
|
|
155
|
+
# return {"id": user_id, "name": "Demo User"}
|
|
156
|
+
|
|
157
|
+
# SHELL: I/O layer - database
|
|
158
|
+
# def fetch_user_from_db(user_id: str) -> Result[dict, str]:
|
|
159
|
+
# """Shell function - database I/O, returns Result."""
|
|
160
|
+
# try:
|
|
161
|
+
# # In real code: query database
|
|
162
|
+
# if user_id == "not-found":
|
|
163
|
+
# return Failure("User not found")
|
|
164
|
+
# return Success({"id": user_id, "name": "DB User"})
|
|
165
|
+
# except Exception as e:
|
|
166
|
+
# return Failure(f"Database error: {e}")
|
|
167
|
+
|
|
168
|
+
# SHELL: I/O layer - HTTP endpoint
|
|
169
|
+
# @app.get("/users/{user_id}", response_model=UserResponse)
|
|
170
|
+
# def get_user_endpoint(user_id: str):
|
|
171
|
+
# """
|
|
172
|
+
# FastAPI endpoint - Shell layer.
|
|
173
|
+
#
|
|
174
|
+
# Pattern: Shell → Core → Shell
|
|
175
|
+
# 1. Shell receives HTTP request
|
|
176
|
+
# 2. Core processes business logic
|
|
177
|
+
# 3. Shell converts Result to HTTP response
|
|
178
|
+
# """
|
|
179
|
+
# result = fetch_user_from_db(user_id)
|
|
180
|
+
#
|
|
181
|
+
# # Convert Result to HTTP response
|
|
182
|
+
# match result:
|
|
183
|
+
# case Success(user):
|
|
184
|
+
# return UserResponse(**user)
|
|
185
|
+
# case Failure(error):
|
|
186
|
+
# if "not found" in error.lower():
|
|
187
|
+
# raise HTTPException(status_code=404, detail=error)
|
|
188
|
+
# raise HTTPException(status_code=500, detail=error)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# =============================================================================
|
|
192
|
+
# Result → HTTP Response Mapping
|
|
193
|
+
# =============================================================================
|
|
194
|
+
# Common pattern for converting Result errors to HTTP status codes:
|
|
195
|
+
#
|
|
196
|
+
# | Error Type | HTTP Status | When to Use |
|
|
197
|
+
# |-----------------|-------------|--------------------------------|
|
|
198
|
+
# | NotFoundError | 404 | Resource doesn't exist |
|
|
199
|
+
# | ValidationError | 400 | Invalid input from client |
|
|
200
|
+
# | AuthError | 401/403 | Authentication/authorization |
|
|
201
|
+
# | ConflictError | 409 | Resource state conflict |
|
|
202
|
+
# | InternalError | 500 | Unexpected server error |
|
|
203
|
+
#
|
|
204
|
+
# Example error types:
|
|
205
|
+
# @dataclass(frozen=True)
|
|
206
|
+
# class NotFoundError:
|
|
207
|
+
# resource: str
|
|
208
|
+
# id: str
|
|
209
|
+
#
|
|
210
|
+
# @dataclass(frozen=True)
|
|
211
|
+
# class ValidationError:
|
|
212
|
+
# field: str
|
|
213
|
+
# message: str
|
|
214
|
+
#
|
|
215
|
+
# AppError = NotFoundError | ValidationError | str
|
|
216
|
+
#
|
|
217
|
+
# def result_to_response(result: Result[T, AppError]) -> T:
|
|
218
|
+
# """Convert Result to HTTP response or raise appropriate HTTPException."""
|
|
219
|
+
# match result:
|
|
220
|
+
# case Success(value):
|
|
221
|
+
# return value
|
|
222
|
+
# case Failure(NotFoundError(resource, id)):
|
|
223
|
+
# raise HTTPException(404, f"{resource} {id} not found")
|
|
224
|
+
# case Failure(ValidationError(field, message)):
|
|
225
|
+
# raise HTTPException(400, f"Invalid {field}: {message}")
|
|
226
|
+
# case Failure(error):
|
|
227
|
+
# raise HTTPException(500, str(error))
|