Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
npx lint-staged

# Run ruff and mypy on Python files in flowquery-py
if git diff --cached --name-only | grep -q "^flowquery-py/.*\.py$"; then
echo "Running ruff on Python files..."
cd flowquery-py
conda run -n flowquery ruff check src --select=F401,E,W,I
if [ $? -ne 0 ]; then
echo "Ruff check failed. Please fix the issues before committing."
exit 1
fi
echo "Running mypy on Python files..."
conda run -n flowquery mypy src --no-error-summary
if [ $? -ne 0 ]; then
echo "Mypy check failed. Please fix the type errors before committing."
exit 1
fi
cd ..
fi

# Strip outputs from notebook files in flowquery-py
for notebook in $(git ls-files 'flowquery-py/**/*.ipynb'); do
if [ -f "$notebook" ]; then
Expand Down
3 changes: 3 additions & 0 deletions flowquery-py/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ venv.bak/
.dmypy.json
dmypy.json

# ruff
.ruff_cache/

# Pyre type checker
.pyre/

Expand Down
45 changes: 44 additions & 1 deletion flowquery-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ dev = [
"jupyter>=1.0.0",
"ipykernel>=6.0.0",
"nbstripout>=0.6.0",
"mypy>=1.0.0",
"ruff>=0.1.0",
]

[build-system]
Expand Down Expand Up @@ -75,4 +77,45 @@ python_functions = ["test_*"]
addopts = "-v --tb=short"

[tool.pytest-asyncio]
mode = "auto"
mode = "auto"

[tool.mypy]
python_version = "3.10"
strict = true
ignore_missing_imports = true
exclude = [
"tests/",
"__pycache__",
".git",
"build",
"dist",
]

[[tool.mypy.overrides]]
module = "src.parsing.parser"
warn_return_any = false
disable_error_code = ["union-attr", "arg-type"]

[tool.ruff]
target-version = "py310"
line-length = 120
exclude = [
".git",
"__pycache__",
"build",
"dist",
]

[tool.ruff.lint]
select = [
"F", # Pyflakes (includes F401 unused imports)
"E", # pycodestyle errors
"W", # pycodestyle warnings
"I", # isort
]
ignore = [
"E501", # line too long (handled by formatter)
]

[tool.ruff.lint.isort]
known-first-party = ["flowquery", "src"]
10 changes: 5 additions & 5 deletions flowquery-py/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@

from .compute.runner import Runner
from .io.command_line import CommandLine
from .parsing.parser import Parser
from .parsing.functions.function import Function
from .parsing.functions.aggregate_function import AggregateFunction
from .parsing.functions.async_function import AsyncFunction
from .parsing.functions.predicate_function import PredicateFunction
from .parsing.functions.reducer_element import ReducerElement
from .parsing.functions.function import Function
from .parsing.functions.function_metadata import (
FunctionCategory,
FunctionDef,
FunctionMetadata,
FunctionCategory,
)
from .parsing.functions.predicate_function import PredicateFunction
from .parsing.functions.reducer_element import ReducerElement
from .parsing.parser import Parser

__all__ = [
"Runner",
Expand Down
24 changes: 14 additions & 10 deletions flowquery-py/src/compute/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

class Runner:
"""Executes a FlowQuery statement and retrieves the results.

The Runner class parses a FlowQuery statement into an AST and executes it,
managing the execution flow from the first operation to the final return statement.

Example:
runner = Runner("WITH 1 as x RETURN x")
await runner.run()
Expand All @@ -25,24 +25,28 @@ def __init__(
ast: Optional[ASTNode] = None
):
"""Creates a new Runner instance and parses the FlowQuery statement.

Args:
statement: The FlowQuery statement to execute
ast: An already-parsed AST (optional)

Raises:
ValueError: If neither statement nor AST is provided
"""
if (statement is None or statement == "") and ast is None:
raise ValueError("Either statement or AST must be provided")

_ast = ast if ast is not None else Parser().parse(statement)
self._first: Operation = _ast.first_child()
self._last: Operation = _ast.last_child()

_ast = ast if ast is not None else Parser().parse(statement or "")
first = _ast.first_child()
last = _ast.last_child()
if not isinstance(first, Operation) or not isinstance(last, Operation):
raise ValueError("AST must contain Operations")
self._first: Operation = first
self._last: Operation = last

async def run(self) -> None:
"""Executes the parsed FlowQuery statement.

Raises:
Exception: If an error occurs during execution
"""
Expand All @@ -53,7 +57,7 @@ async def run(self) -> None:
@property
def results(self) -> List[Dict[str, Any]]:
"""Gets the results from the executed statement.

Returns:
The results from the last operation (typically a RETURN statement)
"""
Expand Down
16 changes: 8 additions & 8 deletions flowquery-py/src/extensibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Example:
from flowquery.extensibility import Function, FunctionDef

@FunctionDef({
'description': "Converts a string to uppercase",
'category': "string",
Expand All @@ -15,27 +15,27 @@ class UpperCase(Function):
def __init__(self):
super().__init__("uppercase")
self._expected_parameter_count = 1

def value(self) -> str:
return str(self.get_children()[0].value()).upper()
"""

# Base function classes for creating custom functions
from .parsing.functions.function import Function
from .parsing.functions.aggregate_function import AggregateFunction
from .parsing.functions.async_function import AsyncFunction
from .parsing.functions.predicate_function import PredicateFunction
from .parsing.functions.reducer_element import ReducerElement
from .parsing.functions.function import Function

# Decorator and metadata types for function registration
from .parsing.functions.function_metadata import (
FunctionCategory,
FunctionDef,
FunctionMetadata,
FunctionDefOptions,
ParameterSchema,
FunctionMetadata,
OutputSchema,
FunctionCategory,
ParameterSchema,
)
from .parsing.functions.predicate_function import PredicateFunction
from .parsing.functions.reducer_element import ReducerElement

__all__ = [
"Function",
Expand Down
14 changes: 7 additions & 7 deletions flowquery-py/src/graph/__init__.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"""Graph module for FlowQuery."""

from .node import Node
from .relationship import Relationship
from .pattern import Pattern
from .patterns import Patterns
from .pattern_expression import PatternExpression
from .database import Database
from .hops import Hops
from .node import Node
from .node_data import NodeData
from .node_reference import NodeReference
from .relationship_data import RelationshipData
from .relationship_reference import RelationshipReference
from .pattern import Pattern
from .pattern_expression import PatternExpression
from .patterns import Patterns
from .physical_node import PhysicalNode
from .physical_relationship import PhysicalRelationship
from .relationship import Relationship
from .relationship_data import RelationshipData
from .relationship_reference import RelationshipReference

__all__ = [
"Node",
Expand Down
30 changes: 10 additions & 20 deletions flowquery-py/src/graph/database.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""Graph database for FlowQuery."""

from typing import Any, Dict, Optional, Union, TYPE_CHECKING
from __future__ import annotations

from ..parsing.ast_node import ASTNode
from typing import Dict, Optional, Union

if TYPE_CHECKING:
from .node import Node
from .relationship import Relationship
from .node_data import NodeData
from .relationship_data import RelationshipData
from ..parsing.ast_node import ASTNode
from .node import Node
from .node_data import NodeData
from .physical_node import PhysicalNode
from .physical_relationship import PhysicalRelationship
from .relationship import Relationship
from .relationship_data import RelationshipData


class Database:
Expand All @@ -18,7 +20,7 @@ class Database:
_nodes: Dict[str, 'PhysicalNode'] = {}
_relationships: Dict[str, 'PhysicalRelationship'] = {}

def __init__(self):
def __init__(self) -> None:
pass

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

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

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

async def get_data(self, element: Union['Node', 'Relationship']) -> Union['NodeData', 'RelationshipData']:
"""Gets data for a node or relationship."""
from .node import Node
from .relationship import Relationship
from .node_data import NodeData
from .relationship_data import RelationshipData

if isinstance(element, Node):
node = self.get_node(element)
if node is None:
Expand All @@ -75,8 +70,3 @@ async def get_data(self, element: Union['Node', 'Relationship']) -> Union['NodeD
return RelationshipData(data)
else:
raise ValueError("Element is neither Node nor Relationship")


# Import for type hints
from .physical_node import PhysicalNode
from .physical_relationship import PhysicalRelationship
Loading