memgraph-toolbox 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 (27) hide show
  1. memgraph_toolbox-0.1.0/.gitignore +174 -0
  2. memgraph_toolbox-0.1.0/.python-version +1 -0
  3. memgraph_toolbox-0.1.0/LICENSE +21 -0
  4. memgraph_toolbox-0.1.0/PKG-INFO +80 -0
  5. memgraph_toolbox-0.1.0/README.md +60 -0
  6. memgraph_toolbox-0.1.0/pyproject.toml +36 -0
  7. memgraph_toolbox-0.1.0/src/memgraph_toolbox/__init__.py +0 -0
  8. memgraph_toolbox-0.1.0/src/memgraph_toolbox/api/__init__.py +0 -0
  9. memgraph_toolbox-0.1.0/src/memgraph_toolbox/api/memgraph.py +98 -0
  10. memgraph_toolbox-0.1.0/src/memgraph_toolbox/api/tool.py +51 -0
  11. memgraph_toolbox-0.1.0/src/memgraph_toolbox/api/toolkit.py +41 -0
  12. memgraph_toolbox-0.1.0/src/memgraph_toolbox/py.typed +0 -0
  13. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tests/__init__.py +0 -0
  14. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tests/test_toolkit.py +35 -0
  15. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tests/test_tools.py +193 -0
  16. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/__init__.py +0 -0
  17. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/betweenness_centrality.py +57 -0
  18. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/config.py +23 -0
  19. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/constraint.py +23 -0
  20. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/cypher.py +33 -0
  21. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/index.py +23 -0
  22. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/page_rank.py +43 -0
  23. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/schema.py +22 -0
  24. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/storage.py +23 -0
  25. memgraph_toolbox-0.1.0/src/memgraph_toolbox/tools/trigger.py +23 -0
  26. memgraph_toolbox-0.1.0/src/memgraph_toolbox/utils/logging.py +15 -0
  27. memgraph_toolbox-0.1.0/uv.lock +84 -0
@@ -0,0 +1,174 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Memgraph
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: memgraph-toolbox
3
+ Version: 0.1.0
4
+ Summary: Memgraph toolbox library for Memgraph AI tools and utilities
5
+ Author-email: antejavor <ante.javor@memgraph.io>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,graph,memgraph,toolkit
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest-asyncio>=0.26.0; extra == 'test'
18
+ Requires-Dist: pytest>=8.3.5; extra == 'test'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Memgraph Toolbox
22
+
23
+ The **Memgraph Toolbox** is a collection of tools designed to interact with a
24
+ Memgraph database. These tools provide functionality for querying, analyzing,
25
+ and managing data within Memgraph, making it easier to work with graph data.
26
+ They are made to be easily called from other framework implementations such as
27
+ **MCP**, **Langchain** or **Lamaindex**.
28
+
29
+ ## Available Tools
30
+
31
+ Below is a list of tools included in the toolbox, along with their descriptions:
32
+
33
+ 1. `ShowTriggersTool` - Shows trigger information from a Memgraph database.
34
+ 2. `ShowStorageInfoTool` - Shows storage information from a Memgraph database.
35
+ 3. `ShowSchemaInfoTool` - Shows schema information from a Memgraph database.
36
+ 4. `PageRankTool` - Calculates PageRank on a graph in Memgraph.
37
+ 5. `ShowIndexInfoTool` - Shows index information from a Memgraph database.
38
+ 6. `CypherTool` - Executes arbitrary Cypher queries on a Memgraph database.
39
+ 7. `ShowConstraintInfoTool` - Shows constraint information from a Memgraph database.
40
+ 8. `ShowConfigTool` - Shows configuration information from a Memgraph database.
41
+ 9. `BetweennessCentralityTool` - Calculates betweenness centrality for nodes in a graph.
42
+
43
+ ## Usage
44
+
45
+ Each tool is implemented as a Python class inheriting from `BaseTool`. To use a
46
+ tool:
47
+
48
+ 1. Instantiate the tool with a `Memgraph` database connection.
49
+ 2. Call the `call` method with the required arguments.
50
+
51
+ Example:
52
+
53
+ ```python
54
+ from memgraph_toolbox.tools.trigger import ShowTriggersTool
55
+ from memgraph_toolbox.api.memgraph import Memgraph
56
+
57
+ # Connect to Memgraph
58
+ db = Memgraph()
59
+
60
+ # Use the ShowTriggersTool
61
+ tool = ShowTriggersTool(db)
62
+ triggers = tool.call({})
63
+ print(triggers)
64
+ ```
65
+
66
+ ## Requirements
67
+
68
+ - Python 3.10+
69
+ - Memgraph database
70
+ - Memgraph Mage (for certain tools like `pagerank` and `run_betweenness_centrality`)
71
+
72
+ ## Contributing
73
+
74
+ Contributions are welcome! Feel free to submit issues or pull requests to
75
+ improve the toolbox.
76
+
77
+ ## License
78
+
79
+ This project is licensed under the MIT License. See the `LICENSE` file for
80
+ details.
@@ -0,0 +1,60 @@
1
+ # Memgraph Toolbox
2
+
3
+ The **Memgraph Toolbox** is a collection of tools designed to interact with a
4
+ Memgraph database. These tools provide functionality for querying, analyzing,
5
+ and managing data within Memgraph, making it easier to work with graph data.
6
+ They are made to be easily called from other framework implementations such as
7
+ **MCP**, **Langchain** or **Lamaindex**.
8
+
9
+ ## Available Tools
10
+
11
+ Below is a list of tools included in the toolbox, along with their descriptions:
12
+
13
+ 1. `ShowTriggersTool` - Shows trigger information from a Memgraph database.
14
+ 2. `ShowStorageInfoTool` - Shows storage information from a Memgraph database.
15
+ 3. `ShowSchemaInfoTool` - Shows schema information from a Memgraph database.
16
+ 4. `PageRankTool` - Calculates PageRank on a graph in Memgraph.
17
+ 5. `ShowIndexInfoTool` - Shows index information from a Memgraph database.
18
+ 6. `CypherTool` - Executes arbitrary Cypher queries on a Memgraph database.
19
+ 7. `ShowConstraintInfoTool` - Shows constraint information from a Memgraph database.
20
+ 8. `ShowConfigTool` - Shows configuration information from a Memgraph database.
21
+ 9. `BetweennessCentralityTool` - Calculates betweenness centrality for nodes in a graph.
22
+
23
+ ## Usage
24
+
25
+ Each tool is implemented as a Python class inheriting from `BaseTool`. To use a
26
+ tool:
27
+
28
+ 1. Instantiate the tool with a `Memgraph` database connection.
29
+ 2. Call the `call` method with the required arguments.
30
+
31
+ Example:
32
+
33
+ ```python
34
+ from memgraph_toolbox.tools.trigger import ShowTriggersTool
35
+ from memgraph_toolbox.api.memgraph import Memgraph
36
+
37
+ # Connect to Memgraph
38
+ db = Memgraph()
39
+
40
+ # Use the ShowTriggersTool
41
+ tool = ShowTriggersTool(db)
42
+ triggers = tool.call({})
43
+ print(triggers)
44
+ ```
45
+
46
+ ## Requirements
47
+
48
+ - Python 3.10+
49
+ - Memgraph database
50
+ - Memgraph Mage (for certain tools like `pagerank` and `run_betweenness_centrality`)
51
+
52
+ ## Contributing
53
+
54
+ Contributions are welcome! Feel free to submit issues or pull requests to
55
+ improve the toolbox.
56
+
57
+ ## License
58
+
59
+ This project is licensed under the MIT License. See the `LICENSE` file for
60
+ details.
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "memgraph-toolbox"
3
+ version = "0.1.0"
4
+ description = "Memgraph toolbox library for Memgraph AI tools and utilities"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "antejavor", email = "ante.javor@memgraph.io" }
8
+ ]
9
+ license = { text = "MIT" }
10
+ requires-python = ">=3.10"
11
+ url = "https://github.com/memgraph/ai-toolkit"
12
+
13
+ keywords = ["memgraph", "graph", "ai", "toolkit"]
14
+
15
+ classifiers = [
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ test = [
26
+ "pytest>=8.3.5",
27
+ "pytest-asyncio>=0.26.0",
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["hatchling", "build"]
32
+ build-backend = "hatchling.build"
33
+
34
+ [tool.pytest.ini_options]
35
+ log_cli = true
36
+ log_cli_level = "INFO"
@@ -0,0 +1,98 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from neo4j import GraphDatabase
4
+
5
+
6
+ class Memgraph:
7
+ """
8
+ Base Memgraph client for interacting with Memgraph database.
9
+ """
10
+
11
+ def __init__(
12
+ self,
13
+ uri: str,
14
+ username: str,
15
+ password: str,
16
+ driver_config: Optional[Dict] = None,
17
+ ):
18
+ self.driver = GraphDatabase.driver(
19
+ uri, auth=(username, password), **(driver_config or {})
20
+ )
21
+ try:
22
+ import neo4j
23
+ except ImportError:
24
+ raise ImportError(
25
+ "Could not import neo4j python package. "
26
+ "Please install it with `pip install neo4j`."
27
+ )
28
+ try:
29
+ self.driver.verify_connectivity()
30
+ except neo4j.exceptions.ServiceUnavailable:
31
+ raise ValueError(
32
+ "Could not connect to Memgraph database. "
33
+ "Please ensure that the url is correct"
34
+ )
35
+ except neo4j.exceptions.AuthError:
36
+ raise ValueError(
37
+ "Could not connect to Memgraph database. "
38
+ "Please ensure that the username and password are correct"
39
+ )
40
+
41
+ def query(self, query: str, params: dict = {}) -> List[Dict[str, Any]]:
42
+ """
43
+ Execute a Cypher query and return results as a list of dictionaries.
44
+
45
+ Args:
46
+ query: The Cypher query to execute
47
+
48
+ Returns:
49
+ List of dictionaries containing query results
50
+ """
51
+ from neo4j.exceptions import Neo4jError
52
+
53
+ try:
54
+ data, _, _ = self.driver.execute_query(
55
+ query,
56
+ parameters_=params,
57
+ )
58
+ json_data = [r.data() for r in data]
59
+ return json_data
60
+ except Neo4jError as e:
61
+ if not (
62
+ (
63
+ ( # isCallInTransactionError
64
+ e.code == "Neo.DatabaseError.Statement.ExecutionFailed"
65
+ or e.code
66
+ == "Neo.DatabaseError.Transaction.TransactionStartFailed"
67
+ )
68
+ and "in an implicit transaction" in e.message
69
+ )
70
+ or ( # isPeriodicCommitError
71
+ e.code == "Neo.ClientError.Statement.SemanticError"
72
+ and (
73
+ "in an open transaction is not possible" in e.message
74
+ or "tried to execute in an explicit transaction" in e.message
75
+ )
76
+ )
77
+ or (
78
+ e.code == "Memgraph.ClientError.MemgraphError.MemgraphError"
79
+ and ("in multicommand transactions" in e.message)
80
+ )
81
+ or (
82
+ e.code == "Memgraph.ClientError.MemgraphError.MemgraphError"
83
+ and "SchemaInfo disabled" in e.message
84
+ )
85
+ ):
86
+ raise
87
+
88
+ # fallback to allow implicit transactions
89
+ with self.driver.session() as session:
90
+ data = session.run(query, params)
91
+ json_data = [r.data() for r in data]
92
+ return json_data
93
+
94
+ def close(self) -> None:
95
+ """
96
+ Close the database connection.
97
+ """
98
+ self.driver.close()
@@ -0,0 +1,51 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, List
3
+
4
+
5
+ class BaseTool(ABC):
6
+ """
7
+ Base class for all tools.
8
+ """
9
+
10
+ name: str
11
+ description: str
12
+ input_schema: Dict[str, Any]
13
+
14
+ def __init__(self, name: str, description: str, input_schema: Dict[str, Any]):
15
+ self.name = name
16
+ self.description = description
17
+ self.input_schema = input_schema
18
+
19
+ @abstractmethod
20
+ def call(self, arguments: Dict[str, Any]) -> List[Any]:
21
+ """
22
+ Execute the tool with the provided arguments.
23
+
24
+ Parameters:
25
+ arguments (dict): A dictionary of arguments as defined by the input schema.
26
+
27
+ Returns:
28
+ List containing one or more output values.
29
+ """
30
+ pass
31
+
32
+ def __repr__(self):
33
+ return f"Tool(name={self.name}, description={self.description})"
34
+
35
+ def get_name(self) -> str:
36
+ """
37
+ Get the name of the tool.
38
+
39
+ Returns:
40
+ str: The name of the tool.
41
+ """
42
+ return self.name
43
+
44
+ def get_description(self) -> str:
45
+ """
46
+ Get the description of the tool.
47
+
48
+ Returns:
49
+ str: The description of the tool.
50
+ """
51
+ return self.description
@@ -0,0 +1,41 @@
1
+ from typing import Dict, List
2
+ from .tool import BaseTool
3
+
4
+
5
+ class BaseToolkit:
6
+ """
7
+ A Toolkit for managing tools.
8
+ """
9
+
10
+ def __init__(self):
11
+ self._tools: Dict[str, BaseTool] = {}
12
+
13
+ def add_tool(self, tool: BaseTool) -> None:
14
+ """
15
+ Add a new tool in the registry.
16
+
17
+ Raises:
18
+ ValueError: If a tool with the same name is already toolkit.
19
+ """
20
+ if tool.name in self._tools:
21
+ raise ValueError(
22
+ f"Tool with name '{tool.name}' is already present in toolkit."
23
+ )
24
+ self._tools[tool.name] = tool
25
+
26
+ def get_tool(self, name: str) -> BaseTool:
27
+ """
28
+ Retrieve a tool by its name.
29
+
30
+ Raises:
31
+ ValueError: If no tool with the given name is found.
32
+ """
33
+ if name not in self._tools:
34
+ raise ValueError(f"Tool with name '{name}' not found.")
35
+ return self._tools[name]
36
+
37
+ def get_all_tools(self) -> List[BaseTool]:
38
+ """
39
+ List all added tools.
40
+ """
41
+ return list(self._tools.values())
File without changes
@@ -0,0 +1,35 @@
1
+ from typing import Any, Dict, List
2
+
3
+ import pytest
4
+
5
+ from ..api.toolkit import BaseToolkit
6
+ from ..api.tool import BaseTool
7
+ from ..utils.logging import logger_init
8
+
9
+ logger = logger_init("test-toolkit") # Set up logger for the test
10
+
11
+
12
+ def test_toolkit():
13
+ """Test the Toolkit class."""
14
+
15
+ toolkit = BaseToolkit()
16
+
17
+ class DummyTool(BaseTool):
18
+ def __init__(self):
19
+ super().__init__(
20
+ name="dummy_tool",
21
+ description="A dummy tool for testing",
22
+ input_schema={},
23
+ )
24
+
25
+ def call(self, arguments: Dict[str, Any]) -> List[Any]:
26
+ return ["dummy_result"]
27
+
28
+ dummy_tool = DummyTool()
29
+ toolkit.add_tool(dummy_tool)
30
+
31
+ assert toolkit.get_tool("dummy_tool") == dummy_tool
32
+ assert len(toolkit.get_all_tools()) == 1
33
+
34
+ with pytest.raises(ValueError):
35
+ toolkit.add_tool(dummy_tool)
@@ -0,0 +1,193 @@
1
+ import pytest
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..tools.betweenness_centrality import BetweennessCentralityTool
5
+ from ..tools.config import ShowConfigTool
6
+ from ..tools.constraint import ShowConstraintInfoTool
7
+ from ..tools.cypher import CypherTool
8
+ from ..tools.index import ShowIndexInfoTool
9
+ from ..tools.page_rank import PageRankTool
10
+ from ..tools.schema import ShowSchemaInfoTool
11
+ from ..tools.storage import ShowStorageInfoTool
12
+ from ..tools.trigger import ShowTriggersTool
13
+ from ..utils.logging import logger_init
14
+
15
+ logger = logger_init("test-tools")
16
+
17
+
18
+ def test_show_schema_info_tool():
19
+ """Test the ShowSchemaInfo tool."""
20
+
21
+ uri = "bolt://localhost:7687"
22
+ user = "memgraph"
23
+ password = "memgraph"
24
+
25
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
26
+
27
+ schema_tool = ShowSchemaInfoTool(db=memgraph_client)
28
+ assert "show_schema_info" in schema_tool.name
29
+
30
+ result = schema_tool.call({})
31
+
32
+ assert isinstance(result, list)
33
+ assert len(result) >= 1
34
+
35
+
36
+ def test_show_config_tool():
37
+ """Test the ShowConfig tool."""
38
+
39
+ uri = "bolt://localhost:7687"
40
+ user = "memgraph"
41
+ password = "memgraph"
42
+
43
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
44
+
45
+ config_tool = ShowConfigTool(db=memgraph_client)
46
+ assert "show_config" in config_tool.name
47
+ result = config_tool.call({})
48
+ assert isinstance(result, list)
49
+ assert len(result) >= 1
50
+
51
+
52
+ def test_index_tool():
53
+ """Test the ShowIndexInfo tool."""
54
+
55
+ uri = "bolt://localhost:7687"
56
+ user = "memgraph"
57
+ password = "memgraph"
58
+
59
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
60
+
61
+ # Create an index for testing
62
+
63
+ memgraph_client.query("CREATE INDEX ON :Person(name)")
64
+
65
+ index_tool = ShowIndexInfoTool(db=memgraph_client)
66
+ assert "show_index_info" in index_tool.name
67
+ result = index_tool.call({})
68
+
69
+ memgraph_client.query("DROP INDEX ON :Person(name)")
70
+
71
+ assert isinstance(result, list)
72
+
73
+ assert len(result) >= 1
74
+
75
+
76
+ def test_storage_tool():
77
+ """Test the ShowStorageInfo tool."""
78
+
79
+ uri = "bolt://localhost:7687"
80
+ user = "memgraph"
81
+ password = "memgraph"
82
+
83
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
84
+
85
+ storage_tool = ShowStorageInfoTool(db=memgraph_client)
86
+ assert "show_storage_info" in storage_tool.name
87
+ result = storage_tool.call({})
88
+
89
+ assert isinstance(result, list)
90
+ assert len(result) >= 1
91
+
92
+
93
+ def test_show_constraint_info_tool():
94
+ """Test the ShowConstraintInfo tool."""
95
+ uri = "bolt://localhost:7687"
96
+ user = "memgraph"
97
+ password = "memgraph"
98
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
99
+ # Create a sample constraint
100
+ memgraph_client.query("CREATE CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE")
101
+
102
+ constraint_tool = ShowConstraintInfoTool(db=memgraph_client)
103
+ result = constraint_tool.call({})
104
+
105
+ memgraph_client.query("DROP CONSTRAINT ON (n:Person) ASSERT n.id IS UNIQUE")
106
+
107
+ assert isinstance(result, list)
108
+ assert len(result) > 0
109
+
110
+
111
+ def test_show_triggers_tool():
112
+ """Test the ShowTriggers tool."""
113
+
114
+ uri = "bolt://localhost:7687"
115
+ user = "memgraph"
116
+ password = "memgraph"
117
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
118
+
119
+ memgraph_client.query(
120
+ """
121
+ CREATE TRIGGER my_trigger ON () CREATE AFTER COMMIT EXECUTE
122
+ UNWIND createdVertices AS newNodes
123
+ SET newNodes.created = timestamp();
124
+ """
125
+ )
126
+
127
+ trigger_tool = ShowTriggersTool(db=memgraph_client)
128
+ result = trigger_tool.call({})
129
+
130
+ memgraph_client.query("DROP TRIGGER my_trigger;")
131
+
132
+ assert isinstance(result, list)
133
+ assert len(result) > 0
134
+
135
+
136
+ def test_page_rank():
137
+ """Test the PageRank tool."""
138
+
139
+ uri = "bolt://localhost:7687"
140
+ user = "memgraph"
141
+ password = "memgraph"
142
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
143
+
144
+ # Create a sample graph for testing
145
+ memgraph_client.query(
146
+ """UNWIND range(1, 10) AS i
147
+ CREATE (:Test {id: i})-[:LINK]->(:Test {id: i + 1});
148
+ """
149
+ )
150
+
151
+ # Run the PageRank tool
152
+ page_rank_tool = PageRankTool(db=memgraph_client)
153
+ result = page_rank_tool.call({"limit": 20})
154
+ assert isinstance(result, list)
155
+ assert len(result) > 0
156
+
157
+
158
+ def test_cypher():
159
+ """Test the Cypher tool."""
160
+
161
+ uri = "bolt://localhost:7687"
162
+ user = "memgraph"
163
+ password = "memgraph"
164
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
165
+
166
+ cypher_tool = CypherTool(db=memgraph_client)
167
+ result = cypher_tool.call({"query": "RETURN 0;"})
168
+ assert isinstance(result, list)
169
+ assert len(result) == 1
170
+
171
+
172
+ def test_betweenness_centrality_tool():
173
+ """Test the RunBetweennessCentralityTool."""
174
+
175
+ uri = "bolt://localhost:7687"
176
+ user = "memgraph"
177
+ password = "memgraph"
178
+ memgraph_client = Memgraph(uri=uri, username=user, password=password)
179
+
180
+ memgraph_client.query(
181
+ """
182
+ UNWIND range(1, 5) AS i
183
+ CREATE (a:Node {id: i})-[:RELATES]->(b:Node {id: i + 1});
184
+ """
185
+ )
186
+
187
+ betweenness_tool = BetweennessCentralityTool(db=memgraph_client)
188
+ result = betweenness_tool.call({"isDirectionIgnored": True, "limit": 5})
189
+
190
+ assert isinstance(result, list)
191
+ assert len(result) > 0
192
+ assert "node" in result[0]
193
+ assert "betweenness_centrality" in result[0]
@@ -0,0 +1,57 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class BetweennessCentralityTool(BaseTool):
8
+ """
9
+ Tool for calculating betweenness centrality on a graph in Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="run_betweenness_centrality",
15
+ description=(
16
+ "Calculates betweenness centrality for nodes in a graph. "
17
+ "Betweenness centrality measures the importance of a node based on how frequently "
18
+ "it lies on the shortest paths between other nodes, indicating its influence on "
19
+ "information flow within the network."
20
+ ),
21
+ input_schema={
22
+ "type": "object",
23
+ "properties": {
24
+ "isDirectionIgnored": {
25
+ "type": "boolean",
26
+ "description": "Set to false to consider the direction of relationships. Default is true.",
27
+ "default": True,
28
+ },
29
+ "limit": {
30
+ "type": "integer",
31
+ "description": "Limit the number of nodes to return. Default is 10.",
32
+ "default": 10,
33
+ },
34
+ },
35
+ "required": [],
36
+ },
37
+ )
38
+ self.db = db
39
+
40
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
41
+ """Execute the betweenness centrality algorithm and return the results."""
42
+ is_direction_ignored = arguments.get("isDirectionIgnored", True)
43
+ limit = arguments.get("limit", 10)
44
+
45
+ query = (
46
+ "CALL betweenness_centrality.get($directed, True) "
47
+ "YIELD node, betweenness_centrality "
48
+ "RETURN node, betweenness_centrality "
49
+ "ORDER BY betweenness_centrality DESC "
50
+ "LIMIT $limit"
51
+ )
52
+ params = {"directed": not is_direction_ignored, "limit": limit}
53
+
54
+ results = self.db.query(query, params)
55
+
56
+ # Convert the results to a list of dictionaries
57
+ return results
@@ -0,0 +1,23 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowConfigTool(BaseTool):
8
+ """
9
+ Tool for showing configuration information from Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_config",
15
+ description="Shows configuration information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ """Execute the SHOW CONFIG query and return the results."""
22
+ config_info = self.db.query("SHOW CONFIG")
23
+ return config_info
@@ -0,0 +1,23 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowConstraintInfoTool(BaseTool):
8
+ """
9
+ Tool for showing constraint information from Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_constraint_info",
15
+ description="Shows constraint information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ """Execute the SHOW CONSTRAINT INFO query and return the results."""
22
+ constraint_info = self.db.query("SHOW CONSTRAINT INFO")
23
+ return constraint_info
@@ -0,0 +1,33 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class CypherTool(BaseTool):
8
+ """
9
+ Tool for running arbitrary Cypher queries on Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="run_cypher",
15
+ description="Executes a Cypher query on a Memgraph database",
16
+ input_schema={
17
+ "type": "object",
18
+ "properties": {
19
+ "query": {
20
+ "type": "string",
21
+ "description": "The Cypher query to execute",
22
+ }
23
+ },
24
+ "required": ["query"],
25
+ },
26
+ )
27
+ self.db = db
28
+
29
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
30
+ """Execute the provided Cypher query and return the results."""
31
+ query = arguments["query"]
32
+ result = self.db.query(query)
33
+ return [dict(record) for record in result]
@@ -0,0 +1,23 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowIndexInfoTool(BaseTool):
8
+ """
9
+ Tool for showing index information in Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_index_info",
15
+ description="Shows index information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ """Execute the SHOW INDEX INFO query and return the results."""
22
+ index_info = self.db.query("SHOW INDEX INFO")
23
+ return index_info
@@ -0,0 +1,43 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class PageRankTool(BaseTool):
8
+ """
9
+ Tool for calculating PageRank on a graph in Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="pagerank",
15
+ description="Calculates PageRank on a graph in Memgraph",
16
+ input_schema={
17
+ "type": "object",
18
+ "properties": {
19
+ "limit": {
20
+ "type": "integer",
21
+ "description": "Number of nodes to return ",
22
+ "default": 10,
23
+ },
24
+ },
25
+ "required": [],
26
+ },
27
+ )
28
+ self.db = db
29
+
30
+ # TODO:(@antejavor) This will fail if user is not running Memgraph Mage since memgraph does not have this built in.
31
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
32
+ """Execute the PageRank algorithm and return the results."""
33
+ limit = arguments.get("limit", 20)
34
+
35
+ query = f"""
36
+ CALL pagerank.get()
37
+ YIELD node, rank
38
+ RETURN node, rank
39
+ ORDER BY rank DESC LIMIT {limit}
40
+ """
41
+ pagerank_results = self.db.query(query)
42
+
43
+ return pagerank_results
@@ -0,0 +1,22 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowSchemaInfoTool(BaseTool):
8
+ """
9
+ Tool for showing schema information from Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_schema_info",
15
+ description="Shows schema information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ schema_info = self.db.query("SHOW SCHEMA INFO")
22
+ return schema_info
@@ -0,0 +1,23 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowStorageInfoTool(BaseTool):
8
+ """
9
+ Tool for showing storage information from Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_storage_info",
15
+ description="Shows storage information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ """Execute the SHOW STORAGE INFO query and return the results."""
22
+ storage_info = self.db.query("SHOW STORAGE INFO")
23
+ return storage_info
@@ -0,0 +1,23 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from ..api.memgraph import Memgraph
4
+ from ..api.tool import BaseTool
5
+
6
+
7
+ class ShowTriggersTool(BaseTool):
8
+ """
9
+ Tool for showing trigger information from Memgraph.
10
+ """
11
+
12
+ def __init__(self, db: Memgraph):
13
+ super().__init__(
14
+ name="show_triggers",
15
+ description="Shows trigger information from a Memgraph database",
16
+ input_schema={"type": "object", "properties": {}, "required": []},
17
+ )
18
+ self.db = db
19
+
20
+ def call(self, arguments: Dict[str, Any]) -> List[Dict[str, Any]]:
21
+ """Execute the SHOW TRIGGERS query and return the results."""
22
+ trigger_info = self.db.query("SHOW TRIGGERS")
23
+ return trigger_info
@@ -0,0 +1,15 @@
1
+ import logging
2
+
3
+
4
+ def logger_init(name: str) -> logging.Logger:
5
+ """Set up a logger with a consistent configuration."""
6
+ logger = logging.getLogger(name)
7
+ if not logger.hasHandlers():
8
+ handler = logging.StreamHandler()
9
+ formatter = logging.Formatter(
10
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
11
+ )
12
+ handler.setFormatter(formatter)
13
+ logger.addHandler(handler)
14
+ logger.setLevel(logging.INFO)
15
+ return logger
@@ -0,0 +1,84 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.13"
4
+
5
+ [[package]]
6
+ name = "colorama"
7
+ version = "0.4.6"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "core"
16
+ version = "0.1.0"
17
+ source = { editable = "." }
18
+
19
+ [package.optional-dependencies]
20
+ test = [
21
+ { name = "pytest" },
22
+ { name = "pytest-asyncio" },
23
+ ]
24
+
25
+ [package.metadata]
26
+ requires-dist = [
27
+ { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.5" },
28
+ { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.26.0" },
29
+ ]
30
+ provides-extras = ["test"]
31
+
32
+ [[package]]
33
+ name = "iniconfig"
34
+ version = "2.1.0"
35
+ source = { registry = "https://pypi.org/simple" }
36
+ sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
37
+ wheels = [
38
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
39
+ ]
40
+
41
+ [[package]]
42
+ name = "packaging"
43
+ version = "24.2"
44
+ source = { registry = "https://pypi.org/simple" }
45
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
46
+ wheels = [
47
+ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
48
+ ]
49
+
50
+ [[package]]
51
+ name = "pluggy"
52
+ version = "1.5.0"
53
+ source = { registry = "https://pypi.org/simple" }
54
+ sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
55
+ wheels = [
56
+ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
57
+ ]
58
+
59
+ [[package]]
60
+ name = "pytest"
61
+ version = "8.3.5"
62
+ source = { registry = "https://pypi.org/simple" }
63
+ dependencies = [
64
+ { name = "colorama", marker = "sys_platform == 'win32'" },
65
+ { name = "iniconfig" },
66
+ { name = "packaging" },
67
+ { name = "pluggy" },
68
+ ]
69
+ sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
70
+ wheels = [
71
+ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
72
+ ]
73
+
74
+ [[package]]
75
+ name = "pytest-asyncio"
76
+ version = "0.26.0"
77
+ source = { registry = "https://pypi.org/simple" }
78
+ dependencies = [
79
+ { name = "pytest" },
80
+ ]
81
+ sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 }
82
+ wheels = [
83
+ { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 },
84
+ ]