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
2 changes: 2 additions & 0 deletions edgedb/datatypes/datatypes.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,5 @@ cdef namedtuple_new(object namedtuple_type)
cdef namedtuple_type_new(object desc)
cdef object_new(object desc)
cdef object_set(object tuple, Py_ssize_t pos, object elem)

cdef extern cpython.PyObject* at_sign_ptr
2 changes: 2 additions & 0 deletions edgedb/datatypes/datatypes.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Array = list
Link = EdgeLink_InitType()
LinkSet = EdgeLinkSet_InitType()

cdef str at_sign = "@"
at_sign_ptr = <cpython.PyObject*>at_sign

_EDGE_POINTER_IS_IMPLICIT = EDGE_POINTER_IS_IMPLICIT
_EDGE_POINTER_IS_LINKPROP = EDGE_POINTER_IS_LINKPROP
Expand Down
113 changes: 106 additions & 7 deletions edgedb/datatypes/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@


static int init_type_called = 0;
PyObject* at_sign_ptr;


EDGE_SETUP_FREELIST(
Expand Down Expand Up @@ -186,7 +187,7 @@ object_getattr(EdgeObject *o, PyObject *name)
case L_ERROR:
return NULL;

case L_NOT_FOUND:
case L_NOT_FOUND: {
// Used in `dataclasses.as_dict()`
if (
PyUnicode_CompareWithASCIIString(
Expand All @@ -195,7 +196,46 @@ object_getattr(EdgeObject *o, PyObject *name)
) {
return EdgeRecordDesc_GetDataclassFields((PyObject *)o->desc);
}

// getattr(obj, "@...") for link property
int prefixed = PyUnicode_Tailmatch(
name, at_sign_ptr, 0, PY_SSIZE_T_MAX, -1
);
if (prefixed == -1) {
return NULL;
}
if (prefixed) {
PyObject *stripped = PyUnicode_Substring(
name, 1, PyUnicode_GET_LENGTH(name)
);
if (stripped == NULL) {
return NULL;
}
ret = EdgeRecordDesc_Lookup(
(PyObject *)o->desc, stripped, &pos);
Py_DECREF(stripped);
switch (ret) {
case L_ERROR:
return NULL;

case L_NOT_FOUND:
case L_LINK:
case L_PROPERTY:
return PyObject_GenericGetAttr((PyObject *)o, name);

case L_LINKPROP: {
PyObject *val = EdgeObject_GET_ITEM(o, pos);
Py_INCREF(val);
return val;
}

default:
abort();
}
}

return PyObject_GenericGetAttr((PyObject *)o, name);
}

case L_LINKPROP:
return PyObject_GenericGetAttr((PyObject *)o, name);
Expand All @@ -216,28 +256,87 @@ static PyObject *
object_getitem(EdgeObject *o, PyObject *name)
{
Py_ssize_t pos;
int prefixed = 0;
PyObject *stripped = name;
if (PyUnicode_Check(name)) {
prefixed = PyUnicode_Tailmatch(
name, at_sign_ptr, 0, PY_SSIZE_T_MAX, -1
);
if (prefixed == -1) {
return NULL;
}
if (prefixed) {
stripped = PyUnicode_Substring(
name, 1, PyUnicode_GET_LENGTH(name)
);
if (stripped == NULL) {
return NULL;
}
}
}

edge_attr_lookup_t ret = EdgeRecordDesc_Lookup(
(PyObject *)o->desc, name, &pos);
(PyObject *)o->desc, stripped, &pos
);
if (prefixed) {
Py_DECREF(stripped);
}
switch (ret) {
case L_ERROR:
return NULL;

case L_PROPERTY:
PyErr_Format(
PyExc_TypeError,
"property %R should be accessed via dot notation",
name);
if (prefixed) {
PyErr_Format(
PyExc_KeyError,
"link property %R does not exist",
name);
} else {
PyErr_Format(
PyExc_TypeError,
"property %R should be accessed via dot notation",
name);
}
return NULL;

case L_LINKPROP:
if (prefixed) {
PyObject *val = EdgeObject_GET_ITEM(o, pos);
Py_INCREF(val);
return val;
} else {
PyErr_Format(
PyExc_TypeError,
"link property %R should be accessed with '@' prefix",
name);
return NULL;
}

case L_NOT_FOUND:
PyErr_Format(
PyExc_KeyError,
"link %R does not exist",
"link property %R does not exist",
name);
return NULL;

case L_LINK: {
if (prefixed) {
PyErr_Format(
PyExc_KeyError,
"link property %R does not exist",
name);
return NULL;
}
int res = PyErr_WarnEx(
PyExc_DeprecationWarning,
"getting link on object is deprecated since 1.0, "
"please use dot notation to access linked objects, "
"and a following ['@...'] for the link properties.",
1
);
if (res != 0) {
return NULL;
}
PyObject *val = EdgeObject_GET_ITEM(o, pos);

if (PyList_Check(val)) {
Expand Down
70 changes: 68 additions & 2 deletions tests/datatypes/test_datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@
import random
import string
import unittest
import warnings
import weakref

import edgedb
from edgedb.datatypes import datatypes as private
from edgedb import introspect


def test_deprecated(f):
def wrapper(*args, **kwargs):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return f(*args, **kwargs)
return wrapper


class TestRecordDesc(unittest.TestCase):

def test_recorddesc_1(self):
Expand Down Expand Up @@ -568,6 +577,7 @@ def test_object_6(self):
"accessed via dot notation"):
u['name']

@test_deprecated
def test_object_links_1(self):
O2 = private.create_object_factory(
id='property',
Expand Down Expand Up @@ -616,6 +626,7 @@ def test_object_links_1(self):
with self.assertRaises(AttributeError):
link2.aaaa

@test_deprecated
def test_object_links_2(self):
User = private.create_object_factory(
id='property',
Expand All @@ -634,6 +645,7 @@ def test_object_links_2(self):

self.assertEqual(set(dir(u1)), {'id', 'friends', 'enemies'})

@test_deprecated
def test_object_links_3(self):
User = private.create_object_factory(
id='property',
Expand All @@ -659,6 +671,7 @@ def test_object_links_3(self):
repr(u2['friend']),
"Link(name='friend', source_id=2, target_id=1)")

@test_deprecated
def test_object_links_4(self):
User = private.create_object_factory(
id='property',
Expand All @@ -667,10 +680,63 @@ def test_object_links_4(self):

u = User(1, None)

with self.assertRaisesRegex(KeyError,
"link 'error_key' does not exist"):
with self.assertRaisesRegex(
KeyError, "link property 'error_key' does not exist"
):
u['error_key']

def test_object_link_property_1(self):
O2 = private.create_object_factory(
id='property',
lb='link-property',
c='property'
)

O1 = private.create_object_factory(
id='property',
o2s='link'
)

o2_1 = O2(1, 'linkprop o2 1', 3)
o2_2 = O2(4, 'linkprop o2 2', 6)
o1 = O1(2, edgedb.Set((o2_1, o2_2)))

o2s = o1.o2s
self.assertEqual(len(o2s), 2)
self.assertEqual(o2s, o1.o2s)
self.assertEqual(
repr(o2s),
"[Object{id := 1, @lb := 'linkprop o2 1', c := 3},"
" Object{id := 4, @lb := 'linkprop o2 2', c := 6}]"
)

self.assertEqual(o2s[0]['@lb'], 'linkprop o2 1')
self.assertEqual(o2s[1]['@lb'], 'linkprop o2 2')
self.assertEqual(getattr(o2s[0], '@lb'), 'linkprop o2 1')
self.assertEqual(getattr(o2s[1], '@lb'), 'linkprop o2 2')

with self.assertRaises(AttributeError):
o2s[0].lb

with self.assertRaises(AttributeError):
getattr(o2s[0], "@lb2")

with self.assertRaisesRegex(
TypeError,
"link property 'lb' should be accessed with '@' prefix",
):
o2s[0]['lb']

with self.assertRaisesRegex(
TypeError, "property 'c' should be accessed via dot notation"
):
o2s[0]['c']

with self.assertRaisesRegex(
KeyError, "link property '@c' does not exist"
):
o2s[0]['@c']

def test_object_dataclass_1(self):
User = private.create_object_factory(
id='property',
Expand Down