Skip to content

Commit bb40cc3

Browse files
committed
refactor: AttributeHandler is not meant to be subclassed any more, but AttributeStorage is
1 parent 740c65d commit bb40cc3

File tree

10 files changed

+324
-195
lines changed

10 files changed

+324
-195
lines changed

src/codegen/internal_lib.py.in

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ from ctypes import cdll, c_char_p, c_double, c_int, c_void_p, POINTER
44
from ctypes.util import find_library
55
from typing import Any
66

7-
from .attributes import (
8-
igraph_attribute_combination_t,
9-
igraph_attribute_table_t,
10-
)
117
from .errors import handle_igraph_error_t
128
from .types import (
139
FILE,
10+
igraph_attribute_combination_t,
11+
igraph_attribute_table_t,
1412
igraph_bool_t,
1513
igraph_integer_t,
1614
igraph_real_t,
@@ -175,6 +173,14 @@ igraph_vector_ptr_destroy = _lib.igraph_vector_ptr_destroy
175173
igraph_vector_ptr_destroy.restype = None
176174
igraph_vector_ptr_destroy.argtypes = [c_void_p]
177175

176+
igraph_vector_ptr_get = _lib.igraph_vector_ptr_get
177+
igraph_vector_ptr_get.restype = c_void_p
178+
igraph_vector_ptr_get.argtypes = [POINTER(igraph_vector_ptr_t), igraph_integer_t]
179+
180+
igraph_vector_ptr_size = _lib.igraph_vector_ptr_size
181+
igraph_vector_ptr_size.restype = igraph_integer_t
182+
igraph_vector_ptr_size.argtypes = [POINTER(igraph_vector_ptr_t)]
183+
178184
# Matrix type
179185

180186
igraph_matrix_init = _lib.igraph_matrix_init
@@ -404,6 +410,14 @@ igraph_set_attribute_table.argtypes = [POINTER(igraph_attribute_table_t)]
404410

405411
# Error handling and interruptions
406412

413+
igraph_error = _lib.igraph_error
414+
igraph_error.restype = igraph_error_t # this is OK; it will be called from attribute handlers in Python
415+
igraph_error.argtypes = [c_char_p, c_char_p, c_int, igraph_error_t]
416+
417+
IGRAPH_FINALLY_FREE = _lib.IGRAPH_FINALLY_FREE
418+
IGRAPH_FINALLY_FREE.restype = None
419+
IGRAPH_FINALLY_FREE.argtypes = []
420+
407421
igraph_set_error_handler = _lib.igraph_set_error_handler
408422
igraph_set_error_handler.restype = igraph_error_handler_t
409423
igraph_set_error_handler.argtypes = [igraph_error_handler_t]

src/igraph_ctypes/_internal/attributes.py

Lines changed: 81 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,33 @@
1+
from abc import ABC, abstractmethod
12
from ctypes import (
2-
c_char_p,
3-
c_int,
4-
c_void_p,
53
pointer,
64
py_object,
7-
CFUNCTYPE,
8-
POINTER,
9-
Structure,
105
)
116
from dataclasses import dataclass, field
127
from typing import Any, Callable, Dict, Optional
138

9+
from .lib import igraph_error, igraph_vector_ptr_size
1410
from .refcount import incref, decref
15-
from .types import (
16-
igraph_bool_t,
17-
igraph_error_t,
18-
igraph_es_t,
19-
igraph_integer_t,
20-
igraph_strvector_t,
21-
igraph_t,
22-
igraph_vector_bool_t,
23-
igraph_vector_int_t,
24-
igraph_vector_int_list_t,
25-
igraph_vector_ptr_t,
26-
igraph_vector_t,
27-
igraph_vs_t,
28-
)
29-
from .utils import nop, protect
30-
31-
__all__ = ("AttributeHandlerBase", "DictAttributeHandler")
32-
33-
34-
p_igraph_t = POINTER(igraph_t)
35-
p_strvector_t = POINTER(igraph_strvector_t)
36-
p_vector_t = POINTER(igraph_vector_t)
37-
p_vector_bool_t = POINTER(igraph_vector_bool_t)
38-
p_vector_int_t = POINTER(igraph_vector_int_t)
39-
p_vector_int_list_t = POINTER(igraph_vector_int_list_t)
40-
p_vector_ptr_t = POINTER(igraph_vector_ptr_t)
41-
42-
43-
class igraph_attribute_combination_t(Structure):
44-
"""ctypes representation of ``igraph_attribute_combination_t``"""
45-
46-
_fields_ = [("list", igraph_vector_ptr_t)]
47-
48-
49-
class igraph_attribute_combination_record_t(Structure):
50-
"""ctypes representation of ``igraph_attribute_combination_record_t``"""
51-
52-
_fields_ = [("name", c_char_p), ("type", c_int), ("func", CFUNCTYPE(c_void_p))]
53-
54-
55-
p_attribute_combination_t = POINTER(igraph_attribute_combination_t)
56-
57-
58-
class igraph_attribute_table_t(Structure):
59-
"""ctypes representation of ``igraph_attribute_table_t``"""
60-
61-
TYPES = {
62-
"init": CFUNCTYPE(igraph_error_t, p_igraph_t, p_vector_ptr_t),
63-
"destroy": CFUNCTYPE(None, p_igraph_t),
64-
"copy": CFUNCTYPE(
65-
igraph_error_t,
66-
p_igraph_t,
67-
p_igraph_t,
68-
igraph_bool_t,
69-
igraph_bool_t,
70-
igraph_bool_t,
71-
),
72-
"add_vertices": CFUNCTYPE(
73-
igraph_error_t, p_igraph_t, igraph_integer_t, p_vector_ptr_t
74-
),
75-
"permute_vertices": CFUNCTYPE(
76-
igraph_error_t, p_igraph_t, p_igraph_t, p_vector_int_t
77-
),
78-
"combine_vertices": CFUNCTYPE(
79-
igraph_error_t,
80-
p_igraph_t,
81-
p_igraph_t,
82-
p_vector_int_list_t,
83-
p_attribute_combination_t,
84-
),
85-
"add_edges": CFUNCTYPE(
86-
igraph_error_t, p_igraph_t, p_vector_int_t, p_vector_ptr_t
87-
),
88-
"permute_edges": CFUNCTYPE(
89-
igraph_error_t, p_igraph_t, p_igraph_t, p_vector_int_t
90-
),
91-
"combine_edges": CFUNCTYPE(
92-
igraph_error_t,
93-
p_igraph_t,
94-
p_igraph_t,
95-
p_vector_int_list_t,
96-
p_attribute_combination_t,
97-
),
98-
"get_info": CFUNCTYPE(
99-
igraph_error_t,
100-
p_igraph_t,
101-
p_strvector_t,
102-
p_vector_int_t,
103-
p_strvector_t,
104-
p_vector_int_t,
105-
p_strvector_t,
106-
p_vector_int_t,
107-
),
108-
"has_attr": CFUNCTYPE(igraph_bool_t, p_igraph_t, c_int, c_char_p),
109-
"get_type": CFUNCTYPE(
110-
igraph_error_t, p_igraph_t, POINTER(c_int), c_int, c_char_p
111-
),
112-
"get_numeric_graph_attr": CFUNCTYPE(
113-
igraph_error_t, p_igraph_t, c_char_p, p_vector_t
114-
),
115-
"get_string_graph_attr": CFUNCTYPE(
116-
igraph_error_t, p_igraph_t, c_char_p, p_strvector_t
117-
),
118-
"get_bool_graph_attr": CFUNCTYPE(
119-
igraph_error_t, p_igraph_t, c_char_p, p_vector_bool_t
120-
),
121-
"get_numeric_vertex_attr": CFUNCTYPE(
122-
igraph_error_t, p_igraph_t, c_char_p, igraph_vs_t, p_vector_t
123-
),
124-
"get_string_vertex_attr": CFUNCTYPE(
125-
igraph_error_t, p_igraph_t, c_char_p, igraph_vs_t, p_strvector_t
126-
),
127-
"get_bool_vertex_attr": CFUNCTYPE(
128-
igraph_error_t, p_igraph_t, c_char_p, igraph_vs_t, p_vector_bool_t
129-
),
130-
"get_numeric_edge_attr": CFUNCTYPE(
131-
igraph_error_t, p_igraph_t, c_char_p, igraph_es_t, p_vector_t
132-
),
133-
"get_string_edge_attr": CFUNCTYPE(
134-
igraph_error_t, p_igraph_t, c_char_p, igraph_es_t, p_strvector_t
135-
),
136-
"get_bool_edge_attr": CFUNCTYPE(
137-
igraph_error_t, p_igraph_t, c_char_p, igraph_es_t, p_vector_bool_t
138-
),
139-
}
140-
141-
_fields_ = [
142-
("init", TYPES["init"]),
143-
("destroy", TYPES["destroy"]),
144-
("copy", TYPES["copy"]),
145-
("add_vertices", TYPES["add_vertices"]),
146-
("permute_vertices", TYPES["permute_vertices"]),
147-
("combine_vertices", TYPES["combine_vertices"]),
148-
("add_edges", TYPES["add_edges"]),
149-
("permute_edges", TYPES["permute_edges"]),
150-
("combine_edges", TYPES["combine_edges"]),
151-
("get_info", TYPES["get_info"]),
152-
("has_attr", TYPES["has_attr"]),
153-
("get_numeric_graph_attr", TYPES["get_numeric_graph_attr"]),
154-
("get_string_graph_attr", TYPES["get_string_graph_attr"]),
155-
("get_bool_graph_attr", TYPES["get_bool_graph_attr"]),
156-
("get_numeric_vertex_attr", TYPES["get_numeric_vertex_attr"]),
157-
("get_string_vertex_attr", TYPES["get_string_vertex_attr"]),
158-
("get_bool_vertex_attr", TYPES["get_bool_vertex_attr"]),
159-
("get_numeric_edge_attr", TYPES["get_numeric_edge_attr"]),
160-
("get_string_edge_attr", TYPES["get_string_edge_attr"]),
161-
("get_bool_edge_attr", TYPES["get_bool_edge_attr"]),
162-
]
11+
from .types import igraph_attribute_table_t
12+
from .utils import nop, protect_with
13+
14+
__all__ = ("AttributeHandlerBase", "AttributeHandler", "AttributeStorage")
16315

16416

16517
################################################################################
16618

16719

20+
def _trigger_error(error: int) -> int:
21+
return int(
22+
igraph_error(
23+
b"Attribute handler triggered an error",
24+
b"<py-attribute-handler>",
25+
1,
26+
int(error),
27+
)
28+
)
29+
30+
16831
class AttributeHandlerBase:
16932
"""Base class for igraph attribute handlers."""
17033

@@ -175,6 +38,7 @@ def _get_attribute_handler_functions(self) -> Dict[str, Callable]:
17538
"""Returns an ``igraph_attribute_table_t`` instance that can be used
17639
to register this attribute handler in the core igraph library.
17740
"""
41+
protect = protect_with(_trigger_error)
17842
return {
17943
key: igraph_attribute_table_t.TYPES[key](protect(getattr(self, key, nop)))
18044
for key in igraph_attribute_table_t.TYPES.keys()
@@ -190,8 +54,35 @@ def _as_parameter_(self):
19054
return self._table_ptr
19155

19256

57+
class AttributeStorage(ABC):
58+
"""Interface specification for objects that store graph, vertex and edge
59+
attributes.
60+
"""
61+
62+
@abstractmethod
63+
def add_vertices(self, n: int) -> None:
64+
"""Notifies the attribute storage object that the given number of
65+
new vertices were added to the graph.
66+
"""
67+
raise NotImplementedError
68+
69+
@abstractmethod
70+
def clear(self):
71+
"""Clears the storage area, removing all attributes."""
72+
raise NotImplementedError
73+
74+
@abstractmethod
75+
def copy(
76+
self,
77+
copy_graph_attributes: bool = True,
78+
copy_vertex_attributes: bool = True,
79+
copy_edge_attributes: bool = True,
80+
):
81+
raise NotImplementedError
82+
83+
19384
@dataclass(frozen=True)
194-
class _DictAttributeStorage:
85+
class DictAttributeStorage(AttributeStorage):
19586
"""Dictionary-based storage area for the graph, vertex and edge attributes
19687
of a graph.
19788
"""
@@ -200,6 +91,9 @@ class _DictAttributeStorage:
20091
vertex_attributes: Dict[str, Any] = field(default_factory=dict)
20192
edge_attributes: Dict[str, Any] = field(default_factory=dict)
20293

94+
def add_vertices(self, graph, n: int) -> None:
95+
print("Added", n, "vertices")
96+
20397
def clear(self) -> None:
20498
"""Clears the storage area, removing all attributes from the
20599
attribute dictionaries.
@@ -222,47 +116,50 @@ def copy(
222116
)
223117

224118

225-
_MISSING = object()
226-
227-
228-
def _assign_storage_to_graph(graph, storage: Any = _MISSING):
119+
def _assign_storage_to_graph(graph, storage: Optional[AttributeStorage] = None) -> None:
229120
"""Assigns an attribute storage object to a graph, taking care of
230121
increasing or decreasing the reference count of the storage object if needed.
231122
"""
232123
try:
233124
old_storage = graph.contents.attr
234125
except ValueError:
235126
# No storage yet, this is OK
236-
old_storage = _MISSING
127+
old_storage = None
237128

238129
if old_storage is storage:
239130
# Nothing to do
240131
return
241132

242-
if old_storage is not _MISSING:
133+
if old_storage is not None:
243134
decref(old_storage)
244135

245-
if storage is not _MISSING:
136+
if storage is not None:
246137
graph.contents.attr = py_object(incref(storage))
247138
else:
248139
graph.contents.attr = py_object()
249140

250141

251-
def _detach_storage_from_graph(graph):
252-
return _assign_storage_to_graph(graph, _MISSING)
142+
def _get_storage_from_graph(graph) -> AttributeStorage:
143+
return graph.contents.attr
144+
253145

146+
def _detach_storage_from_graph(graph) -> None:
147+
return _assign_storage_to_graph(graph, None)
254148

255-
class DictAttributeHandler(AttributeHandlerBase):
256-
"""Attribute handler implementation that stores graph, vertex and edge
257-
attributes in dictionaries.
149+
150+
class AttributeHandler(AttributeHandlerBase):
151+
"""Attribute handler implementation that uses a DictAttributeStorage_
152+
as its storage backend.
258153
"""
259154

260155
def init(self, graph, attr):
261-
_assign_storage_to_graph(graph, _DictAttributeStorage())
156+
_assign_storage_to_graph(graph, DictAttributeStorage())
262157

263158
def destroy(self, graph) -> None:
264-
storage: _DictAttributeStorage = graph.contents.attr
265-
storage.clear()
159+
storage = _get_storage_from_graph(graph)
160+
if storage:
161+
storage.clear()
162+
266163
_detach_storage_from_graph(graph)
267164

268165
def copy(
@@ -273,19 +170,22 @@ def copy(
273170
copy_vertex_attributes: bool,
274171
copy_edge_attributes: bool,
275172
):
276-
try:
277-
storage = incref(
278-
graph.contents.attr.copy(
279-
copy_graph_attributes, copy_vertex_attributes, copy_edge_attributes
280-
)
281-
)
282-
to.contents.attr = py_object(storage)
283-
except Exception as ex:
284-
print(repr(ex))
285-
raise
173+
storage = _get_storage_from_graph(graph)
174+
new_storage = storage.copy(
175+
copy_graph_attributes, copy_vertex_attributes, copy_edge_attributes
176+
)
177+
_assign_storage_to_graph(to, new_storage)
286178

287179
def add_vertices(self, graph, n: int, attr) -> None:
288-
pass
180+
# attr will only ever be NULL here so raise an error if it is not
181+
if attr:
182+
raise RuntimeError(
183+
"add_vertices() attribute handler called with non-null attr; "
184+
"this is most likely a bug"
185+
)
186+
187+
# Extend the existing attribute containers
188+
_get_storage_from_graph(graph).add_vertices(graph, n)
289189

290190
def permute_vertices(self, graph, to, mapping):
291191
pass

0 commit comments

Comments
 (0)