clickdetect 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. clickdetect-0.1.0/PKG-INFO +78 -0
  2. clickdetect-0.1.0/README.md +62 -0
  3. clickdetect-0.1.0/api/detector.py +70 -0
  4. clickdetect-0.1.0/api/health.py +6 -0
  5. clickdetect-0.1.0/api/rules.py +45 -0
  6. clickdetect-0.1.0/clickdetect.egg-info/PKG-INFO +78 -0
  7. clickdetect-0.1.0/clickdetect.egg-info/SOURCES.txt +31 -0
  8. clickdetect-0.1.0/clickdetect.egg-info/dependency_links.txt +1 -0
  9. clickdetect-0.1.0/clickdetect.egg-info/entry_points.txt +2 -0
  10. clickdetect-0.1.0/clickdetect.egg-info/requires.txt +9 -0
  11. clickdetect-0.1.0/clickdetect.egg-info/top_level.txt +3 -0
  12. clickdetect-0.1.0/clickdetect.py +79 -0
  13. clickdetect-0.1.0/detector/__init__.py +1 -0
  14. clickdetect-0.1.0/detector/config.py +36 -0
  15. clickdetect-0.1.0/detector/datasource/__init__.py +14 -0
  16. clickdetect-0.1.0/detector/datasource/base.py +31 -0
  17. clickdetect-0.1.0/detector/datasource/clickhouse.py +76 -0
  18. clickdetect-0.1.0/detector/datasource/elasticsearch.py +109 -0
  19. clickdetect-0.1.0/detector/datasource/loki.py +125 -0
  20. clickdetect-0.1.0/detector/datasource/postgresql.py +73 -0
  21. clickdetect-0.1.0/detector/detector.py +218 -0
  22. clickdetect-0.1.0/detector/manager.py +86 -0
  23. clickdetect-0.1.0/detector/rules.py +61 -0
  24. clickdetect-0.1.0/detector/runner.py +129 -0
  25. clickdetect-0.1.0/detector/utils.py +58 -0
  26. clickdetect-0.1.0/detector/webhooks/__init__.py +14 -0
  27. clickdetect-0.1.0/detector/webhooks/base.py +25 -0
  28. clickdetect-0.1.0/detector/webhooks/email.py +113 -0
  29. clickdetect-0.1.0/detector/webhooks/generic.py +70 -0
  30. clickdetect-0.1.0/detector/webhooks/matrix.py +97 -0
  31. clickdetect-0.1.0/detector/webhooks/teams.py +86 -0
  32. clickdetect-0.1.0/pyproject.toml +38 -0
  33. clickdetect-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: clickdetect
3
+ Version: 0.1.0
4
+ Summary: Generic SIEM detector
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: aiohttp[speedups]>=3.13.2
8
+ Requires-Dist: apscheduler>=3.11.1
9
+ Requires-Dist: asyncpg>=0.31.0
10
+ Requires-Dist: clickhouse-connect>=0.10.0
11
+ Requires-Dist: colorlog>=6.10.1
12
+ Requires-Dist: fastapi[standard]>=0.127.1
13
+ Requires-Dist: jinja2>=3.1.6
14
+ Requires-Dist: matrix-nio[e2e]>=0.25.2
15
+ Requires-Dist: pyyaml>=6.0.3
16
+
17
+ # Overview
18
+
19
+ **Clickdetect** is a framework for threshold-based detection and alerting. It periodically queries your data sources, evaluates rules against the results, and sends alerts to one or more destinations when conditions are met.
20
+
21
+ You can pull events from any DataSource implemented, and push alerts to any webhook.
22
+
23
+ If you use elastalert, you will like this!
24
+
25
+ ## Documentation
26
+
27
+ Documentation: [https://clickdetect.souzo.me](https://clickdetect.souzo.me)
28
+
29
+ ## Next steps
30
+
31
+ * Implement timeframe []
32
+ * Grouping alerts []
33
+ * Suppress alerts []
34
+ * Hot reload rules []
35
+ * Add new rules using api []
36
+ * Add api endpoint to silence detectors []
37
+ * Sigma converter in rule (sigma: true) []
38
+ * Sync schedulers for scalability []
39
+ * Get rules from S3
40
+
41
+
42
+ ## Installation
43
+
44
+ ### Using uv
45
+
46
+ Download here:
47
+
48
+ 1. https://docs.astral.sh/uv/getting-started/installation/
49
+
50
+ ```sh
51
+ git clone https://github.com/clicksiem/clickdetect
52
+ cd clickdetect
53
+ uv sync --no-dev
54
+ uv run clickdetect
55
+ ```
56
+
57
+ ### Using Docker / Podman
58
+
59
+ From repository
60
+
61
+ ```sh
62
+ git clone https://github.com/clicksiem/clickdetect
63
+ cd clickdetect
64
+ podman build -t clickdetect .
65
+ podman run --rm -v ./runner.yml:/app/runner.yml -p 8080:8080 clickdetect --api
66
+ ```
67
+
68
+ ## Contribution
69
+
70
+ * Like this project
71
+ * Help me to create a sigma converter for clickhouse.
72
+ * Report bugs in the issues
73
+
74
+ ## Contact
75
+
76
+ * E-mail: me@souzo.me <vinicius morais>
77
+ * [Linkedin](https://www.linkedin.com/in/vinicius-f-a76ba51b5/)
78
+
@@ -0,0 +1,62 @@
1
+ # Overview
2
+
3
+ **Clickdetect** is a framework for threshold-based detection and alerting. It periodically queries your data sources, evaluates rules against the results, and sends alerts to one or more destinations when conditions are met.
4
+
5
+ You can pull events from any DataSource implemented, and push alerts to any webhook.
6
+
7
+ If you use elastalert, you will like this!
8
+
9
+ ## Documentation
10
+
11
+ Documentation: [https://clickdetect.souzo.me](https://clickdetect.souzo.me)
12
+
13
+ ## Next steps
14
+
15
+ * Implement timeframe []
16
+ * Grouping alerts []
17
+ * Suppress alerts []
18
+ * Hot reload rules []
19
+ * Add new rules using api []
20
+ * Add api endpoint to silence detectors []
21
+ * Sigma converter in rule (sigma: true) []
22
+ * Sync schedulers for scalability []
23
+ * Get rules from S3
24
+
25
+
26
+ ## Installation
27
+
28
+ ### Using uv
29
+
30
+ Download here:
31
+
32
+ 1. https://docs.astral.sh/uv/getting-started/installation/
33
+
34
+ ```sh
35
+ git clone https://github.com/clicksiem/clickdetect
36
+ cd clickdetect
37
+ uv sync --no-dev
38
+ uv run clickdetect
39
+ ```
40
+
41
+ ### Using Docker / Podman
42
+
43
+ From repository
44
+
45
+ ```sh
46
+ git clone https://github.com/clicksiem/clickdetect
47
+ cd clickdetect
48
+ podman build -t clickdetect .
49
+ podman run --rm -v ./runner.yml:/app/runner.yml -p 8080:8080 clickdetect --api
50
+ ```
51
+
52
+ ## Contribution
53
+
54
+ * Like this project
55
+ * Help me to create a sigma converter for clickhouse.
56
+ * Report bugs in the issues
57
+
58
+ ## Contact
59
+
60
+ * E-mail: me@souzo.me <vinicius morais>
61
+ * [Linkedin](https://www.linkedin.com/in/vinicius-f-a76ba51b5/)
62
+
@@ -0,0 +1,70 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ from detector.manager import get_manager_instance
3
+ from detector.detector import Detector
4
+ from datetime import datetime
5
+
6
+ router = APIRouter(prefix='/detector')
7
+
8
+ def detector_to_dict(job_id: str, d: Detector):
9
+ return {
10
+ 'id': job_id,
11
+ 'name': d.name,
12
+ 'description': d.description,
13
+ 'tenant': d.tenant,
14
+ 'active': d.active,
15
+ 'for_time': d.for_time,
16
+ 'rules_count': len(d._rules),
17
+ 'webhooks': d.webhooks,
18
+ 'last_time_exec': datetime.fromtimestamp(d._last_time).isoformat(),
19
+ 'next_time_exec': datetime.fromtimestamp(d._next_time).isoformat()
20
+ }
21
+
22
+
23
+ @router.get('/list')
24
+ async def listDetectors():
25
+ manager = get_manager_instance()
26
+ detectors = await manager.get_detectors()
27
+ return [detector_to_dict(job_id, d) for job_id, d in detectors.items()]
28
+
29
+
30
+ @router.get('/tenant/{tenant}')
31
+ async def getDetectorsByTenant(tenant: str):
32
+ manager = get_manager_instance()
33
+ detectors = await manager.get_detectors()
34
+ return [detector_to_dict(job_id, d) for job_id, d in detectors.items() if d.tenant == tenant]
35
+
36
+
37
+ @router.get('/{id}')
38
+ async def getDetector(id: str):
39
+ manager = get_manager_instance()
40
+ detector = await manager.get_detector_by_id(id)
41
+ if not detector:
42
+ raise HTTPException(status_code=404, detail='Detector not found')
43
+ return detector_to_dict(id, detector)
44
+
45
+
46
+ @router.delete('/{id}')
47
+ async def deleteDetector(id: str):
48
+ manager = get_manager_instance()
49
+ result = await manager.remove_scheduler(id)
50
+ if not result:
51
+ raise HTTPException(status_code=404, detail='Detector not found')
52
+ return {'deleted': id}
53
+
54
+
55
+ @router.post('/{id}/stop')
56
+ async def stopDetector(id: str):
57
+ manager = get_manager_instance()
58
+ result = await manager.stop_scheduler(id)
59
+ if not result:
60
+ raise HTTPException(status_code=404, detail='Detector not found')
61
+ return {'stopped': id}
62
+
63
+
64
+ @router.post('/{id}/resume')
65
+ async def resumeDetector(id: str):
66
+ manager = get_manager_instance()
67
+ result = await manager.resume_scheduler(id)
68
+ if not result:
69
+ raise HTTPException(status_code=404, detail='Detector not found')
70
+ return {'resumed': id}
@@ -0,0 +1,6 @@
1
+ from fastapi import APIRouter
2
+ router = APIRouter(prefix='/health')
3
+
4
+ @router.get('/ok')
5
+ def isOk():
6
+ return { 'ok': True}
@@ -0,0 +1,45 @@
1
+ from fastapi import APIRouter, HTTPException
2
+ from detector.manager import get_manager_instance
3
+ router = APIRouter(prefix='/rules')
4
+
5
+ @router.get('/{detector_id}')
6
+ async def listRules(detector_id: str):
7
+ manager = get_manager_instance()
8
+ detector = await manager.get_detector_by_id(detector_id)
9
+ if not detector:
10
+ raise HTTPException(status_code=404, detail='Detector not found')
11
+ return [x.to_dict() for x in detector._rules]
12
+
13
+ @router.get('/{detector_id}/{rule_id}')
14
+ async def getRuleById(detector_id: str, rule_id: str):
15
+ manager = get_manager_instance()
16
+ detector = await manager.get_detector_by_id(detector_id)
17
+ if not detector:
18
+ raise HTTPException(status_code=404, detail='Detector not found')
19
+ rule = await detector.get_rule_by_id(rule_id)
20
+ if not rule:
21
+ raise HTTPException(status_code=404, detail='Rule not found')
22
+ return rule.to_dict()
23
+
24
+ @router.get('/{detector_id}/{rule_id}/pause')
25
+ async def pauseRule(detector_id: str, rule_id: str):
26
+ manager = get_manager_instance()
27
+ detector = await manager.get_detector_by_id(detector_id)
28
+ if not detector:
29
+ raise HTTPException(status_code=404, detail='Detector not found')
30
+ ok = await detector.setRuleActive(rule_id, False)
31
+ if not ok:
32
+ raise HTTPException(status_code=404, detail='Rule not found')
33
+ return { 'ok': True }
34
+
35
+ @router.get('/{detector_id}/{rule_id}/resume')
36
+ async def resumeRule(detector_id: str, rule_id: str):
37
+ manager = get_manager_instance()
38
+ detector = await manager.get_detector_by_id(detector_id)
39
+ if not detector:
40
+ raise HTTPException(status_code=404, detail='Detector not found')
41
+ ok = await detector.setRuleActive(rule_id, True)
42
+ if not ok:
43
+ raise HTTPException(status_code=404, detail='Rule not found')
44
+ return { 'ok': True }
45
+
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: clickdetect
3
+ Version: 0.1.0
4
+ Summary: Generic SIEM detector
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: aiohttp[speedups]>=3.13.2
8
+ Requires-Dist: apscheduler>=3.11.1
9
+ Requires-Dist: asyncpg>=0.31.0
10
+ Requires-Dist: clickhouse-connect>=0.10.0
11
+ Requires-Dist: colorlog>=6.10.1
12
+ Requires-Dist: fastapi[standard]>=0.127.1
13
+ Requires-Dist: jinja2>=3.1.6
14
+ Requires-Dist: matrix-nio[e2e]>=0.25.2
15
+ Requires-Dist: pyyaml>=6.0.3
16
+
17
+ # Overview
18
+
19
+ **Clickdetect** is a framework for threshold-based detection and alerting. It periodically queries your data sources, evaluates rules against the results, and sends alerts to one or more destinations when conditions are met.
20
+
21
+ You can pull events from any DataSource implemented, and push alerts to any webhook.
22
+
23
+ If you use elastalert, you will like this!
24
+
25
+ ## Documentation
26
+
27
+ Documentation: [https://clickdetect.souzo.me](https://clickdetect.souzo.me)
28
+
29
+ ## Next steps
30
+
31
+ * Implement timeframe []
32
+ * Grouping alerts []
33
+ * Suppress alerts []
34
+ * Hot reload rules []
35
+ * Add new rules using api []
36
+ * Add api endpoint to silence detectors []
37
+ * Sigma converter in rule (sigma: true) []
38
+ * Sync schedulers for scalability []
39
+ * Get rules from S3
40
+
41
+
42
+ ## Installation
43
+
44
+ ### Using uv
45
+
46
+ Download here:
47
+
48
+ 1. https://docs.astral.sh/uv/getting-started/installation/
49
+
50
+ ```sh
51
+ git clone https://github.com/clicksiem/clickdetect
52
+ cd clickdetect
53
+ uv sync --no-dev
54
+ uv run clickdetect
55
+ ```
56
+
57
+ ### Using Docker / Podman
58
+
59
+ From repository
60
+
61
+ ```sh
62
+ git clone https://github.com/clicksiem/clickdetect
63
+ cd clickdetect
64
+ podman build -t clickdetect .
65
+ podman run --rm -v ./runner.yml:/app/runner.yml -p 8080:8080 clickdetect --api
66
+ ```
67
+
68
+ ## Contribution
69
+
70
+ * Like this project
71
+ * Help me to create a sigma converter for clickhouse.
72
+ * Report bugs in the issues
73
+
74
+ ## Contact
75
+
76
+ * E-mail: me@souzo.me <vinicius morais>
77
+ * [Linkedin](https://www.linkedin.com/in/vinicius-f-a76ba51b5/)
78
+
@@ -0,0 +1,31 @@
1
+ README.md
2
+ clickdetect.py
3
+ pyproject.toml
4
+ api/detector.py
5
+ api/health.py
6
+ api/rules.py
7
+ clickdetect.egg-info/PKG-INFO
8
+ clickdetect.egg-info/SOURCES.txt
9
+ clickdetect.egg-info/dependency_links.txt
10
+ clickdetect.egg-info/entry_points.txt
11
+ clickdetect.egg-info/requires.txt
12
+ clickdetect.egg-info/top_level.txt
13
+ detector/__init__.py
14
+ detector/config.py
15
+ detector/detector.py
16
+ detector/manager.py
17
+ detector/rules.py
18
+ detector/runner.py
19
+ detector/utils.py
20
+ detector/datasource/__init__.py
21
+ detector/datasource/base.py
22
+ detector/datasource/clickhouse.py
23
+ detector/datasource/elasticsearch.py
24
+ detector/datasource/loki.py
25
+ detector/datasource/postgresql.py
26
+ detector/webhooks/__init__.py
27
+ detector/webhooks/base.py
28
+ detector/webhooks/email.py
29
+ detector/webhooks/generic.py
30
+ detector/webhooks/matrix.py
31
+ detector/webhooks/teams.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ clickdetect = clickdetect:run
@@ -0,0 +1,9 @@
1
+ aiohttp[speedups]>=3.13.2
2
+ apscheduler>=3.11.1
3
+ asyncpg>=0.31.0
4
+ clickhouse-connect>=0.10.0
5
+ colorlog>=6.10.1
6
+ fastapi[standard]>=0.127.1
7
+ jinja2>=3.1.6
8
+ matrix-nio[e2e]>=0.25.2
9
+ pyyaml>=6.0.3
@@ -0,0 +1,3 @@
1
+ api
2
+ clickdetect
3
+ detector
@@ -0,0 +1,79 @@
1
+ import asyncio
2
+ import uvicorn
3
+ import detector.config as config
4
+ import argparse
5
+ from typing import Any
6
+ from detector.runner import Runner
7
+ from detector.manager import Manager, get_manager_instance
8
+ from logging import getLogger
9
+ from fastapi import FastAPI
10
+ from api.detector import router as detector_router
11
+ from api.rules import router as rules_router
12
+ from os.path import exists as f_exists
13
+
14
+ config.logConfig()
15
+ logger = getLogger(__name__)
16
+
17
+ async def load_api(args: Any):
18
+ app = FastAPI(title=config.app_name)
19
+ app.include_router(detector_router)
20
+ app.include_router(rules_router)
21
+
22
+ server_config = uvicorn.Config(app, host='0.0.0.0', port=args.port, log_level='info')
23
+ server = uvicorn.Server(server_config)
24
+ await server.serve()
25
+
26
+ async def load_runner(args: Any) -> Runner | None:
27
+ runner = await Runner(args.runner).init()
28
+ if not runner:
29
+ logger.debug('Runner not loaded')
30
+ return None
31
+
32
+ detectors = await runner.get_detectors()
33
+ manager = Manager()
34
+
35
+ if not detectors:
36
+ logger.error('No detector found')
37
+ return None
38
+
39
+ for detector in detectors:
40
+ await manager.run_detector(detector)
41
+
42
+ return runner
43
+
44
+ async def loop_run(runner: Runner | None = None):
45
+ try:
46
+ while await config.is_running():
47
+ await asyncio.sleep(1)
48
+ except asyncio.CancelledError:
49
+ logger.warning('received kill event')
50
+ finally:
51
+ await get_manager_instance().shutdown()
52
+ if runner:
53
+ await runner.close()
54
+
55
+ async def main():
56
+ parser = argparse.ArgumentParser(description=f'{config.app_name} is a tool to detect patterns and alerts in clickhouse and others database')
57
+ parser.add_argument('--api', required=False, default=False, action='store_true', help='Enable api, required for clicksiem-backend')
58
+ parser.add_argument('-p', '--port', default=config.default_port, type=int, help=f'specify api port, default: {config.default_port}')
59
+ parser.add_argument('-r', '--runner', default=config.default_runner, type=str, help=f'Runner file containing webhook, datasources, detectors and rules. Default: {config.default_runner}')
60
+ parser.add_argument('--stdin', default=False, type=bool, help='Read file from stdin')
61
+ args = parser.parse_args()
62
+
63
+ tasks = []
64
+ if args.runner:
65
+ if not f_exists(args.runner):
66
+ logger.fatal(f'File {args.runner} does not exists')
67
+ exit(1)
68
+ tasks.append(await load_runner(args))
69
+ if args.api:
70
+ tasks.append(load_api(args))
71
+ if tasks:
72
+ await asyncio.gather(*tasks)
73
+
74
+
75
+ def run():
76
+ asyncio.run(main())
77
+
78
+ if __name__ == "__main__":
79
+ run()
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,36 @@
1
+ import logging
2
+ from asyncio import Lock
3
+ import colorlog
4
+
5
+ _lock = Lock()
6
+ running = True
7
+ rule_eval_semaphore = 7
8
+ webhook_send_semaphore = 7
9
+
10
+ def logConfig():
11
+ colorlog.basicConfig(
12
+ level=colorlog.DEBUG,
13
+ format="%(asctime)s | %(log_color)s%(levelname)-8s%(reset)s | %(name)s | %(message)s",
14
+ log_colors={
15
+ 'DEBUG': 'cyan',
16
+ 'INFO': 'green',
17
+ 'WARNING': 'yellow',
18
+ 'ERROR': 'red',
19
+ 'CRITICAL': 'red,bg_white',
20
+ }
21
+ )
22
+
23
+ async def is_running():
24
+ global running
25
+ async with _lock:
26
+ return running
27
+
28
+ async def stop_running():
29
+ global running
30
+ async with _lock:
31
+ running = False
32
+
33
+
34
+ app_name = 'ClickDetector'
35
+ default_runner = 'runner.yml'
36
+ default_port = 8080
@@ -0,0 +1,14 @@
1
+ from typing import Dict, List, Type
2
+ from detector.datasource.base import BaseDataSource
3
+ from detector.datasource.clickhouse import ClickhouseDataSource
4
+ from detector.datasource.loki import LokiDataSource
5
+ from detector.datasource.elasticsearch import ElasticsearchDataSource
6
+ from detector.datasource.postgresql import PostgreSQLDataSource
7
+
8
+
9
+ datasources: List[Type[BaseDataSource]] = [
10
+ ClickhouseDataSource,
11
+ LokiDataSource,
12
+ ElasticsearchDataSource,
13
+ PostgreSQLDataSource,
14
+ ]
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict
3
+
4
+
5
+ @dataclass()
6
+ class DataSourceQueryResult:
7
+ len: int
8
+ value: Any
9
+
10
+ def to_dict(self) -> Dict[str, Any]:
11
+ return {
12
+ 'len': self.len,
13
+ 'value': self.value
14
+ }
15
+
16
+ class BaseDataSource:
17
+ async def connect(self):
18
+ raise NotImplementedError()
19
+
20
+ async def query(self, data: str) -> DataSourceQueryResult | None:
21
+ raise NotImplementedError()
22
+
23
+ @classmethod
24
+ def _name(cls) -> str:
25
+ raise NotImplementedError()
26
+
27
+ async def _parse(self, _obj: Any):
28
+ raise NotImplementedError()
29
+
30
+ def to_dict(self) -> Dict:
31
+ raise NotImplementedError()
@@ -0,0 +1,76 @@
1
+ from typing import Any, Dict
2
+ from clickhouse_connect import get_async_client
3
+ from clickhouse_connect.driver.asyncclient import AsyncClient
4
+ from logging import getLogger
5
+
6
+ from .base import BaseDataSource, DataSourceQueryResult
7
+
8
+ logger = getLogger(__name__)
9
+
10
+ class ClickhouseDataSource(BaseDataSource):
11
+ database: str
12
+ host: str
13
+ port: int
14
+ username: str
15
+ password: str
16
+ verify: bool = False
17
+ client: AsyncClient | None = None
18
+
19
+ async def connect(self):
20
+ try:
21
+ self.client = await get_async_client(
22
+ database=self.database,
23
+ host=self.host,
24
+ username=self.username,
25
+ password=self.password,
26
+ port=self.port,
27
+ secure=self.verify
28
+ )
29
+ except Exception as ex:
30
+ logger.error(f'Failed to connect to ClickHouse at {self.host}:{self.port} | {ex}')
31
+ self.client = None
32
+
33
+ async def query(self, data: str) -> DataSourceQueryResult | None:
34
+ if not self.client:
35
+ await self.connect()
36
+ if not self.client:
37
+ return None
38
+ try:
39
+ result = await self.client.query(data)
40
+ return DataSourceQueryResult(result.row_count, list(result.named_results()))
41
+ except Exception as ex:
42
+ logger.error(f'Query failed, resetting client | {ex}')
43
+ self.client = None
44
+ return None
45
+
46
+ @classmethod
47
+ def _name(cls) -> str:
48
+ return 'clickhouse'
49
+
50
+ def to_dict(self) -> Dict:
51
+ return {
52
+ 'database': self.database,
53
+ 'host': self.host,
54
+ 'port': self.port,
55
+ 'username': self.username,
56
+ 'password': self.password,
57
+ 'verify': self.verify
58
+ }
59
+
60
+ async def _parse(self, _obj: Any):
61
+ database = _obj.get('database', 'default')
62
+ host = _obj.get('host')
63
+ port = _obj.get('port')
64
+ username = _obj.get('username')
65
+ password = _obj.get('password')
66
+ verify = _obj.get('verify', False)
67
+
68
+ if not host or not port or not username or not password:
69
+ raise Exception(f'Invalid parameters: {self.to_dict().items()}')
70
+
71
+ self.database = database
72
+ self.host = host
73
+ self.port = port
74
+ self.username = username
75
+ self.password = password
76
+ self.verify = verify