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
71 changes: 53 additions & 18 deletions drivers/python/age/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,16 @@ def visitAgValue(self, ctx:AgtypeParser.AgValueContext):

if annoCtx is not None:
annoCtx.accept(self)
anno = annoCtx.IDENT().getText()
identNode = annoCtx.IDENT()
if identNode is None:
raise AGTypeError(ctx.getText(), "Missing type annotation identifier")
anno = identNode.getText()
if valueCtx is None:
raise AGTypeError(ctx.getText(), "Missing value for annotated type")
return self.handleAnnotatedValue(anno, valueCtx)
else:
if valueCtx is None:
return None
return valueCtx.accept(self)


Expand All @@ -109,9 +116,14 @@ def visitIntegerValue(self, ctx:AgtypeParser.IntegerValueContext):

# Visit a parse tree produced by AgtypeParser#floatLiteral.
def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):
text = ctx.getText()
c = ctx.getChild(0)
if c is None or not hasattr(c, 'symbol') or c.symbol is None:
raise AGTypeError(
str(text),
"Malformed float literal: missing or invalid child node"
)
tp = c.symbol.type
text = ctx.getText()
if tp == AgtypeParser.RegularFloat:
return float(text)
elif tp == AgtypeParser.ExponentFloat:
Expand Down Expand Up @@ -150,15 +162,27 @@ def visitObj(self, ctx:AgtypeParser.ObjContext):
namVal = self.visitPair(c)
name = namVal[0]
valCtx = namVal[1]
val = valCtx.accept(self)
obj[name] = val
# visitPair() raises AGTypeError when the value node is
# missing, so valCtx should never be None here. The
# guard is kept as a defensive fallback only.
if valCtx is not None:
val = valCtx.accept(self)
obj[name] = val
else:
obj[name] = None
return obj


# Visit a parse tree produced by AgtypeParser#pair.
def visitPair(self, ctx:AgtypeParser.PairContext):
self.visitChildren(ctx)
return (ctx.STRING().getText().strip('"') , ctx.agValue())
strNode = ctx.STRING()
agValNode = ctx.agValue()
if strNode is None:
raise AGTypeError(ctx.getText(), "Missing key in object pair")
if agValNode is None:
raise AGTypeError(ctx.getText(), "Missing value in object pair")
return (strNode.getText().strip('"') , agValNode)


# Visit a parse tree produced by AgtypeParser#array.
Expand All @@ -171,38 +195,49 @@ def visitArray(self, ctx:AgtypeParser.ArrayContext):
return li

def handleAnnotatedValue(self, anno:str, ctx:ParserRuleContext):
# Each branch below constructs a model object (Vertex, Edge, Path)
# and populates it from the parsed dict/list. If a type check
# fails (e.g. the parsed value is not a dict), AGTypeError is
# raised and the partially-constructed object is discarded — no
# cleanup is needed because the caller propagates the exception.
if anno == "numeric":
return Decimal(ctx.getText())
elif anno == "vertex":
dict = ctx.accept(self)
vid = dict["id"]
d = ctx.accept(self)
if not isinstance(d, dict):
raise AGTypeError(str(ctx.getText()), "Expected dict for vertex, got " + type(d).__name__)
vid = d.get("id")
vertex = None
if self.vertexCache != None and vid in self.vertexCache :
if self.vertexCache is not None and vid in self.vertexCache:
vertex = self.vertexCache[vid]
else:
vertex = Vertex()
vertex.id = dict["id"]
vertex.label = dict["label"]
vertex.properties = dict["properties"]
vertex.id = d.get("id")
vertex.label = d.get("label")
vertex.properties = d.get("properties") or {}

if self.vertexCache != None:
if self.vertexCache is not None:
self.vertexCache[vid] = vertex
Comment thread
jrgemignani marked this conversation as resolved.

return vertex

elif anno == "edge":
edge = Edge()
dict = ctx.accept(self)
edge.id = dict["id"]
edge.label = dict["label"]
edge.end_id = dict["end_id"]
edge.start_id = dict["start_id"]
edge.properties = dict["properties"]
d = ctx.accept(self)
if not isinstance(d, dict):
raise AGTypeError(str(ctx.getText()), "Expected dict for edge, got " + type(d).__name__)
edge.id = d.get("id")
edge.label = d.get("label")
edge.end_id = d.get("end_id")
edge.start_id = d.get("start_id")
edge.properties = d.get("properties") or {}

return edge

elif anno == "path":
arr = ctx.accept(self)
if not isinstance(arr, list):
raise AGTypeError(str(ctx.getText()), "Expected list for path, got " + type(arr).__name__)
path = Path(arr)

return path
Expand Down
7 changes: 2 additions & 5 deletions drivers/python/age/gen/AgtypeLexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Generated from ../Agtype.g4 by ANTLR 4.11.1

# Generated from ../Agtype.g4 by ANTLR 4.13.2
from antlr4 import *
from io import StringIO
import sys
Expand Down Expand Up @@ -142,9 +141,7 @@ class AgtypeLexer(Lexer):

def __init__(self, input=None, output:TextIO = sys.stdout):
super().__init__(input, output)
self.checkVersion("4.11.1")
self.checkVersion("4.13.2")
self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
self._actions = None
self._predicates = None


7 changes: 3 additions & 4 deletions drivers/python/age/gen/AgtypeListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Generated from ../Agtype.g4 by ANTLR 4.11.1

# Generated from ../Agtype.g4 by ANTLR 4.13.2
from antlr4 import *
if __name__ is not None and "." in __name__:
if "." in __name__:
from .AgtypeParser import AgtypeParser
else:
from AgtypeParser import AgtypeParser
Expand Down Expand Up @@ -159,4 +158,4 @@ def exitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):



del AgtypeParser
del AgtypeParser
7 changes: 2 additions & 5 deletions drivers/python/age/gen/AgtypeParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Generated from ../Agtype.g4 by ANTLR 4.11.1
# Generated from ../Agtype.g4 by ANTLR 4.13.2
# encoding: utf-8

from antlr4 import *
from io import StringIO
import sys
Expand Down Expand Up @@ -108,7 +107,7 @@ class AgtypeParser ( Parser ):

def __init__(self, input:TokenStream, output:TextIO = sys.stdout):
super().__init__(input, output)
self.checkVersion("4.11.1")
self.checkVersion("4.13.2")
self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache)
self._predicates = None

Expand Down Expand Up @@ -854,5 +853,3 @@ def floatLiteral(self):
finally:
self.exitRule()
return localctx


7 changes: 3 additions & 4 deletions drivers/python/age/gen/AgtypeVisitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Generated from ../Agtype.g4 by ANTLR 4.11.1

# Generated from ../Agtype.g4 by ANTLR 4.13.2
from antlr4 import *
if __name__ is not None and "." in __name__:
if "." in __name__:
from .AgtypeParser import AgtypeParser
else:
from AgtypeParser import AgtypeParser
Expand Down Expand Up @@ -100,4 +99,4 @@ def visitFloatLiteral(self, ctx:AgtypeParser.FloatLiteralContext):



del AgtypeParser
del AgtypeParser
7 changes: 6 additions & 1 deletion drivers/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ authors = [
{name = "Ikchan Kwon, Apache AGE", email = "dev-subscribe@age.apache.org"}
]
classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -37,7 +38,11 @@ classifiers = [
]
dependencies = [
"psycopg",
"antlr4-python3-runtime==4.11.1",
# Parser is generated with ANTLR 4.13.2. Runtime is forward- and
# backward-compatible within the 4.x series (checkVersion() emits a
# warning on mismatch but parsing still works); tested on 4.11.1–4.13.2
# with Python 3.9–3.14.
"antlr4-python3-runtime>=4.13.2,<5.0",
]

[project.urls]
Expand Down
2 changes: 1 addition & 1 deletion drivers/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
psycopg
antlr4-python3-runtime==4.11.1
antlr4-python3-runtime>=4.11.1,<5.0
setuptools
networkx
Loading
Loading