scriptgini 0.1.2__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.
- app/__init__.py +0 -0
- app/agents/__init__.py +0 -0
- app/agents/prompts.py +147 -0
- app/agents/script_gini_agent.py +342 -0
- app/config.py +59 -0
- app/database.py +23 -0
- app/llm/__init__.py +0 -0
- app/llm/provider.py +192 -0
- app/main.py +76 -0
- app/models/__init__.py +0 -0
- app/models/bulk_job.py +67 -0
- app/models/generated_script.py +39 -0
- app/models/project.py +46 -0
- app/models/script_run.py +32 -0
- app/models/test_case.py +34 -0
- app/routers/__init__.py +0 -0
- app/routers/analytics.py +73 -0
- app/routers/bulk_jobs.py +277 -0
- app/routers/demo.py +86 -0
- app/routers/projects.py +51 -0
- app/routers/scripts.py +549 -0
- app/routers/test_cases.py +64 -0
- app/schemas/__init__.py +0 -0
- app/schemas/analytics.py +27 -0
- app/schemas/bulk_job.py +48 -0
- app/schemas/generated_script.py +50 -0
- app/schemas/project.py +36 -0
- app/schemas/test_case.py +34 -0
- app/services/git_export.py +133 -0
- scriptgini-0.1.2.dist-info/METADATA +381 -0
- scriptgini-0.1.2.dist-info/RECORD +33 -0
- scriptgini-0.1.2.dist-info/WHEEL +5 -0
- scriptgini-0.1.2.dist-info/top_level.txt +1 -0
app/routers/bulk_jobs.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from app.database import get_db
|
|
9
|
+
from app.agents.script_gini_agent import run_agent
|
|
10
|
+
from app.models.bulk_job import BulkJob, BulkJobItem, BulkJobItemStatus, BulkJobKind, BulkJobStatus
|
|
11
|
+
from app.models.generated_script import GeneratedScript, ScriptStatus
|
|
12
|
+
from app.models.project import Project
|
|
13
|
+
from app.models.script_run import ScriptRunStatus
|
|
14
|
+
from app.models.test_case import TestCase
|
|
15
|
+
from app.schemas.bulk_job import BulkGenerateRequest, BulkJobResponse, BulkRunRequest
|
|
16
|
+
from app.routers.scripts import (
|
|
17
|
+
_create_script_run,
|
|
18
|
+
_get_project_or_404,
|
|
19
|
+
_run_python_script,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
router = APIRouter(prefix="/projects/{project_id}/scripts", tags=["Bulk Jobs"])
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_bulk_job_or_404(project_id: int, job_id: int, db: Session) -> BulkJob:
|
|
27
|
+
job = db.query(BulkJob).filter(BulkJob.id == job_id, BulkJob.project_id == project_id).first()
|
|
28
|
+
if not job:
|
|
29
|
+
raise HTTPException(status_code=404, detail="Bulk job not found")
|
|
30
|
+
return job
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _refresh_job_counts(job: BulkJob, db: Session) -> None:
|
|
34
|
+
items = db.query(BulkJobItem).filter(BulkJobItem.job_id == job.id).all()
|
|
35
|
+
job.total_items = len(items)
|
|
36
|
+
job.completed_items = sum(1 for i in items if i.status == BulkJobItemStatus.completed)
|
|
37
|
+
job.failed_items = sum(1 for i in items if i.status == BulkJobItemStatus.failed)
|
|
38
|
+
job.skipped_items = sum(1 for i in items if i.status == BulkJobItemStatus.skipped)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _serialize_bulk_job(job: BulkJob, db: Session) -> BulkJobResponse:
|
|
42
|
+
items = (
|
|
43
|
+
db.query(BulkJobItem)
|
|
44
|
+
.filter(BulkJobItem.job_id == job.id)
|
|
45
|
+
.order_by(BulkJobItem.created_at.asc(), BulkJobItem.id.asc())
|
|
46
|
+
.all()
|
|
47
|
+
)
|
|
48
|
+
payload = BulkJobResponse.model_validate(job)
|
|
49
|
+
payload.items = items
|
|
50
|
+
return payload
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _resolve_test_cases(project_id: int, ids: list[int] | None, db: Session) -> list[TestCase]:
|
|
54
|
+
query = db.query(TestCase).filter(TestCase.project_id == project_id)
|
|
55
|
+
if ids:
|
|
56
|
+
query = query.filter(TestCase.id.in_(ids))
|
|
57
|
+
return query.order_by(TestCase.id.asc()).all()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _run_bulk_generation(job_id: int, request: BulkGenerateRequest):
|
|
61
|
+
from app.database import SessionLocal
|
|
62
|
+
|
|
63
|
+
db = SessionLocal()
|
|
64
|
+
try:
|
|
65
|
+
job = db.query(BulkJob).filter(BulkJob.id == job_id).first()
|
|
66
|
+
if not job:
|
|
67
|
+
return
|
|
68
|
+
job.status = BulkJobStatus.running
|
|
69
|
+
db.commit()
|
|
70
|
+
|
|
71
|
+
project = db.query(Project).filter(Project.id == job.project_id).first()
|
|
72
|
+
if not project:
|
|
73
|
+
job.status = BulkJobStatus.failed
|
|
74
|
+
db.commit()
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
items = db.query(BulkJobItem).filter(BulkJobItem.job_id == job.id).order_by(BulkJobItem.id.asc()).all()
|
|
78
|
+
framework = (request.framework or project.default_framework).value
|
|
79
|
+
|
|
80
|
+
for item in items:
|
|
81
|
+
tc = db.query(TestCase).filter(TestCase.id == item.test_case_id, TestCase.project_id == job.project_id).first()
|
|
82
|
+
if not tc:
|
|
83
|
+
item.status = BulkJobItemStatus.skipped
|
|
84
|
+
item.message = "Test case not found"
|
|
85
|
+
db.commit()
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
item.status = BulkJobItemStatus.running
|
|
89
|
+
db.commit()
|
|
90
|
+
|
|
91
|
+
script_record = GeneratedScript(
|
|
92
|
+
project_id=job.project_id,
|
|
93
|
+
test_case_id=tc.id,
|
|
94
|
+
framework=framework,
|
|
95
|
+
llm_provider=request.llm_provider,
|
|
96
|
+
llm_model=request.llm_model or "",
|
|
97
|
+
status=ScriptStatus.generating,
|
|
98
|
+
)
|
|
99
|
+
db.add(script_record)
|
|
100
|
+
db.commit()
|
|
101
|
+
db.refresh(script_record)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
result = run_agent(
|
|
105
|
+
test_case_title=tc.title,
|
|
106
|
+
test_case_content=tc.content,
|
|
107
|
+
preconditions=tc.preconditions or "",
|
|
108
|
+
test_data_hints=tc.test_data_hints or "",
|
|
109
|
+
aut_base_url=project.aut_base_url,
|
|
110
|
+
framework=framework,
|
|
111
|
+
auth_hints=project.auth_hints or "",
|
|
112
|
+
llm_provider=request.llm_provider,
|
|
113
|
+
llm_model=request.llm_model,
|
|
114
|
+
)
|
|
115
|
+
script_record.script_content = result.get("script")
|
|
116
|
+
script_record.error_message = result.get("error")
|
|
117
|
+
script_record.token_usage = result.get("token_usage")
|
|
118
|
+
script_record.status = ScriptStatus.failed if result.get("error") else ScriptStatus.completed
|
|
119
|
+
item.status = BulkJobItemStatus.failed if result.get("error") else BulkJobItemStatus.completed
|
|
120
|
+
item.message = result.get("error")
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
logger.exception("Bulk generate failed for test_case_id=%s", tc.id)
|
|
123
|
+
script_record.status = ScriptStatus.failed
|
|
124
|
+
script_record.error_message = str(exc)
|
|
125
|
+
item.status = BulkJobItemStatus.failed
|
|
126
|
+
item.message = str(exc)
|
|
127
|
+
|
|
128
|
+
item.script_id = script_record.id
|
|
129
|
+
db.commit()
|
|
130
|
+
|
|
131
|
+
_refresh_job_counts(job, db)
|
|
132
|
+
job.status = BulkJobStatus.failed if job.failed_items > 0 else BulkJobStatus.completed
|
|
133
|
+
db.commit()
|
|
134
|
+
finally:
|
|
135
|
+
db.close()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _run_bulk_execution(job_id: int, request: BulkRunRequest):
|
|
139
|
+
from app.database import SessionLocal
|
|
140
|
+
|
|
141
|
+
db = SessionLocal()
|
|
142
|
+
try:
|
|
143
|
+
job = db.query(BulkJob).filter(BulkJob.id == job_id).first()
|
|
144
|
+
if not job:
|
|
145
|
+
return
|
|
146
|
+
job.status = BulkJobStatus.running
|
|
147
|
+
db.commit()
|
|
148
|
+
|
|
149
|
+
items = db.query(BulkJobItem).filter(BulkJobItem.job_id == job.id).order_by(BulkJobItem.id.asc()).all()
|
|
150
|
+
for item in items:
|
|
151
|
+
item.status = BulkJobItemStatus.running
|
|
152
|
+
db.commit()
|
|
153
|
+
|
|
154
|
+
latest_script = (
|
|
155
|
+
db.query(GeneratedScript)
|
|
156
|
+
.filter(
|
|
157
|
+
GeneratedScript.project_id == job.project_id,
|
|
158
|
+
GeneratedScript.test_case_id == item.test_case_id,
|
|
159
|
+
GeneratedScript.status == ScriptStatus.completed,
|
|
160
|
+
)
|
|
161
|
+
.order_by(GeneratedScript.created_at.desc(), GeneratedScript.id.desc())
|
|
162
|
+
.first()
|
|
163
|
+
)
|
|
164
|
+
if not latest_script:
|
|
165
|
+
item.status = BulkJobItemStatus.skipped
|
|
166
|
+
item.message = "No completed script available"
|
|
167
|
+
db.commit()
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
item.script_id = latest_script.id
|
|
171
|
+
if latest_script.framework != "playwright_python":
|
|
172
|
+
item.status = BulkJobItemStatus.skipped
|
|
173
|
+
item.message = "Only playwright_python scripts can be executed"
|
|
174
|
+
db.commit()
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
run_result = _run_python_script(latest_script.script_content or "")
|
|
179
|
+
status = ScriptRunStatus.completed if run_result["success"] else ScriptRunStatus.failed
|
|
180
|
+
_create_script_run(db, latest_script, run_result, status)
|
|
181
|
+
item.status = BulkJobItemStatus.completed if run_result["success"] else BulkJobItemStatus.failed
|
|
182
|
+
item.message = None if run_result["success"] else "Scenario run failed"
|
|
183
|
+
except ValueError as exc:
|
|
184
|
+
run_result = {
|
|
185
|
+
"success": False,
|
|
186
|
+
"exit_code": 2,
|
|
187
|
+
"stdout": "",
|
|
188
|
+
"stderr": str(exc),
|
|
189
|
+
"duration_seconds": 0.0,
|
|
190
|
+
"command": f"{sys.executable} generated_scenario.py",
|
|
191
|
+
}
|
|
192
|
+
_create_script_run(db, latest_script, run_result, ScriptRunStatus.failed)
|
|
193
|
+
item.status = BulkJobItemStatus.failed
|
|
194
|
+
item.message = str(exc)
|
|
195
|
+
except subprocess.TimeoutExpired as exc:
|
|
196
|
+
run_result = {
|
|
197
|
+
"success": False,
|
|
198
|
+
"exit_code": 124,
|
|
199
|
+
"stdout": exc.stdout or "",
|
|
200
|
+
"stderr": (exc.stderr or "") + f"\nScript execution timed out after {exc.timeout} seconds",
|
|
201
|
+
"duration_seconds": float(exc.timeout),
|
|
202
|
+
"command": f"{sys.executable} generated_scenario.py",
|
|
203
|
+
}
|
|
204
|
+
_create_script_run(db, latest_script, run_result, ScriptRunStatus.timed_out)
|
|
205
|
+
item.status = BulkJobItemStatus.failed
|
|
206
|
+
item.message = f"Timed out after {exc.timeout} seconds"
|
|
207
|
+
|
|
208
|
+
db.commit()
|
|
209
|
+
|
|
210
|
+
_refresh_job_counts(job, db)
|
|
211
|
+
job.status = BulkJobStatus.failed if job.failed_items > 0 else BulkJobStatus.completed
|
|
212
|
+
db.commit()
|
|
213
|
+
finally:
|
|
214
|
+
db.close()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@router.post("/bulk-generate", response_model=BulkJobResponse, status_code=status.HTTP_202_ACCEPTED)
|
|
218
|
+
def bulk_generate_scripts(
|
|
219
|
+
project_id: int,
|
|
220
|
+
payload: BulkGenerateRequest,
|
|
221
|
+
background_tasks: BackgroundTasks,
|
|
222
|
+
db: Session = Depends(get_db),
|
|
223
|
+
):
|
|
224
|
+
_get_project_or_404(project_id, db)
|
|
225
|
+
test_cases = _resolve_test_cases(project_id, payload.test_case_ids, db)
|
|
226
|
+
if not test_cases:
|
|
227
|
+
raise HTTPException(status_code=400, detail="No test cases found for bulk generation")
|
|
228
|
+
|
|
229
|
+
job = BulkJob(project_id=project_id, kind=BulkJobKind.generate, status=BulkJobStatus.pending)
|
|
230
|
+
db.add(job)
|
|
231
|
+
db.commit()
|
|
232
|
+
db.refresh(job)
|
|
233
|
+
|
|
234
|
+
for tc in test_cases:
|
|
235
|
+
db.add(BulkJobItem(job_id=job.id, test_case_id=tc.id, status=BulkJobItemStatus.pending))
|
|
236
|
+
db.commit()
|
|
237
|
+
|
|
238
|
+
_refresh_job_counts(job, db)
|
|
239
|
+
db.commit()
|
|
240
|
+
|
|
241
|
+
background_tasks.add_task(_run_bulk_generation, job.id, payload)
|
|
242
|
+
return _serialize_bulk_job(job, db)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@router.post("/bulk-run", response_model=BulkJobResponse, status_code=status.HTTP_202_ACCEPTED)
|
|
246
|
+
def bulk_run_scripts(
|
|
247
|
+
project_id: int,
|
|
248
|
+
payload: BulkRunRequest,
|
|
249
|
+
background_tasks: BackgroundTasks,
|
|
250
|
+
db: Session = Depends(get_db),
|
|
251
|
+
):
|
|
252
|
+
_get_project_or_404(project_id, db)
|
|
253
|
+
test_cases = _resolve_test_cases(project_id, payload.test_case_ids, db)
|
|
254
|
+
if not test_cases:
|
|
255
|
+
raise HTTPException(status_code=400, detail="No test cases found for bulk run")
|
|
256
|
+
|
|
257
|
+
job = BulkJob(project_id=project_id, kind=BulkJobKind.run, status=BulkJobStatus.pending)
|
|
258
|
+
db.add(job)
|
|
259
|
+
db.commit()
|
|
260
|
+
db.refresh(job)
|
|
261
|
+
|
|
262
|
+
for tc in test_cases:
|
|
263
|
+
db.add(BulkJobItem(job_id=job.id, test_case_id=tc.id, status=BulkJobItemStatus.pending))
|
|
264
|
+
db.commit()
|
|
265
|
+
|
|
266
|
+
_refresh_job_counts(job, db)
|
|
267
|
+
db.commit()
|
|
268
|
+
|
|
269
|
+
background_tasks.add_task(_run_bulk_execution, job.id, payload)
|
|
270
|
+
return _serialize_bulk_job(job, db)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@router.get("/bulk-jobs/{job_id}", response_model=BulkJobResponse)
|
|
274
|
+
def get_bulk_job(project_id: int, job_id: int, db: Session = Depends(get_db)):
|
|
275
|
+
_get_project_or_404(project_id, db)
|
|
276
|
+
job = _get_bulk_job_or_404(project_id, job_id, db)
|
|
277
|
+
return _serialize_bulk_job(job, db)
|
app/routers/demo.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, status
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
|
|
5
|
+
from app.database import get_db
|
|
6
|
+
from app.models.project import Project
|
|
7
|
+
from app.models.test_case import TestCase, TestCaseFormat
|
|
8
|
+
|
|
9
|
+
router = APIRouter(prefix="/demo", tags=["Demo"])
|
|
10
|
+
|
|
11
|
+
_DEMO_PROJECT_NAME = "Demo Retail Checkout"
|
|
12
|
+
_DEMO_AUT_BASE_URL = "https://demo.retail-checkout.local"
|
|
13
|
+
|
|
14
|
+
_DEMO_TEST_CASES = [
|
|
15
|
+
{
|
|
16
|
+
"title": "TC-001 Successful Login",
|
|
17
|
+
"format": TestCaseFormat.step_based,
|
|
18
|
+
"content": (
|
|
19
|
+
"Step 1: Navigate to /login\n"
|
|
20
|
+
"Step 2: Enter valid username and password\n"
|
|
21
|
+
"Step 3: Click Login\n"
|
|
22
|
+
"Expected: User is redirected to /dashboard"
|
|
23
|
+
),
|
|
24
|
+
"preconditions": "Valid user account exists",
|
|
25
|
+
"test_data_hints": "username=demo.user, password=Demo@123",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"title": "TC-002 Add Product To Cart",
|
|
29
|
+
"format": TestCaseFormat.step_based,
|
|
30
|
+
"content": (
|
|
31
|
+
"Step 1: Navigate to /products\n"
|
|
32
|
+
"Step 2: Open a product detail page\n"
|
|
33
|
+
"Step 3: Click Add to Cart\n"
|
|
34
|
+
"Expected: Cart badge count increases by 1"
|
|
35
|
+
),
|
|
36
|
+
"preconditions": "User is logged in",
|
|
37
|
+
"test_data_hints": "product=Basic Tee",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "TC-003 Checkout Confirmation",
|
|
41
|
+
"format": TestCaseFormat.bdd,
|
|
42
|
+
"content": (
|
|
43
|
+
"Given a logged-in user with items in cart\n"
|
|
44
|
+
"When the user completes checkout with valid payment details\n"
|
|
45
|
+
"Then an order confirmation message is displayed"
|
|
46
|
+
),
|
|
47
|
+
"preconditions": "At least one item is present in cart",
|
|
48
|
+
"test_data_hints": "payment_method=card",
|
|
49
|
+
},
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@router.post("/load", status_code=status.HTTP_201_CREATED)
|
|
54
|
+
def load_demo_project(db: Session = Depends(get_db)):
|
|
55
|
+
existing = db.query(Project).filter(Project.name == _DEMO_PROJECT_NAME).first()
|
|
56
|
+
if existing:
|
|
57
|
+
existing_count = db.query(TestCase).filter(TestCase.project_id == existing.id).count()
|
|
58
|
+
return JSONResponse(
|
|
59
|
+
status_code=status.HTTP_200_OK,
|
|
60
|
+
content={
|
|
61
|
+
"created": False,
|
|
62
|
+
"project_id": existing.id,
|
|
63
|
+
"project_name": existing.name,
|
|
64
|
+
"test_case_count": existing_count,
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
project = Project(
|
|
69
|
+
name=_DEMO_PROJECT_NAME,
|
|
70
|
+
aut_base_url=_DEMO_AUT_BASE_URL,
|
|
71
|
+
auth_hints="Use demo credentials on /login when needed.",
|
|
72
|
+
)
|
|
73
|
+
db.add(project)
|
|
74
|
+
db.commit()
|
|
75
|
+
db.refresh(project)
|
|
76
|
+
|
|
77
|
+
for item in _DEMO_TEST_CASES:
|
|
78
|
+
db.add(TestCase(project_id=project.id, **item))
|
|
79
|
+
db.commit()
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"created": True,
|
|
83
|
+
"project_id": project.id,
|
|
84
|
+
"project_name": project.name,
|
|
85
|
+
"test_case_count": len(_DEMO_TEST_CASES),
|
|
86
|
+
}
|
app/routers/projects.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
2
|
+
from sqlalchemy.orm import Session
|
|
3
|
+
|
|
4
|
+
from app.database import get_db
|
|
5
|
+
from app.models.project import Project
|
|
6
|
+
from app.schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/projects", tags=["Projects"])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.post("/", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
|
|
12
|
+
def create_project(payload: ProjectCreate, db: Session = Depends(get_db)):
|
|
13
|
+
project = Project(**payload.model_dump())
|
|
14
|
+
db.add(project)
|
|
15
|
+
db.commit()
|
|
16
|
+
db.refresh(project)
|
|
17
|
+
return project
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/", response_model=list[ProjectResponse])
|
|
21
|
+
def list_projects(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
|
|
22
|
+
return db.query(Project).offset(skip).limit(limit).all()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get("/{project_id}", response_model=ProjectResponse)
|
|
26
|
+
def get_project(project_id: int, db: Session = Depends(get_db)):
|
|
27
|
+
project = db.query(Project).filter(Project.id == project_id).first()
|
|
28
|
+
if not project:
|
|
29
|
+
raise HTTPException(status_code=404, detail="Project not found")
|
|
30
|
+
return project
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.patch("/{project_id}", response_model=ProjectResponse)
|
|
34
|
+
def update_project(project_id: int, payload: ProjectUpdate, db: Session = Depends(get_db)):
|
|
35
|
+
project = db.query(Project).filter(Project.id == project_id).first()
|
|
36
|
+
if not project:
|
|
37
|
+
raise HTTPException(status_code=404, detail="Project not found")
|
|
38
|
+
for field, value in payload.model_dump(exclude_none=True).items():
|
|
39
|
+
setattr(project, field, value)
|
|
40
|
+
db.commit()
|
|
41
|
+
db.refresh(project)
|
|
42
|
+
return project
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
46
|
+
def delete_project(project_id: int, db: Session = Depends(get_db)):
|
|
47
|
+
project = db.query(Project).filter(Project.id == project_id).first()
|
|
48
|
+
if not project:
|
|
49
|
+
raise HTTPException(status_code=404, detail="Project not found")
|
|
50
|
+
db.delete(project)
|
|
51
|
+
db.commit()
|