@cryptotaxi247 / CoPilot / commits / afe355bc

chore: pydantic 2 / SQLAlchemy 2 cleanup — eliminate deprecations (#850)

Follow-up to #849. Addresses every deprecation warning the upgrade left behind. Pure cleanup — no behavior changes. 1. .dict() → .model_dump() (261 sites, 72 files) 2. update_forward_refs() → model_rebuild() (1 site) 3. 10 deprecated @validator decorators migrated: app/utils.py → @model_validator app/connectors/cortex/schema/analyzers.py → @model_validator app/connectors/shuffle/schema/organizations.py → @field_validator app/connectors/grafana/schema/dashboards.py → @field_validator (each_item=True replaced with explicit list iteration) app/integrations/copilot_action/schema/copilot_action.py → @model_validator app/notifications/schema/notifications.py → one @model_validator (combined the two always= validators into a single check) app/incidents/schema/db_operations.py → @field_validator + @model_validator app/incidents/schema/alert_collection.py → @model_validator(mode="before") (the @validator with kwargs["field"].name was the trickiest — rewritten to set both target fields atomically from origin_context) 4. SAWarning: relationships' overlapping FKs. Added sa_relationship_kwargs={"overlaps": "..."} on: CustomerNotificationRoute.dispatches / .shuffle_integration NotificationDispatchLog.route CustomerShuffleIntegration.routes Makes the deliberate one-way nature explicit to SQLAlchemy 2 (back_populates is intentionally omitted to avoid AsyncSession MissingGreenlet on flush). 5. FastAPI on_event → asynccontextmanager lifespan handler in copilot.py. init_db (startup) and shutdown_scheduler combined into one lifespan generator with yield in the middle. Passes lifespan=lifespan to FastAPI(...) constructor. Verified locally: - docker build succeeds - backend boots cleanly via lifespan handler (visible as __main__:lifespan:106 instead of __main__:init_db:191 in the logs) - all ~50 routers import without ModuleNotFoundError - all smoke tests pass: bcrypt round-trip, Fernet round-trip, @field_validator (codemod-migrated), @model_validator (hand-migrated), SQLAlchemy 2 select(), SQLModel table creation, every code-path covered by the migrated validators - no DeprecationWarning or SAWarning remain in startup logs (the only warning that fires is the upstream grafana-client SyntaxWarning at elements/datasource.py:414, unrelated to this PR — see #848) - existing MySQL test data preserved across the rebuild Co-authored-by: taylor_socfortress <taylor.walton@socfortress.co> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

taylorcopilot committed May 7, 2026 at 21:03 UTC afe355bc6a16c8d4227aabe0127a0c204cb49dd9
81 files changed +365 -386
backend/app/agents/sca/routes/sca.py
+1 -1
@@ -362,7 +362,7 @@ async def generate_sca_report(
362 Returns:
363 SCAReportGenerateResponse: Report generation status and details
364 """
365 - logger.info(f"Generating SCA report for customer {request.customer_code} " f"with filters: {request.dict(exclude_none=True)}")
365 + logger.info(f"Generating SCA report for customer {request.customer_code} " f"with filters: {request.model_dump(exclude_none=True)}")
366
367 try:
368 # Note: This will be processed synchronously for now
backend/app/agents/services/sync.py
+1 -1
@@ -265,7 +265,7 @@ async def sync_agents_wazuh() -> SyncedAgentsResponse:
265 else:
266 await add_wazuh_agent_in_db(session, wazuh_agent, customer_code)
267
268 - synced_wazuh_agent = SyncedWazuhAgent(**wazuh_agent.dict())
268 + synced_wazuh_agent = SyncedWazuhAgent(**wazuh_agent.model_dump())
269 agents_added_list.append(synced_wazuh_agent)
270
271 logger.info(f"Agents Added List: {agents_added_list}")
backend/app/agents/wazuh/services/vulnerabilities.py
+2 -2
@@ -337,7 +337,7 @@ async def sync_agent_vulnerabilities(agent_name: str, customer_code: str):
337 integration="vulnerabilities",
338 customer_code=customer_code,
339 agent_name=agent_name,
340 - **vulnerability.dict(),
340 + **vulnerability.model_dump(),
341 ),
342 )
343 return True
@@ -350,7 +350,7 @@ async def sync_agent_vulnerabilities(agent_name: str, customer_code: str):
350 integration="vulnerabilities",
351 customer_code=customer_code,
352 agent_name=agent_name,
353 - **vulnerability.dict(),
353 + **vulnerability.model_dump(),
354 ),
355 )
356 return True
backend/app/auth/routes/sso.py
+1 -1
@@ -148,7 +148,7 @@ async def get_sso_settings():
148 )
149 async def update_sso_settings(body: SSOConfigUpdate):
150 """Update SSO configuration. Admin only."""
151 - data = body.dict(exclude_none=False)
151 + data = body.model_dump(exclude_none=False)
152
153 # Don't overwrite secrets if empty/None
154 if not data.get("azure_client_secret"):
backend/app/connectors/cortex/schema/analyzers.py
+6 -8
@@ -8,7 +8,7 @@ from typing import Tuple
8
9 from pydantic import BaseModel
10 from pydantic import Field
11 -from pydantic import validator
11 +from pydantic import model_validator
12
13 HASH_REGEX = re.compile(
14 r"[a-fA-F\d]{32}|[a-fA-F\d]{64}",
@@ -35,15 +35,13 @@ class RunAnalyzerBody(BaseModel):
35 description="Data type determined after validation",
36 )
37
38 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
39 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
40 - @validator("analyzer_data", pre=True, always=True)
41 - def validate_and_set_data_type(cls, value: str, values: dict) -> str:
42 - is_valid, data_type = cls.is_valid_datatype(value)
38 + @model_validator(mode="after")
39 + def validate_and_set_data_type(self):
40 + is_valid, data_type = self.is_valid_datatype(self.analyzer_data)
41 if not is_valid:
42 raise ValueError(f"Invalid data type: {data_type}")
45 - values["data_type"] = data_type
46 - return value
43 + self.data_type = data_type
44 + return self
45
46 @classmethod
47 def is_valid_datatype(cls, value: str) -> Tuple[bool, str]:
backend/app/connectors/cortex/utils/universal.py
+1 -1
@@ -104,7 +104,7 @@ async def run_and_wait_for_analyzer(
104 if api is None:
105 return {"success": False, "message": "API initialization failed"}
106 try:
107 - job = api.analyzers.run_by_name(analyzer_name, job_data.dict(), force=1)
107 + job = api.analyzers.run_by_name(analyzer_name, job_data.model_dump(), force=1)
108 return await monitor_analyzer_job(api, job)
109 except Exception as e:
110 raise HTTPException(
backend/app/connectors/grafana/schema/dashboards.py
+8 -8
@@ -3,7 +3,7 @@ from typing import List
3
4 from pydantic import BaseModel
5 from pydantic import Field
6 -from pydantic import validator
6 +from pydantic import field_validator
7
8
9 class GrafanaDashboard(BaseModel):
@@ -155,10 +155,9 @@ class DashboardProvisionRequest(BaseModel):
155 description="URL of the Grafana instance for the links within the dashboards.",
156 )
157
158 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
159 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
160 - @validator("dashboards", each_item=True)
161 - def check_dashboard_exists(cls, e):
158 + @field_validator("dashboards")
159 + @classmethod
160 + def check_dashboards_exist(cls, dashboards):
161 valid_dashboards = {
162 item.name: item
163 for item in list(WazuhDashboard)
@@ -177,6 +176,7 @@ class DashboardProvisionRequest(BaseModel):
176 + list(SonicwallDashboard)
177 + list(SentinelOneDashboard)
178 }
180 - if e not in valid_dashboards:
181 - raise ValueError(f'Dashboard identifier "{e}" is not recognized.')
182 - return e
179 + for e in dashboards:
180 + if e not in valid_dashboards:
181 + raise ValueError(f'Dashboard identifier "{e}" is not recognized.')
182 + return dashboards
backend/app/connectors/graylog/routes/pipelines.py
+2 -2
@@ -57,7 +57,7 @@ def transform_stages_with_rule_ids(
57 new_stages = []
58 for stage in stages:
59 rule_ids = [rule_title_to_id.get(rule_title, None) for rule_title in stage.rules]
60 - new_stage = StageWithRuleID(**stage.dict(), rule_ids=rule_ids)
60 + new_stage = StageWithRuleID(**stage.model_dump(), rule_ids=rule_ids)
61 new_stages.append(new_stage)
62 return new_stages
63
@@ -78,7 +78,7 @@ def transform_pipeline_with_rule_ids(
78
79 """
80 new_stages = transform_stages_with_rule_ids(pipeline.stages, rule_title_to_id)
81 - pipeline_dict = pipeline.dict()
81 + pipeline_dict = pipeline.model_dump()
82 pipeline_dict["stages"] = new_stages
83 return PipelineWithRuleID(**pipeline_dict)
84
backend/app/connectors/graylog/schema/events.py
+1 -1
@@ -26,7 +26,7 @@ class ExpressionItem(BaseModel):
26 right: Optional["ExpressionItem"] = None
27
28
29 -ExpressionItem.update_forward_refs()
29 +ExpressionItem.model_rebuild()
30
31
32 class Conditions(BaseModel):
backend/app/connectors/graylog/services/events.py
+1 -1
@@ -64,7 +64,7 @@ async def get_alerts(alert_query: AlertQuery) -> GraylogAlertsResponse:
64 logger.info("Getting alerts from Graylog")
65 response = await send_post_request(
66 endpoint="/api/events/search",
67 - data=alert_query.dict(),
67 + data=alert_query.model_dump(),
68 )
69
70 if response["success"]:
backend/app/connectors/graylog/services/pipelines.py
+1 -1
@@ -206,7 +206,7 @@ async def connect_stream_to_pipeline(
206 )
207 response_json = await send_post_request(
208 endpoint="/api/system/pipelines/connections/to_stream",
209 - data=stream_and_pipeline.dict(),
209 + data=stream_and_pipeline.model_dump(),
210 )
211 logger.info(f"Response: {response_json}")
212 return StreamConnectionToPipelineResponse(**response_json)
backend/app/connectors/shuffle/schema/organizations.py
+3 -4
@@ -6,7 +6,7 @@ from typing import Optional
6
7 from pydantic import BaseModel
8 from pydantic import Field
9 -from pydantic import validator
9 +from pydantic import field_validator
10
11
12 class SyncConfig(BaseModel):
@@ -103,9 +103,8 @@ class DetailedOrganization(BaseModel):
103 region_url: str = ""
104 tutorials: List[Any] = []
105
106 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
107 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
108 - @validator("manager_orgs", pre=True, always=True)
106 + @field_validator("manager_orgs", mode="before")
107 + @classmethod
108 def validate_manager_orgs(cls, v):
109 """
110 Validate and normalize manager_orgs field.
backend/app/connectors/shuffle/services/integrations.py
+3 -3
@@ -16,7 +16,7 @@ async def execute_integration(request: IntegrationRequest) -> dict:
16 dict: The response containing the execution ID.
17 """
18 logger.info(f"Executing integration: {request}")
19 - response = await send_post_request("/api/v1/apps/categories/run", request.dict())
19 + response = await send_post_request("/api/v1/apps/categories/run", request.model_dump())
20 logger.info(f"Response: {response}")
21 return response
22
@@ -31,9 +31,9 @@ async def execute_workflow(request: ExecuteWorkflowRequest) -> dict:
31 Returns:
32 dict: The response containing the execution ID.
33 """
34 - logger.info(f"Executing workflow: {request.dict()}")
34 + logger.info(f"Executing workflow: {request.model_dump()}")
35 try:
36 - response = await send_post_request(f"/api/v1/workflows/{request.workflow_id}/execute", request.dict())
36 + response = await send_post_request(f"/api/v1/workflows/{request.workflow_id}/execute", request.model_dump())
37 logger.info(f"Response: {response}")
38 return response
39 except Exception as e:
backend/app/connectors/talon/services/talon.py
+2 -2
@@ -34,7 +34,7 @@ async def send_talon_message(request: TalonMessageRequest) -> TalonMessageRespon
34 logger.info(f"Sending message to Talon: {request.message}")
35 response = await send_post_request(
36 endpoint="/message",
37 - data=request.dict(),
37 + data=request.model_dump(),
38 timeout=600,
39 )
40 if not response.get("success"):
@@ -62,7 +62,7 @@ async def stream_talon_message(request: TalonMessageRequest):
62 logger.info(f"Streaming message to Talon: {request.message}")
63 async for chunk in send_post_request_sse(
64 endpoint="/message",
65 - data=request.dict(),
65 + data=request.model_dump(),
66 ):
67 yield chunk
68
backend/app/connectors/utils.py
+1 -1
@@ -32,7 +32,7 @@ async def get_connector_info_from_db(
32 connector = result.scalars().first()
33 if connector:
34 connector_pydantic = ConnectorResponse.from_orm(connector)
35 - return connector_pydantic.dict()
35 + return connector_pydantic.model_dump()
36 else:
37 logger.warning("No connector found.")
38 return None
backend/app/connectors/velociraptor/services/artifacts.py
+2 -2
@@ -1062,12 +1062,12 @@ async def post_to_copilot_ai_module(data: ArtifactReccomendationRequest) -> Arti
1062 Args:
1063 data (ArtifactReccomendationRequest): The data to send to the copilot-ai-module Docker container.
1064 """
1065 - logger.info(f"Sending POST request to http://copilot-ai-module/velociraptor-artifact-recommendation with data: {data.dict()}")
1065 + logger.info(f"Sending POST request to http://copilot-ai-module/velociraptor-artifact-recommendation with data: {data.model_dump()}")
1066 # raise HTTPException(status_code=501, detail="Not Implemented Yet")
1067 async with httpx.AsyncClient() as client:
1068 data = await client.post(
1069 "http://copilot-ai-module/velociraptor-artifact-recommendation",
1070 - json=data.dict(),
1070 + json=data.model_dump(),
1071 timeout=120,
1072 )
1073 response_data = data.json()
backend/app/connectors/wazuh_indexer/schema/alerts.py
+1 -1
@@ -157,4 +157,4 @@ class AlertNotFound(BaseModel):
157 source: Dict[str, str] = Field(alias="_source")
158
159 def to_dict(self) -> Dict[str, str]:
160 - return self.dict(by_alias=True)
160 + return self.model_dump(by_alias=True)
backend/app/connectors/wazuh_manager/services/rules.py
+2 -2
@@ -532,12 +532,12 @@ async def post_to_copilot_ai_module(data: RuleExcludeRequest) -> RuleExcludeResp
532 Args:
533 data (CollectHuntress): The data to send to the copilot-ai-module Docker container.
534 """
535 - logger.info(f"Sending POST request to http://copilot-ai-module/wazuh-rule-exclusion with data: {data.dict()}")
535 + logger.info(f"Sending POST request to http://copilot-ai-module/wazuh-rule-exclusion with data: {data.model_dump()}")
536 # raise HTTPException(status_code=501, detail="Not Implemented Yet")
537 async with httpx.AsyncClient() as client:
538 data = await client.post(
539 "http://copilot-ai-module/wazuh-rule-exclusion",
540 - json=data.dict(),
540 + json=data.model_dump(),
541 timeout=120,
542 )
543 return RuleExcludeResponse(**data.json())
backend/app/customer_provisioning/routes/default_settings.py
+1 -1
@@ -92,7 +92,7 @@ async def update_customer_provisioning_default_settings(
92 raise HTTPException(status_code=404, detail="Settings not found")
93
94 # Update the fields
95 - for key, value in customer_provisioning_default_settings.dict().items():
95 + for key, value in customer_provisioning_default_settings.model_dump().items():
96 setattr(existing_settings, key, value)
97
98 await db.commit()
backend/app/customer_provisioning/services/decommission.py
+3 -3
@@ -209,7 +209,7 @@ async def decommission_wazuh_worker(
209 request.portainer_deployment = False
210 response = requests.post(
211 url=f"{api_endpoint}/provision_worker/decommission",
212 - json=request.dict(),
212 + json=request.model_dump(),
213 )
214 # Check the response status code
215 if response.status_code != 200:
@@ -237,7 +237,7 @@ async def decommission_wazuh_worker(
237 logger.info(f"Provisioning Wazuh worker on IP: {ip}")
238 response = requests.post(
239 url=f"http://{ip}:5003/provision_worker/decommission",
240 - json=request.dict(),
240 + json=request.model_dump(),
241 )
242 logger.info(f"Status code from Wazuh Worker: {response.status_code}")
243 if response.status_code != 200:
@@ -290,7 +290,7 @@ async def decommission_haproxy(
290 # Send the POST request to the HAProxy worker
291 response = requests.post(
292 url=f"{api_endpoint}/provision_worker/haproxy/decommission",
293 - json=request.dict(),
293 + json=request.model_dump(),
294 )
295 # Check the response status code
296 if response.status_code != 200:
backend/app/customer_provisioning/services/grafana.py
+2 -2
@@ -110,7 +110,7 @@ async def create_grafana_datasource(
110 readOnly=True,
111 )
112 results = grafana_client.datasource.create_datasource(
113 - datasource=datasource_payload.dict(),
113 + datasource=datasource_payload.model_dump(),
114 )
115 return GrafanaDataSourceCreationResponse(**results)
116
@@ -189,7 +189,7 @@ async def create_vulnerability_datasource(
189 readOnly=True,
190 )
191 results = grafana_client.datasource.create_datasource(
192 - datasource=datasource_payload.dict(),
192 + datasource=datasource_payload.model_dump(),
193 )
194 return GrafanaDataSourceCreationResponse(**results)
195
backend/app/customer_provisioning/services/graylog.py
+5 -5
@@ -69,11 +69,11 @@ async def send_index_set_creation_request(
69 Returns:
70 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
71 """
72 - json_index_set = json.dumps(index_set.dict())
72 + json_index_set = json.dumps(index_set.model_dump())
73 logger.info(f"json_index_set set: {json_index_set}")
74 response_json = await send_post_request(
75 endpoint="/api/system/indices/index_sets",
76 - data=index_set.dict(),
76 + data=index_set.model_dump(),
77 )
78 return GraylogIndexSetCreationResponse(**response_json)
79
@@ -158,11 +158,11 @@ async def send_event_stream_creation_request(
158 Returns:
159 StreamCreationResponse: The response containing the created event stream.
160 """
161 - json_event_stream = json.dumps(event_stream.dict())
161 + json_event_stream = json.dumps(event_stream.model_dump())
162 logger.info(f"json_event_stream set: {json_event_stream}")
163 response_json = await send_post_request(
164 endpoint="/api/streams",
165 - data=event_stream.dict(),
165 + data=event_stream.model_dump(),
166 )
167 return StreamCreationResponse(**response_json)
168
@@ -238,7 +238,7 @@ async def connect_stream_to_pipeline(
238 )
239 response_json = await send_post_request(
240 endpoint="/api/system/pipelines/connections/to_stream",
241 - data=stream_and_pipeline.dict(),
241 + data=stream_and_pipeline.model_dump(),
242 )
243 logger.info(f"Response: {response_json}")
244 return StreamConnectionToPipelineResponse(**response_json)
backend/app/customer_provisioning/services/provision.py
+7 -7
@@ -166,7 +166,7 @@ async def provision_wazuh_customer(
166 return CustomerProvisionResponse(
167 message=f"Customer {request.customer_name} provisioned successfully, but the Wazuh worker failed to provision",
168 success=True,
169 - customer_meta=customer_meta.dict(),
169 + customer_meta=customer_meta.model_dump(),
170 wazuh_worker_provisioned=False,
171 )
172
@@ -185,14 +185,14 @@ async def provision_wazuh_customer(
185 return CustomerProvisionResponse(
186 message=f"Customer {request.customer_name} provisioned successfully, but the HAProxy failed to provision",
187 success=True,
188 - customer_meta=customer_meta.dict(),
188 + customer_meta=customer_meta.model_dump(),
189 wazuh_worker_provisioned=True,
190 )
191
192 return CustomerProvisionResponse(
193 message=f"Customer {request.customer_name} provisioned successfully",
194 success=True,
195 - customer_meta=customer_meta.dict(),
195 + customer_meta=customer_meta.model_dump(),
196 wazuh_worker_provisioned=True,
197 )
198
@@ -330,7 +330,7 @@ async def provision_wazuh_worker(
330 request.wazuh_manager_version = await get_wazuh_manager_version()
331 response = requests.post(
332 url=f"{api_endpoint}/provision_worker",
333 - json=request.dict(),
333 + json=request.model_dump(),
334 )
335 logger.info(f"Status code from Wazuh Worker: {response.status_code}")
336 # Check the response status code
@@ -356,7 +356,7 @@ async def provision_wazuh_worker(
356
357 response = requests.post(
358 url=f"http://{ip}:5003/provision_worker",
359 - json=request.dict(),
359 + json=request.model_dump(),
360 )
361 logger.info(f"Status code from Wazuh Worker: {response.status_code}")
362 if response.status_code != 200:
@@ -404,7 +404,7 @@ async def provision_haproxy(
404 # Send the POST request to the Wazuh worker
405 response = requests.post(
406 url=f"{api_endpoint}/provision_worker/haproxy",
407 - json=request.dict(),
407 + json=request.model_dump(),
408 )
409 # Check the response status code
410 if response.status_code != 200:
@@ -429,7 +429,7 @@ async def provision_haproxy(
429 logger.info(f"Invoking the customer provisioning application on the swarm node IPs: {request.swarm_nodes}")
430 response = requests.post(
431 url=f"{api_endpoint}/provision_worker/haproxy",
432 - json=request.dict(),
432 + json=request.model_dump(),
433 )
434 # Check the response status code
435 if response.status_code != 200:
backend/app/customers/routes/customers.py
+4 -4
@@ -211,7 +211,7 @@ async def create_customer(
211 await mssp_license_check(session)
212 await verify_unique_customer_code(session, customer)
213 logger.info(f"Creating new customer: {customer}")
214 - new_customer = Customers(**customer.dict())
214 + new_customer = Customers(**customer.model_dump())
215 session.add(new_customer)
216 await session.commit()
217 return CustomerResponse(
@@ -348,7 +348,7 @@ async def update_customer(
348 )
349
350 # Update model instance with input data
351 - for key, value in customer.dict(exclude={"is_provisioned"}).items():
351 + for key, value in customer.model_dump(exclude={"is_provisioned"}).items():
352 setattr(existing_customer, key, value)
353
354 await session.commit() # Commit changes asynchronously
@@ -478,7 +478,7 @@ async def add_customer_meta(
478 )
479
480 logger.info(f"Got existing customer: {existing_customer}")
481 - new_customer_meta = CustomersMeta(**customer_meta.dict())
481 + new_customer_meta = CustomersMeta(**customer_meta.model_dump())
482 new_customer_meta.customer_code = existing_customer.customer_code
483 new_customer_meta.customer_name = existing_customer.customer_name
484
@@ -578,7 +578,7 @@ async def update_customer_meta(
578 )
579
580 # Update the existing record with new values
581 - for key, value in customer_meta.dict(exclude_unset=True).items():
581 + for key, value in customer_meta.model_dump(exclude_unset=True).items():
582 setattr(existing_customer_meta, key, value)
583
584 await session.commit() # Commit the changes to the database asynchronously
backend/app/db/universal_models.py
+5 -4
@@ -791,8 +791,8 @@ class CustomerNotificationRoute(SQLModel, table=True):
791 # sync, which throws MissingGreenlet under AsyncSession. One-way
792 # foreign keys are fine here; we walk them via explicit queries
793 # (`session.get(...)`) when we need them.
794 - dispatches: list["NotificationDispatchLog"] = Relationship()
795 - shuffle_integration: Optional["CustomerShuffleIntegration"] = Relationship()
794 + dispatches: list["NotificationDispatchLog"] = Relationship(sa_relationship_kwargs={"overlaps": "route"})
795 + shuffle_integration: Optional["CustomerShuffleIntegration"] = Relationship(sa_relationship_kwargs={"overlaps": "routes"})
796
797
798 class NotificationDispatchLog(SQLModel, table=True):
@@ -844,7 +844,8 @@ class NotificationDispatchLog(SQLModel, table=True):
844
845 # See note on CustomerNotificationRoute.dispatches — back_populates
846 # removed deliberately to keep AsyncSession flush() synchronous-IO-free.
847 - route: Optional["CustomerNotificationRoute"] = Relationship()
847 + # `overlaps` makes the deliberate one-way nature explicit to SQLAlchemy 2.
848 + route: Optional["CustomerNotificationRoute"] = Relationship(sa_relationship_kwargs={"overlaps": "dispatches"})
849
850
851 class CustomerShuffleIntegration(SQLModel, table=True):
@@ -884,4 +885,4 @@ class CustomerShuffleIntegration(SQLModel, table=True):
885 customer: Optional["Customers"] = Relationship()
886 # See note on CustomerNotificationRoute.dispatches — back_populates
887 # removed for AsyncSession compatibility.
887 - routes: list["CustomerNotificationRoute"] = Relationship()
888 + routes: list["CustomerNotificationRoute"] = Relationship(sa_relationship_kwargs={"overlaps": "shuffle_integration"})
backend/app/healthchecks/agents/services/agents.py
+6 -6
@@ -36,7 +36,7 @@ def is_wazuh_agent_unhealthy(
36 # If wazuh_last_seen is None, consider it unhealthy
37 if agent.wazuh_last_seen is None:
38 logger.info(f"Agent {agent.hostname} (ID: {agent.agent_id}) has no Wazuh last seen time - marking as unhealthy")
39 - return ExtendedAgentModel(**agent.dict(), unhealthy_wazuh_agent=True)
39 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_wazuh_agent=True)
40
41 current_time = datetime.now()
42 wazuh_last_seen = agent.wazuh_last_seen
@@ -45,14 +45,14 @@ def is_wazuh_agent_unhealthy(
45 logger.info(
46 f"Agent {agent} has a wazuh_last_seen time in the future: {wazuh_last_seen}",
47 )
48 - return ExtendedAgentModel(**agent.dict(), unhealthy_wazuh_agent=True)
48 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_wazuh_agent=True)
49
50 # Calculate the total time delta based on the criteria
51 total_minutes = time_criteria.minutes + time_criteria.hours * 60 + time_criteria.days * 24 * 60
52 time_delta = timedelta(minutes=total_minutes)
53
54 is_unhealthy = (current_time - wazuh_last_seen) > time_delta
55 - return ExtendedAgentModel(**agent.dict(), unhealthy_wazuh_agent=is_unhealthy)
55 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_wazuh_agent=is_unhealthy)
56
57
58 def is_velociraptor_agent_unhealthy(
@@ -72,7 +72,7 @@ def is_velociraptor_agent_unhealthy(
72 # If velociraptor_id is None or velociraptor_last_seen is None, consider it unhealthy
73 if agent.velociraptor_id is None or agent.velociraptor_last_seen is None:
74 logger.info(f"Agent {agent.hostname} (ID: {agent.agent_id}) has no Velociraptor ID or last seen time - marking as unhealthy")
75 - return ExtendedAgentModel(**agent.dict(), unhealthy_velociraptor_agent=True)
75 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_velociraptor_agent=True)
76
77 current_time = datetime.now()
78 velociraptor_last_seen = agent.velociraptor_last_seen
@@ -81,14 +81,14 @@ def is_velociraptor_agent_unhealthy(
81 logger.info(
82 f"Agent {agent} has a velociraptor_last_seen time in the future: {velociraptor_last_seen}",
83 )
84 - return ExtendedAgentModel(**agent.dict(), unhealthy_velociraptor_agent=True)
84 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_velociraptor_agent=True)
85
86 # Calculate the total time delta based on the criteria
87 total_minutes = time_criteria.minutes + time_criteria.hours * 60 + time_criteria.days * 24 * 60
88 time_delta = timedelta(minutes=total_minutes)
89
90 is_unhealthy = (current_time - velociraptor_last_seen) > time_delta
91 - return ExtendedAgentModel(**agent.dict(), unhealthy_velociraptor_agent=is_unhealthy)
91 + return ExtendedAgentModel(**agent.model_dump(), unhealthy_velociraptor_agent=is_unhealthy)
92
93
94 async def wazuh_agents_healthcheck(
backend/app/incidents/routes/incident_alert.py
+4 -4
@@ -359,7 +359,7 @@ async def invoke_alert_threshold_graylog_route(
359
360 alert_id = await create_alert_full(
361 alert_payload=CreatedAlertPayload(
362 - alert_context_payload=request.event.fields.dict(),
362 + alert_context_payload=request.event.fields.model_dump(),
363 asset_payload=asset_name,
364 timefield_payload=str(request.event.timestamp),
365 alert_title_payload=request.event.message,
@@ -439,12 +439,12 @@ async def create_exclusion(
439 logger.info(f"Current user: {current_user}")
440
441 # Take only needed fields from exclusion, excluding created_by
442 - exclusion_dict = exclusion.dict(exclude={"created_by"})
442 + exclusion_dict = exclusion.model_dump(exclude={"created_by"})
443 # Create a new exclusion with the current user
444 updated_exclusion = VeloSigmaExclusionCreate(**exclusion_dict, created_by=current_user)
445
446 # Log the exclusion data for debugging
447 - logger.info(f"Exclusion data: {updated_exclusion.dict()}")
447 + logger.info(f"Exclusion data: {updated_exclusion.model_dump()}")
448
449 service = VeloSigmaExclusionService(db)
450 # return await service.create_exclusion(updated_exclusion)
@@ -518,7 +518,7 @@ async def update_exclusion(
518 ):
519 """Update an existing exclusion rule."""
520 service = VeloSigmaExclusionService(db)
521 - updated = await service.update_exclusion(exclusion_id, exclusion.dict(exclude_unset=True))
521 + updated = await service.update_exclusion(exclusion_id, exclusion.model_dump(exclude_unset=True))
522
523 if not updated:
524 raise HTTPException(status_code=404, detail="Exclusion rule not found")
backend/app/incidents/schema/alert_collection.py
+17 -19
@@ -4,7 +4,7 @@ from typing import Optional
4
5 from pydantic import BaseModel
6 from pydantic import Field
7 -from pydantic import validator
7 +from pydantic import model_validator
8
9
10 class Fields(BaseModel):
@@ -36,24 +36,22 @@ class Source(BaseModel):
36 original_alert_id: Optional[str] = Field(None, alias="original_alert_id")
37 original_alert_index_name: Optional[str] = Field(None, alias="original_alert_index_name")
38
39 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
40 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
41 - @validator("original_alert_id", "original_alert_index_name", allow_reuse=True, pre=True)
42 - def extract_origin_context(cls, v, values, **kwargs):
43 - origin_context = values.get("origin_context", "")
44 - try:
45 - # Assuming the format is always as given in the example
46 - parts = origin_context.split(":")
47 - if len(parts) == 6:
48 - _, _, _, _, index_name, alert_id = parts
49 - if kwargs["field"].name == "original_alert_id":
50 - return alert_id
51 - elif kwargs["field"].name == "original_alert_index_name":
52 - return index_name
53 - except Exception as e:
54 - # Consider logging the exception to understand what's going wrong
55 - print(f"Error parsing origin_context: {e}")
56 - return v
39 + @model_validator(mode="before")
40 + @classmethod
41 + def extract_origin_context(cls, data):
42 + if isinstance(data, dict):
43 + origin_context = data.get("origin_context", "")
44 + try:
45 + # Assuming the format is always as given in the example
46 + parts = origin_context.split(":")
47 + if len(parts) == 6:
48 + _, _, _, _, index_name, alert_id = parts
49 + data["original_alert_id"] = alert_id
50 + data["original_alert_index_name"] = index_name
51 + except Exception as e:
52 + # Consider logging the exception to understand what's going wrong
53 + print(f"Error parsing origin_context: {e}")
54 + return data
55
56
57 class AlertPayloadItem(BaseModel):
backend/app/incidents/schema/db_operations.py
+7 -10
@@ -6,7 +6,7 @@ from typing import Optional
6
7 from fastapi import HTTPException
8 from pydantic import field_validator, BaseModel
9 -from pydantic import validator
9 +from pydantic import model_validator
10
11 from app.incidents.models import Alert
12 from app.incidents.models import AlertContext
@@ -565,9 +565,8 @@ class CaseDownloadDocxRequest(BaseModel):
565 template_name: str
566 file_name: Optional[str] = "case_report.docx"
567
568 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
569 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
570 - @validator("file_name", pre=True, always=True)
568 + @field_validator("file_name", mode="before")
569 + @classmethod
570 def ensure_docx_extension(cls, v):
571 if v and not v.endswith(".docx"):
572 return f"{v}.docx"
@@ -665,16 +664,14 @@ class TagAccessSettingsUpdate(BaseModel):
664 untagged_alert_behavior: UntaggedAlertBehavior = UntaggedAlertBehavior.VISIBLE_TO_ALL
665 default_tag_id: Optional[int] = None
666
668 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
669 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
670 - @validator("default_tag_id")
671 - def validate_default_tag(cls, v, values):
672 - if values.get("untagged_alert_behavior") == UntaggedAlertBehavior.DEFAULT_TAG and v is None:
667 + @model_validator(mode="after")
668 + def validate_default_tag(self):
669 + if self.untagged_alert_behavior == UntaggedAlertBehavior.DEFAULT_TAG and self.default_tag_id is None:
670 raise HTTPException(
671 status_code=400,
672 detail="default_tag_id is required when untagged_alert_behavior is 'default_tag'",
673 )
677 - return v
674 + return self
675
676
677 class TagAccessSettingsItem(BaseModel):
backend/app/incidents/schema/incident_alert.py
+1 -1
@@ -110,7 +110,7 @@ class GenericSourceModel(BaseModel):
110 model_config = ConfigDict(extra="allow")
111
112 def to_dict(self):
113 - return self.dict(exclude_none=True)
113 + return self.model_dump(exclude_none=True)
114
115
116 class GenericAlertModel(BaseModel):
backend/app/incidents/services/db_operations.py
+10 -10
@@ -744,7 +744,7 @@ async def put_customer_ai_trigger(notification: PutNotification, session: AsyncS
744 )
745 existing_notification = result.scalars().first()
746 if existing_notification is None:
747 - new_notification = AIAnalystTriggerEnabled(**notification.dict())
747 + new_notification = AIAnalystTriggerEnabled(**notification.model_dump())
748 session.add(new_notification)
749 else:
750 existing_notification.customer_code = notification.customer_code
@@ -763,7 +763,7 @@ async def put_customer_notification(notification: PutNotification, session: Asyn
763 result = await session.execute(select(Notification).where(Notification.customer_code == notification.customer_code))
764 existing_notification = result.scalars().first()
765 if existing_notification is None:
766 - new_notification = Notification(**notification.dict())
766 + new_notification = Notification(**notification.model_dump())
767 session.add(new_notification)
768 else:
769 existing_notification.customer_code = notification.customer_code
@@ -982,7 +982,7 @@ async def delete_customer_code_name(source: str, customer_code_name: str, sessio
982
983
984 async def create_alert(alert: AlertCreate, db: AsyncSession) -> Alert:
985 - db_alert = Alert(**alert.dict())
985 + db_alert = Alert(**alert.model_dump())
986 db.add(db_alert)
987 try:
988 await db.flush()
@@ -1104,7 +1104,7 @@ async def create_comment(comment: CommentCreate, db: AsyncSession) -> Comment:
1104 raise HTTPException(status_code=404, detail="Alert not found")
1105
1106 # Create comment with automatic timestamp if not provided
1107 - comment_data = comment.dict()
1107 + comment_data = comment.model_dump()
1108 if comment_data.get("created_at") is None:
1109 comment_data["created_at"] = datetime.utcnow()
1110
@@ -1146,7 +1146,7 @@ async def create_case_comment(comment: CaseCommentCreate, db: AsyncSession) -> C
1146 raise HTTPException(status_code=404, detail="Case not found")
1147
1148 # Create comment with automatic timestamp if not provided
1149 - comment_data = comment.dict()
1149 + comment_data = comment.model_dump()
1150 if comment_data.get("created_at") is None:
1151 comment_data["created_at"] = datetime.utcnow()
1152
@@ -1193,7 +1193,7 @@ async def create_asset(asset: AssetCreate, db: AsyncSession) -> Asset:
1193 if not alert_context:
1194 raise HTTPException(status_code=404, detail="Alert context not found")
1195
1196 - db_asset = Asset(**asset.dict())
1196 + db_asset = Asset(**asset.model_dump())
1197 db.add(db_asset)
1198 try:
1199 await db.commit()
@@ -1246,7 +1246,7 @@ async def delete_alert_ioc(ioc: AlertIoCDelete, db: AsyncSession) -> AlertToIoC:
1246
1247 async def create_alert_tag(alert_tag: AlertTagCreate, db: AsyncSession) -> AlertTag:
1248 # Create the AlertTag instance
1249 - db_alert_tag = AlertTag(**alert_tag.dict())
1249 + db_alert_tag = AlertTag(**alert_tag.model_dump())
1250 db.add(db_alert_tag)
1251 await db.flush()
1252
@@ -1301,7 +1301,7 @@ async def delete_alert_tag(alert_id: int, tag_id: int, db: AsyncSession):
1301
1302
1303 async def create_alert_context(alert_context: AlertContextCreate, db: AsyncSession) -> AlertContext:
1304 - db_alert_context = AlertContext(**alert_context.dict())
1304 + db_alert_context = AlertContext(**alert_context.model_dump())
1305 db.add(db_alert_context)
1306 try:
1307 await db.flush()
@@ -1432,7 +1432,7 @@ async def create_case(
1432 has no source hint to pick from. Analysts can apply a template later
1433 via ``POST /case/{id}/apply-template/{template_id}``.
1434 """
1435 - db_case = Case(**case.dict())
1435 + db_case = Case(**case.model_dump())
1436 db.add(db_case)
1437 try:
1438 await db.flush()
@@ -1538,7 +1538,7 @@ async def create_case_alert_link(case_alert_link: CaseAlertLinkCreate, db: Async
1538 if not alert:
1539 raise HTTPException(status_code=404, detail="Alert not found")
1540
1541 - db_case_alert_link = CaseAlertLink(**case_alert_link.dict())
1541 + db_case_alert_link = CaseAlertLink(**case_alert_link.model_dump())
1542 db.add(db_case_alert_link)
1543 try:
1544 await db.commit()
backend/app/incidents/services/velo_sigma.py
+2 -2
@@ -342,7 +342,7 @@ class VeloSigmaExclusionService:
342
343 async def create_exclusion(self, exclusion: VeloSigmaExclusionCreate) -> VeloSigmaExclusion:
344 """Create a new exclusion rule."""
345 - exclusion_data = exclusion.dict()
345 + exclusion_data = exclusion.model_dump()
346
347 # Ensure created_by is set to something non-null
348 if not exclusion_data.get("created_by"):
@@ -453,7 +453,7 @@ class VelociraptorSigmaService:
453 if hasattr(parsed_event, "EventData"):
454 # Try to convert EventData to dict for context
455 try:
456 - event_context = parsed_event.EventData.dict()
456 + event_context = parsed_event.EventData.model_dump()
457 except AttributeError:
458 # If not directly convertible, extract key attributes
459 event_context = {
backend/app/integrations/alert_creation_settings/routes/alert_creation_settings.py
+5 -5
@@ -94,7 +94,7 @@ async def create_alert_creation_settings(
94 Returns:
95 AlertCreationSettings: The created alert creation setting.
96 """
97 - logger.info(f"alert_creation_settings: {alert_creation_settings.dict()}")
97 + logger.info(f"alert_creation_settings: {alert_creation_settings.model_dump()}")
98
99 result = await session.execute(
100 select(AlertCreationSettings).where(
@@ -113,7 +113,7 @@ async def create_alert_creation_settings(
113 )
114
115 alert_creation_settings_db = AlertCreationSettings(
116 - **alert_creation_settings.dict(exclude={"event_orders"}),
116 + **alert_creation_settings.model_dump(exclude={"event_orders"}),
117 )
118
119 if alert_creation_settings.event_orders is not None:
@@ -125,7 +125,7 @@ async def create_alert_creation_settings(
125 session.add(event_order_db)
126 for event_config in event_order.event_configs:
127 event_config_db = AlertCreationEventConfig(
128 - **event_config.dict(),
128 + **event_config.model_dump(),
129 event_order=event_order_db,
130 )
131 session.add(event_config_db)
@@ -226,7 +226,7 @@ async def add_event_order(
226 session.add(event_order_db)
227 for event_config in event_order.event_configs:
228 event_config_db = AlertCreationEventConfig(
229 - **event_config.dict(),
229 + **event_config.model_dump(),
230 event_order=event_order_db,
231 )
232 session.add(event_config_db)
@@ -293,7 +293,7 @@ async def update_event_orders(
293 # If it does, add the new EventConfig instances to it
294 for event_config in event_order.event_configs:
295 event_config_db = AlertCreationEventConfig(
296 - **event_config.dict(),
296 + **event_config.model_dump(),
297 event_order=existing_order,
298 )
299 session.add(event_config_db)
backend/app/integrations/alert_escalation/schema/escalate_alert.py
+4 -4
@@ -75,7 +75,7 @@ class GenericSourceModel(BaseModel):
75 model_config = ConfigDict(extra="allow")
76
77 def to_dict(self):
78 - return self.dict(exclude_none=True)
78 + return self.model_dump(exclude_none=True)
79
80
81 class GenericAlertModel(BaseModel):
@@ -145,7 +145,7 @@ class IrisAsset(BaseModel):
145 )
146
147 def to_dict(self):
148 - return self.dict(exclude_none=True)
148 + return self.model_dump(exclude_none=True)
149
150
151 class IrisIoc(BaseModel):
@@ -163,7 +163,7 @@ class IrisIoc(BaseModel):
163 ioc_type_id: int = Field(20, description="Type ID of the IoC", examples=[20])
164
165 def to_dict(self):
166 - return self.dict(exclude_none=True)
166 + return self.model_dump(exclude_none=True)
167
168
169 class IrisAlertContext(BaseModel):
@@ -215,4 +215,4 @@ class IrisAlertPayload(BaseModel):
215 model_config = ConfigDict(extra="allow")
216
217 def to_dict(self):
218 - return self.dict(exclude_none=True)
218 + return self.model_dump(exclude_none=True)
backend/app/integrations/bitdefender/services/provision.py
+3 -3
@@ -172,11 +172,11 @@ async def send_index_set_creation_request(
172 Returns:
173 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
174 """
175 - json_index_set = json.dumps(index_set.dict())
175 + json_index_set = json.dumps(index_set.model_dump())
176 logger.info(f"json_index_set set: {json_index_set}")
177 response_json = await send_post_request(
178 endpoint="/api/system/indices/index_sets",
179 - data=index_set.dict(),
179 + data=index_set.model_dump(),
180 )
181 return GraylogIndexSetCreationResponse(**response_json)
182
@@ -309,7 +309,7 @@ async def create_grafana_datasource(
309 readOnly=True,
310 )
311 results = grafana_client.datasource.create_datasource(
312 - datasource=datasource_payload.dict(),
312 + datasource=datasource_payload.model_dump(),
313 )
314 return GrafanaDataSourceCreationResponse(**results)
315
backend/app/integrations/carbonblack/services/provision.py
+5 -5
@@ -87,11 +87,11 @@ async def send_index_set_creation_request(
87 Returns:
88 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
89 """
90 - json_index_set = json.dumps(index_set.dict())
90 + json_index_set = json.dumps(index_set.model_dump())
91 logger.info(f"json_index_set set: {json_index_set}")
92 response_json = await send_post_request(
93 endpoint="/api/system/indices/index_sets",
94 - data=index_set.dict(),
94 + data=index_set.model_dump(),
95 )
96 return GraylogIndexSetCreationResponse(**response_json)
97
@@ -168,11 +168,11 @@ async def send_event_stream_creation_request(
168 Returns:
169 StreamCreationResponse: The response containing the created event stream.
170 """
171 - json_event_stream = json.dumps(event_stream.dict())
171 + json_event_stream = json.dumps(event_stream.model_dump())
172 logger.info(f"json_event_stream set: {json_event_stream}")
173 response_json = await send_post_request(
174 endpoint="/api/streams",
175 - data=event_stream.dict(),
175 + data=event_stream.model_dump(),
176 )
177 return StreamCreationResponse(**response_json)
178
@@ -261,7 +261,7 @@ async def create_grafana_datasource(
261 readOnly=True,
262 )
263 results = grafana_client.datasource.create_datasource(
264 - datasource=datasource_payload.dict(),
264 + datasource=datasource_payload.model_dump(),
265 )
266 return GrafanaDataSourceCreationResponse(**results)
267
backend/app/integrations/cato/services/provision.py
+5 -5
@@ -87,11 +87,11 @@ async def send_index_set_creation_request(
87 Returns:
88 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
89 """
90 - json_index_set = json.dumps(index_set.dict())
90 + json_index_set = json.dumps(index_set.model_dump())
91 logger.info(f"json_index_set set: {json_index_set}")
92 response_json = await send_post_request(
93 endpoint="/api/system/indices/index_sets",
94 - data=index_set.dict(),
94 + data=index_set.model_dump(),
95 )
96 return GraylogIndexSetCreationResponse(**response_json)
97
@@ -168,11 +168,11 @@ async def send_event_stream_creation_request(
168 Returns:
169 StreamCreationResponse: The response containing the created event stream.
170 """
171 - json_event_stream = json.dumps(event_stream.dict())
171 + json_event_stream = json.dumps(event_stream.model_dump())
172 logger.info(f"json_event_stream set: {json_event_stream}")
173 response_json = await send_post_request(
174 endpoint="/api/streams",
175 - data=event_stream.dict(),
175 + data=event_stream.model_dump(),
176 )
177 return StreamCreationResponse(**response_json)
178
@@ -261,7 +261,7 @@ async def create_grafana_datasource(
261 readOnly=True,
262 )
263 results = grafana_client.datasource.create_datasource(
264 - datasource=datasource_payload.dict(),
264 + datasource=datasource_payload.model_dump(),
265 )
266 return GrafanaDataSourceCreationResponse(**results)
267
backend/app/integrations/copilot_action/routes/copilot_action.py
+3 -3
@@ -490,19 +490,19 @@ async def invoke_action(body: InvokeCopilotActionBody, session: AsyncSession = D
490 # Return structured response
491 if len(failed_agents) == 0:
492 return InvokeCopilotActionResponse(
493 - responses=[response.dict() for response in responses],
493 + responses=[response.model_dump() for response in responses],
494 message=f"Successfully invoked action on all {len(successful_agents)} agent(s). Check the appropriate Grafana dashboard for results.",
495 success=True,
496 )
497 elif len(successful_agents) == 0:
498 return InvokeCopilotActionResponse(
499 - responses=[response.dict() for response in responses],
499 + responses=[response.model_dump() for response in responses],
500 message=f"Failed to invoke action on all {len(failed_agents)} agent(s)",
501 success=False,
502 )
503 else:
504 return InvokeCopilotActionResponse(
505 - responses=[response.dict() for response in responses],
505 + responses=[response.model_dump() for response in responses],
506 message=f"Partially successful: {len(successful_agents)} succeeded, {len(failed_agents)} failed",
507 success=True, # Consider partial success as success
508 )
backend/app/integrations/copilot_action/schema/copilot_action.py
+6 -8
@@ -8,7 +8,7 @@ from typing import Union
8
9 from pydantic import field_validator, BaseModel
10 from pydantic import Field
11 -from pydantic import validator
11 +from pydantic import model_validator
12
13
14 class Technology(str, Enum):
@@ -58,13 +58,11 @@ class ActiveResponseItem(BaseModel):
58 category: Optional[str] = None
59 tags: Optional[List[str]] = None
60
61 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
62 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
63 - @validator("icon", always=True)
64 - def set_icon_default(cls, v, values):
65 - if v is None and "technology" in values:
66 - return values["technology"].value.lower()
67 - return v
61 + @model_validator(mode="after")
62 + def set_icon_default(self):
63 + if self.icon is None and self.technology is not None:
64 + self.icon = self.technology.value.lower()
65 + return self
66
67
68 class InventoryQueryRequest(BaseModel):
backend/app/integrations/copilot_mcp/routes/copilot_mcp.py
+1 -1
@@ -91,7 +91,7 @@ async def get_mcp_server_details(mcp_server: MCPServerType) -> dict:
91 total_questions = len(ExampleQuestionsService.get_example_questions(mcp_server))
92
93 return {
94 - "server": server_info.dict(),
94 + "server": server_info.model_dump(),
95 "available_categories": categories,
96 "total_example_questions": total_questions,
97 "message": f"Successfully retrieved details for {mcp_server.value}",
backend/app/integrations/copilot_mcp/services/copilot_mcp.py
+2 -2
@@ -118,7 +118,7 @@ class MCPService:
118 is_cloud = cls.is_cloud_service(data.mcp_server)
119
120 logger.info(f"Sending MCP query to {data.mcp_server.value} ({'cloud' if is_cloud else 'local'}) at {full_url}")
121 - logger.debug(f"Query data: {data.dict()}")
121 + logger.debug(f"Query data: {data.model_dump()}")
122
123 # Set different timeout for cloud vs local services
124 timeout = 300 if is_cloud else 300
@@ -136,7 +136,7 @@ class MCPService:
136 async with httpx.AsyncClient() as client:
137 response = await client.post(
138 full_url,
139 - json=data.dict(),
139 + json=data.model_dump(),
140 headers=headers,
141 timeout=timeout,
142 )
backend/app/integrations/copilot_searches/routes/copilot_searches.py
+3 -3
@@ -99,7 +99,7 @@ async def check_if_event_definition_exists(event_definition_title: str) -> bool:
99 )
100
101 event_definitions_response = GraylogEventDefinitionsResponse(
102 - **event_definitions_response.dict(),
102 + **event_definitions_response.model_dump(),
103 )
104
105 existing_titles = [ed.title for ed in event_definitions_response.event_definitions]
@@ -623,7 +623,7 @@ async def check_graylog_provisioning_status(request: RulesByIdsRequest):
623 try:
624 ed_resp = await get_all_event_definitions()
625 if ed_resp.success:
626 - ed = GraylogEventDefinitionsResponse(**ed_resp.dict())
626 + ed = GraylogEventDefinitionsResponse(**ed_resp.model_dump())
627 existing_titles = {e.title for e in ed.event_definitions}
628 else:
629 warning = "Failed to read event definitions from Graylog"
@@ -673,7 +673,7 @@ async def bulk_provision_graylog_alerts(request: BulkProvisionGraylogAlertReques
673 try:
674 ed_resp = await get_all_event_definitions()
675 if ed_resp.success:
676 - ed = GraylogEventDefinitionsResponse(**ed_resp.dict())
676 + ed = GraylogEventDefinitionsResponse(**ed_resp.model_dump())
677 existing_titles = {e.title for e in ed.event_definitions}
678 except Exception as e:
679 # If we can't pre-fetch the existing list, fall back to skipping the
backend/app/integrations/crowdstrike/services/provision.py
+3 -3
@@ -103,11 +103,11 @@ async def send_index_set_creation_request(
103 Returns:
104 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
105 """
106 - json_index_set = json.dumps(index_set.dict())
106 + json_index_set = json.dumps(index_set.model_dump())
107 logger.info(f"json_index_set set: {json_index_set}")
108 response_json = await send_post_request(
109 endpoint="/api/system/indices/index_sets",
110 - data=index_set.dict(),
110 + data=index_set.model_dump(),
111 )
112 return GraylogIndexSetCreationResponse(**response_json)
113
@@ -240,7 +240,7 @@ async def create_grafana_datasource(
240 readOnly=True,
241 )
242 results = grafana_client.datasource.create_datasource(
243 - datasource=datasource_payload.dict(),
243 + datasource=datasource_payload.model_dump(),
244 )
245 return GrafanaDataSourceCreationResponse(**results)
246
backend/app/integrations/darktrace/services/provision.py
+5 -5
@@ -87,11 +87,11 @@ async def send_index_set_creation_request(
87 Returns:
88 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
89 """
90 - json_index_set = json.dumps(index_set.dict())
90 + json_index_set = json.dumps(index_set.model_dump())
91 logger.info(f"json_index_set set: {json_index_set}")
92 response_json = await send_post_request(
93 endpoint="/api/system/indices/index_sets",
94 - data=index_set.dict(),
94 + data=index_set.model_dump(),
95 )
96 return GraylogIndexSetCreationResponse(**response_json)
97
@@ -168,11 +168,11 @@ async def send_event_stream_creation_request(
168 Returns:
169 StreamCreationResponse: The response containing the created event stream.
170 """
171 - json_event_stream = json.dumps(event_stream.dict())
171 + json_event_stream = json.dumps(event_stream.model_dump())
172 logger.info(f"json_event_stream set: {json_event_stream}")
173 response_json = await send_post_request(
174 endpoint="/api/streams",
175 - data=event_stream.dict(),
175 + data=event_stream.model_dump(),
176 )
177 return StreamCreationResponse(**response_json)
178
@@ -261,7 +261,7 @@ async def create_grafana_datasource(
261 readOnly=True,
262 )
263 results = grafana_client.datasource.create_datasource(
264 - datasource=datasource_payload.dict(),
264 + datasource=datasource_payload.model_dump(),
265 )
266 return GrafanaDataSourceCreationResponse(**results)
267
backend/app/integrations/defender_for_endpoint/services/provision.py
+3 -3
@@ -106,11 +106,11 @@ async def send_index_set_creation_request(
106 Returns:
107 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
108 """
109 - json_index_set = json.dumps(index_set.dict())
109 + json_index_set = json.dumps(index_set.model_dump())
110 logger.info(f"json_index_set set: {json_index_set}")
111 response_json = await send_post_request(
112 endpoint="/api/system/indices/index_sets",
113 - data=index_set.dict(),
113 + data=index_set.model_dump(),
114 )
115 return GraylogIndexSetCreationResponse(**response_json)
116
@@ -239,7 +239,7 @@ async def create_grafana_datasource(
239 readOnly=True,
240 )
241 results = grafana_client.datasource.create_datasource(
242 - datasource=datasource_payload.dict(),
242 + datasource=datasource_payload.model_dump(),
243 )
244 return GrafanaDataSourceCreationResponse(**results)
245
backend/app/integrations/duo/services/provision.py
+5 -5
@@ -87,11 +87,11 @@ async def send_index_set_creation_request(
87 Returns:
88 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
89 """
90 - json_index_set = json.dumps(index_set.dict())
90 + json_index_set = json.dumps(index_set.model_dump())
91 logger.info(f"json_index_set set: {json_index_set}")
92 response_json = await send_post_request(
93 endpoint="/api/system/indices/index_sets",
94 - data=index_set.dict(),
94 + data=index_set.model_dump(),
95 )
96 return GraylogIndexSetCreationResponse(**response_json)
97
@@ -168,11 +168,11 @@ async def send_event_stream_creation_request(
168 Returns:
169 StreamCreationResponse: The response containing the created event stream.
170 """
171 - json_event_stream = json.dumps(event_stream.dict())
171 + json_event_stream = json.dumps(event_stream.model_dump())
172 logger.info(f"json_event_stream set: {json_event_stream}")
173 response_json = await send_post_request(
174 endpoint="/api/streams",
175 - data=event_stream.dict(),
175 + data=event_stream.model_dump(),
176 )
177 return StreamCreationResponse(**response_json)
178
@@ -261,7 +261,7 @@ async def create_grafana_datasource(
261 readOnly=True,
262 )
263 results = grafana_client.datasource.create_datasource(
264 - datasource=datasource_payload.dict(),
264 + datasource=datasource_payload.model_dump(),
265 )
266 return GrafanaDataSourceCreationResponse(**results)
267
backend/app/integrations/github_audit/routes/github_audit.py
+4 -4
@@ -189,7 +189,7 @@ async def update_config(
189 raise HTTPException(status_code=404, detail="Configuration not found")
190
191 # Update fields that were provided
192 - update_data = config_update.dict(exclude_unset=True)
192 + update_data = config_update.model_dump(exclude_unset=True)
193
194 for field, value in update_data.items():
195 if value is not None:
@@ -342,8 +342,8 @@ async def run_audit_from_config(
342 report.score = audit_response.summary.score
343 report.grade = audit_response.summary.grade
344
345 - report.full_report = audit_response.dict()
346 - report.top_findings = [f.dict() for f in audit_response.top_findings[:20]]
345 + report.full_report = audit_response.model_dump()
346 + report.top_findings = [f.model_dump() for f in audit_response.top_findings[:20]]
347
348 # Update config with last audit info
349 config.last_audit_at = end_time
@@ -683,7 +683,7 @@ async def update_exclusion(
683 if not exclusion:
684 raise HTTPException(status_code=404, detail="Exclusion not found")
685
686 - update_data = exclusion_update.dict(exclude_unset=True)
686 + update_data = exclusion_update.model_dump(exclude_unset=True)
687 for field, value in update_data.items():
688 if value is not None:
689 setattr(exclusion, field, value)
backend/app/integrations/huntress/services/provision.py
+5 -5
@@ -87,11 +87,11 @@ async def send_index_set_creation_request(
87 Returns:
88 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
89 """
90 - json_index_set = json.dumps(index_set.dict())
90 + json_index_set = json.dumps(index_set.model_dump())
91 logger.info(f"json_index_set set: {json_index_set}")
92 response_json = await send_post_request(
93 endpoint="/api/system/indices/index_sets",
94 - data=index_set.dict(),
94 + data=index_set.model_dump(),
95 )
96 return GraylogIndexSetCreationResponse(**response_json)
97
@@ -168,11 +168,11 @@ async def send_event_stream_creation_request(
168 Returns:
169 StreamCreationResponse: The response containing the created event stream.
170 """
171 - json_event_stream = json.dumps(event_stream.dict())
171 + json_event_stream = json.dumps(event_stream.model_dump())
172 logger.info(f"json_event_stream set: {json_event_stream}")
173 response_json = await send_post_request(
174 endpoint="/api/streams",
175 - data=event_stream.dict(),
175 + data=event_stream.model_dump(),
176 )
177 return StreamCreationResponse(**response_json)
178
@@ -261,7 +261,7 @@ async def create_grafana_datasource(
261 readOnly=True,
262 )
263 results = grafana_client.datasource.create_datasource(
264 - datasource=datasource_payload.dict(),
264 + datasource=datasource_payload.model_dump(),
265 )
266 return GrafanaDataSourceCreationResponse(**results)
267
backend/app/integrations/mimecast/services/mimecast.py
+3 -3
@@ -411,14 +411,14 @@ async def invoke_mimecast_api_ttp_urls(
411 """Invoke the Mimecast API call to get TTP URLs."""
412 logger.info("Mimecast TTP URL request received")
413 request_body = await create_ttp_request_body(mimecast_request)
414 - request_dict = request_body.dict(by_alias=True)
414 + request_dict = request_body.model_dump(by_alias=True)
415 logger.info(f"Request: {request_dict}")
416 for item in request_dict["data"]:
417 item["from"] = await custom_datetime_format(item["from"])
418 item["to"] = await custom_datetime_format(item["to"])
419 response = requests.post(
420 url=mimecast_request.BaseURL + "/api/ttp/url/get-logs",
421 - headers=mimecast_request.headers.dict(by_alias=True),
421 + headers=mimecast_request.headers.model_dump(by_alias=True),
422 data=str(request_dict),
423 )
424 return TtpURLResponseBody(**response.json())
@@ -454,7 +454,7 @@ async def get_ttp_urls(
454 customer_code=customer_code,
455 integration="mimecast",
456 version="1.0",
457 - **data.dict(by_alias=True),
457 + **data.model_dump(by_alias=True),
458 )
459 await event_shipper(message)
460
backend/app/integrations/mimecast/services/provision.py
+5 -5
@@ -84,11 +84,11 @@ async def send_index_set_creation_request(
84 Returns:
85 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
86 """
87 - json_index_set = json.dumps(index_set.dict())
87 + json_index_set = json.dumps(index_set.model_dump())
88 logger.info(f"json_index_set set: {json_index_set}")
89 response_json = await send_post_request(
90 endpoint="/api/system/indices/index_sets",
91 - data=index_set.dict(),
91 + data=index_set.model_dump(),
92 )
93 return GraylogIndexSetCreationResponse(**response_json)
94
@@ -165,11 +165,11 @@ async def send_event_stream_creation_request(
165 Returns:
166 StreamCreationResponse: The response containing the created event stream.
167 """
168 - json_event_stream = json.dumps(event_stream.dict())
168 + json_event_stream = json.dumps(event_stream.model_dump())
169 logger.info(f"json_event_stream set: {json_event_stream}")
170 response_json = await send_post_request(
171 endpoint="/api/streams",
172 - data=event_stream.dict(),
172 + data=event_stream.model_dump(),
173 )
174 return StreamCreationResponse(**response_json)
175
@@ -258,7 +258,7 @@ async def create_grafana_datasource(
258 readOnly=True,
259 )
260 results = grafana_client.datasource.create_datasource(
261 - datasource=datasource_payload.dict(),
261 + datasource=datasource_payload.model_dump(),
262 )
263 return GrafanaDataSourceCreationResponse(**results)
264
backend/app/integrations/modules/schema/sap_siem.py
+1 -1
@@ -151,7 +151,7 @@ class CollectSapSiemRequest(BaseModel):
151 return values
152
153 def to_dict(self):
154 - return self.dict()
154 + return self.model_dump()
155
156
157 class InvokeSapSiemAnalysis(BaseModel):
backend/app/integrations/modules/services/carbonblack.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_carbonblack_module(data: CollectCarbonBlack, license_k
11 Args:
12 data (CollectHuntress): The data to send to the copilot-huntress-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-carbonblack-module/collect with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-carbonblack-module/collect with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-carbonblack-module/collect",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 # params={"license_key": license_key, "feature_name": "CARBONBLACK"},
20 timeout=120,
21 )
backend/app/integrations/modules/services/cato.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_cato_module(data: CollectCato, license_key: str = None
11 Args:
12 data (CollectCato): The data to send to the copilot-cato-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-cato-module/collect with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-cato-module/collect with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-cato-module/collect",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 timeout=120,
20 )
21 return None
backend/app/integrations/modules/services/darktrace.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_darktrace_module(data: CollectDarktrace):
11 Args:
12 data (CollectDarktrace): The data to send to the copilot-darktrace-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-darktrace-module/all_logs with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-darktrace-module/all_logs with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-darktrace-module/all_logs",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 timeout=120,
20 )
21 return None
backend/app/integrations/modules/services/duo.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_duo_module(data: CollectDuo):
11 Args:
12 data (CollectDuo): The data to send to the copilot-duo-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-duo-module/auth with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-duo-module/auth with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-duo-module/auth",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 timeout=120,
20 )
21 return None
backend/app/integrations/modules/services/huntress.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_huntress_module(data: CollectHuntress, license_key: st
11 Args:
12 data (CollectHuntress): The data to send to the copilot-huntress-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-huntress-module/collect with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-huntress-module/collect with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-huntress-module/collect",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 # params={"license_key": license_key, "feature_name": "HUNTRESS"},
20 timeout=120,
21 )
backend/app/integrations/modules/services/mimecast.py
+2 -2
@@ -11,11 +11,11 @@ async def post_to_copilot_mimecast_module(data: CollectMimecast, license_key: st
11 Args:
12 data (CollectMimecast): The data to send to the copilot-mimecast-module Docker container.
13 """
14 - logger.info(f"Sending POST request to http://copilot-huntress-module/collect with data: {data.dict()}")
14 + logger.info(f"Sending POST request to http://copilot-huntress-module/collect with data: {data.model_dump()}")
15 async with httpx.AsyncClient() as client:
16 await client.post(
17 "http://copilot-mimecast-module/collect",
18 - json=data.dict(),
18 + json=data.model_dump(),
19 # params={"license_key": license_key, "feature_name": "MIMECAST"},
20 timeout=120,
21 )
backend/app/integrations/modules/services/sap_siem/collect.py
+15 -15
@@ -12,7 +12,7 @@ async def post_to_copilot_sap_module_collect(data: CollectSapSiemRequest):
12 Args:
13 data (CollectHuntress): The data to send to the copilot-sap-module Docker container.
14 """
15 - logger.info(f"Sending POST request to http://copilot-sap-module/collect with data: {data.dict()}")
15 + logger.info(f"Sending POST request to http://copilot-sap-module/collect with data: {data.model_dump()}")
16 async with httpx.AsyncClient() as client:
17 try:
18 response = await client.post(
@@ -34,13 +34,13 @@ async def post_to_copilot_sap_module_sap_siem_successful_user_login_with_differe
34 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
35 """
36 logger.info(
37 - f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_ip with data: {data.dict()}",
37 + f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_ip with data: {data.model_dump()}",
38 )
39 async with httpx.AsyncClient() as client:
40 try:
41 response = await client.post(
42 "http://copilot-sap-module/sap-siem/successful_user_login_with_different_ip",
43 - json=data.dict(),
43 + json=data.model_dump(),
44 timeout=120,
45 )
46 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -57,13 +57,13 @@ async def post_to_copilot_sap_module_same_user_failed_login_from_different_ip(da
57 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
58 """
59 logger.info(
60 - f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_ip with data: {data.dict()}",
60 + f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_ip with data: {data.model_dump()}",
61 )
62 async with httpx.AsyncClient() as client:
63 try:
64 response = await client.post(
65 "http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_ip",
66 - json=data.dict(),
66 + json=data.model_dump(),
67 timeout=120,
68 )
69 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -80,13 +80,13 @@ async def post_to_copilot_sap_module_same_user_failed_login_from_different_geo_l
80 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
81 """
82 logger.info(
83 - f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_geo_location with data: {data.dict()}",
83 + f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_geo_location with data: {data.model_dump()}",
84 )
85 async with httpx.AsyncClient() as client:
86 try:
87 response = await client.post(
88 "http://copilot-sap-module/sap-siem/same_user_failed_login_from_different_geo_location",
89 - json=data.dict(),
89 + json=data.model_dump(),
90 timeout=120,
91 )
92 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -103,13 +103,13 @@ async def post_to_copilot_sap_module_same_user_successful_login_from_different_g
103 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
104 """
105 logger.info(
106 - f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_successful_login_from_different_geo_location with data: {data.dict()}",
106 + f"Sending POST request to http://copilot-sap-module/sap-siem/same_user_successful_login_from_different_geo_location with data: {data.model_dump()}",
107 )
108 async with httpx.AsyncClient() as client:
109 try:
110 response = await client.post(
111 "http://copilot-sap-module/sap-siem/same_user_successful_login_from_different_geo_location",
112 - json=data.dict(),
112 + json=data.model_dump(),
113 timeout=120,
114 )
115 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -126,13 +126,13 @@ async def post_to_copilot_sap_module_brute_force_failed_logins_multiple_ips(data
126 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
127 """
128 logger.info(
129 - f"Sending POST request to http://copilot-sap-module/sap-siem/brute_force_failed_logins_multiple_ips with data: {data.dict()}",
129 + f"Sending POST request to http://copilot-sap-module/sap-siem/brute_force_failed_logins_multiple_ips with data: {data.model_dump()}",
130 )
131 async with httpx.AsyncClient() as client:
132 try:
133 response = await client.post(
134 "http://copilot-sap-module/sap-siem/brute_force_failed_logins_multiple_ips",
135 - json=data.dict(),
135 + json=data.model_dump(),
136 timeout=120,
137 )
138 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -148,12 +148,12 @@ async def post_to_copilot_sap_module_brute_force_failed_logins_same_ip(data: Inv
148 Args:
149 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
150 """
151 - logger.info(f"Sending POST request to http://copilot-sap-module/sap-siem/brute_force_failed_logins_same_ip with data: {data.dict()}")
151 + logger.info(f"Sending POST request to http://copilot-sap-module/sap-siem/brute_force_failed_logins_same_ip with data: {data.model_dump()}")
152 async with httpx.AsyncClient() as client:
153 try:
154 response = await client.post(
155 "http://copilot-sap-module/sap-siem/brute_force_failed_logins_same_ip",
156 - json=data.dict(),
156 + json=data.model_dump(),
157 timeout=120,
158 )
159 logger.info(f"Response from copilot-sap-module: {response.json()}")
@@ -170,13 +170,13 @@ async def post_to_copilot_sap_module_successful_login_after_multiple_failed_logi
170 data (InvokeSapSiemAnalysis): The data to send to the copilot-sap-module Docker container.
171 """
172 logger.info(
173 - f"Sending POST request to http://copilot-sap-module/sap-siem/successful_login_after_multiple_failed_logins with data: {data.dict()}",
173 + f"Sending POST request to http://copilot-sap-module/sap-siem/successful_login_after_multiple_failed_logins with data: {data.model_dump()}",
174 )
175 async with httpx.AsyncClient() as client:
176 try:
177 response = await client.post(
178 "http://copilot-sap-module/sap-siem/successful_login_after_multiple_failed_logins",
179 - json=data.dict(),
179 + json=data.model_dump(),
180 timeout=120,
181 )
182 logger.info(f"Response from copilot-sap-module: {response.json()}")
backend/app/integrations/monitoring_alert/routes/provision.py
+1 -1
@@ -656,7 +656,7 @@ async def check_if_event_definition_exists(event_definition: str) -> bool:
656 detail="Failed to collect event definitions",
657 )
658 event_definitions_response = GraylogEventDefinitionsResponse(
659 - **event_definitions_response.dict(),
659 + **event_definitions_response.model_dump(),
660 )
661 logger.info(
662 f"Event definitions collected: {event_definitions_response.event_definitions}",
backend/app/integrations/monitoring_alert/services/provision.py
+58 -58
@@ -89,7 +89,7 @@ async def check_if_url_whitelist_entry_exists(url: str) -> bool:
89 detail="Failed to collect url whitelist entries",
90 )
91 url_whitelist_entries_response = UrlWhitelistEntryResponse(
92 - **url_whitelist_entries_response.dict(),
92 + **url_whitelist_entries_response.model_dump(),
93 )
94 logger.info(
95 f"Url whitelist entries collected: {url_whitelist_entries_response.url_whitelist_entries}",
@@ -117,7 +117,7 @@ async def get_notification_id(notification_title: str) -> Optional[str]:
117 detail="Failed to collect event notifications",
118 )
119 event_notifications_response = GraylogEventNotificationsResponse(
120 - **event_notifications_response.dict(),
120 + **event_notifications_response.model_dump(),
121 )
122 logger.info(
123 f"Event notifications collected: {event_notifications_response.event_notifications}",
@@ -144,7 +144,7 @@ async def build_url_whitelisted_entries(
144 detail="Failed to collect url whitelist entries",
145 )
146 url_whitelist_entries_response = UrlWhitelistEntryResponse(
147 - **url_whitelist_entries_response.dict(),
147 + **url_whitelist_entries_response.model_dump(),
148 )
149 logger.info(f"Url whitelist entries collected: {url_whitelist_entries_response}")
150 url_whitelist_entries = url_whitelist_entries_response.url_whitelist_entries.entries
@@ -167,10 +167,10 @@ async def provision_webhook_url_whitelist(
167 Returns:
168 bool: True if the webhook URL was provisioned successfully, False otherwise.
169 """
170 - logger.info(f"Provisioning URL Whitelist: {whitelist_url_model.dict()}")
170 + logger.info(f"Provisioning URL Whitelist: {whitelist_url_model.model_dump()}")
171 response = await send_put_request(
172 endpoint="/api/system/urlwhitelist",
173 - data=whitelist_url_model.dict(),
173 + data=whitelist_url_model.model_dump(),
174 )
175 logger.info(f"URL Whitelist provisioned: {response}")
176 if response["success"]:
@@ -195,7 +195,7 @@ async def check_if_event_notification_exists(event_notification: str) -> bool:
195 detail="Failed to collect event notifications",
196 )
197 event_notifications_response = GraylogEventNotificationsResponse(
198 - **event_notifications_response.dict(),
198 + **event_notifications_response.model_dump(),
199 )
200 logger.info(
201 f"Event notifications collected: {event_notifications_response.event_notifications}",
@@ -221,7 +221,7 @@ async def provision_webhook(
221 """
222 response = await send_post_request(
223 endpoint="/api/events/notifications",
224 - data=webhook_model.dict(),
224 + data=webhook_model.model_dump(),
225 )
226 if response["success"]:
227 logger.info(f"response: {response}")
@@ -251,7 +251,7 @@ async def provision_alert_definition(
251
252 response = await send_post_request(
253 endpoint="/api/events/definitions",
254 - data=alert_definition_model.dict(),
254 + data=alert_definition_model.model_dump(),
255 )
256 logger.info(f"Graylog alert definition provisioned response: {response}")
257 if response["success"]:
@@ -270,7 +270,7 @@ async def provision_wazuh_monitoring_alert(
270 """
271 #
272 logger.info(
273 - f"Invoking provision_wazuh_monitoring_alert with request: {request.dict()}",
273 + f"Invoking provision_wazuh_monitoring_alert with request: {request.model_dump()}",
274 )
275 # ! TODO Commenting out for now since the plan is to pull from gl-events ! #
276 # notification_exists = await check_if_event_notification_exists("SEND TO COPILOT")
@@ -400,7 +400,7 @@ async def provision_suricata_monitoring_alert(
400 """
401 #
402 logger.info(
403 - f"Invoking provision_suricata_monitoring_alert with request: {request.dict()}",
403 + f"Invoking provision_suricata_monitoring_alert with request: {request.model_dump()}",
404 )
405 await provision_alert_definition(
406 GraylogAlertProvisionModel(
@@ -493,7 +493,7 @@ async def provision_office365_exchange_online_alert(
493 """
494 #
495 logger.info(
496 - f"Invoking provision_office365_exchange_online_alert with request: {request.dict()}",
496 + f"Invoking provision_office365_exchange_online_alert with request: {request.model_dump()}",
497 )
498 await provision_alert_definition(
499 GraylogAlertProvisionModel(
@@ -586,7 +586,7 @@ async def provision_office365_threat_intel_alert(
586 """
587 #
588 logger.info(
589 - f"Invoking provision_office365_threat_intel_alert with request: {request.dict()}",
589 + f"Invoking provision_office365_threat_intel_alert with request: {request.model_dump()}",
590 )
591 await provision_alert_definition(
592 GraylogAlertProvisionModel(
@@ -679,7 +679,7 @@ async def provision_crowdstrike_monitoring_alert(
679 """
680 #
681 logger.info(
682 - f"Invoking provision_crowdstrike_monitoring_alert with request: {request.dict()}",
682 + f"Invoking provision_crowdstrike_monitoring_alert with request: {request.model_dump()}",
683 )
684 await provision_alert_definition(
685 GraylogAlertProvisionModel(
@@ -772,7 +772,7 @@ async def provision_fortinet_system_monitoring_alert(
772 """
773 #
774 logger.info(
775 - f"Invoking provision_fortinet_system_monitoring_alert with request: {request.dict()}",
775 + f"Invoking provision_fortinet_system_monitoring_alert with request: {request.model_dump()}",
776 )
777 await provision_alert_definition(
778 GraylogAlertProvisionModel(
@@ -865,7 +865,7 @@ async def provision_fortinet_utm_monitoring_alert(
865 """
866 #
867 logger.info(
868 - f"Invoking provision_fortinet_utm_monitoring_alert with request: {request.dict()}",
868 + f"Invoking provision_fortinet_utm_monitoring_alert with request: {request.model_dump()}",
869 )
870 await provision_alert_definition(
871 GraylogAlertProvisionModel(
@@ -955,7 +955,7 @@ async def provision_fortinet_fortiweb_path_traversal_vulnerability_exploitation_
955 """
956 logger.info(
957 "Invoking provision_fortinet_fortiweb_path_traversal_vulnerability_exploitation_attempt_monitoring_alert "
958 - f"with request: {request.dict()}",
958 + f"with request: {request.model_dump()}",
959 )
960 await provision_alert_definition(
961 GraylogAlertProvisionModel(
@@ -1013,7 +1013,7 @@ async def provision_fortinet_wids_wireless_valid_client_misassociation_detected_
1013 """
1014 logger.info(
1015 "Invoking provision_fortinet_wids_wireless_valid_client_misassociation_detected_monitoring_alert "
1016 - f"with request: {request.dict()}",
1016 + f"with request: {request.model_dump()}",
1017 )
1018 await provision_alert_definition(
1019 GraylogAlertProvisionModel(
@@ -1070,7 +1070,7 @@ async def provision_fortinet_wids_wireless_management_flooding_detected_monitori
1070 Provisions Fortinet WIDS Wireless Management Flooding Detected monitoring alert.
1071 """
1072 logger.info(
1073 - "Invoking provision_fortinet_wids_wireless_management_flooding_detected_monitoring_alert " f"with request: {request.dict()}",
1073 + "Invoking provision_fortinet_wids_wireless_management_flooding_detected_monitoring_alert " f"with request: {request.model_dump()}",
1074 )
1075 await provision_alert_definition(
1076 GraylogAlertProvisionModel(
@@ -1127,7 +1127,7 @@ async def provision_fortinet_wids_wireless_eapol_packet_flooding_detected_monito
1127 Provisions Fortinet WIDS Wireless EAPOL Packet Flooding Detected monitoring alert.
1128 """
1129 logger.info(
1130 - "Invoking provision_fortinet_wids_wireless_eapol_packet_flooding_detected_monitoring_alert " f"with request: {request.dict()}",
1130 + "Invoking provision_fortinet_wids_wireless_eapol_packet_flooding_detected_monitoring_alert " f"with request: {request.model_dump()}",
1131 )
1132 await provision_alert_definition(
1133 GraylogAlertProvisionModel(
@@ -1184,7 +1184,7 @@ async def provision_fortinet_wids_rogue_access_point_detected_monitoring_alert(
1184 Provisions Fortinet WIDS Rogue Access Point Detected monitoring alert.
1185 """
1186 logger.info(
1187 - "Invoking provision_fortinet_wids_rogue_access_point_detected_monitoring_alert " f"with request: {request.dict()}",
1187 + "Invoking provision_fortinet_wids_rogue_access_point_detected_monitoring_alert " f"with request: {request.model_dump()}",
1188 )
1189 await provision_alert_definition(
1190 GraylogAlertProvisionModel(
@@ -1241,7 +1241,7 @@ async def provision_fortinet_wids_wireless_long_duration_attack_detected_monitor
1241 Provisions Fortinet WIDS Wireless Long Duration Attack Detected monitoring alert.
1242 """
1243 logger.info(
1244 - "Invoking provision_fortinet_wids_wireless_long_duration_attack_detected_monitoring_alert " f"with request: {request.dict()}",
1244 + "Invoking provision_fortinet_wids_wireless_long_duration_attack_detected_monitoring_alert " f"with request: {request.model_dump()}",
1245 )
1246 await provision_alert_definition(
1247 GraylogAlertProvisionModel(
@@ -1298,7 +1298,7 @@ async def provision_fortinet_firewall_virus_detected_monitoring_alert(
1298 Provisions Fortinet Firewall Virus Detected monitoring alert.
1299 """
1300 logger.info(
1301 - "Invoking provision_fortinet_firewall_virus_detected_monitoring_alert " f"with request: {request.dict()}",
1301 + "Invoking provision_fortinet_firewall_virus_detected_monitoring_alert " f"with request: {request.model_dump()}",
1302 )
1303 await provision_alert_definition(
1304 GraylogAlertProvisionModel(
@@ -1355,7 +1355,7 @@ async def provision_fortinet_wids_wireless_threat_detected_monitoring_alert(
1355 Provisions Fortinet WIDS Wireless Threat Detected monitoring alert.
1356 """
1357 logger.info(
1358 - "Invoking provision_fortinet_wids_wireless_threat_detected_monitoring_alert " f"with request: {request.dict()}",
1358 + "Invoking provision_fortinet_wids_wireless_threat_detected_monitoring_alert " f"with request: {request.model_dump()}",
1359 )
1360 await provision_alert_definition(
1361 GraylogAlertProvisionModel(
@@ -1412,7 +1412,7 @@ async def provision_fortinet_wids_wireless_invalid_mac_oui_detected_monitoring_a
1412 Provisions Fortinet WIDS Wireless Invalid MAC OUI Detected monitoring alert.
1413 """
1414 logger.info(
1415 - "Invoking provision_fortinet_wids_wireless_invalid_mac_oui_detected_monitoring_alert " f"with request: {request.dict()}",
1415 + "Invoking provision_fortinet_wids_wireless_invalid_mac_oui_detected_monitoring_alert " f"with request: {request.model_dump()}",
1416 )
1417 await provision_alert_definition(
1418 GraylogAlertProvisionModel(
@@ -1469,7 +1469,7 @@ async def provision_fortinet_wids_wireless_asleap_attack_detected_monitoring_ale
1469 Provisions Fortinet WIDS Wireless Asleap Attack Detected monitoring alert.
1470 """
1471 logger.info(
1472 - "Invoking provision_fortinet_wids_wireless_asleap_attack_detected_monitoring_alert " f"with request: {request.dict()}",
1472 + "Invoking provision_fortinet_wids_wireless_asleap_attack_detected_monitoring_alert " f"with request: {request.model_dump()}",
1473 )
1474 await provision_alert_definition(
1475 GraylogAlertProvisionModel(
@@ -1526,7 +1526,7 @@ async def provision_fortinet_ips_malicious_url_detected_monitoring_alert(
1526 Provisions Fortinet IPS Malicious URL Detected monitoring alert.
1527 """
1528 logger.info(
1529 - "Invoking provision_fortinet_ips_malicious_url_detected_monitoring_alert " f"with request: {request.dict()}",
1529 + "Invoking provision_fortinet_ips_malicious_url_detected_monitoring_alert " f"with request: {request.model_dump()}",
1530 )
1531 await provision_alert_definition(
1532 GraylogAlertProvisionModel(
@@ -1583,7 +1583,7 @@ async def provision_fortinet_ips_botnet_activity_detected_monitoring_alert(
1583 Provisions Fortinet IPS Botnet Activity Detected monitoring alert.
1584 """
1585 logger.info(
1586 - "Invoking provision_fortinet_ips_botnet_activity_detected_monitoring_alert " f"with request: {request.dict()}",
1586 + "Invoking provision_fortinet_ips_botnet_activity_detected_monitoring_alert " f"with request: {request.model_dump()}",
1587 )
1588 await provision_alert_definition(
1589 GraylogAlertProvisionModel(
@@ -1640,7 +1640,7 @@ async def provision_fortinet_admin_user_created_from_public_ip_monitoring_alert(
1640 Provisions Fortinet Admin User Created from Public IP monitoring alert.
1641 """
1642 logger.info(
1643 - "Invoking provision_fortinet_admin_user_created_from_public_ip_monitoring_alert " f"with request: {request.dict()}",
1643 + "Invoking provision_fortinet_admin_user_created_from_public_ip_monitoring_alert " f"with request: {request.model_dump()}",
1644 )
1645 await provision_alert_definition(
1646 GraylogAlertProvisionModel(
@@ -1698,7 +1698,7 @@ async def provision_fortinet_suspicious_config_file_access_from_external_network
1698 """
1699 logger.info(
1700 "Invoking provision_fortinet_suspicious_config_file_access_from_external_network_monitoring_alert "
1701 - f"with request: {request.dict()}",
1701 + f"with request: {request.model_dump()}",
1702 )
1703 await provision_alert_definition(
1704 GraylogAlertProvisionModel(
@@ -1755,7 +1755,7 @@ async def provision_fortinet_wids_wireless_weak_encryption_detected_monitoring_a
1755 Provisions Fortinet WIDS Wireless Weak Encryption Detected monitoring alert.
1756 """
1757 logger.info(
1758 - "Invoking provision_fortinet_wids_wireless_weak_encryption_detected_monitoring_alert " f"with request: {request.dict()}",
1758 + "Invoking provision_fortinet_wids_wireless_weak_encryption_detected_monitoring_alert " f"with request: {request.model_dump()}",
1759 )
1760 await provision_alert_definition(
1761 GraylogAlertProvisionModel(
@@ -1812,7 +1812,7 @@ async def provision_fortinet_suspicious_super_admin_login_detected_monitoring_al
1812 Provisions Fortinet Suspicious Super Admin Login Detected monitoring alert.
1813 """
1814 logger.info(
1815 - "Invoking provision_fortinet_suspicious_super_admin_login_detected_monitoring_alert " f"with request: {request.dict()}",
1815 + "Invoking provision_fortinet_suspicious_super_admin_login_detected_monitoring_alert " f"with request: {request.model_dump()}",
1816 )
1817 await provision_alert_definition(
1818 GraylogAlertProvisionModel(
@@ -1874,7 +1874,7 @@ async def provision_paloalto_monitoring_alert(
1874 """
1875 #
1876 logger.info(
1877 - f"Invoking provision_paloalto_monitoring_alert with request: {request.dict()}",
1877 + f"Invoking provision_paloalto_monitoring_alert with request: {request.model_dump()}",
1878 )
1879 await provision_alert_definition(
1880 GraylogAlertProvisionModel(
@@ -1963,7 +1963,7 @@ async def provision_paloalto_firewall_traffic_to_phishing_url_allowed_monitoring
1963 Provisions PaloAlto Firewall Traffic to Phishing URL Allowed monitoring alert.
1964 """
1965 logger.info(
1966 - "Invoking provision_paloalto_firewall_traffic_to_phishing_url_allowed_monitoring_alert " f"with request: {request.dict()}",
1966 + "Invoking provision_paloalto_firewall_traffic_to_phishing_url_allowed_monitoring_alert " f"with request: {request.model_dump()}",
1967 )
1968 await provision_alert_definition(
1969 GraylogAlertProvisionModel(
@@ -2035,7 +2035,7 @@ async def provision_paloalto_firewall_traffic_to_malicious_url_allowed_monitorin
2035 Provisions PaloAlto Firewall Traffic to Malicious URL Allowed monitoring alert.
2036 """
2037 logger.info(
2038 - "Invoking provision_paloalto_firewall_traffic_to_malicious_url_allowed_monitoring_alert " f"with request: {request.dict()}",
2038 + "Invoking provision_paloalto_firewall_traffic_to_malicious_url_allowed_monitoring_alert " f"with request: {request.model_dump()}",
2039 )
2040 await provision_alert_definition(
2041 GraylogAlertProvisionModel(
@@ -2108,7 +2108,7 @@ async def provision_paloalto_firewall_virus_allowed_monitoring_alert(
2108 Provisions PaloAlto Firewall Virus Allowed monitoring alert.
2109 """
2110 logger.info(
2111 - "Invoking provision_paloalto_firewall_virus_allowed_monitoring_alert " f"with request: {request.dict()}",
2111 + "Invoking provision_paloalto_firewall_virus_allowed_monitoring_alert " f"with request: {request.model_dump()}",
2112 )
2113 await provision_alert_definition(
2114 GraylogAlertProvisionModel(
@@ -2179,7 +2179,7 @@ async def provision_paloalto_firewall_tor_traffic_allowed_monitoring_alert(
2179 Provisions PaloAlto Firewall TOR Traffic Allowed monitoring alert.
2180 """
2181 logger.info(
2182 - "Invoking provision_paloalto_firewall_tor_traffic_allowed_monitoring_alert " f"with request: {request.dict()}",
2182 + "Invoking provision_paloalto_firewall_tor_traffic_allowed_monitoring_alert " f"with request: {request.model_dump()}",
2183 )
2184 await provision_alert_definition(
2185 GraylogAlertProvisionModel(
@@ -2247,7 +2247,7 @@ async def provision_paloalto_firewall_medium_severity_correlation_event_detected
2247 """
2248 logger.info(
2249 "Invoking provision_paloalto_firewall_medium_severity_correlation_event_detected_monitoring_alert "
2250 - f"with request: {request.dict()}",
2250 + f"with request: {request.model_dump()}",
2251 )
2252 await provision_alert_definition(
2253 GraylogAlertProvisionModel(
@@ -2318,7 +2318,7 @@ async def provision_sentinelone_new_active_threat_malicious_detected_monitoring_
2318 Provisions SentinelOne: New Active Threat Malicious Detected.
2319 """
2320 logger.info(
2321 - "Invoking provision_sentinelone_new_active_threat_malicious_detected_monitoring_alert " f"with request: {request.dict()}",
2321 + "Invoking provision_sentinelone_new_active_threat_malicious_detected_monitoring_alert " f"with request: {request.model_dump()}",
2322 )
2323 await provision_alert_definition(
2324 GraylogAlertProvisionModel(
@@ -2375,7 +2375,7 @@ async def provision_sentinelone_new_active_threat_suspicious_detected_monitoring
2375 Provisions SentinelOne: New Active Threat Suspicious Detected.
2376 """
2377 logger.info(
2378 - "Invoking provision_sentinelone_new_active_threat_suspicious_detected_monitoring_alert " f"with request: {request.dict()}",
2378 + "Invoking provision_sentinelone_new_active_threat_suspicious_detected_monitoring_alert " f"with request: {request.model_dump()}",
2379 )
2380 await provision_alert_definition(
2381 GraylogAlertProvisionModel(
@@ -2432,7 +2432,7 @@ async def provision_sentinelone_new_mitigation_kill_performed_successfully_monit
2432 Provisions SentinelOne: New Mitigation, Kill performed successfully.
2433 """
2434 logger.info(
2435 - "Invoking provision_sentinelone_new_mitigation_kill_performed_successfully_monitoring_alert " f"with request: {request.dict()}",
2435 + "Invoking provision_sentinelone_new_mitigation_kill_performed_successfully_monitoring_alert " f"with request: {request.model_dump()}",
2436 )
2437 await provision_alert_definition(
2438 GraylogAlertProvisionModel(
@@ -2490,7 +2490,7 @@ async def provision_sentinelone_new_mitigation_quarantine_performed_successfully
2490 """
2491 logger.info(
2492 "Invoking provision_sentinelone_new_mitigation_quarantine_performed_successfully_monitoring_alert "
2493 - f"with request: {request.dict()}",
2493 + f"with request: {request.model_dump()}",
2494 )
2495 await provision_alert_definition(
2496 GraylogAlertProvisionModel(
@@ -2547,7 +2547,7 @@ async def provision_sentinelone_new_exclusion_was_added_or_modified_by_user_moni
2547 Provisions SentinelOne: New Exclusion was added/modified by user.
2548 """
2549 logger.info(
2550 - "Invoking provision_sentinelone_new_exclusion_was_added_or_modified_by_user_monitoring_alert " f"with request: {request.dict()}",
2550 + "Invoking provision_sentinelone_new_exclusion_was_added_or_modified_by_user_monitoring_alert " f"with request: {request.model_dump()}",
2551 )
2552 await provision_alert_definition(
2553 GraylogAlertProvisionModel(
@@ -2604,7 +2604,7 @@ async def provision_sentinelone_new_path_exclusion_added_monitoring_alert(
2604 Provisions SentinelOne: New Path Exclusion added.
2605 """
2606 logger.info(
2607 - "Invoking provision_sentinelone_new_path_exclusion_added_monitoring_alert " f"with request: {request.dict()}",
2607 + "Invoking provision_sentinelone_new_path_exclusion_added_monitoring_alert " f"with request: {request.model_dump()}",
2608 )
2609 await provision_alert_definition(
2610 GraylogAlertProvisionModel(
@@ -2661,7 +2661,7 @@ async def provision_sentinelone_analyst_verdict_changed_to_true_positive_monitor
2661 Provisions SentinelOne: Analyst verdict changed to True Positive.
2662 """
2663 logger.info(
2664 - "Invoking provision_sentinelone_analyst_verdict_changed_to_true_positive_monitoring_alert " f"with request: {request.dict()}",
2664 + "Invoking provision_sentinelone_analyst_verdict_changed_to_true_positive_monitoring_alert " f"with request: {request.model_dump()}",
2665 )
2666 await provision_alert_definition(
2667 GraylogAlertProvisionModel(
@@ -2718,7 +2718,7 @@ async def provision_sentinelone_analyst_verdict_changed_to_false_positive_monito
2718 Provisions SentinelOne: Analyst verdict changed to False Positive.
2719 """
2720 logger.info(
2721 - "Invoking provision_sentinelone_analyst_verdict_changed_to_false_positive_monitoring_alert " f"with request: {request.dict()}",
2721 + "Invoking provision_sentinelone_analyst_verdict_changed_to_false_positive_monitoring_alert " f"with request: {request.model_dump()}",
2722 )
2723 await provision_alert_definition(
2724 GraylogAlertProvisionModel(
@@ -2776,7 +2776,7 @@ async def provision_mimecast_compromised_site_url_accessed_monitoring_alert(
2776 Provisions Mimecast Compromised Site URL Accessed monitoring alert.
2777 """
2778 logger.info(
2779 - "Invoking provision_mimecast_compromised_site_url_accessed_monitoring_alert " f"with request: {request.dict()}",
2779 + "Invoking provision_mimecast_compromised_site_url_accessed_monitoring_alert " f"with request: {request.model_dump()}",
2780 )
2781 await provision_alert_definition(
2782 GraylogAlertProvisionModel(
@@ -2833,7 +2833,7 @@ async def provision_mimecast_executable_file_attachment_delivered_monitoring_ale
2833 Provisions Mimecast Executable File Attachment Delivered monitoring alert.
2834 """
2835 logger.info(
2836 - "Invoking provision_mimecast_executable_file_attachment_delivered_monitoring_alert " f"with request: {request.dict()}",
2836 + "Invoking provision_mimecast_executable_file_attachment_delivered_monitoring_alert " f"with request: {request.model_dump()}",
2837 )
2838 await provision_alert_definition(
2839 GraylogAlertProvisionModel(
@@ -2890,7 +2890,7 @@ async def provision_mimecast_malicious_email_attachment_delivered_monitoring_ale
2890 Provisions Mimecast Malicious Email Attachment Delivered monitoring alert.
2891 """
2892 logger.info(
2893 - "Invoking provision_mimecast_malicious_email_attachment_delivered_monitoring_alert " f"with request: {request.dict()}",
2893 + "Invoking provision_mimecast_malicious_email_attachment_delivered_monitoring_alert " f"with request: {request.model_dump()}",
2894 )
2895 await provision_alert_definition(
2896 GraylogAlertProvisionModel(
@@ -2947,7 +2947,7 @@ async def provision_mimecast_malicious_email_link_accessed_monitoring_alert(
2947 Provisions Mimecast Malicious Email Link Accessed monitoring alert.
2948 """
2949 logger.info(
2950 - "Invoking provision_mimecast_malicious_email_link_accessed_monitoring_alert " f"with request: {request.dict()}",
2950 + "Invoking provision_mimecast_malicious_email_link_accessed_monitoring_alert " f"with request: {request.model_dump()}",
2951 )
2952 await provision_alert_definition(
2953 GraylogAlertProvisionModel(
@@ -3004,7 +3004,7 @@ async def provision_mimecast_p2p_file_sharing_url_accessed_monitoring_alert(
3004 Provisions Mimecast P2P File Sharing URL Accessed monitoring alert.
3005 """
3006 logger.info(
3007 - "Invoking provision_mimecast_p2p_file_sharing_url_accessed_monitoring_alert " f"with request: {request.dict()}",
3007 + "Invoking provision_mimecast_p2p_file_sharing_url_accessed_monitoring_alert " f"with request: {request.model_dump()}",
3008 )
3009 await provision_alert_definition(
3010 GraylogAlertProvisionModel(
@@ -3061,7 +3061,7 @@ async def provision_mimecast_anonymizer_url_accessed_monitoring_alert(
3061 Provisions Mimecast Anonymizer URL Accessed monitoring alert.
3062 """
3063 logger.info(
3064 - "Invoking provision_mimecast_anonymizer_url_accessed_monitoring_alert " f"with request: {request.dict()}",
3064 + "Invoking provision_mimecast_anonymizer_url_accessed_monitoring_alert " f"with request: {request.model_dump()}",
3065 )
3066 await provision_alert_definition(
3067 GraylogAlertProvisionModel(
@@ -3118,7 +3118,7 @@ async def provision_mimecast_impersonation_email_delivered_monitoring_alert(
3118 Provisions Mimecast Impersonation Email Delivered monitoring alert.
3119 """
3120 logger.info(
3121 - "Invoking provision_mimecast_impersonation_email_delivered_monitoring_alert " f"with request: {request.dict()}",
3121 + "Invoking provision_mimecast_impersonation_email_delivered_monitoring_alert " f"with request: {request.model_dump()}",
3122 )
3123 await provision_alert_definition(
3124 GraylogAlertProvisionModel(
@@ -3175,7 +3175,7 @@ async def provision_mimecast_malicious_outbound_email_monitoring_alert(
3175 Provisions Mimecast Malicious Outbound Email monitoring alert.
3176 """
3177 logger.info(
3178 - "Invoking provision_mimecast_malicious_outbound_email_monitoring_alert " f"with request: {request.dict()}",
3178 + "Invoking provision_mimecast_malicious_outbound_email_monitoring_alert " f"with request: {request.model_dump()}",
3179 )
3180 await provision_alert_definition(
3181 GraylogAlertProvisionModel(
@@ -3232,7 +3232,7 @@ async def provision_mimecast_malicious_rtf_attachment_delivered_monitoring_alert
3232 Provisions Mimecast Malicious RTF Attachment Delivered monitoring alert.
3233 """
3234 logger.info(
3235 - "Invoking provision_mimecast_malicious_rtf_attachment_delivered_monitoring_alert " f"with request: {request.dict()}",
3235 + "Invoking provision_mimecast_malicious_rtf_attachment_delivered_monitoring_alert " f"with request: {request.model_dump()}",
3236 )
3237 await provision_alert_definition(
3238 GraylogAlertProvisionModel(
@@ -3289,7 +3289,7 @@ async def provision_mimecast_phishing_email_delivered_monitoring_alert(
3289 Provisions Mimecast Phishing Email Delivered monitoring alert.
3290 """
3291 logger.info(
3292 - "Invoking provision_mimecast_phishing_email_delivered_monitoring_alert " f"with request: {request.dict()}",
3292 + "Invoking provision_mimecast_phishing_email_delivered_monitoring_alert " f"with request: {request.model_dump()}",
3293 )
3294 await provision_alert_definition(
3295 GraylogAlertProvisionModel(
@@ -3346,7 +3346,7 @@ async def provision_mimecast_source_code_file_in_email_attachment_monitoring_ale
3346 Provisions Mimecast Source Code File In Email Attachment monitoring alert.
3347 """
3348 logger.info(
3349 - "Invoking provision_mimecast_source_code_file_in_email_attachment_monitoring_alert " f"with request: {request.dict()}",
3349 + "Invoking provision_mimecast_source_code_file_in_email_attachment_monitoring_alert " f"with request: {request.model_dump()}",
3350 )
3351 await provision_alert_definition(
3352 GraylogAlertProvisionModel(
@@ -3403,7 +3403,7 @@ async def provision_mimecast_url_with_dangerous_file_type_accessed_monitoring_al
3403 Provisions Mimecast URL with Dangerous File Type Accessed monitoring alert.
3404 """
3405 logger.info(
3406 - "Invoking provision_mimecast_url_with_dangerous_file_type_accessed_monitoring_alert " f"with request: {request.dict()}",
3406 + "Invoking provision_mimecast_url_with_dangerous_file_type_accessed_monitoring_alert " f"with request: {request.model_dump()}",
3407 )
3408 await provision_alert_definition(
3409 GraylogAlertProvisionModel(
@@ -3462,7 +3462,7 @@ async def provision_custom_alert(request: CustomMonitoringAlertProvisionModel) -
3462 """
3463 #
3464 logger.info(
3465 - f"Invoking provision_custom_alert with request: {request.dict()}",
3465 + f"Invoking provision_custom_alert with request: {request.model_dump()}",
3466 )
3467 await provision_alert_definition(
3468 GraylogAlertProvisionModel(
backend/app/integrations/nuclei/services/nuclei.py
+2 -2
@@ -73,12 +73,12 @@ async def post_to_copilot_nuclei_module(data: NucleiScanRequest) -> NucleiScanRe
73 Args:
74 data (NucleiScanRequest): The data to send to the copilot-nuclei-module Docker container.
75 """
76 - logger.info(f"Sending POST request to http://copilot-nuclei-module/scan with data: {data.dict()}")
76 + logger.info(f"Sending POST request to http://copilot-nuclei-module/scan with data: {data.model_dump()}")
77 # raise HTTPException(status_code=501, detail="Not Implemented Yet")
78 async with httpx.AsyncClient() as client:
79 data = await client.post(
80 "http://copilot-nuclei-module/scan",
81 - json=data.dict(),
81 + json=data.model_dump(),
82 timeout=120,
83 )
84 return NucleiScanResponse(**data.json())
backend/app/integrations/office365/services/provision.py
+5 -5
@@ -416,11 +416,11 @@ async def send_index_set_creation_request(
416 Returns:
417 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
418 """
419 - json_index_set = json.dumps(index_set.dict())
419 + json_index_set = json.dumps(index_set.model_dump())
420 logger.info(f"json_index_set set: {json_index_set}")
421 response_json = await send_post_request(
422 endpoint="/api/system/indices/index_sets",
423 - data=index_set.dict(),
423 + data=index_set.model_dump(),
424 )
425 return GraylogIndexSetCreationResponse(**response_json)
426
@@ -512,11 +512,11 @@ async def send_event_stream_creation_request(
512 Returns:
513 StreamCreationResponse: The response containing the created event stream.
514 """
515 - json_event_stream = json.dumps(event_stream.dict())
515 + json_event_stream = json.dumps(event_stream.model_dump())
516 logger.info(f"json_event_stream set: {json_event_stream}")
517 response_json = await send_post_request(
518 endpoint="/api/streams",
519 - data=event_stream.dict(),
519 + data=event_stream.model_dump(),
520 )
521 return StreamCreationResponse(**response_json)
522
@@ -854,7 +854,7 @@ async def create_grafana_datasource(
854 readOnly=True,
855 )
856 results = grafana_client.datasource.create_datasource(
857 - datasource=datasource_payload.dict(),
857 + datasource=datasource_payload.model_dump(),
858 )
859 return GrafanaDataSourceCreationResponse(**results)
860
backend/app/integrations/routes.py
+2 -2
@@ -807,7 +807,7 @@ async def create_integration_meta(
807 )
808 try:
809 new_customer_integration_meta = CustomerIntegrationsMeta(
810 - **customer_integration_meta.dict(),
810 + **customer_integration_meta.model_dump(),
811 )
812 session.add(new_customer_integration_meta)
813 await session.commit()
@@ -1218,7 +1218,7 @@ async def get_meta_auto(
1218 return {
1219 "success": True,
1220 "message": f"Successfully retrieved metadata for {customer_code}/{integration_name}",
1221 - "data": meta_record.dict() if hasattr(meta_record, "dict") else meta_record.__dict__,
1221 + "data": meta_record.model_dump() if hasattr(meta_record, "dict") else meta_record.__dict__,
1222 "table_type": "network_connector" if is_network_integration else "integration",
1223 }
1224
backend/app/integrations/sap_siem/schema/sap_siem.py
+2 -2
@@ -338,7 +338,7 @@ class IrisCasePayload(BaseModel):
338 )
339
340 def to_dict(self):
341 - return self.dict(exclude_none=True)
341 + return self.model_dump(exclude_none=True)
342
343
344 class ModificationHistoryEntry(BaseModel):
@@ -408,4 +408,4 @@ class AddAssetModel(BaseModel):
408 )
409
410 def to_dict(self):
411 - return self.dict(exclude_none=True)
411 + return self.model_dump(exclude_none=True)
backend/app/integrations/sap_siem/services/provision.py
+5 -5
@@ -84,11 +84,11 @@ async def send_index_set_creation_request(
84 Returns:
85 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
86 """
87 - json_index_set = json.dumps(index_set.dict())
87 + json_index_set = json.dumps(index_set.model_dump())
88 logger.info(f"json_index_set set: {json_index_set}")
89 response_json = await send_post_request(
90 endpoint="/api/system/indices/index_sets",
91 - data=index_set.dict(),
91 + data=index_set.model_dump(),
92 )
93 return GraylogIndexSetCreationResponse(**response_json)
94
@@ -165,11 +165,11 @@ async def send_event_stream_creation_request(
165 Returns:
166 StreamCreationResponse: The response containing the created event stream.
167 """
168 - json_event_stream = json.dumps(event_stream.dict())
168 + json_event_stream = json.dumps(event_stream.model_dump())
169 logger.info(f"json_event_stream set: {json_event_stream}")
170 response_json = await send_post_request(
171 endpoint="/api/streams",
172 - data=event_stream.dict(),
172 + data=event_stream.model_dump(),
173 )
174 return StreamCreationResponse(**response_json)
175
@@ -258,7 +258,7 @@ async def create_grafana_datasource(
258 readOnly=True,
259 )
260 results = grafana_client.datasource.create_datasource(
261 - datasource=datasource_payload.dict(),
261 + datasource=datasource_payload.model_dump(),
262 )
263 return GrafanaDataSourceCreationResponse(**results)
264
backend/app/integrations/utils/schema.py
+4 -4
@@ -114,7 +114,7 @@ class WazuhSocketPayload(BaseModel):
114 model_config = ConfigDict(extra="allow")
115
116 def to_dict(self):
117 - return self.dict(exclude_none=True)
117 + return self.model_dump(exclude_none=True)
118
119
120 ############################### ! Sublime ! ###############################
@@ -154,7 +154,7 @@ class WazuhSublimeSocketPayload(WazuhSocketPayload):
154 # If `display_name` is an empty string, set it to `None`.
155 if self.display_name == "":
156 self.display_name = None
157 - return self.dict(exclude_none=True)
157 + return self.model_dump(exclude_none=True)
158
159
160 ######### ! SEND TO SHUFFLE PAYLOAD ! #########
@@ -192,7 +192,7 @@ class ShufflePayload(BaseModel):
192 model_config = ConfigDict(extra="allow")
193
194 def to_dict(self):
195 - return self.dict(exclude_none=True)
195 + return self.model_dump(exclude_none=True)
196
197
198 ######### ! SEND TO EVENT SHIPPER ! #########
@@ -210,7 +210,7 @@ class EventShipperPayload(BaseModel):
210 model_config = ConfigDict(extra="allow")
211
212 def to_dict(self):
213 - return self.dict(exclude_none=True)
213 + return self.model_dump(exclude_none=True)
214
215
216 class EventShipperPayloadResponse(BaseModel):
backend/app/middleware/exception_handlers.py
+1 -1
@@ -82,7 +82,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
82
83 return JSONResponse(
84 status_code=422,
85 - content=ValidationErrorResponse(message=main_message, details=details).dict(),
85 + content=ValidationErrorResponse(message=main_message, details=details).model_dump(),
86 )
87
88
backend/app/network_connectors/routes.py
+1 -1
@@ -734,7 +734,7 @@ async def create_network_connector_meta(
734 )
735 try:
736 new_customer_network_connector_meta = CustomerNetworkConnectorsMeta(
737 - **customer_network_connector_meta.dict(),
737 + **customer_network_connector_meta.model_dump(),
738 )
739 session.add(new_customer_network_connector_meta)
740 await session.commit()
backend/app/notifications/schema/notifications.py
+9 -16
@@ -17,7 +17,7 @@ from typing import Optional
17
18 from pydantic import field_validator, ConfigDict, BaseModel
19 from pydantic import Field
20 -from pydantic import validator
20 +from pydantic import model_validator
21
22 # ---------------------------------------------------------------------------
23 # Enums (input validation only — DB stores strings)
@@ -150,21 +150,14 @@ class NotificationRouteBase(BaseModel):
150 def _strip_destination(cls, v: str) -> str:
151 return v.strip()
152
153 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
154 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
155 - @validator("shuffle_integration_id", always=True)
156 - def _shuffle_integration_required(cls, v, values):
157 - if values.get("channel") == NotificationChannel.SHUFFLE and not v:
158 - raise ValueError("shuffle_integration_id is required when channel='shuffle'")
159 - return v
160 -
161 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
162 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
163 - @validator("shuffle_app_id", always=True)
164 - def _shuffle_app_required(cls, v, values):
165 - if values.get("channel") == NotificationChannel.SHUFFLE and not v:
166 - raise ValueError("shuffle_app_id is required when channel='shuffle'")
167 - return v
153 + @model_validator(mode="after")
154 + def _shuffle_fields_required(self):
155 + if self.channel == NotificationChannel.SHUFFLE:
156 + if not self.shuffle_integration_id:
157 + raise ValueError("shuffle_integration_id is required when channel='shuffle'")
158 + if not self.shuffle_app_id:
159 + raise ValueError("shuffle_app_id is required when channel='shuffle'")
160 + return self
161
162
163 class NotificationRouteCreate(NotificationRouteBase):
backend/app/notifications/services/notifications.py
+2 -2
@@ -158,7 +158,7 @@ async def update_route(
158 # Pydantic v1 vs v2 parity — exclude_unset returns only the fields
159 # the client actually sent so a PATCH that omits `enabled` doesn't
160 # accidentally re-flag it.
161 - data = payload.dict(exclude_unset=True)
161 + data = payload.model_dump(exclude_unset=True)
162
163 # If the PATCH switches the channel to Shuffle (or re-points an
164 # existing Shuffle route at a different integration), the new
@@ -265,7 +265,7 @@ async def update_shuffle_integration(
265 session: AsyncSession,
266 ) -> CustomerShuffleIntegration:
267 integration = await _ensure_integration_belongs_to_customer(integration_id, customer_code, session)
268 - data = payload.dict(exclude_unset=True)
268 + data = payload.model_dump(exclude_unset=True)
269 for field, value in data.items():
270 setattr(integration, field, value)
271 integration.updated_at = datetime.utcnow()
backend/app/siem/services/event_sources.py
+1 -1
@@ -52,7 +52,7 @@ async def create_event_source(
52 detail=f"Event source '{event_source_data.name}' already exists for customer {event_source_data.customer_code}",
53 )
54
55 - db_event_source = EventSources(**event_source_data.dict())
55 + db_event_source = EventSources(**event_source_data.model_dump())
56 db.add(db_event_source)
57 await db.flush()
58 await db.refresh(db_event_source)
backend/app/stack_provisioning/graylog/services/fortinet.py
+3 -3
@@ -99,11 +99,11 @@ async def send_index_set_creation_request(
99 Returns:
100 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
101 """
102 - json_index_set = json.dumps(index_set.dict())
102 + json_index_set = json.dumps(index_set.model_dump())
103 logger.info(f"json_index_set set: {json_index_set}")
104 response_json = await send_post_request(
105 endpoint="/api/system/indices/index_sets",
106 - data=index_set.dict(),
106 + data=index_set.model_dump(),
107 )
108 return GraylogIndexSetCreationResponse(**response_json)
109
@@ -236,7 +236,7 @@ async def create_grafana_datasource(
236 readOnly=True,
237 )
238 results = grafana_client.datasource.create_datasource(
239 - datasource=datasource_payload.dict(),
239 + datasource=datasource_payload.model_dump(),
240 )
241 return GrafanaDataSourceCreationResponse(**results)
242
backend/app/stack_provisioning/graylog/services/provision.py
+1 -1
@@ -264,7 +264,7 @@ async def process_content_pack(content_pack, content_pack_request):
264 TLS_KEY_FILE=content_pack_request.keywords.tls_key_file,
265 )
266 if "PROCESSING_PIPELINE" not in content_pack:
267 - content_pack = replace_keywords_in_json_complex(content_pack, replace_content_pack_keywords.dict())
267 + content_pack = replace_keywords_in_json_complex(content_pack, replace_content_pack_keywords.model_dump())
268 content_pack = convert_port_value_to_int(content_pack)
269 await insert_and_install_content_pack(content_pack)
270
backend/app/stack_provisioning/graylog/services/sentinelone.py
+3 -3
@@ -100,11 +100,11 @@ async def send_index_set_creation_request(
100 Returns:
101 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
102 """
103 - json_index_set = json.dumps(index_set.dict())
103 + json_index_set = json.dumps(index_set.model_dump())
104 logger.info(f"json_index_set set: {json_index_set}")
105 response_json = await send_post_request(
106 endpoint="/api/system/indices/index_sets",
107 - data=index_set.dict(),
107 + data=index_set.model_dump(),
108 )
109 return GraylogIndexSetCreationResponse(**response_json)
110
@@ -235,7 +235,7 @@ async def create_grafana_datasource(
235 readOnly=True,
236 )
237 results = grafana_client.datasource.create_datasource(
238 - datasource=datasource_payload.dict(),
238 + datasource=datasource_payload.model_dump(),
239 )
240 return GrafanaDataSourceCreationResponse(**results)
241
backend/app/stack_provisioning/graylog/services/sonicwall.py
+3 -3
@@ -98,11 +98,11 @@ async def send_index_set_creation_request(
98 Returns:
99 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
100 """
101 - json_index_set = json.dumps(index_set.dict())
101 + json_index_set = json.dumps(index_set.model_dump())
102 logger.info(f"json_index_set set: {json_index_set}")
103 response_json = await send_post_request(
104 endpoint="/api/system/indices/index_sets",
105 - data=index_set.dict(),
105 + data=index_set.model_dump(),
106 )
107 return GraylogIndexSetCreationResponse(**response_json)
108
@@ -233,7 +233,7 @@ async def create_grafana_datasource(
233 readOnly=True,
234 )
235 results = grafana_client.datasource.create_datasource(
236 - datasource=datasource_payload.dict(),
236 + datasource=datasource_payload.model_dump(),
237 )
238 return GrafanaDataSourceCreationResponse(**results)
239
backend/app/threat_intel/routes/socfortress.py
+3 -3
@@ -454,7 +454,7 @@ async def ai_anaylze_alert_socfortress(
454
455 ai_request = SocfortressAiAlertRequest(
456 integration="SOCFORTRESS AI",
457 - alert_payload=alert_details._source.dict(),
457 + alert_payload=alert_details._source.model_dump(),
458 )
459
460 socfortress_lookup = await socfortress_ai_alert_lookup(
@@ -492,7 +492,7 @@ async def ai_wazuh_exclusion_rule_socfortress(
492
493 ai_request = SocfortressAiAlertRequest(
494 integration="SOCFORTRESS AI",
495 - alert_payload=alert_details._source.dict(),
495 + alert_payload=alert_details._source.model_dump(),
496 )
497
498 logger.info(f"Sending request: {request}")
@@ -605,7 +605,7 @@ async def ai_velociraptor_artifact_recommendation_socfortress(
605
606 ai_request = VelociraptorArtifactRecommendationRequest(
607 integration="SOCFORTRESS AI",
608 - alert_payload=alert_payload._source.dict(),
608 + alert_payload=alert_payload._source.model_dump(),
609 os=os,
610 artifacts=await fetch_artifacts(os),
611 )
backend/app/threat_intel/schema/epss.py
+1 -1
@@ -31,7 +31,7 @@ class EpssApiResponse(BaseModel):
31 data: List[EpssData]
32
33 def to_dict(self):
34 - return self.dict()
34 + return self.model_dump()
35
36
37 class EpssThreatIntelResponse(BaseModel):
backend/app/threat_intel/schema/socfortress.py
+3 -3
@@ -60,7 +60,7 @@ class IoCMapping(BaseModel):
60 return v
61
62 def to_dict(self):
63 - return self.dict()
63 + return self.model_dump()
64
65
66 class IoCResponse(BaseModel):
@@ -69,7 +69,7 @@ class IoCResponse(BaseModel):
69 message: Optional[str] = Field(None, description="Message about the IoC")
70
71 def to_dict(self):
72 - return self.dict()
72 + return self.model_dump()
73
74
75 class SocfortressProcessNameAnalysisRequest(BaseModel):
@@ -217,7 +217,7 @@ class SocfortressProcessNameAnalysisResponse(BaseModel):
217 data: SocfortressProcessNameAnalysisAPIResponse
218
219 def to_dict(self):
220 - return self.dict()
220 + return self.model_dump()
221
222
223 class Artifacts(BaseModel):
backend/app/threat_intel/services/socfortress.py
+1 -1
@@ -303,7 +303,7 @@ async def invoke_socfortress_ai_alert_api(
303 headers = {"module-version": "1.0", "x-api-key": api_key}
304 try:
305 async with httpx.AsyncClient(timeout=timeout) as client:
306 - response = await client.post(url, json=request.dict(), headers=headers)
306 + response = await client.post(url, json=request.model_dump(), headers=headers)
307 response.raise_for_status() # Raise an exception for non-successful status codes
308 return response.json()
309 except httpx.HTTPStatusError as e:
backend/app/utils.py
+7 -11
@@ -17,7 +17,7 @@ from fastapi.exceptions import RequestValidationError
17 from loguru import logger
18 from pydantic import field_validator, BaseModel
19 from pydantic import Field
20 -from pydantic import validator
20 +from pydantic import model_validator
21 from sqlalchemy.ext.asyncio import AsyncSession
22 from sqlalchemy.future import select
23 from sqlalchemy.orm import joinedload
@@ -72,13 +72,8 @@ class ValidationErrorItem(BaseModel):
72 error_type: ErrorType
73 message: str = None # Initialize as None
74
75 - # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
76 - # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
77 - @validator("message", pre=True, always=True)
78 - def set_message(cls, value, values):
79 - error_type = values.get("error_type")
80 - logger.info(error_type)
81 -
75 + @model_validator(mode="after")
76 + def set_message(self):
77 error_messages = {
78 ErrorType.PASSWORD_REGEX: "Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one "
79 "special character.",
@@ -99,8 +94,9 @@ class ValidationErrorItem(BaseModel):
94 ErrorType.GENERAL: "Invalid value.",
95 ErrorType.INVALID_ENUM: "Value is not a valid enumeration member.",
96 }
102 -
103 - return error_messages.get(error_type, value)
97 + if self.error_type in error_messages:
98 + self.message = error_messages[self.error_type]
99 + return self
100
101
102 class ValidationErrorResponse(BaseModel):
@@ -275,7 +271,7 @@ class Logger:
271 Returns:
272 None
273 """
278 - log_entry = LogEntry(**log_entry_model.dict())
274 + log_entry = LogEntry(**log_entry_model.model_dump())
275 self.session.add(log_entry)
276 await self.session.commit()
277
backend/copilot.py
+36 -37
@@ -1,4 +1,5 @@
1 import os
2 +from contextlib import asynccontextmanager
3
4 import uvicorn
5 from dotenv import load_dotenv
@@ -99,7 +100,41 @@ environment = os.getenv("ENVIRONMENT", "PRODUCTION")
100 # ssl_keyfile = os.path.join(os.path.dirname(__file__), "../nginx/server.key")
101 # ssl_certfile = os.path.join(os.path.dirname(__file__), "../nginx/server.crt")
102
102 -app = FastAPI(description="CoPilot API", version="0.1.0", title="CoPilot API")
103 +@asynccontextmanager
104 +async def lifespan(_app: FastAPI):
105 + # ── startup ──
106 + logger.info("Initializing database")
107 + if environment == "PRODUCTION":
108 + await create_database_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_name="copilot")
109 + await create_copilot_user_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_user_name="copilot")
110 + apply_migrations()
111 + await create_buckets()
112 + await add_connectors(async_engine)
113 + await delete_connectors(async_engine)
114 + await create_roles(async_engine)
115 + await create_available_integrations(async_engine)
116 + await create_available_network_connectors(async_engine)
117 + await ensure_admin_user(async_engine)
118 + await ensure_scheduler_user(async_engine)
119 +
120 + # Initialize the scheduler
121 + scheduler = await init_scheduler()
122 + if not scheduler.running:
123 + logger.info("Scheduler is not running, starting now...")
124 + scheduler.start()
125 +
126 + yield
127 +
128 + # ── shutdown ──
129 + logger.info("Shutting down scheduler")
130 + scheduler = await get_scheduler_instance()
131 + if scheduler.running:
132 + logger.info("Scheduler is running, shutting down now...")
133 + scheduler.shutdown()
134 + await ensure_scheduler_user_removed(async_engine)
135 +
136 +
137 +app = FastAPI(description="CoPilot API", version="0.1.0", title="CoPilot API", lifespan=lifespan)
138
139 # Create an APIRouter with a prefix of `/api`
140 api_router = APIRouter(prefix="/api")
@@ -187,30 +222,6 @@ api_router.include_router(talon.router)
222 app.include_router(api_router)
223
224
190 -@app.on_event("startup")
191 -async def init_db():
192 - logger.info("Initializing database")
193 - if environment == "PRODUCTION":
194 - await create_database_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_name="copilot")
195 - await create_copilot_user_if_not_exists(db_url=SQLALCHEMY_DATABASE_URI_NO_DB, db_user_name="copilot")
196 - apply_migrations()
197 - await create_buckets()
198 - await add_connectors(async_engine)
199 - await delete_connectors(async_engine)
200 - await create_roles(async_engine)
201 - await create_available_integrations(async_engine)
202 - await create_available_network_connectors(async_engine)
203 - await ensure_admin_user(async_engine)
204 - await ensure_scheduler_user(async_engine)
205 -
206 - # Initialize the scheduler
207 - scheduler = await init_scheduler()
208 -
209 - if not scheduler.running:
210 - logger.info("Scheduler is not running, starting now...")
211 - scheduler.start()
212 -
213 -
225 # Create `scoutsuite-report` directory if it doesnt exist
226 if not os.path.exists("scoutsuite-report"):
227 os.makedirs("scoutsuite-report")
@@ -223,17 +234,5 @@ def hello():
234 return {"message": "CoPilot - We Made It!"}
235
236
226 -@app.on_event("shutdown")
227 -async def shutdown_scheduler():
228 - logger.info("Shutting down scheduler")
229 - # Initialize the scheduler
230 - scheduler = await get_scheduler_instance()
231 - if scheduler.running:
232 - logger.info("Scheduler is running, shutting down now...")
233 - scheduler.shutdown()
234 -
235 - await ensure_scheduler_user_removed(async_engine)
236 -
237 -
237 if __name__ == "__main__":
238 uvicorn.run(app, host=server_ip, port=5000)