spatial-graph 0.0.4__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/.github/workflows/ci.yml +22 -5
  2. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/.pre-commit-config.yaml +3 -3
  3. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/Dockerfile +15 -11
  4. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/PKG-INFO +9 -4
  5. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/pyproject.toml +15 -5
  6. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/graph_base.py +29 -0
  7. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/wrapper_template.pyx +7 -7
  8. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/src/rtree.h +3 -2
  9. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/wrapper_template.pyx +6 -10
  10. spatial_graph-0.0.6/src/spatial_graph/py.typed +0 -0
  11. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_attributes.py +55 -0
  12. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/.github/workflows/docs.yaml +0 -0
  13. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/.gitignore +0 -0
  14. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/LICENSE +0 -0
  15. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/README.md +0 -0
  16. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/docs/index.md +0 -0
  17. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/examples/basic_usage.py +0 -0
  18. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/examples/query_nearest_vispy.py +0 -0
  19. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/mkdocs.yml +0 -0
  20. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/__init__.py +0 -0
  21. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_dtypes.py +0 -0
  22. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/__init__.py +0 -0
  23. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/graph.py +0 -0
  24. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/src/LICENSE.txt +0 -0
  25. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/src/graph_lite.h +0 -0
  26. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_graph/views.py +0 -0
  27. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/__init__.py +0 -0
  28. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/line_rtree.py +0 -0
  29. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/point_rtree.py +0 -0
  30. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/rtree.py +0 -0
  31. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/src/ARCHITECTURE.md +0 -0
  32. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/src/LICENSE +0 -0
  33. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/src/config.h +0 -0
  34. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/src/rtree.c +0 -0
  35. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_rtree/tree_base.py +0 -0
  36. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_spatial_graph.py +0 -0
  37. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/src/spatial_graph/_util.py +0 -0
  38. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_bench.py +0 -0
  39. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_dtype.py +0 -0
  40. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_graph.py +0 -0
  41. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_invalid_inputs.py +0 -0
  42. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_rtree.py +0 -0
  43. {spatial_graph-0.0.4 → spatial_graph-0.0.6}/tests/test_spatial_graph.py +0 -0
@@ -22,22 +22,34 @@ jobs:
22
22
  run: pipx run pre-commit run --all-files
23
23
 
24
24
  test:
25
+ name: ${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.resolution }}
25
26
  runs-on: ${{ matrix.os }}
26
27
  strategy:
27
28
  fail-fast: false
28
29
  matrix:
29
- os: [ubuntu-latest, windows-latest, macos-latest]
30
- python-version: ["3.9", "3.11", "3.13"]
30
+ # ubuntu: full python range x both resolutions
31
+ os: [ubuntu-latest]
32
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
33
+ resolution: [lowest-direct, highest]
34
+ # windows/macos: only the endpoints, highest resolution
35
+ include:
36
+ - { os: windows-latest, python-version: "3.10", resolution: highest }
37
+ - { os: windows-latest, python-version: "3.14", resolution: highest }
38
+ - { os: macos-latest, python-version: "3.10", resolution: highest }
39
+ - { os: macos-latest, python-version: "3.14", resolution: highest }
40
+
41
+ env:
42
+ UV_RESOLUTION: ${{ matrix.resolution }}
31
43
 
32
44
  steps:
33
- - uses: actions/checkout@v2
45
+ - uses: actions/checkout@v4
34
46
  - uses: astral-sh/setup-uv@v6
35
47
  with:
36
48
  python-version: ${{ matrix.python-version }}
37
49
  enable-cache: true
38
50
  cache-dependency-glob: "**/pyproject.toml"
39
51
  - name: Test with coverage
40
- run: uv run pytest -v --cov=spatial_graph --cov-report=xml
52
+ run: uv run --no-dev --group test pytest -v --cov=spatial_graph --cov-report=xml
41
53
  - uses: codecov/codecov-action@v5
42
54
  with:
43
55
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -95,7 +107,12 @@ jobs:
95
107
  files: "./dist/*"
96
108
 
97
109
  docker-test:
110
+ name: Docker Test gxx${{ matrix.gxx_major }}
98
111
  runs-on: ubuntu-latest
112
+ strategy:
113
+ fail-fast: false
114
+ matrix:
115
+ gxx_major: [14, 15]
99
116
 
100
117
  steps:
101
118
  - name: Checkout code
@@ -103,5 +120,5 @@ jobs:
103
120
 
104
121
  - name: Build and test Docker image
105
122
  run: |
106
- docker build -t spatial_graph .
123
+ docker build --build-arg GXX_MAJOR=${{ matrix.gxx_major }} -t spatial_graph .
107
124
  docker run --rm spatial_graph
@@ -7,7 +7,7 @@ default_install_hook_types: [pre-commit, commit-msg]
7
7
 
8
8
  repos:
9
9
  - repo: https://github.com/pre-commit/pre-commit-hooks
10
- rev: v5.0.0
10
+ rev: v6.0.0
11
11
  hooks:
12
12
  - id: trailing-whitespace
13
13
  - id: end-of-file-fixer
@@ -15,13 +15,13 @@ repos:
15
15
  - id: check-added-large-files
16
16
 
17
17
  - repo: https://github.com/astral-sh/ruff-pre-commit
18
- rev: v0.12.2
18
+ rev: v0.15.16
19
19
  hooks:
20
20
  - id: ruff-check
21
21
  args: [--fix, --unsafe-fixes]
22
22
  - id: ruff-format
23
23
 
24
24
  - repo: https://github.com/pre-commit/mirrors-mypy
25
- rev: v1.16.1
25
+ rev: v2.1.0
26
26
  hooks:
27
27
  - id: mypy
@@ -4,32 +4,36 @@ ENV DEBIAN_FRONTEND=noninteractive \
4
4
  MAMBA_ROOT_PREFIX=/opt/conda \
5
5
  PATH=/opt/conda/bin:$PATH
6
6
 
7
+ # Allow overriding the major GCC/G++ version at build time (e.g. --build-arg GXX_MAJOR=14)
8
+ ARG GXX_MAJOR=15
9
+
7
10
  RUN apt-get update && \
8
11
  apt-get install -y --no-install-recommends \
9
12
  git curl tar bzip2 ca-certificates && \
10
13
  rm -rf /var/lib/apt/lists/*
11
14
 
15
+
16
+ WORKDIR /app
17
+ COPY . /app
18
+
12
19
  # auto-detect arch and grab the matching micromamba binary
13
20
  RUN set -eux; \
14
21
  arch="$(uname -m)"; \
15
22
  case "$arch" in \
16
- x86_64) url_arch=linux-64 ;; \
17
- aarch64|arm64) url_arch=linux-aarch64 ;; \
18
- ppc64le) url_arch=linux-ppc64le ;; \
19
- *) echo "Unsupported arch: $arch"; exit 1 ;; \
23
+ x86_64) url_arch=linux-64; gxx_pkg=gxx_linux-64 ;; \
24
+ aarch64|arm64) url_arch=linux-aarch64; gxx_pkg=gxx_linux-aarch64 ;; \
25
+ ppc64le) url_arch=linux-ppc64le; gxx_pkg=gxx_linux-ppc64le ;; \
26
+ *) echo "Unsupported arch: $arch"; exit 1 ;; \
20
27
  esac; \
21
28
  curl -Ls "https://micro.mamba.pm/api/micromamba/$url_arch/latest" \
22
29
  | tar -xvj -C /usr/local/bin bin/micromamba; \
23
30
  chmod +x /usr/local/bin/bin/micromamba; \
24
31
  mv /usr/local/bin/bin/micromamba /usr/local/bin/micromamba; \
25
- rmdir /usr/local/bin/bin
26
-
27
-
28
- WORKDIR /app
29
- COPY . /app
32
+ rmdir /usr/local/bin/bin; \
33
+ micromamba create -y -n test-env -c conda-forge \
34
+ python=3.12 pip gxx "${gxx_pkg}=${GXX_MAJOR}.*"; \
35
+ micromamba run -n test-env pip install -e . --group test
30
36
 
31
- RUN micromamba create -y -n test-env -c conda-forge python=3.12 pip compilers
32
- RUN micromamba run -n test-env pip install -e . --group test
33
37
 
34
38
  SHELL ["micromamba", "run", "-n", "test-env", "/bin/bash", "-o", "pipefail", "-c"]
35
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatial-graph
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: A spatial graph datastructure for python.
5
5
  Project-URL: homepage, https://github.com/funkelab/spatial_graph
6
6
  Project-URL: repository, https://github.com/funkelab/spatial_graph
@@ -10,16 +10,21 @@ License-File: LICENSE
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
18
  Classifier: Typing :: Typed
19
- Requires-Python: >=3.9
19
+ Requires-Python: >=3.10
20
20
  Requires-Dist: ct3>=3.3.3
21
- Requires-Dist: numpy
21
+ Requires-Dist: numpy>=1.21.2
22
+ Requires-Dist: numpy>=1.23.2; python_version >= '3.11'
23
+ Requires-Dist: numpy>=1.26.0; python_version >= '3.12'
24
+ Requires-Dist: numpy>=2.1.0; python_version >= '3.13'
25
+ Requires-Dist: numpy>=2.3.2; python_version >= '3.14'
22
26
  Requires-Dist: setuptools>=75.8.0
27
+ Requires-Dist: typing-extensions>=4.5.0
23
28
  Requires-Dist: witty>=v0.3.0
24
29
  Description-Content-Type: text/markdown
25
30
 
@@ -10,7 +10,7 @@ name = "spatial-graph"
10
10
  dynamic = ["version"]
11
11
  description = "A spatial graph datastructure for python."
12
12
  readme = "README.md"
13
- requires-python = ">=3.9"
13
+ requires-python = ">=3.10"
14
14
  license = { text = "MIT" }
15
15
  authors = [
16
16
  { email = "funkej@janelia.hhmi.org", name = "Jan Funke" },
@@ -20,14 +20,24 @@ classifiers = [
20
20
  "Development Status :: 3 - Alpha",
21
21
  "Intended Audience :: Developers",
22
22
  "License :: OSI Approved :: MIT License",
23
- "Programming Language :: Python :: 3.9",
24
23
  "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
27
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
28
  "Typing :: Typed",
29
29
  ]
30
- dependencies = ["witty>=v0.3.0", "CT3>=3.3.3", "numpy", "setuptools>=75.8.0"]
30
+ dependencies = [
31
+ "witty>=v0.3.0",
32
+ "CT3>=3.3.3",
33
+ "numpy>=2.3.2; python_version >= '3.14'",
34
+ "numpy>=2.1.0; python_version >= '3.13'",
35
+ "numpy>=1.26.0; python_version >= '3.12'",
36
+ "numpy>=1.23.2; python_version >= '3.11'",
37
+ "numpy>=1.21.2",
38
+ "setuptools>=75.8.0",
39
+ "typing_extensions>=4.5.0", # witty<=0.3.1 imports it without declaring it
40
+ ]
31
41
 
32
42
  [dependency-groups]
33
43
  test = ["pytest>=8.3.5", "pytest-cov>=6.1.1"]
@@ -36,7 +46,7 @@ dev = [
36
46
  { include-group = "test" },
37
47
  "ipython>=8.18.1",
38
48
  "mypy>=1.15.0",
39
- "pre-commit>=4.2.0",
49
+ "prek>=0.4.4",
40
50
  "pytest-benchmark>=5.1.0", # specifically excluded from test group for ci
41
51
  "ruff>=0.11.10",
42
52
  ]
@@ -54,7 +64,7 @@ homepage = "https://github.com/funkelab/spatial_graph"
54
64
  repository = "https://github.com/funkelab/spatial_graph"
55
65
 
56
66
  [tool.ruff]
57
- target-version = "py39"
67
+ target-version = "py310"
58
68
  line-length = 88
59
69
  fix = true
60
70
  unsafe-fixes = true
@@ -33,6 +33,33 @@ else:
33
33
  SRC_DIR = Path(__file__).parent
34
34
 
35
35
 
36
+ def _cpp_member_name(name: str) -> str:
37
+ """C++ struct member name for a user-provided node/edge attribute.
38
+
39
+ User attribute names are used verbatim as the Python-facing API (property
40
+ names, ``get_*``/``set_*`` methods), but they cannot be used directly as
41
+ C++ identifiers: a name like ``int16`` collides with the MSVC built-in
42
+ ``__int16`` (Microsoft extensions treat ``int8``/``int16``/``int32``/
43
+ ``int64`` *and* their single-underscore forms ``_int8``/... as synonyms for
44
+ the ``__intN`` keywords), and others may clash with C++ keywords (``class``,
45
+ ``new``, ...). We therefore mangle the C++ member with a fixed prefix and
46
+ alias it back to the user name on the Cython side, so the public API is
47
+ unchanged while the generated C++ always compiles. The prefix matters: a
48
+ bare ``_`` prefix would turn ``int8`` into ``_int8``, which is itself an
49
+ MSVC keyword, so the prefix must not be ``_`` followed by a digit.
50
+ """
51
+ return "_attr_" + name
52
+
53
+
54
+ def _cpp_arg_name(name: str) -> str:
55
+ """C++ constructor parameter name for a user-provided attribute.
56
+
57
+ Must be distinct from :func:`_cpp_member_name` (the constructor initializes
58
+ ``member(arg)``) and, like it, must avoid the MSVC ``_intN`` keyword forms.
59
+ """
60
+ return "_arg_" + name
61
+
62
+
36
63
  def _build_wrapper(
37
64
  node_dtype: str,
38
65
  node_attr_dtypes: Mapping[str, str] | None = None,
@@ -60,6 +87,8 @@ def _build_wrapper(
60
87
  name: DType(dtype) for name, dtype in edge_attr_dtypes.items()
61
88
  }
62
89
  wrapper_template.directed = directed
90
+ wrapper_template.cpp_member = _cpp_member_name
91
+ wrapper_template.cpp_arg = _cpp_arg_name
63
92
 
64
93
  return str(wrapper_template)
65
94
 
@@ -95,7 +95,7 @@ cdef extern from *:
95
95
  ${class_name}(
96
96
  %set $sep=""
97
97
  %for name, dtype in $dtypes.items()
98
- $sep$dtype.to_c_decl("_" + $name)
98
+ $sep$dtype.to_c_decl($cpp_arg(name))
99
99
  %set $sep=", "
100
100
  %end for
101
101
  ) :
@@ -103,15 +103,15 @@ cdef extern from *:
103
103
  %for name, dtype in $dtypes.items()
104
104
  $sep
105
105
  %if $dtype.is_array
106
- ${name}{
106
+ ${cpp_member(name)}{
107
107
  %set $isep=""
108
108
  %for i in range($dtype.size)
109
- ${isep}_${name}[$i]
109
+ ${isep}${cpp_arg(name)}[$i]
110
110
  %set $isep=", "
111
111
  %end for
112
112
  }
113
113
  %else
114
- ${name}(_$name)%slurp
114
+ ${cpp_member(name)}(${cpp_arg(name)})%slurp
115
115
  %end if
116
116
  %set $sep=", "
117
117
  %end for
@@ -119,7 +119,7 @@ cdef extern from *:
119
119
  %end if
120
120
 
121
121
  %for name, dtype in $dtypes.items()
122
- $dtype.to_c_decl($name);
122
+ $dtype.to_c_decl($cpp_member(name));
123
123
  %end for
124
124
  };
125
125
  """
@@ -129,13 +129,13 @@ cdef extern from *:
129
129
  ${class_name}(
130
130
  %set $sep=""
131
131
  %for name, dtype in $dtypes.items()
132
- $sep$dtype.to_c_decl("_" + $name)
132
+ $sep$dtype.to_c_decl($cpp_arg(name))
133
133
  %set $sep=", "
134
134
  %end for
135
135
  ) except +
136
136
 
137
137
  %for name, dtype in $dtypes.items()
138
- $dtype.to_pyxtype() $name
138
+ $dtype.to_pyxtype() $name "$cpp_member(name)"
139
139
  %end for
140
140
 
141
141
  cdef class ${class_name}View:
@@ -6,6 +6,7 @@
6
6
  #define RTREE_H
7
7
 
8
8
  #include <stdlib.h>
9
+ #include <stdbool.h>
9
10
  #include "config.h"
10
11
 
11
12
  // rtree_new returns a new rtree
@@ -83,8 +84,8 @@ int rtree_delete(struct rtree *tr, const coord_t *min, const coord_t *max, const
83
84
  // rectangle, and perform a comparison of its data to the provided data using
84
85
  // a compare function. The first item that is found is deleted.
85
86
  //
86
- // Returns false if the system is out of memory.
87
- bool rtree_delete_with_comparator(struct rtree *tr, const coord_t *min,
87
+ // Returns the number of deleted items (0 or 1) or -1 if an OOM error occured.
88
+ int rtree_delete_with_comparator(struct rtree *tr, const coord_t *min,
88
89
  const coord_t *max, const item_t item,
89
90
  int (*compare)(const item_t a, const item_t b, void *udata),
90
91
  void *udata);
@@ -1,19 +1,15 @@
1
1
  from libc.stdint cimport *
2
+ from libcpp cimport bool
2
3
  import numpy as np
3
4
 
4
-
5
- ctypedef int bool
6
-
7
5
  cdef extern from *:
8
6
  """
9
- typedef int bool;
10
- #define false 0
11
- #define true 1
12
-
13
7
  %if $c_distance_function
14
8
  #define KNN_USE_EXACT_DISTANCE
15
9
  %end if
16
10
  #define DIMS $dims
11
+ #include <stdbool.h>
12
+ #include <string.h>
17
13
 
18
14
  typedef $coord_dtype.to_pyxtype() coord_t;
19
15
  typedef $item_dtype.base_c_type item_base_t;
@@ -152,7 +148,7 @@ cdef pyx_items_t memview_to_pyx_items_t($item_dtype.to_pyxtype(add_dim=True) ite
152
148
  %end if
153
149
 
154
150
 
155
- cdef bint count_iterator(
151
+ cdef bool count_iterator(
156
152
  const coord_t* bb_min,
157
153
  const coord_t* bb_max,
158
154
  const item_t item,
@@ -174,7 +170,7 @@ cdef init_search_results_from_memview(search_results* r, $item_dtype.to_pyxtype(
174
170
  r.items = memview_to_pyx_items_t(items)
175
171
 
176
172
 
177
- cdef bint search_iterator(
173
+ cdef bool search_iterator(
178
174
  const coord_t* bb_min,
179
175
  const coord_t* bb_max,
180
176
  const item_t item,
@@ -203,7 +199,7 @@ cdef init_nearest_results_from_memview(nearest_results* r,
203
199
  r.distances = &distances[0] if distances is not None else NULL
204
200
 
205
201
 
206
- cdef bint nearest_iterator(
202
+ cdef bool nearest_iterator(
207
203
  const item_t item,
208
204
  coord_t distance,
209
205
  void* udata
File without changes
@@ -95,3 +95,58 @@ def test_attr_dtypes(dtype):
95
95
  graph.add_node(1, position=np.array([0.0, 0.0, 0.0]), **{f"node_attr_{dtype}": 0})
96
96
  graph.add_node(2, position=np.array([0.0, 0.0, 0.0]), **{f"node_attr_{dtype}": 1})
97
97
  graph.add_edge([1, 2], **{f"edge_attr_{dtype}": 0})
98
+
99
+
100
+ # Attribute names that collide with C++ keywords or MSVC built-in type
101
+ # specifiers (but are not Python keywords, so they are valid attribute names).
102
+ # These cannot appear verbatim as C++ struct members or constructor parameters.
103
+ # In particular MSVC (with Microsoft extensions) treats ``int8``/``int16``/
104
+ # ``int32``/``int64`` *and* their single-underscore forms ``_int8``/... as
105
+ # aliases for the ``__intN`` keywords, so a member named ``int16`` (or a
106
+ # constructor parameter ``_int16``) expands to ``__int16`` and fails to compile.
107
+ @pytest.mark.parametrize(
108
+ "attr_name", ["int8", "int16", "int32", "int64", "new", "double"]
109
+ )
110
+ def test_attr_name_collides_with_cpp_keyword(attr_name):
111
+ graph = sg.SpatialGraph(
112
+ ndims=3,
113
+ node_dtype="uint64",
114
+ node_attr_dtypes={attr_name: "int16", "position": "double[3]"},
115
+ edge_attr_dtypes={attr_name: "int16"},
116
+ position_attr="position",
117
+ )
118
+
119
+ graph.add_node(1, position=np.array([1.0, 1.0, 1.0]), **{attr_name: 5})
120
+ graph.add_node(2, position=np.array([2.0, 2.0, 2.0]), **{attr_name: 7})
121
+ graph.add_edge([1, 2], **{attr_name: 9})
122
+
123
+ # The user-provided name stays the public attribute (the colliding name only
124
+ # affects the internal C++ identifier). Reading it back through every public
125
+ # access path must return the stored value -- a regression in the C++ member
126
+ # mangling / Cython cname-aliasing would silently corrupt these.
127
+ nodes = np.array([1, 2], dtype="uint64")
128
+
129
+ # single node
130
+ assert getattr(graph.node_attrs[1], attr_name) == 5
131
+ assert getattr(graph.node_attrs[2], attr_name) == 7
132
+ # array of nodes
133
+ np.testing.assert_array_equal(
134
+ getattr(graph.node_attrs[nodes], attr_name), np.array([5, 7], dtype="int16")
135
+ )
136
+ # all nodes (order is unspecified, so compare sorted)
137
+ np.testing.assert_array_equal(
138
+ np.sort(getattr(graph.node_attrs, attr_name)), np.array([5, 7], dtype="int16")
139
+ )
140
+ # edge
141
+ assert getattr(graph.edge_attrs[(1, 2)], attr_name) == 9
142
+
143
+ # the position attribute is mangled the same way and must be unaffected
144
+ np.testing.assert_array_equal(
145
+ graph.node_attrs[1].position, np.array([1.0, 1.0, 1.0])
146
+ )
147
+
148
+ # setting through the public attribute round-trips too
149
+ setattr(graph.node_attrs[1], attr_name, 11)
150
+ assert getattr(graph.node_attrs[1], attr_name) == 11
151
+ setattr(graph.edge_attrs[(1, 2)], attr_name, 13)
152
+ assert getattr(graph.edge_attrs[(1, 2)], attr_name) == 13
File without changes
File without changes
File without changes
File without changes