Skip to content

Commit 68480f9

Browse files
authored
Use @-prefixed keys in object codec for link properties (#384)
Fixes #377
1 parent 288eb72 commit 68480f9

File tree

11 files changed

+102
-112
lines changed

11 files changed

+102
-112
lines changed

edgedb/codegen/generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ def _generate_code(
442442
print(f"{INDENT}@typing.overload", file=buf)
443443
print(
444444
f'{INDENT}def __getitem__'
445-
f'(self, key: {typing_literal}["@{el_name}"]) '
445+
f'(self, key: {typing_literal}["{el_name}"]) '
446446
f'-> {el_code}:',
447447
file=buf,
448448
)

edgedb/datatypes/datatypes.pyx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,33 @@ def create_object_factory(**pointers):
5454
names = ()
5555
fields = {}
5656
for pname, ptype in pointers.items():
57-
names += (pname,)
58-
5957
if not isinstance(ptype, set):
6058
ptype = {ptype}
6159

6260
flag = 0
61+
is_linkprop = False
6362
for pt in ptype:
6463
if pt == 'link':
6564
flag |= EDGE_POINTER_IS_LINK
6665
elif pt == 'property':
6766
pass
6867
elif pt == 'link-property':
6968
flag |= EDGE_POINTER_IS_LINKPROP
69+
is_linkprop = True
7070
elif pt == 'implicit':
7171
flag |= EDGE_POINTER_IS_IMPLICIT
7272
else:
7373
raise ValueError(f'unknown pointer type {pt}')
74+
if is_linkprop:
75+
names += ("@" + pname,)
76+
else:
77+
names += (pname,)
78+
field = dataclasses.field()
79+
field.name = pname
80+
field._field_type = dataclasses._FIELD
81+
fields[pname] = field
7482

7583
flags += (flag,)
76-
field = dataclasses.field()
77-
field.name = pname
78-
field._field_type = dataclasses._FIELD
79-
fields[pname] = field
8084

8185
desc = EdgeRecordDesc_New(names, flags, <object>NULL)
8286
size = len(pointers)

edgedb/datatypes/link.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
static int init_type_called = 0;
2626
static Py_hash_t base_hash = -1;
27+
extern PyObject* at_sign_ptr;
2728

2829

2930
PyObject *
@@ -276,7 +277,12 @@ link_getattr(EdgeLinkObject *o, PyObject *name)
276277
assert(EdgeRecordDesc_Check(desc));
277278

278279
Py_ssize_t pos;
279-
edge_attr_lookup_t ret = EdgeRecordDesc_Lookup(desc, name, &pos);
280+
PyObject *prefixed_name = PyUnicode_Concat(at_sign_ptr, name);
281+
if (prefixed_name == NULL) {
282+
return NULL;
283+
}
284+
edge_attr_lookup_t ret = EdgeRecordDesc_Lookup(desc, prefixed_name, &pos);
285+
Py_DECREF(prefixed_name);
280286
switch (ret) {
281287
case L_ERROR:
282288
return NULL;
@@ -313,6 +319,18 @@ link_dir(EdgeLinkObject *o, PyObject *args)
313319
return NULL;
314320
}
315321

322+
PyObject *name, *stripped;
323+
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(ret); i++) {
324+
name = PyList_GET_ITEM(ret, i);
325+
stripped = PyUnicode_Substring(name, 1, PyUnicode_GET_LENGTH(name));
326+
if (stripped == NULL) {
327+
Py_DECREF(ret);
328+
return NULL;
329+
}
330+
PyList_SET_ITEM(ret, i, stripped);
331+
Py_DECREF(name);
332+
}
333+
316334
PyObject *str = PyUnicode_FromString("source");
317335
if (str == NULL) {
318336
Py_DECREF(ret);

edgedb/datatypes/object.c

Lines changed: 24 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -196,50 +196,10 @@ object_getattr(EdgeObject *o, PyObject *name)
196196
) {
197197
return EdgeRecordDesc_GetDataclassFields((PyObject *)o->desc);
198198
}
199-
200-
// getattr(obj, "@...") for link property
201-
int prefixed = PyUnicode_Tailmatch(
202-
name, at_sign_ptr, 0, PY_SSIZE_T_MAX, -1
203-
);
204-
if (prefixed == -1) {
205-
return NULL;
206-
}
207-
if (prefixed) {
208-
PyObject *stripped = PyUnicode_Substring(
209-
name, 1, PyUnicode_GET_LENGTH(name)
210-
);
211-
if (stripped == NULL) {
212-
return NULL;
213-
}
214-
ret = EdgeRecordDesc_Lookup(
215-
(PyObject *)o->desc, stripped, &pos);
216-
Py_DECREF(stripped);
217-
switch (ret) {
218-
case L_ERROR:
219-
return NULL;
220-
221-
case L_NOT_FOUND:
222-
case L_LINK:
223-
case L_PROPERTY:
224-
return PyObject_GenericGetAttr((PyObject *)o, name);
225-
226-
case L_LINKPROP: {
227-
PyObject *val = EdgeObject_GET_ITEM(o, pos);
228-
Py_INCREF(val);
229-
return val;
230-
}
231-
232-
default:
233-
abort();
234-
}
235-
}
236-
237199
return PyObject_GenericGetAttr((PyObject *)o, name);
238200
}
239201

240202
case L_LINKPROP:
241-
return PyObject_GenericGetAttr((PyObject *)o, name);
242-
243203
case L_LINK:
244204
case L_PROPERTY: {
245205
PyObject *val = EdgeObject_GET_ITEM(o, pos);
@@ -256,77 +216,51 @@ static PyObject *
256216
object_getitem(EdgeObject *o, PyObject *name)
257217
{
258218
Py_ssize_t pos;
259-
int prefixed = 0;
260-
PyObject *stripped = name;
261-
if (PyUnicode_Check(name)) {
262-
prefixed = PyUnicode_Tailmatch(
263-
name, at_sign_ptr, 0, PY_SSIZE_T_MAX, -1
264-
);
265-
if (prefixed == -1) {
266-
return NULL;
267-
}
268-
if (prefixed) {
269-
stripped = PyUnicode_Substring(
270-
name, 1, PyUnicode_GET_LENGTH(name)
271-
);
272-
if (stripped == NULL) {
273-
return NULL;
274-
}
275-
}
276-
}
277-
278219
edge_attr_lookup_t ret = EdgeRecordDesc_Lookup(
279-
(PyObject *)o->desc, stripped, &pos
220+
(PyObject *)o->desc, name, &pos
280221
);
281-
if (prefixed) {
282-
Py_DECREF(stripped);
283-
}
284222
switch (ret) {
285223
case L_ERROR:
286224
return NULL;
287225

288226
case L_PROPERTY:
227+
PyErr_Format(
228+
PyExc_TypeError,
229+
"property %R should be accessed via dot notation",
230+
name);
231+
return NULL;
232+
233+
case L_LINKPROP: {
234+
PyObject *val = EdgeObject_GET_ITEM(o, pos);
235+
Py_INCREF(val);
236+
return val;
237+
}
238+
239+
case L_NOT_FOUND: {
240+
int prefixed = 0;
241+
if (PyUnicode_Check(name)) {
242+
prefixed = PyUnicode_Tailmatch(
243+
name, at_sign_ptr, 0, PY_SSIZE_T_MAX, -1
244+
);
245+
if (prefixed == -1) {
246+
return NULL;
247+
}
248+
}
289249
if (prefixed) {
290250
PyErr_Format(
291251
PyExc_KeyError,
292252
"link property %R does not exist",
293253
name);
294-
} else {
295-
PyErr_Format(
296-
PyExc_TypeError,
297-
"property %R should be accessed via dot notation",
298-
name);
299-
}
300-
return NULL;
301-
302-
case L_LINKPROP:
303-
if (prefixed) {
304-
PyObject *val = EdgeObject_GET_ITEM(o, pos);
305-
Py_INCREF(val);
306-
return val;
307254
} else {
308255
PyErr_Format(
309256
PyExc_TypeError,
310257
"link property %R should be accessed with '@' prefix",
311258
name);
312-
return NULL;
313259
}
314-
315-
case L_NOT_FOUND:
316-
PyErr_Format(
317-
PyExc_KeyError,
318-
"link property %R does not exist",
319-
name);
320260
return NULL;
261+
}
321262

322263
case L_LINK: {
323-
if (prefixed) {
324-
PyErr_Format(
325-
PyExc_KeyError,
326-
"link property %R does not exist",
327-
name);
328-
return NULL;
329-
}
330264
int res = PyErr_WarnEx(
331265
PyExc_DeprecationWarning,
332266
"getting link on object is deprecated since 1.0, "

edgedb/datatypes/repr.c

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,7 @@ _EdgeGeneric_RenderItems(_PyUnicodeWriter *writer,
118118
}
119119

120120
if (is_linkprop) {
121-
if (include_link_props) {
122-
if (_PyUnicodeWriter_WriteChar(writer, '@') < 0) {
123-
goto error;
124-
}
125-
}
126-
else {
121+
if (!include_link_props) {
127122
continue;
128123
}
129124
}

edgedb/protocol/codecs/codecs.pyx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ cdef class CodecsRegistry:
194194
frb_read(spec, str_len), str_len)
195195
pos = <uint16_t>hton.unpack_int16(frb_read(spec, 2))
196196

197+
if flag & datatypes._EDGE_POINTER_IS_LINKPROP:
198+
name = "@" + name
197199
cpython.Py_INCREF(name)
198200
cpython.PyTuple_SetItem(names, i, name)
199201

tests/codegen/test-project2/generated_async_edgeql.py.assert

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class LinkPropResult:
3434
class LinkPropResultFriendsItem:
3535
id: uuid.UUID
3636
name: str
37+
created_at: datetime.datetime | None
3738

3839
@typing.overload
3940
def __getitem__(self, key: typing.Literal["@created_at"]) -> datetime.datetime | None:

tests/codegen/test-project2/object/link_prop_async_edgeql.py.assert

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class LinkPropResult(NoPydanticValidation):
3131
class LinkPropResultFriendsItem(NoPydanticValidation):
3232
id: uuid.UUID
3333
name: str
34+
created_at: typing.Optional[datetime.datetime]
3435

3536
@typing.overload
3637
def __getitem__(self, key: typing_extensions.Literal["@created_at"]) -> typing.Optional[datetime.datetime]:

tests/codegen/test-project2/object/link_prop_edgeql.py.assert

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class LinkPropResult:
2121
class LinkPropResultFriendsItem:
2222
id: uuid.UUID
2323
name: str
24+
created_at: typing.Optional[datetime.datetime]
2425

2526
@typing.overload
2627
def __getitem__(self, key: typing.Literal["@created_at"]) -> typing.Optional[datetime.datetime]:

tests/datatypes/test_datatypes.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,24 +99,24 @@ def test_recorddesc_3(self):
9999
o = f(1, 2, 3, 4)
100100

101101
desc = private.get_object_descriptor(o)
102-
self.assertEqual(set(dir(desc)), set(('id', 'lb', 'c', 'd')))
102+
self.assertEqual(set(dir(desc)), set(('id', '@lb', 'c', 'd')))
103103

104-
self.assertTrue(desc.is_linkprop('lb'))
104+
self.assertTrue(desc.is_linkprop('@lb'))
105105
self.assertFalse(desc.is_linkprop('id'))
106106
self.assertFalse(desc.is_linkprop('c'))
107107
self.assertFalse(desc.is_linkprop('d'))
108108

109-
self.assertFalse(desc.is_link('lb'))
109+
self.assertFalse(desc.is_link('@lb'))
110110
self.assertFalse(desc.is_link('id'))
111111
self.assertFalse(desc.is_link('c'))
112112
self.assertTrue(desc.is_link('d'))
113113

114-
self.assertFalse(desc.is_implicit('lb'))
114+
self.assertFalse(desc.is_implicit('@lb'))
115115
self.assertTrue(desc.is_implicit('id'))
116116
self.assertFalse(desc.is_implicit('c'))
117117
self.assertFalse(desc.is_implicit('d'))
118118

119-
self.assertEqual(desc.get_pos('lb'), 1)
119+
self.assertEqual(desc.get_pos('@lb'), 1)
120120
self.assertEqual(desc.get_pos('id'), 0)
121121
self.assertEqual(desc.get_pos('c'), 2)
122122
self.assertEqual(desc.get_pos('d'), 3)
@@ -509,7 +509,7 @@ def test_object_1(self):
509509
with self.assertRaises(TypeError):
510510
len(o)
511511

512-
with self.assertRaises(KeyError):
512+
with self.assertRaises(TypeError):
513513
o[0]
514514

515515
with self.assertRaises(TypeError):
@@ -681,9 +681,9 @@ def test_object_links_4(self):
681681
u = User(1, None)
682682

683683
with self.assertRaisesRegex(
684-
KeyError, "link property 'error_key' does not exist"
684+
KeyError, "link property '@error_key' does not exist"
685685
):
686-
u['error_key']
686+
u['@error_key']
687687

688688
def test_object_link_property_1(self):
689689
O2 = private.create_object_factory(
@@ -743,13 +743,15 @@ def test_object_dataclass_1(self):
743743
name='property',
744744
tuple='property',
745745
namedtuple='property',
746+
linkprop="link-property",
746747
)
747748

748749
u = User(
749750
1,
750751
'Bob',
751752
edgedb.Tuple((1, 2.0, '3')),
752753
edgedb.NamedTuple(a=1, b="Y"),
754+
123,
753755
)
754756
self.assertTrue(dataclasses.is_dataclass(u))
755757
self.assertEqual(

0 commit comments

Comments
 (0)