Skip to content

Commit 9b90e61

Browse files
authored
Merge pull request #15 from microsoft/refactoring_flowquery_py
Refactoring flowquery py
2 parents c24de17 + 7e18ece commit 9b90e61

99 files changed

Lines changed: 811 additions & 700 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.husky/pre-commit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
npx lint-staged
22

3+
# Run ruff and mypy on Python files in flowquery-py
4+
if git diff --cached --name-only | grep -q "^flowquery-py/.*\.py$"; then
5+
echo "Running ruff on Python files..."
6+
cd flowquery-py
7+
conda run -n flowquery ruff check src --select=F401,E,W,I
8+
if [ $? -ne 0 ]; then
9+
echo "Ruff check failed. Please fix the issues before committing."
10+
exit 1
11+
fi
12+
echo "Running mypy on Python files..."
13+
conda run -n flowquery mypy src --no-error-summary
14+
if [ $? -ne 0 ]; then
15+
echo "Mypy check failed. Please fix the type errors before committing."
16+
exit 1
17+
fi
18+
cd ..
19+
fi
20+
321
# Strip outputs from notebook files in flowquery-py
422
for notebook in $(git ls-files 'flowquery-py/**/*.ipynb'); do
523
if [ -f "$notebook" ]; then

flowquery-py/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ venv.bak/
7373
.dmypy.json
7474
dmypy.json
7575

76+
# ruff
77+
.ruff_cache/
78+
7679
# Pyre type checker
7780
.pyre/
7881

flowquery-py/pyproject.toml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ dev = [
4141
"jupyter>=1.0.0",
4242
"ipykernel>=6.0.0",
4343
"nbstripout>=0.6.0",
44+
"mypy>=1.0.0",
45+
"ruff>=0.1.0",
4446
]
4547

4648
[build-system]
@@ -75,4 +77,45 @@ python_functions = ["test_*"]
7577
addopts = "-v --tb=short"
7678

7779
[tool.pytest-asyncio]
78-
mode = "auto"
80+
mode = "auto"
81+
82+
[tool.mypy]
83+
python_version = "3.10"
84+
strict = true
85+
ignore_missing_imports = true
86+
exclude = [
87+
"tests/",
88+
"__pycache__",
89+
".git",
90+
"build",
91+
"dist",
92+
]
93+
94+
[[tool.mypy.overrides]]
95+
module = "src.parsing.parser"
96+
warn_return_any = false
97+
disable_error_code = ["union-attr", "arg-type"]
98+
99+
[tool.ruff]
100+
target-version = "py310"
101+
line-length = 120
102+
exclude = [
103+
".git",
104+
"__pycache__",
105+
"build",
106+
"dist",
107+
]
108+
109+
[tool.ruff.lint]
110+
select = [
111+
"F", # Pyflakes (includes F401 unused imports)
112+
"E", # pycodestyle errors
113+
"W", # pycodestyle warnings
114+
"I", # isort
115+
]
116+
ignore = [
117+
"E501", # line too long (handled by formatter)
118+
]
119+
120+
[tool.ruff.lint.isort]
121+
known-first-party = ["flowquery", "src"]

flowquery-py/src/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88

99
from .compute.runner import Runner
1010
from .io.command_line import CommandLine
11-
from .parsing.parser import Parser
12-
from .parsing.functions.function import Function
1311
from .parsing.functions.aggregate_function import AggregateFunction
1412
from .parsing.functions.async_function import AsyncFunction
15-
from .parsing.functions.predicate_function import PredicateFunction
16-
from .parsing.functions.reducer_element import ReducerElement
13+
from .parsing.functions.function import Function
1714
from .parsing.functions.function_metadata import (
15+
FunctionCategory,
1816
FunctionDef,
1917
FunctionMetadata,
20-
FunctionCategory,
2118
)
19+
from .parsing.functions.predicate_function import PredicateFunction
20+
from .parsing.functions.reducer_element import ReducerElement
21+
from .parsing.parser import Parser
2222

2323
__all__ = [
2424
"Runner",

flowquery-py/src/compute/runner.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
class Runner:
1111
"""Executes a FlowQuery statement and retrieves the results.
12-
12+
1313
The Runner class parses a FlowQuery statement into an AST and executes it,
1414
managing the execution flow from the first operation to the final return statement.
15-
15+
1616
Example:
1717
runner = Runner("WITH 1 as x RETURN x")
1818
await runner.run()
@@ -25,24 +25,28 @@ def __init__(
2525
ast: Optional[ASTNode] = None
2626
):
2727
"""Creates a new Runner instance and parses the FlowQuery statement.
28-
28+
2929
Args:
3030
statement: The FlowQuery statement to execute
3131
ast: An already-parsed AST (optional)
32-
32+
3333
Raises:
3434
ValueError: If neither statement nor AST is provided
3535
"""
3636
if (statement is None or statement == "") and ast is None:
3737
raise ValueError("Either statement or AST must be provided")
38-
39-
_ast = ast if ast is not None else Parser().parse(statement)
40-
self._first: Operation = _ast.first_child()
41-
self._last: Operation = _ast.last_child()
38+
39+
_ast = ast if ast is not None else Parser().parse(statement or "")
40+
first = _ast.first_child()
41+
last = _ast.last_child()
42+
if not isinstance(first, Operation) or not isinstance(last, Operation):
43+
raise ValueError("AST must contain Operations")
44+
self._first: Operation = first
45+
self._last: Operation = last
4246

4347
async def run(self) -> None:
4448
"""Executes the parsed FlowQuery statement.
45-
49+
4650
Raises:
4751
Exception: If an error occurs during execution
4852
"""
@@ -53,7 +57,7 @@ async def run(self) -> None:
5357
@property
5458
def results(self) -> List[Dict[str, Any]]:
5559
"""Gets the results from the executed statement.
56-
60+
5761
Returns:
5862
The results from the last operation (typically a RETURN statement)
5963
"""

flowquery-py/src/extensibility.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
Example:
66
from flowquery.extensibility import Function, FunctionDef
7-
7+
88
@FunctionDef({
99
'description': "Converts a string to uppercase",
1010
'category': "string",
@@ -15,27 +15,27 @@ class UpperCase(Function):
1515
def __init__(self):
1616
super().__init__("uppercase")
1717
self._expected_parameter_count = 1
18-
18+
1919
def value(self) -> str:
2020
return str(self.get_children()[0].value()).upper()
2121
"""
2222

2323
# Base function classes for creating custom functions
24-
from .parsing.functions.function import Function
2524
from .parsing.functions.aggregate_function import AggregateFunction
2625
from .parsing.functions.async_function import AsyncFunction
27-
from .parsing.functions.predicate_function import PredicateFunction
28-
from .parsing.functions.reducer_element import ReducerElement
26+
from .parsing.functions.function import Function
2927

3028
# Decorator and metadata types for function registration
3129
from .parsing.functions.function_metadata import (
30+
FunctionCategory,
3231
FunctionDef,
33-
FunctionMetadata,
3432
FunctionDefOptions,
35-
ParameterSchema,
33+
FunctionMetadata,
3634
OutputSchema,
37-
FunctionCategory,
35+
ParameterSchema,
3836
)
37+
from .parsing.functions.predicate_function import PredicateFunction
38+
from .parsing.functions.reducer_element import ReducerElement
3939

4040
__all__ = [
4141
"Function",

flowquery-py/src/graph/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
"""Graph module for FlowQuery."""
22

3-
from .node import Node
4-
from .relationship import Relationship
5-
from .pattern import Pattern
6-
from .patterns import Patterns
7-
from .pattern_expression import PatternExpression
83
from .database import Database
94
from .hops import Hops
5+
from .node import Node
106
from .node_data import NodeData
117
from .node_reference import NodeReference
12-
from .relationship_data import RelationshipData
13-
from .relationship_reference import RelationshipReference
8+
from .pattern import Pattern
9+
from .pattern_expression import PatternExpression
10+
from .patterns import Patterns
1411
from .physical_node import PhysicalNode
1512
from .physical_relationship import PhysicalRelationship
13+
from .relationship import Relationship
14+
from .relationship_data import RelationshipData
15+
from .relationship_reference import RelationshipReference
1616

1717
__all__ = [
1818
"Node",

flowquery-py/src/graph/database.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"""Graph database for FlowQuery."""
22

3-
from typing import Any, Dict, Optional, Union, TYPE_CHECKING
3+
from __future__ import annotations
44

5-
from ..parsing.ast_node import ASTNode
5+
from typing import Dict, Optional, Union
66

7-
if TYPE_CHECKING:
8-
from .node import Node
9-
from .relationship import Relationship
10-
from .node_data import NodeData
11-
from .relationship_data import RelationshipData
7+
from ..parsing.ast_node import ASTNode
8+
from .node import Node
9+
from .node_data import NodeData
10+
from .physical_node import PhysicalNode
11+
from .physical_relationship import PhysicalRelationship
12+
from .relationship import Relationship
13+
from .relationship_data import RelationshipData
1214

1315

1416
class Database:
@@ -18,7 +20,7 @@ class Database:
1820
_nodes: Dict[str, 'PhysicalNode'] = {}
1921
_relationships: Dict[str, 'PhysicalRelationship'] = {}
2022

21-
def __init__(self):
23+
def __init__(self) -> None:
2224
pass
2325

2426
@classmethod
@@ -29,7 +31,6 @@ def get_instance(cls) -> 'Database':
2931

3032
def add_node(self, node: 'Node', statement: ASTNode) -> None:
3133
"""Adds a node to the database."""
32-
from .physical_node import PhysicalNode
3334
if node.label is None:
3435
raise ValueError("Node label is null")
3536
physical = PhysicalNode(None, node.label)
@@ -42,7 +43,6 @@ def get_node(self, node: 'Node') -> Optional['PhysicalNode']:
4243

4344
def add_relationship(self, relationship: 'Relationship', statement: ASTNode) -> None:
4445
"""Adds a relationship to the database."""
45-
from .physical_relationship import PhysicalRelationship
4646
if relationship.type is None:
4747
raise ValueError("Relationship type is null")
4848
physical = PhysicalRelationship()
@@ -56,11 +56,6 @@ def get_relationship(self, relationship: 'Relationship') -> Optional['PhysicalRe
5656

5757
async def get_data(self, element: Union['Node', 'Relationship']) -> Union['NodeData', 'RelationshipData']:
5858
"""Gets data for a node or relationship."""
59-
from .node import Node
60-
from .relationship import Relationship
61-
from .node_data import NodeData
62-
from .relationship_data import RelationshipData
63-
6459
if isinstance(element, Node):
6560
node = self.get_node(element)
6661
if node is None:
@@ -75,8 +70,3 @@ async def get_data(self, element: Union['Node', 'Relationship']) -> Union['NodeD
7570
return RelationshipData(data)
7671
else:
7772
raise ValueError("Element is neither Node nor Relationship")
78-
79-
80-
# Import for type hints
81-
from .physical_node import PhysicalNode
82-
from .physical_relationship import PhysicalRelationship

0 commit comments

Comments
 (0)