rule-engine-core 0.0.2__tar.gz → 0.0.3__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 (22) hide show
  1. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/PKG-INFO +1 -1
  2. rule_engine_core-0.0.3/rule_engine_core/metadata_dao.py +89 -0
  3. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core.egg-info/PKG-INFO +1 -1
  4. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core.egg-info/SOURCES.txt +2 -0
  5. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/setup.py +1 -1
  6. rule_engine_core-0.0.3/tests/test_metadata_dao.py +82 -0
  7. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/README.md +0 -0
  8. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/pyproject.toml +0 -0
  9. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/__init__.py +0 -0
  10. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/entity_base.py +0 -0
  11. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/entity_dao.py +0 -0
  12. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/entity_types.py +0 -0
  13. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/filter.py +0 -0
  14. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/metadata_store.py +0 -0
  15. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/parser.py +0 -0
  16. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core/rule.py +0 -0
  17. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core.egg-info/dependency_links.txt +0 -0
  18. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core.egg-info/requires.txt +0 -0
  19. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/rule_engine_core.egg-info/top_level.txt +0 -0
  20. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/setup.cfg +0 -0
  21. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/tests/test_rule_creation_storage.py +0 -0
  22. {rule_engine_core-0.0.2 → rule_engine_core-0.0.3}/tests/test_rule_evaluation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rule-engine-core
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A rule engine core library for Python.
5
5
  Home-page: https://github.com/temp-noob/rule-engine
6
6
  Author: Mohit Tripathi
@@ -0,0 +1,89 @@
1
+ """
2
+ DAO and sync helpers for metadata persisted in PostgreSQL.
3
+ """
4
+
5
+ import json
6
+ import os
7
+ import logging
8
+ import psycopg2
9
+ from typing import Optional
10
+ from psycopg2.extensions import connection as PgConnection
11
+ from psycopg2.extras import Json
12
+ from rule_engine_core.entity_dao import create_connection
13
+
14
+ _logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MetadataDao:
18
+ def __init__(self, config: Optional[dict] = None):
19
+ self._connection: Optional[PgConnection] = create_connection(config)
20
+ if self._connection is None:
21
+ raise RuntimeError("Failed to create a connection to the database")
22
+
23
+ def get_all_metadata(self) -> dict[str, dict]:
24
+ with self._connection.cursor() as cursor:
25
+ cursor.execute("SELECT column_name, metadata_json FROM metadata")
26
+ rows = cursor.fetchall()
27
+ return {row[0]: row[1] for row in rows}
28
+
29
+ def get_metadata(self, column_name: str) -> Optional[dict]:
30
+ with self._connection.cursor() as cursor:
31
+ cursor.execute(
32
+ "SELECT metadata_json FROM metadata WHERE column_name = %s",
33
+ (column_name,),
34
+ )
35
+ row = cursor.fetchone()
36
+ return row[0] if row is not None else None
37
+
38
+ def upsert_metadata(self, column_name: str, metadata_json: dict) -> bool:
39
+ with self._connection.cursor() as cursor:
40
+ try:
41
+ cursor.execute(
42
+ """
43
+ INSERT INTO metadata (column_name, metadata_json)
44
+ VALUES (%s, %s)
45
+ ON CONFLICT (column_name)
46
+ DO UPDATE SET metadata_json = EXCLUDED.metadata_json
47
+ """,
48
+ (column_name, Json(metadata_json)),
49
+ )
50
+ self._connection.commit()
51
+ return True
52
+ except psycopg2.Error:
53
+ _logger.exception("Failed to upsert metadata for column: %s", column_name)
54
+ self._connection.rollback()
55
+ return False
56
+
57
+ def delete_metadata(self, column_name: str) -> bool:
58
+ with self._connection.cursor() as cursor:
59
+ try:
60
+ cursor.execute("DELETE FROM metadata WHERE column_name = %s", (column_name,))
61
+ self._connection.commit()
62
+ return True
63
+ except psycopg2.Error:
64
+ _logger.exception("Failed to delete metadata for column: %s", column_name)
65
+ self._connection.rollback()
66
+ return False
67
+
68
+ def dump_metadata_to_file(self, metadata_store_file_path: str) -> dict[str, dict]:
69
+ metadata = self.get_all_metadata()
70
+ parent_dir = os.path.dirname(metadata_store_file_path)
71
+ if parent_dir:
72
+ os.makedirs(parent_dir, exist_ok=True)
73
+ with open(metadata_store_file_path, "w", encoding="utf-8") as metadata_file:
74
+ json.dump(metadata, metadata_file, indent=4)
75
+ return metadata
76
+
77
+ def __del__(self):
78
+ self.close()
79
+
80
+ def close(self):
81
+ if self._connection is not None:
82
+ self._connection.close()
83
+ self._connection = None
84
+
85
+
86
+ def sync_metadata_store_file(config: Optional[dict] = None, metadata_store_file_path: Optional[str] = None) -> dict[str, dict]:
87
+ resolved_metadata_store_file_path = metadata_store_file_path or os.getenv("METADATA_STORE_FILE_PATH", "metadata.json")
88
+ metadata_dao = MetadataDao(config)
89
+ return metadata_dao.dump_metadata_to_file(resolved_metadata_store_file_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rule-engine-core
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: A rule engine core library for Python.
5
5
  Home-page: https://github.com/temp-noob/rule-engine
6
6
  Author: Mohit Tripathi
@@ -6,6 +6,7 @@ rule_engine_core/entity_base.py
6
6
  rule_engine_core/entity_dao.py
7
7
  rule_engine_core/entity_types.py
8
8
  rule_engine_core/filter.py
9
+ rule_engine_core/metadata_dao.py
9
10
  rule_engine_core/metadata_store.py
10
11
  rule_engine_core/parser.py
11
12
  rule_engine_core/rule.py
@@ -14,5 +15,6 @@ rule_engine_core.egg-info/SOURCES.txt
14
15
  rule_engine_core.egg-info/dependency_links.txt
15
16
  rule_engine_core.egg-info/requires.txt
16
17
  rule_engine_core.egg-info/top_level.txt
18
+ tests/test_metadata_dao.py
17
19
  tests/test_rule_creation_storage.py
18
20
  tests/test_rule_evaluation.py
@@ -14,7 +14,7 @@ def parse_requirements(filename):
14
14
  path = os.path.join(os.getenv('BUILD_PATH'), "requirements.txt")
15
15
  setup(
16
16
  name="rule-engine-core",
17
- version="0.0.2",
17
+ version="0.0.3",
18
18
  author="Mohit Tripathi",
19
19
  author_email="tripathimohit051@gmail.com",
20
20
  description="A rule engine core library for Python.",
@@ -0,0 +1,82 @@
1
+ from rule_engine_core.metadata_dao import MetadataDao, sync_metadata_store_file
2
+
3
+
4
+ class StubCursor:
5
+ def __init__(self, connection):
6
+ self._connection = connection
7
+ self._rows = []
8
+ self._single_row = None
9
+
10
+ def __enter__(self):
11
+ return self
12
+
13
+ def __exit__(self, exc_type, exc, tb):
14
+ return False
15
+
16
+ def execute(self, query, params=None):
17
+ if "SELECT column_name, metadata_json FROM metadata" in query:
18
+ self._rows = [(k, v) for k, v in self._connection.store.items()]
19
+ elif "SELECT metadata_json FROM metadata WHERE column_name = %s" in query:
20
+ value = self._connection.store.get(params[0])
21
+ self._single_row = (value,) if value is not None else None
22
+ elif "INSERT INTO metadata" in query:
23
+ self._connection.store[params[0]] = getattr(params[1], "adapted", params[1])
24
+ elif "DELETE FROM metadata WHERE column_name = %s" in query:
25
+ self._connection.store.pop(params[0], None)
26
+
27
+ def fetchall(self):
28
+ return self._rows
29
+
30
+ def fetchone(self):
31
+ return self._single_row
32
+
33
+
34
+ class StubConnection:
35
+ def __init__(self):
36
+ self.store = {"price": {"type": "float"}}
37
+ self.closed = 0
38
+
39
+ def cursor(self):
40
+ return StubCursor(self)
41
+
42
+ def commit(self):
43
+ return None
44
+
45
+ def rollback(self):
46
+ return None
47
+
48
+ def close(self):
49
+ self.closed = 1
50
+
51
+
52
+ def test_metadata_dao_crud_and_dump(monkeypatch, tmp_path):
53
+ stub_connection = StubConnection()
54
+ monkeypatch.setattr("rule_engine_core.metadata_dao.create_connection", lambda _: stub_connection)
55
+ metadata_dao = MetadataDao()
56
+
57
+ assert metadata_dao.get_all_metadata() == {"price": {"type": "float"}}
58
+ assert metadata_dao.get_metadata("price") == {"type": "float"}
59
+ assert metadata_dao.get_metadata("missing") is None
60
+
61
+ assert metadata_dao.upsert_metadata("country", {"type": "string"})
62
+ assert metadata_dao.get_metadata("country") == {"type": "string"}
63
+
64
+ assert metadata_dao.delete_metadata("country")
65
+ assert metadata_dao.get_metadata("country") is None
66
+
67
+ metadata_file_path = tmp_path / "metadata.json"
68
+ metadata = metadata_dao.dump_metadata_to_file(str(metadata_file_path))
69
+ assert metadata == {"price": {"type": "float"}}
70
+ assert metadata_file_path.exists()
71
+
72
+
73
+ def test_sync_metadata_store_file_uses_env_path(monkeypatch, tmp_path):
74
+ stub_connection = StubConnection()
75
+ monkeypatch.setattr("rule_engine_core.metadata_dao.create_connection", lambda _: stub_connection)
76
+ metadata_file_path = tmp_path / "metadata-from-env.json"
77
+ monkeypatch.setenv("METADATA_STORE_FILE_PATH", str(metadata_file_path))
78
+
79
+ metadata = sync_metadata_store_file()
80
+
81
+ assert metadata == {"price": {"type": "float"}}
82
+ assert metadata_file_path.exists()