├── .gitignore ├── CHANGES.txt ├── LICENSE.txt ├── LICENSES.txt ├── Makefile ├── NOTICE.txt ├── README.rst ├── pom.xml └── src ├── bin └── classpath ├── docs ├── conf │ ├── asciidoc.local.conf │ ├── upload.sh │ └── version ├── dev │ ├── index.asciidoc │ ├── installation.asciidoc │ ├── reference │ │ ├── core-getting-started.asciidoc │ │ ├── core-nodes.asciidoc │ │ ├── core-paths.asciidoc │ │ ├── core-properties.asciidoc │ │ ├── core-relationships.asciidoc │ │ ├── core-transactions.asciidoc │ │ ├── core.asciidoc │ │ ├── cypher.asciidoc │ │ ├── indexes.asciidoc │ │ └── traversal.asciidoc │ ├── tutorial.asciidoc │ └── tutorial │ │ ├── helloworld.asciidoc │ │ ├── index.asciidoc │ │ └── invoiceapp.asciidoc ├── docinfo.html └── manual.asciidoc ├── main ├── assembly │ └── python-distribution.xml ├── java │ └── org │ │ └── neo4j │ │ └── cypher │ │ └── pycompat │ │ ├── ExecutionEngine.java │ │ ├── ExecutionResult.java │ │ ├── ScalaToPythonWrapper.java │ │ ├── WrappedCollection.java │ │ ├── WrappedIterable.java │ │ ├── WrappedIterator.java │ │ ├── WrappedMap.java │ │ └── WrappedPath.java └── python │ ├── README.txt │ ├── neo4j │ ├── __init__.py │ ├── __main__.py │ ├── _backend.py │ ├── core.py │ ├── cypher.py │ ├── index.py │ ├── javalib │ │ └── __init__.py │ ├── traversal.py │ └── util.py │ └── setup.py ├── site ├── apt │ └── index.apt └── site.xml └── test ├── format └── junit-noframes.xsl └── python ├── core.py ├── cypher.py ├── examples.py ├── index.py ├── junit_xml.py ├── threads.py ├── traversal.py └── unit_tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /target/ 3 | *.pyc 4 | *.class 5 | /ENV 6 | .classpath 7 | .project 8 | .settings/ 9 | .pydevproject 10 | *.iml 11 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 1.6.M02 (2011-12-16) 2 | -------------------- 3 | o Fix for broken cypher tests 4 | o Added get and set methods to nodes and relationships 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for the Embedded Neo4j Python bindings documentation. 2 | # 3 | 4 | # Project Configuration 5 | project_name = manual 6 | language = en 7 | 8 | # Minimal setup 9 | target = target 10 | build_dir = $(CURDIR)/$(target) 11 | config_dir = $(CURDIR)/docs/conf 12 | tools_dir = $(build_dir)/tools 13 | make_dir = $(tools_dir)/make 14 | 15 | include $(make_dir)/context-manual.make 16 | 17 | main_source = docs 18 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Neo4j 2 | Copyright © 2002-2011 Network Engine for Objects in Lund AB (referred to 3 | in this notice as “Neo Technology”) 4 | [http://neotechnology.com] 5 | 6 | This product includes software ("Software") developed by Neo Technology. 7 | 8 | The copyright in the bundled Neo4j graph database (including the 9 | Software) is owned by Neo Technology. The Software developed and owned 10 | by Neo Technology is licensed under the GNU GENERAL PUBLIC LICENSE 11 | Version 3 (http://www.fsf.org/licensing/licenses/gpl-3.0.html) ("GPL") 12 | to all third parties and that license, as required by the GPL, is 13 | included in the LICENSE.txt file. 14 | 15 | However, if you have executed an End User Software License and Services 16 | Agreement or an OEM Software License and Support Services Agreement, or 17 | another commercial license agreement with Neo Technology or one of its 18 | affiliates (each, a "Commercial Agreement"), the terms of the license in 19 | such Commercial Agreement will supersede the GPL and you may use the 20 | software solely pursuant to the terms of the relevant Commercial 21 | Agreement. 22 | 23 | 24 | Third party libraries 25 | --------------------- 26 | 27 | Full license texts are found in LICENSES.txt. 28 | 29 | The Apache Software License, Version 2.0: 30 | Apache ServiceMix :: Bundles :: lucene, 31 | Apache Commons: 32 | Commons BeanUtils, Commons BeanUtils Core, Commons Collections, Commons IO, 33 | Commons Configuration, Commons Digester, Commons Lang, Commons Logging, 34 | Apache Log4j, 35 | Apache Felix: Felix FileInstall, Felix Framework, Felix Main, 36 | Geronimo Java Transaction API, 37 | Jansi, 38 | 39 | MIT License: 40 | SLF4J API Module, SLF4J Log4j-12 Binding, SLF4J JDK1.4 Logging Binding, 41 | SLF4J Jakarta Commons Logging Binding, 42 | 43 | BSD licence: 44 | ASM: ASM Core, ASM Tree, ASM Commons, ASM Util, ASM Analysis, 45 | Blueprints: Data Models and their Implementations, 46 | Gremlin: A Graph-Based Programming Language, 47 | Pipes: A Data Flow Framework using Process Graphs, 48 | JLine, Scala library 49 | 50 | Public domain: 51 | Dough Lea's util.concurrent package, 52 | ANTLR 2.7.7, 53 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python bindings for embedded Neo4j 2 | ================================== 3 | 4 | **Note**: This project is no longer maintained. The approach with JPype was not workable. Please have a look at some of the alternatives available at http://neo4j.com/developer/python/ 5 | 6 | These are Python bindings for the embedded Neo4j Graph Database. 7 | 8 | Prerequisites 9 | ------------- 10 | 11 | The neo4j embedded database is a java application, which means you have to provide an interface to communicate with java-land. You need: 12 | 13 | - CPython with JPype installed http://jpype.sourceforge.net/ 14 | 15 | Documentation 16 | ------------- 17 | 18 | The documentation contains more detailed help with installation, as well as examples and reference documentation. 19 | 20 | See http://docs.neo4j.org/ 21 | 22 | Installation 23 | ------------ 24 | 25 | :: 26 | 27 | pip install neo4j-embedded 28 | 29 | Installation from source 30 | ------------------------ 31 | 32 | To install neo4j-embedded from this source tree, use the maven build tool to produce the python distribution, then install the distribution normally: 33 | 34 | :: 35 | 36 | mvn package 37 | unzip target/neo4j-python-embedded-[VERSION]-python-dist.zip 38 | cd neo4j-embedded 39 | python setup.py install 40 | 41 | Releasing 42 | ------------------------ 43 | 44 | Make sure you have a .pypirc file in your home folder with correct login information for the neo4j-embedded package on pypi.python.org. The release builds windows installers, so it needs to run on a windows machine. 45 | 46 | :: 47 | 48 | python release.py 49 | 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.neo4j.build 7 | parent-central 8 | 36 9 | 10 | 11 | 12 | org.neo4j.drivers 13 | neo4j-python-embedded 14 | 15 | 1.9-SNAPSHOT 16 | Embedded Neo4j Python bindings 17 | jar 18 | 19 | Python bindings for the embedded Neo4j graph database. 20 | 21 | 22 | 23 | scm:git:git://github.com/neo4j-contrib/python-embedded.git 24 | scm:git:git@github.com:neo4j-contrib/python-embedded.git 25 | https://github.com/neo4j-contrib/python-embedded 26 | 27 | 28 | 29 | GPL-3-header.txt 30 | python 31 | 32 | 33 | ../../main/python:$PYTHONPATH 34 | ../../main/python:$JYTHONPATH 35 | 0 36 | 37 | 1.9-SNAPSHOT 38 | 1.2 39 | http://docs.neo4j.org/python-embedded/${project.version}/index.html 40 | 41 | 42 | 43 | 44 | GNU General Public License, Version 3 45 | http://www.gnu.org/licenses/gpl-3.0-standalone.html 46 | The software ("Software") developed and owned by Network Engine for 47 | Objects in Lund AB (referred to in this notice as "Neo Technology") is 48 | licensed under the GNU GENERAL PUBLIC LICENSE Version 3 to all third 49 | parties and that license is included below. 50 | 51 | However, if you have executed an End User Software License and Services 52 | Agreement or an OEM Software License and Support Services Agreement, or 53 | another commercial license agreement with Neo Technology or one of its 54 | affiliates (each, a "Commercial Agreement"), the terms of the license in 55 | such Commercial Agreement will supersede the GNU GENERAL PUBLIC LICENSE 56 | Version 3 and you may use the Software solely pursuant to the terms of 57 | the relevant Commercial Agreement. 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.neo4j 65 | neo4j 66 | ${neo4j.version} 67 | 68 | 69 | 70 | org.neo4j 71 | neo4j-cypher 72 | ${neo4j.version} 73 | 74 | 75 | 76 | 77 | 78 | 79 | neo4j-dev 80 | Neo4j Developer Repository 81 | http://m2.neo4j.org/content/groups/everything/ 82 | 83 | 84 | 85 | 86 | src/main/java 87 | src/test/python 88 | 89 | 90 | 91 | 92 | org.codehaus.gmaven 93 | gmaven-plugin 94 | 1.3 95 | 96 | 97 | pythonificate-version-number 98 | generate-resources 99 | 100 | execute 101 | 102 | 103 | 104 | def version = project.version 105 | 106 | // 1.5-SNAPSHOT -> 1.5.dev1 107 | version = version.replace("-SNAPSHOT", ".dev" + project.properties['build.number']) 108 | 109 | // 1.5.RC1 -> 1.5.c1 110 | version = version.replace("RC","c") 111 | 112 | // 1.5.M01 -> 1.5.b1 113 | version = version.replace("M0","b") 114 | version = version.replace("M","b") 115 | project.properties['pythonic_version'] = version 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | maven-assembly-plugin 125 | 126 | 127 | python-distribution 128 | package 129 | 130 | false 131 | true 132 | 133 | src/main/assembly/python-distribution.xml 134 | 135 | 136 | 137 | single 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 147 | 148 | org.eclipse.m2e 149 | lifecycle-mapping 150 | 1.0.0 151 | 152 | 153 | 154 | 155 | 156 | 157 | org.codehaus.gmaven 158 | 159 | 160 | gmaven-plugin 161 | 162 | 163 | [1.3,) 164 | 165 | 166 | execute 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | cpython-test 184 | 185 | python 186 | 187 | 188 | 189 | jython-test 190 | 191 | jython 192 | 193 | 194 | 195 | windows 196 | 197 | 198 | windows 199 | 200 | 201 | 202 | ../../main/python 203 | ../../main/python 204 | 205 | 206 | 207 | 208 | run-tests 209 | 210 | 211 | !skipTests 212 | 213 | 214 | 215 | 216 | 217 | 218 | org.codehaus.mojo 219 | exec-maven-plugin 220 | 221 | 222 | 223 | ${python} 224 | src/test/python 225 | 226 | 227 | 228 | unit_tests.py 229 | --classpath 230 | 231 | --junit 232 | ${basedir}/target/surefire-reports 233 | 234 | 235 | ${pythonpath} 236 | ${jythonpath} 237 | 238 | 239 | python-test 240 | test 241 | 242 | exec 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | performance-test 254 | 255 | 256 | 257 | org.codehaus.mojo 258 | exec-maven-plugin 259 | 260 | 261 | python-performance-test 262 | test 263 | 264 | exec 265 | 266 | 267 | pdb 268 | 269 | src/perftest/python 270 | 271 | performance_tests.py 272 | --classpath 273 | 274 | 275 | 276 | ${pythonpath} 277 | ${jythonpath} 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 290 | 291 | pypi 292 | 293 | 294 | 295 | maven-antrun-plugin 296 | 297 | 298 | pypi-deploy 299 | install 300 | 301 | run 302 | 303 | 304 | 305 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | neo-docs-build 343 | 344 | false 345 | 346 | docsBuild 347 | 348 | 349 | 350 | 351 | org.neo4j.build.plugins 352 | neo4j-doctools 353 | 1 354 | provided 355 | 356 | 357 | 358 | 359 | 360 | maven-dependency-plugin 361 | 362 | 363 | unpack-doctools 364 | generate-sources 365 | 366 | unpack-dependencies 367 | 368 | 369 | jar 370 | neo4j-doctools 371 | ${docs.tools} 372 | 373 | 374 | 375 | 376 | 377 | maven-resources-plugin 378 | 379 | 380 | copy-docs 381 | process-resources 382 | 383 | copy-resources 384 | 385 | 386 | ${project.build.directory}/docs 387 | 388 | 389 | src/docs 390 | 391 | 392 | 393 | 394 | 395 | copy-configuration 396 | process-resources 397 | 398 | copy-resources 399 | 400 | 401 | ${project.build.directory}/conf 402 | 403 | 404 | src/docs/conf 405 | 406 | version 407 | 408 | 409 | 410 | 411 | 412 | 413 | copy-version 414 | process-resources 415 | 416 | copy-resources 417 | 418 | 419 | ${project.build.directory}/conf 420 | 421 | 422 | src/docs/conf 423 | true 424 | 425 | version 426 | 427 | 428 | 429 | 430 | 431 | 432 | copy-test-sources 433 | process-resources 434 | 435 | copy-resources 436 | 437 | 438 | ${project.build.directory}/test-sources/neo4j-python-embedded-test-sources-jar 439 | 440 | 441 | src/test/python 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | org.codehaus.mojo 450 | exec-maven-plugin 451 | 452 | 453 | execute-asciidoc 454 | compile 455 | 456 | exec 457 | 458 | 459 | make 460 | 461 | init 462 | preview 463 | VERSION=${project.version} 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | ${project.build.directory}/tools 473 | none 474 | 475 | 476 | 477 | publish-manual 478 | 479 | false 480 | 481 | publish-manual 482 | 483 | 484 | 485 | 486 | 487 | org.codehaus.mojo 488 | exec-maven-plugin 489 | 490 | 491 | upload-to-servers 492 | deploy 493 | 494 | exec 495 | 496 | 497 | ${project.build.directory}/../src/docs/conf/upload.sh 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | neo4j-site 510 | scpexe://static.neo4j.org/var/www/components.neo4j.org/${project.artifactId}/${project.version} 511 | 512 | 513 | 514 | 515 | -------------------------------------------------------------------------------- /src/bin/classpath: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | if [ -n "$(which mvn)" ]; then 22 | cd $(dirname $0)/../.. 23 | if ! mvn dependency:copy-dependencies &> /dev/null; then 24 | echo failed to copy dependencies >2 25 | exit -1 26 | fi 27 | ls target/dependency/*.jar | tr "\\n" ":" | sed 's/:$//' 28 | else 29 | echo need mvn to build classpath >2 30 | exit -1 31 | fi 32 | -------------------------------------------------------------------------------- /src/docs/conf/asciidoc.local.conf: -------------------------------------------------------------------------------- 1 | [attributes] 2 | imagesdir=images 3 | iconsdir=images/icons 4 | imagestargetdir=target/src/images 5 | fontsdir=target/tools/bin/fonts 6 | 7 | -------------------------------------------------------------------------------- /src/docs/conf/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | DIR=$(cd `dirname $0` && cd ../../../ && pwd) 6 | 7 | # Uploads the manual to a public server. 8 | 9 | 10 | # Which version the documentation is now. 11 | VERSION=$(cat $DIR/target/conf/version) 12 | if [ -z "$VERSION" ]; then 13 | exit 1; 14 | fi 15 | 16 | # Name of the symlink created on the docs server, pointing to this version. 17 | if [[ $VERSION == *SNAPSHOT* ]] 18 | then 19 | SYMLINKVERSION=snapshot 20 | else 21 | if [[ $VERSION == *M* ]] 22 | then 23 | SYMLINKVERSION=milestone 24 | else 25 | SYMLINKVERSION=stable 26 | fi 27 | fi 28 | DOCS_SERVER='neo@static.neo4j.org' 29 | ROOTPATHDOCS='/var/www/docs.neo4j.org/drivers/python-embedded' 30 | 31 | echo "VERSION = $VERSION" 32 | echo "SYMLINKVERSION = $SYMLINKVERSION" 33 | 34 | # Create initial directories 35 | ssh $DOCS_SERVER mkdir -p $ROOTPATHDOCS/$VERSION 36 | 37 | # Copy artifacts 38 | rsync -r --delete $DIR/target/html5/ $DOCS_SERVER:$ROOTPATHDOCS/$VERSION/ 39 | # Symlink this version to a generic url 40 | ssh $DOCS_SERVER "cd $ROOTPATHDOCS/ && (rm $SYMLINKVERSION || true); ln -s $VERSION $SYMLINKVERSION" 41 | 42 | echo Apparently, successfully published to $DOCS_SERVER. 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/docs/conf/version: -------------------------------------------------------------------------------- 1 | ${neo4j.version} 2 | -------------------------------------------------------------------------------- /src/docs/dev/index.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded]] 2 | = Reference Documentation = 3 | 4 | The source code for this project lives on GitHub: https://github.com/neo4j-contrib/python-embedded 5 | 6 | :leveloffset: 2 7 | 8 | include::installation.asciidoc[] 9 | 10 | include::reference/core.asciidoc[] 11 | 12 | include::reference/indexes.asciidoc[] 13 | 14 | include::reference/cypher.asciidoc[] 15 | 16 | include::reference/traversal.asciidoc[] 17 | 18 | -------------------------------------------------------------------------------- /src/docs/dev/installation.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-installation]] 2 | = Installation = 3 | 4 | NOTE: The Neo4j database itself (from the http://docs.neo4j.org/chunked/{neo4j-version}/deployment-installation.html#editions[Community Edition]) is included in the neo4j-embedded distribution. 5 | 6 | == Installation on OSX/Linux == 7 | 8 | 9 | === Prerequisites === 10 | 11 | CAUTION: Make sure that the entire stack used is either 64bit or 32bit (no mixing, that is). That means the JVM, Python and JPype. 12 | 13 | First, install JPype: 14 | 15 | . Download the latest version of JPype from http://sourceforge.net/projects/jpype/files/JPype/. 16 | . Unzip the file. 17 | . Open a console and navigate into the unzipped folder. 18 | . Run `sudo python setup.py install` 19 | 20 | JPype is also available in the Debian repos: 21 | 22 | [source, shell] 23 | ---- 24 | sudo apt-get install python-jpype 25 | ---- 26 | 27 | Then, make sure the +JAVA_HOME+ environment variable is set to your 'jre' or 'jdk' folder, so that JPype can find the JVM. 28 | 29 | NOTE: Installation can be problematic on OSX. See the following Stack Overflow discussion for help: http://stackoverflow.com/questions/8525193/cannot-install-jpype-on-os-x-lion-to-use-with-neo4j and this blog post may be of help as well: http://blog.y3xz.com/blog/2011/04/29/installing-jpype-on-mac-os-x/ 30 | 31 | 32 | === Installing neo4j-embedded === 33 | 34 | You can install neo4j-embedded with your python package manager of choice: 35 | 36 | [source, shell] 37 | ---- 38 | sudo pip install neo4j-embedded 39 | ---- 40 | 41 | [source, shell] 42 | ---- 43 | sudo easy_install neo4j-embedded 44 | ---- 45 | 46 | Or install manually: 47 | 48 | . Download the latest appropriate version of JPype from http://sourceforge.net/projects/jpype/files/JPype/ for 32bit or from http://www.lfd.uci.edu/~gohlke/pythonlibs/ for 64bit. 49 | . Unzip the file. 50 | . Open a console and navigate into the unzipped folder. 51 | . Run `sudo python setup.py install` 52 | 53 | == Installation on Windows == 54 | 55 | === Prerequisites === 56 | 57 | WARNING: It is _imperative_ that the entire stack used is either 64bit or 32bit (no mixing, that is). 58 | That means the JVM, Python, JPype and all extra DLLs (see below). 59 | 60 | First, install JPype: 61 | 62 | [NOTE] 63 | Notice that JPype only works with Python 2.6 and 2.7. 64 | Also note that there are different downloads depending on which version you use. 65 | 66 | . Download the latest appropriate version of JPype from http://sourceforge.net/projects/jpype/files/JPype/ for 32bit or from http://www.lfd.uci.edu/~gohlke/pythonlibs/ for 64bit. 67 | . Run the installer. 68 | 69 | Then, make sure the +JAVA_HOME+ environment variable is set to your 'jre' or 'jdk' folder. 70 | There is a description of how to set environment variables in <>. 71 | 72 | NOTE: There may be DLL files missing from your system that are required by JPype. 73 | See <> for instructions for how to fix this. 74 | 75 | === Installing neo4j-embedded === 76 | 77 | . Download the latest version from http://pypi.python.org/pypi/neo4j-embedded/. 78 | . Run the installer. 79 | 80 | [[python-embedded-installation-windows-dlls]] 81 | === Solving problems with missing DLL files === 82 | 83 | Certain versions of Windows ship without DLL files needed to programmatically launch a JVM. 84 | You will need to make +IEShims.dll+ and certain debugging dlls available on Windows. 85 | 86 | +IEShims.dll+ is normally included with Internet Explorer installs. 87 | To make windows find this file globally, you need to add the IE install folder to your +PATH+. 88 | 89 | . Right click on "My Computer" or "Computer". 90 | . Select "Properties". 91 | . Click on "Advanced" or "Advanced system settings". 92 | . Click the "Environment variables" button. 93 | . Find the path varible, and add 'C:\Program Files\Internet Explorer' to it (or the install location of IE, if you have installed it somewhere else). 94 | 95 | Required debugging dlls are bundled with Microsoft Visual C++ Redistributable libraries. 96 | 97 | - 32bit Windows: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555 98 | - 64bit Windows: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=14632 99 | 100 | If you are still getting errors about missing DLL files, you can use http://www.dependencywalker.com/ to open your +jvm.dll+ (located in 'JAVA_HOME/bin/client/' or 'JAVA_HOME/bin/server/'), and it will tell you if there are other missing dlls. 101 | 102 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-getting-started.asciidoc: -------------------------------------------------------------------------------- 1 | == Getting started == 2 | 3 | === Creating a database === 4 | 5 | [snippet,python] 6 | ---- 7 | component=neo4j-python-embedded 8 | source=core.py 9 | tag=creatingDatabase 10 | classifier=test-sources 11 | ---- 12 | 13 | === Creating a database, with configuration === 14 | 15 | Please see http://docs.neo4j.org/chunked/{neo4j-version}/embedded-configuration.html[Neo4j Configuration] for what options you can use here. 16 | 17 | [snippet,python] 18 | ---- 19 | component=neo4j-python-embedded 20 | source=core.py 21 | tag=creatingConfiguredDatabase 22 | classifier=test-sources 23 | ---- 24 | 25 | === JPype JVM configuration === 26 | 27 | You can set extra arguments to be passed to the JVM using the +NEO4J_PYTHON_JVMARGS+ environment variable. 28 | This can be used to, for instance, increase the max memory for the database. 29 | 30 | Note that you must set this before you import the neo4j package, either by setting it before you start python, or by setting it programatically in your app. 31 | 32 | [source,python] 33 | ---- 34 | import os 35 | os.environ['NEO4J_PYTHON_JVMARGS'] = '-Xms128M -Xmx512M' 36 | import neo4j 37 | ---- 38 | 39 | You can also override the classpath used by neo4j-embedded, by setting the +NEO4J_PYTHON_CLASSPATH+ environment variable. 40 | 41 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-nodes.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-core-nodes]] 2 | == Nodes == 3 | 4 | This describes operations that are specific to node objects. 5 | For documentation on how to handle properties on both relationships and nodes, see <>. 6 | 7 | === Creating a node === 8 | 9 | [snippet,python] 10 | ---- 11 | component=neo4j-python-embedded 12 | source=core.py 13 | tag=createNode 14 | classifier=test-sources 15 | ---- 16 | 17 | === Fetching a node by id === 18 | 19 | [snippet,python] 20 | ---- 21 | component=neo4j-python-embedded 22 | source=core.py 23 | tag=getNodeById 24 | classifier=test-sources 25 | ---- 26 | 27 | === Fetching the reference node === 28 | 29 | [snippet,python] 30 | ---- 31 | component=neo4j-python-embedded 32 | source=core.py 33 | tag=getReferenceNode 34 | classifier=test-sources 35 | ---- 36 | 37 | === Removing a node === 38 | 39 | [snippet,python] 40 | ---- 41 | component=neo4j-python-embedded 42 | source=core.py 43 | tag=deleteNode 44 | classifier=test-sources 45 | ---- 46 | 47 | TIP: See also http://docs.neo4j.org/chunked/{neo4j-version}/transactions-delete.html[Neo4j Delete Semantics]. 48 | 49 | === Removing a node by id === 50 | 51 | [snippet,python] 52 | ---- 53 | component=neo4j-python-embedded 54 | source=core.py 55 | tag=deleteByIdNode 56 | classifier=test-sources 57 | ---- 58 | 59 | === Accessing relationships from a node === 60 | 61 | For details on what you can do with the relationship objects, see <>. 62 | 63 | [snippet,python] 64 | ---- 65 | component=neo4j-python-embedded 66 | source=core.py 67 | tag=accessingRelationships 68 | classifier=test-sources 69 | ---- 70 | 71 | === Getting and/or counting all nodes === 72 | 73 | Use this with care, it will become extremely slow in large datasets. 74 | 75 | [snippet,python] 76 | ---- 77 | component=neo4j-python-embedded 78 | source=core.py 79 | tag=getAllNodes 80 | classifier=test-sources 81 | ---- 82 | 83 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-paths.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-core-paths]] 2 | == Paths == 3 | 4 | A path object represents a path between two nodes in the graph. 5 | Paths thus contain at least two nodes and one relationship, but can reach arbitrary length. 6 | It is used in various parts of the API, most notably in <>. 7 | 8 | === Accessing the start and end nodes === 9 | 10 | [snippet,python] 11 | ---- 12 | component=neo4j-python-embedded 13 | source=traversal.py 14 | tag=accessPathStartAndEndNode 15 | classifier=test-sources 16 | ---- 17 | 18 | === Accessing the last relationship === 19 | 20 | [snippet,python] 21 | ---- 22 | component=neo4j-python-embedded 23 | source=traversal.py 24 | tag=accessPathLastRelationship 25 | classifier=test-sources 26 | ---- 27 | 28 | === Looping through the entire path === 29 | 30 | You can loop through all elements of a path directly, or you can choose to only loop through nodes or relationships. 31 | When you loop through all elements, the first item will be the start node, the second will be the first relationship, the third the node that the relationship led to and so on. 32 | 33 | [snippet,python] 34 | ---- 35 | component=neo4j-python-embedded 36 | source=traversal.py 37 | tag=loopThroughPath 38 | classifier=test-sources 39 | ---- 40 | 41 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-properties.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-core-properties]] 2 | == Properties == 3 | 4 | Both nodes and relationships can have properties, so this section applies equally to both node and relationship objects. 5 | Allowed property values include strings, numbers, booleans, as well as arrays of those primitives. 6 | Within each array, all values must be of the same type. 7 | 8 | === Setting properties === 9 | 10 | [snippet,python] 11 | ---- 12 | component=neo4j-python-embedded 13 | source=core.py 14 | tag=setProperties 15 | classifier=test-sources 16 | ---- 17 | 18 | === Getting properties === 19 | 20 | [snippet,python] 21 | ---- 22 | component=neo4j-python-embedded 23 | source=core.py 24 | tag=getProperties 25 | classifier=test-sources 26 | ---- 27 | 28 | === Removing properties === 29 | 30 | [snippet,python] 31 | ---- 32 | component=neo4j-python-embedded 33 | source=core.py 34 | tag=deleteProperties 35 | classifier=test-sources 36 | ---- 37 | 38 | === Looping through properties === 39 | 40 | [snippet,python] 41 | ---- 42 | component=neo4j-python-embedded 43 | source=core.py 44 | tag=loopProperties 45 | classifier=test-sources 46 | ---- 47 | 48 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-relationships.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-core-relationships]] 2 | == Relationships == 3 | 4 | This describes operations that are specific to relationship objects. 5 | For documentation on how to handle properties on both relationships and nodes, see <>. 6 | 7 | === Creating a relationship === 8 | 9 | [snippet,python] 10 | ---- 11 | component=neo4j-python-embedded 12 | source=core.py 13 | tag=createRelationship 14 | classifier=test-sources 15 | ---- 16 | 17 | === Fetching a relationship by id === 18 | 19 | [snippet,python] 20 | ---- 21 | component=neo4j-python-embedded 22 | source=core.py 23 | tag=getRelationshipById 24 | classifier=test-sources 25 | ---- 26 | 27 | === Removing a relationship === 28 | 29 | [snippet,python] 30 | ---- 31 | component=neo4j-python-embedded 32 | source=core.py 33 | tag=deleteRelationship 34 | classifier=test-sources 35 | ---- 36 | 37 | TIP: See also http://docs.neo4j.org/chunked/{neo4j-version}/transactions-delete.html[Neo4j Delete Semantics]. 38 | 39 | === Removing a relationship by id === 40 | 41 | [snippet,python] 42 | ---- 43 | component=neo4j-python-embedded 44 | source=core.py 45 | tag=deleteByIdRelationship 46 | classifier=test-sources 47 | ---- 48 | 49 | === Relationship start node, end node and type === 50 | 51 | [snippet,python] 52 | ---- 53 | component=neo4j-python-embedded 54 | source=core.py 55 | tag=relationshipAttributes 56 | classifier=test-sources 57 | ---- 58 | 59 | === Getting and/or counting all relationships === 60 | 61 | Use this with care, it will become extremely slow in large datasets. 62 | 63 | [snippet,python] 64 | ---- 65 | component=neo4j-python-embedded 66 | source=core.py 67 | tag=getAllRelationships 68 | classifier=test-sources 69 | ---- 70 | 71 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core-transactions.asciidoc: -------------------------------------------------------------------------------- 1 | == Transactions == 2 | 3 | All write operations to the database need to be performed from within transactions. 4 | This ensures that your database never ends up in an inconsistent state. 5 | 6 | See http://docs.neo4j.org/chunked/{neo4j-version}/transactions.html[Neo4j Transactions] for details on how Neo4j handles transactions. 7 | 8 | We use the python +with+ statement to define a transaction context. 9 | If you are using an older version of Python, you may have to import the +with+ statement: 10 | 11 | [source, python] 12 | ---- 13 | from __future__ import with_statement 14 | ---- 15 | 16 | Either way, this is how you get into a transaction: 17 | 18 | [snippet,python] 19 | ---- 20 | component=neo4j-python-embedded 21 | source=core.py 22 | tag=withBasedTransactions 23 | classifier=test-sources 24 | ---- 25 | 26 | -------------------------------------------------------------------------------- /src/docs/dev/reference/core.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-reference-core]] 2 | = Core API = 3 | 4 | This section describes how get get up and running, and how to do basic operations. 5 | 6 | include::core-getting-started.asciidoc[] 7 | 8 | include::core-transactions.asciidoc[] 9 | 10 | include::core-nodes.asciidoc[] 11 | 12 | include::core-relationships.asciidoc[] 13 | 14 | include::core-properties.asciidoc[] 15 | 16 | include::core-paths.asciidoc[] 17 | 18 | -------------------------------------------------------------------------------- /src/docs/dev/reference/cypher.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-reference-cypher]] 2 | = Cypher Queries = 3 | 4 | You can use the Cypher query language from neo4j-embedded. 5 | Read more about cypher syntax and cool stuff you can with it here: http://docs.neo4j.org/chunked/{neo4j-version}/cypher-query-lang.html[Cypher Reference]. 6 | 7 | == Querying and reading the result == 8 | 9 | === Basic query === 10 | 11 | To execute a plain text cypher query, do this: 12 | 13 | [snippet,python] 14 | ---- 15 | component=neo4j-python-embedded 16 | source=cypher.py 17 | tag=basicCypherQuery 18 | classifier=test-sources 19 | ---- 20 | 21 | === Retrieve query result === 22 | 23 | Cypher returns a tabular result. 24 | You can either loop through the table row-by-row, or you can loop through the values in a given column. 25 | Here is how to loop row-by-row: 26 | 27 | [snippet,python] 28 | ---- 29 | component=neo4j-python-embedded 30 | source=cypher.py 31 | tag=iterateCypherResult 32 | classifier=test-sources 33 | ---- 34 | 35 | Here is how to loop through the values of a given column: 36 | 37 | [snippet,python] 38 | ---- 39 | component=neo4j-python-embedded 40 | source=cypher.py 41 | tag=getCypherResultColumn 42 | classifier=test-sources 43 | ---- 44 | 45 | === List the result columns === 46 | 47 | You can get a list of the column names in the result like this: 48 | 49 | [snippet,python] 50 | ---- 51 | component=neo4j-python-embedded 52 | source=cypher.py 53 | tag=listCypherResultColumns 54 | classifier=test-sources 55 | ---- 56 | 57 | == Parameterized and prepared queries == 58 | 59 | === Parameterized queries === 60 | 61 | Cypher supports parameterized queries, see http://docs.neo4j.org/chunked/{neo4j-version}/cypher-parameters.html[Cypher Parameters]. 62 | This is how you use them in neo4j-embedded. 63 | 64 | [snippet,python] 65 | ---- 66 | component=neo4j-python-embedded 67 | source=cypher.py 68 | tag=parameterizedCypherQuery 69 | classifier=test-sources 70 | ---- 71 | 72 | === Prepared queries === 73 | 74 | Prepared queries, where you could retrieve a pre-parsed version of a cypher query to be used later, 75 | is deprecated. Cypher will recognize if it has previously parsed a given query, and won't parse the 76 | same string twice. 77 | 78 | So, in effect, all cypher queries are prepared queries, if you use them more than once. Use parameterized 79 | queries to gain the full power of this - then a generic query can be pre-parsed, and modified with parameters 80 | each time it is executed. 81 | 82 | -------------------------------------------------------------------------------- /src/docs/dev/reference/indexes.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-reference-indexes]] 2 | = Indexes = 3 | 4 | In order to rapidly find nodes or relationship based on properties, Neo4j supports indexing. 5 | This is commonly used to find start nodes for <>. 6 | 7 | By default, the underlying index is powered by http://lucene.apache.org/java/docs/index.html[Apache Lucene], but it is also possible to use Neo4j with other index implementations. 8 | 9 | You can create an arbitrary number of named indexes. 10 | Each index handles either nodes or relationships, and each index works by indexing key/value/object triplets, object being either a node or a relationship, depending on the index type. 11 | 12 | == Index management == 13 | 14 | Just like the rest of the API, all write operations to the index must be performed from within a transaction. 15 | 16 | === Creating an index === 17 | 18 | Create a new index, with optional configuration. 19 | 20 | [snippet,python] 21 | ---- 22 | component=neo4j-python-embedded 23 | source=index.py 24 | tag=createIndex 25 | classifier=test-sources 26 | ---- 27 | 28 | === Retrieving a pre-existing index === 29 | 30 | [snippet,python] 31 | ---- 32 | component=neo4j-python-embedded 33 | source=index.py 34 | tag=getIndex 35 | classifier=test-sources 36 | ---- 37 | 38 | === Deleting indexes === 39 | 40 | [snippet,python] 41 | ---- 42 | component=neo4j-python-embedded 43 | source=index.py 44 | tag=deleteIndex 45 | classifier=test-sources 46 | ---- 47 | 48 | === Checking if an index exists === 49 | 50 | [snippet,python] 51 | ---- 52 | component=neo4j-python-embedded 53 | source=index.py 54 | tag=checkIfIndexExists 55 | classifier=test-sources 56 | ---- 57 | 58 | == Indexing things == 59 | 60 | === Adding nodes or relationships to an index === 61 | 62 | [snippet,python] 63 | ---- 64 | component=neo4j-python-embedded 65 | source=index.py 66 | tag=addToIndex 67 | classifier=test-sources 68 | ---- 69 | 70 | === Removing indexed items === 71 | 72 | Removing items from an index can be done at several levels of granularity. 73 | See the example below. 74 | 75 | [snippet,python] 76 | ---- 77 | component=neo4j-python-embedded 78 | source=index.py 79 | tag=removeFromIndex 80 | classifier=test-sources 81 | ---- 82 | 83 | == Searching the index == 84 | 85 | You can retrieve indexed items in two ways. 86 | Either you do a direct lookup, or you perform a query. 87 | The direct lookup is the same across different index providers while the query syntax depends on what index provider you use. 88 | As mentioned previously, Lucene is the default and by far most common index provider. 89 | For querying Lucene you will want to use the http://lucene.apache.org/java/{lucene-version}/queryparsersyntax.html[Lucene query language]. 90 | 91 | There is a python library for programatically generating Lucene queries, available at https://github.com/scholrly/lucene-querybuilder[GitHub]. 92 | 93 | [IMPORTANT] 94 | Unless you loop through the entire index result, you have to close the result when you are done with it. 95 | If you do not, the database does not know when it can release the resources the result is taking up. 96 | 97 | === Direct lookups === 98 | 99 | [snippet,python] 100 | ---- 101 | component=neo4j-python-embedded 102 | source=index.py 103 | tag=directLookup 104 | classifier=test-sources 105 | ---- 106 | 107 | === Querying === 108 | 109 | [snippet,python] 110 | ---- 111 | component=neo4j-python-embedded 112 | source=index.py 113 | tag=query 114 | classifier=test-sources 115 | ---- 116 | 117 | -------------------------------------------------------------------------------- /src/docs/dev/reference/traversal.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-reference-traversal]] 2 | = Traversals = 3 | 4 | [WARNING] 5 | Traversal support in neo4j-embedded for python is _deprecated_ as of Neo4j 1.7 GA. 6 | Please see <> or the core API instead. 7 | This is done because the traversal framework requires a very tight coupling between the JVM and python. 8 | To keep improving performance, we need to break that coupling. 9 | 10 | The below documentation will be removed in neo4j-embedded 1.8, and support for traversals will be dropped in neo4j-embedded 1.9. 11 | 12 | The traversal API used here is essentially the same as the one used in the Java API, with a few modifications. 13 | 14 | Traversals start at a given node and uses a set of rules to move through the graph and to decide what parts of the graph to return. 15 | 16 | == Basic traversals == 17 | 18 | === Following a relationship === 19 | 20 | The most basic traversals simply follow certain relationship types, and return everything they encounter. 21 | By default, each node is visited only once, so there is no risk of infinite loops. 22 | 23 | [snippet,python] 24 | ---- 25 | component=neo4j-python-embedded 26 | source=traversal.py 27 | tag=basicTraversal 28 | classifier=test-sources 29 | ---- 30 | 31 | === Following a relationship in a specific direction === 32 | 33 | You can tell the traverser to only follow relationships in some specific direction. 34 | 35 | [snippet,python] 36 | ---- 37 | component=neo4j-python-embedded 38 | source=traversal.py 39 | tag=directedTraversal 40 | classifier=test-sources 41 | ---- 42 | 43 | === Following multiple relationship types === 44 | 45 | You can specify an arbitrary number of relationship types and directions to follow. 46 | 47 | [snippet,python] 48 | ---- 49 | component=neo4j-python-embedded 50 | source=traversal.py 51 | tag=multiRelationshipTraversal 52 | classifier=test-sources 53 | ---- 54 | 55 | == Traversal results == 56 | 57 | A traversal can give you one of three different result types: <>, <> or <>. 58 | 59 | Traversals are performed lazily, which means that the graph is traversed as you loop through the result. 60 | 61 | [snippet,python] 62 | ---- 63 | component=neo4j-python-embedded 64 | source=traversal.py 65 | tag=traversalResults 66 | classifier=test-sources 67 | ---- 68 | 69 | == Uniqueness == 70 | 71 | To avoid infinite loops, it's important to define what parts of the graph can be re-visited during a traversal. 72 | By default, uniqueness is set to +NODE_GLOBAL+, which means that each node is only visited once. 73 | 74 | Here are the other options that are available. 75 | 76 | [snippet,python] 77 | ---- 78 | component=neo4j-python-embedded 79 | source=traversal.py 80 | tag=uniqueness 81 | classifier=test-sources 82 | ---- 83 | 84 | == Ordering == 85 | 86 | You can traverse either depth first, or breadth first. Depth first is the default, because it has lower memory overhead. 87 | 88 | [snippet,python] 89 | ---- 90 | component=neo4j-python-embedded 91 | source=traversal.py 92 | tag=ordering 93 | classifier=test-sources 94 | ---- 95 | 96 | == Evaluators - advanced filtering == 97 | 98 | In order to traverse based on other critera, such as node properties, or more complex things like neighboring nodes or patterns, we use evaluators. 99 | An evaluator is a normal Python method that takes a path as an argument, and returns a description of what to do next. 100 | 101 | The path argument is the current position the traverser is at, and the description of what to do can be one of four things, as seen in the example below. 102 | 103 | [snippet,python] 104 | ---- 105 | component=neo4j-python-embedded 106 | source=traversal.py 107 | tag=evaluators 108 | classifier=test-sources 109 | ---- 110 | 111 | -------------------------------------------------------------------------------- /src/docs/dev/tutorial.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-tutorial]] 2 | = Tutorials = 3 | 4 | This describes how to get started with Neo4j embedded in python. 5 | See <> for the full reference documentation. 6 | 7 | You have to have installed the neo4j-embedded python library to try these examples, see <>. 8 | 9 | :leveloffset: 2 10 | 11 | include::tutorial/helloworld.asciidoc[] 12 | 13 | include::tutorial/invoiceapp.asciidoc[] 14 | -------------------------------------------------------------------------------- /src/docs/dev/tutorial/helloworld.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-tutorial-helloworld]] 2 | = Hello, world! = 3 | 4 | Here is a simple example to get you started. 5 | 6 | [snippet,python] 7 | ---- 8 | component=neo4j-python-embedded 9 | source=examples.py 10 | tag=helloworld 11 | classifier=test-sources 12 | ---- 13 | 14 | -------------------------------------------------------------------------------- /src/docs/dev/tutorial/index.asciidoc: -------------------------------------------------------------------------------- 1 | [[tutorials-python-embedded]] 2 | = Using Neo4j embedded in Python applications = 3 | 4 | For instructions on how to install the Python Neo4j driver, see <>. 5 | 6 | For general information on the Python language binding, see <>. 7 | 8 | :leveloffset: 2 9 | 10 | include::helloworld.asciidoc[] 11 | 12 | :leveloffset: 2 13 | 14 | include::invoiceapp.asciidoc[] 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/docs/dev/tutorial/invoiceapp.asciidoc: -------------------------------------------------------------------------------- 1 | [[python-embedded-tutorial-invoiceapp]] 2 | = A sample app using cypher and indexes = 3 | 4 | For detailed documentation on the concepts used here, see <> and <>. 5 | 6 | This example shows you how to get started building something like a simple invoice tracking application with Neo4j. 7 | 8 | We start out by importing Neo4j, and creating some meta data that we will use to organize our actual data with. 9 | 10 | [snippet,python] 11 | ---- 12 | component=neo4j-python-embedded 13 | source=examples.py 14 | tag=invoiceapp-setup 15 | classifier=test-sources 16 | ---- 17 | 18 | == Domain logic == 19 | 20 | Then we define some domain logic that we want our application to be able to perform. Our application has two domain objects, Customers and Invoices. Let's create methods to add new customers and invoices. 21 | 22 | [snippet,python] 23 | ---- 24 | component=neo4j-python-embedded 25 | source=examples.py 26 | tag=invoiceapp-domainlogic-create 27 | classifier=test-sources 28 | ---- 29 | 30 | In the customer case, we create a new node to represent the customer and connect it to the _customers node_. This helps us find customers later on, as well as determine if a given node is a customer. 31 | 32 | We also index the name of the customer, to allow for quickly finding customers by name. 33 | 34 | In the invoice case, we do the same, except no indexing. We also connect each new invoice to the customer it was sent to, using a relationship of type +SENT_TO+. 35 | 36 | Next, we want to be able to retrieve customers and invoices that we have added. Because we are indexing customer names, finding them is quite simple. 37 | 38 | [snippet,python] 39 | ---- 40 | component=neo4j-python-embedded 41 | source=examples.py 42 | tag=invoiceapp-domainlogic-get-by-idx 43 | classifier=test-sources 44 | ---- 45 | 46 | Lets say we also like to do something like finding all invoices for a given customer that are above some given amount. This could be done by writing a cypher query, like this: 47 | 48 | [snippet,python] 49 | ---- 50 | component=neo4j-python-embedded 51 | source=examples.py 52 | tag=invoiceapp-domainlogic-get-by-cypher 53 | classifier=test-sources 54 | ---- 55 | 56 | == Creating data and getting it back == 57 | 58 | Putting it all together, we can create customers and invoices, and use the search methods we wrote to find them. 59 | 60 | [snippet,python] 61 | ---- 62 | component=neo4j-python-embedded 63 | source=examples.py 64 | tag=invoiceapp-create-and-search 65 | classifier=test-sources 66 | ---- 67 | 68 | -------------------------------------------------------------------------------- /src/docs/docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/docs/manual.asciidoc: -------------------------------------------------------------------------------- 1 | = Embedded Neo4j Python Bindings Documentation v{neo4j-version} = 2 | 3 | This describes _neo4j-embedded_, a Python library that lets you use the embedded Neo4j database in Python. 4 | 5 | NOTE: The version found at PyPI might not be updated. To get a fresh version, you can build it yourself. 6 | 7 | :leveloffset: 1 8 | 9 | include::dev/tutorial.asciidoc[] 10 | 11 | :leveloffset: 1 12 | 13 | include::dev/index.asciidoc[] 14 | 15 | -------------------------------------------------------------------------------- /src/main/assembly/python-distribution.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | python-dist 24 | neo4j-embedded 25 | 26 | zip 27 | 28 | 29 | 30 | 31 | **/*.py 32 | README* 33 | 34 | 35 | 38 | setup.py 39 | README.txt 40 | neo4j/__init__.py 41 | 42 | / 43 | src/main/python 44 | 45 | 46 | true 47 | 48 | neo4j/__init__.py 49 | setup.py 50 | README.txt 51 | 52 | / 53 | src/main/python 54 | 55 | 56 | 57 | **/*.py 58 | 59 | src/test/python 60 | /tests 61 | 62 | 63 | 64 | LICENSE* 65 | NOTICE* 66 | 67 | 68 | 69 | 70 | 71 | /neo4j/javalib 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/ExecutionEngine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Map; 23 | 24 | import org.neo4j.cypher.SyntaxException; 25 | import org.neo4j.graphdb.GraphDatabaseService; 26 | import org.neo4j.kernel.impl.util.StringLogger; 27 | 28 | /** 29 | * A special execution engine for JPype, hiding scala 30 | * classes deeper down in the result set. 31 | */ 32 | public class ExecutionEngine 33 | { 34 | 35 | private org.neo4j.cypher.ExecutionEngine inner; 36 | 37 | /** 38 | * Creates an execution engine around the give graph database 39 | * @param database The database to wrap 40 | */ 41 | public ExecutionEngine( GraphDatabaseService database ) 42 | { 43 | inner = new org.neo4j.cypher.ExecutionEngine( database, StringLogger.DEV_NULL ); 44 | } 45 | 46 | /** 47 | * Executes a query and returns an iterable that contains the result set 48 | * @param query The query to execute 49 | * @return A ExecutionResult that contains the result set 50 | * @throws org.neo4j.cypher.SyntaxException If the Query contains errors, 51 | * a SyntaxException exception might be thrown 52 | */ 53 | public ExecutionResult execute( String query ) throws SyntaxException 54 | { 55 | return new ExecutionResult(inner.execute( query )); 56 | } 57 | 58 | /** 59 | * Executes a {@link org.neo4j.cypher.internal.commands.Query} and returns an iterable that contains the result set 60 | * @param query The query to execute 61 | * @param params Parameters for the query 62 | * @return A ExecutionResult that contains the result set 63 | * @throws org.neo4j.cypher.SyntaxException If the Query contains errors, 64 | * a SyntaxException exception might be thrown 65 | */ 66 | public ExecutionResult execute( String query, Map params) throws SyntaxException 67 | { 68 | return new ExecutionResult(inner.execute(query, params)); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/ExecutionResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.io.PrintWriter; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | public class ExecutionResult implements Iterable> 28 | { 29 | 30 | private org.neo4j.cypher.ExecutionResult inner; 31 | 32 | /** 33 | * Constructor used by the Cypher framework. End-users should not 34 | * create an ExecutionResult directly. 35 | * 36 | * @param projection 37 | */ 38 | public ExecutionResult( org.neo4j.cypher.ExecutionResult projection ) 39 | { 40 | inner = projection; 41 | } 42 | 43 | /** 44 | * Provides result objects from a single column of the result set. This method is best used for 45 | * single column results. 46 | * 47 | * @param n exact name of the column, as it appeared in the original query 48 | * @return an iterator of the result objects, possibly empty 49 | * @throws ClassCastException when the result object can not be cast to the requested type 50 | * @throws org.neo4j.graphdb.NotFoundException when the column name does not appear in the original query 51 | */ 52 | public Iterator columnAs( String n ) 53 | { 54 | return new WrappedIterator(inner.javaColumnAs( n )); 55 | } 56 | 57 | /** 58 | * The exact names used to represent each column in the result set. 59 | * 60 | * @return List of the column names. 61 | */ 62 | public List columns() 63 | { 64 | return inner.javaColumns(); 65 | } 66 | 67 | @Override 68 | public Iterator> iterator() 69 | { 70 | return new WrappedIterator>(inner.javaIterator()); 71 | } 72 | 73 | @Override 74 | public String toString() 75 | { 76 | return inner.dumpToString(); 77 | } 78 | 79 | public void toString( PrintWriter writer ) 80 | { 81 | inner.dumpToString( writer ); 82 | } 83 | 84 | public int length() 85 | { 86 | return inner.length(); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/ScalaToPythonWrapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Collection; 23 | import java.util.Map; 24 | 25 | import org.neo4j.graphdb.Path; 26 | 27 | /** 28 | * Used to wrap gnarly scala classes into something that JPype understands. 29 | */ 30 | public class ScalaToPythonWrapper 31 | { 32 | 33 | public static Object wrap( Object obj ) 34 | { 35 | if(obj instanceof Path ) 36 | { 37 | return new WrappedPath((Path)obj); 38 | } 39 | else if(obj instanceof Map) 40 | { 41 | return new WrappedMap( (Map) obj ); 42 | } 43 | else if(obj instanceof Collection ) 44 | { 45 | return new WrappedCollection( (Collection) obj ); 46 | } 47 | return obj; 48 | } 49 | 50 | public static Map wrapMap( Map map ) 51 | { 52 | return new WrappedMap(map); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/WrappedCollection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Collection; 23 | 24 | public class WrappedCollection extends WrappedIterable implements Collection 25 | { 26 | private final Collection inner; 27 | 28 | public WrappedCollection( Collection inner ) 29 | { 30 | super(inner); 31 | this.inner = inner; 32 | } 33 | 34 | @Override 35 | public int size() 36 | { 37 | return inner.size(); 38 | } 39 | 40 | @Override 41 | public boolean isEmpty() 42 | { 43 | return inner.isEmpty(); 44 | } 45 | 46 | @Override 47 | public boolean contains( Object o ) 48 | { 49 | return inner.contains( o ); 50 | } 51 | 52 | @Override 53 | public Object[] toArray() 54 | { 55 | return inner.toArray(); 56 | } 57 | 58 | @Override 59 | public T[] toArray( T[] ts ) 60 | { 61 | return inner.toArray( ts ); 62 | } 63 | 64 | @Override 65 | public boolean add( T o ) 66 | { 67 | return inner.add( o ); 68 | } 69 | 70 | @Override 71 | public boolean remove( Object o ) 72 | { 73 | return inner.remove( o ); 74 | } 75 | 76 | @Override 77 | public boolean containsAll( Collection objects ) 78 | { 79 | return inner.containsAll( objects ); 80 | } 81 | 82 | @Override 83 | public boolean addAll( Collection objects ) 84 | { 85 | return inner.addAll( objects ); 86 | } 87 | 88 | @Override 89 | public boolean removeAll( Collection objects ) 90 | { 91 | return inner.removeAll( objects ); 92 | } 93 | 94 | @Override 95 | public boolean retainAll( Collection objects ) 96 | { 97 | return inner.retainAll( objects ); 98 | } 99 | 100 | @Override 101 | public void clear() 102 | { 103 | inner.clear(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/WrappedIterable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Iterator; 23 | 24 | public class WrappedIterable implements Iterable 25 | { 26 | 27 | private final Iterable inner; 28 | 29 | public WrappedIterable( Iterable inner ) 30 | { 31 | this.inner = inner; 32 | } 33 | 34 | @Override 35 | public Iterator iterator() 36 | { 37 | return new WrappedIterator(inner.iterator()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/WrappedIterator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Iterator; 23 | 24 | public class WrappedIterator implements Iterator 25 | { 26 | private Iterator inner; 27 | 28 | public WrappedIterator( Iterator inner ) 29 | { 30 | this.inner = inner; 31 | } 32 | 33 | @Override 34 | public boolean hasNext() 35 | { 36 | return inner.hasNext(); 37 | } 38 | 39 | @Override 40 | public T next() 41 | { 42 | return (T) ScalaToPythonWrapper.wrap( inner.next() ); 43 | } 44 | 45 | @Override 46 | public void remove() 47 | { 48 | inner.remove(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/WrappedMap.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Collection; 23 | import java.util.Map; 24 | import java.util.Set; 25 | 26 | class WrappedMap implements Map 27 | { 28 | 29 | private Map inner; 30 | 31 | public WrappedMap( Map inner ) 32 | { 33 | this.inner = inner; 34 | } 35 | 36 | @Override 37 | public int size() 38 | { 39 | return inner.size(); 40 | } 41 | 42 | @Override 43 | public boolean isEmpty() 44 | { 45 | return inner.isEmpty(); 46 | } 47 | 48 | @Override 49 | public boolean containsKey( Object o ) 50 | { 51 | return inner.containsKey( o ); 52 | } 53 | 54 | @Override 55 | public boolean containsValue( Object o ) 56 | { 57 | return inner.containsValue( o ); 58 | } 59 | 60 | @Override 61 | public Object get( Object o ) 62 | { 63 | return ScalaToPythonWrapper.wrap( inner.get( o ) ); 64 | } 65 | 66 | @Override 67 | public Object put( String s, Object o ) 68 | { 69 | return inner.put( s, o ); 70 | } 71 | 72 | @Override 73 | public Object remove( Object o ) 74 | { 75 | return inner.remove( o ); 76 | } 77 | 78 | @Override 79 | public void putAll( Map map ) 80 | { 81 | inner.putAll( map ); 82 | } 83 | 84 | @Override 85 | public void clear() 86 | { 87 | inner.clear(); 88 | } 89 | 90 | @Override 91 | public Set keySet() 92 | { 93 | return inner.keySet(); 94 | } 95 | 96 | @Override 97 | public Collection values() 98 | { 99 | return new WrappedCollection(inner.values()); 100 | } 101 | 102 | @Override 103 | public Set> entrySet() 104 | { 105 | return inner.entrySet(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/cypher/pycompat/WrappedPath.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2013 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package org.neo4j.cypher.pycompat; 21 | 22 | import java.util.Iterator; 23 | 24 | import org.neo4j.graphdb.Node; 25 | import org.neo4j.graphdb.Path; 26 | import org.neo4j.graphdb.PropertyContainer; 27 | import org.neo4j.graphdb.Relationship; 28 | 29 | public class WrappedPath implements Path 30 | { 31 | private final Path inner; 32 | 33 | public WrappedPath( Path inner ) 34 | { 35 | this.inner = inner; 36 | } 37 | 38 | @Override 39 | public Node startNode() 40 | { 41 | return inner.startNode(); 42 | } 43 | 44 | @Override 45 | public Node endNode() 46 | { 47 | return inner.endNode(); 48 | } 49 | 50 | @Override 51 | public Relationship lastRelationship() 52 | { 53 | return inner.lastRelationship(); 54 | } 55 | 56 | @Override 57 | public Iterable relationships() 58 | { 59 | return new WrappedIterable(inner.relationships()); 60 | } 61 | 62 | @Override 63 | public Iterable reverseRelationships() 64 | { 65 | return new WrappedIterable(inner.reverseRelationships()); 66 | } 67 | 68 | @Override 69 | public Iterable nodes() 70 | { 71 | return new WrappedIterable(inner.nodes()); 72 | } 73 | 74 | @Override 75 | public Iterable reverseNodes() 76 | { 77 | return new WrappedIterable(inner.reverseNodes()); 78 | } 79 | 80 | @Override 81 | public int length() 82 | { 83 | return inner.length(); 84 | } 85 | 86 | @Override 87 | public Iterator iterator() 88 | { 89 | return new WrappedIterator(inner.iterator()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/python/README.txt: -------------------------------------------------------------------------------- 1 | Python bindings for embedded Neo4j 2 | ================================== 3 | 4 | These are Python bindings for the embedded Neo4j Graph Database. 5 | 6 | Source: https://github.com/neo4j/python-embedded 7 | 8 | Prerequisites 9 | ------------- 10 | 11 | The Neo4j database itself is a java application, which means you have to provide an interface to communicate with java-land. Specifically, you need to have JPype installed. See the installation section in the documentation for details. 12 | 13 | Documentation 14 | ------------- 15 | 16 | Documentation can be found in the Neo4j Manual: 17 | 18 | http://docs.neo4j.org/chunked/${neo4j.version}/python-embedded.html 19 | 20 | 21 | Versions 22 | -------- 23 | 24 | The version number used for neo4j-embedded matches the version of neo4j it comes shipped with. The following examples should serve as a guide to determine what neo4j version is under the hood: 25 | 26 | 1.5 : 27 | Neo4j version 1.5 28 | 1.5.dev1 : 29 | Neo4j version 1.5-SNAPSHOT 30 | 1.5.b1 : 31 | Neo4j version 1.5.M01 32 | 1.5.r1 : 33 | Neo4j version 1.5.RC1 34 | 35 | -------------------------------------------------------------------------------- /src/main/python/neo4j/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | """Python bindings for the embedded Neo4j Graph Database. 22 | """ 23 | 24 | __all__ = 'GraphDatabase',\ 25 | 'Direction', 'Evaluation', 'Uniqueness', 'BOTH', 'ANY', 'INCOMING', 'OUTGOING' 26 | 27 | __version__ = "${pythonic_version}" 28 | 29 | from neo4j.core import GraphDatabase, Direction, NotFoundException, BOTH, ANY, INCOMING, OUTGOING 30 | from neo4j.traversal import Traversal, Evaluation, Uniqueness 31 | from neo4j.index import NodeIndexManager, RelationshipIndexManager 32 | from neo4j.util import rethrow_current_exception_as 33 | from neo4j.cypher import CypherEngine 34 | 35 | class Nodes(object): 36 | 37 | def __init__(self, db): 38 | self.db = db 39 | self.indexes = NodeIndexManager(db) 40 | 41 | def __call__(self, **properties): 42 | return self.create(**properties) 43 | 44 | def __getitem__(self, items): 45 | return self.get(items) 46 | 47 | def __delitem__(self, item): 48 | return self[item].delete() 49 | 50 | def __iter__(self): 51 | for node in self.db.getAllNodes().iterator(): 52 | yield node 53 | 54 | def __len__(self): 55 | return sum(1 for _ in self) 56 | 57 | def create(self, **properties): 58 | node = self.db.createNode() 59 | for key, val in properties.items(): 60 | node[key] = val 61 | return node 62 | 63 | def get(self, id): 64 | if not isinstance(id, (int, long)): 65 | raise TypeError("Only integer and long values allowed as node ids.") 66 | try: 67 | return self.db.getNodeById( id ) 68 | except: 69 | rethrow_current_exception_as(KeyError) 70 | 71 | 72 | class Relationships(object): 73 | 74 | def __init__(self, db): 75 | self.db = db 76 | self.indexes = RelationshipIndexManager(db) 77 | 78 | def __getitem__(self, items): 79 | return self.get(items) 80 | 81 | def __delitem__(self, item): 82 | return self[item].delete() 83 | 84 | def __iter__(self): 85 | for node in self.db.nodes: 86 | for rel in node.relationships.incoming: 87 | yield rel 88 | 89 | def __len__(self): 90 | return sum(1 for _ in self) 91 | 92 | def get(self, id): 93 | if not isinstance(id, (int, long)): 94 | raise TypeError("Only integer and long values allowed as relationship ids.") 95 | 96 | try: 97 | return self.db.getRelationshipById( id ) 98 | except: 99 | rethrow_current_exception_as(KeyError) 100 | 101 | 102 | class GraphDatabase(GraphDatabase): 103 | 104 | from core import __new__ 105 | 106 | try: 107 | from contextlib import contextmanager 108 | except: 109 | pass 110 | else: 111 | ## yield in try is a recent feature in Python, 112 | ## guard with exec to support old versions of Python 113 | ## This should pass since import contextlib has worked 114 | exec('''def transaction(self): 115 | """Allows usage of the with-statement for Neo4j transactions: 116 | with graphdb.transaction: 117 | doMutatingOperations() 118 | """ 119 | tx = self.beginTx() 120 | try: 121 | yield tx 122 | tx.success() 123 | finally: 124 | tx.finish() 125 | ''') 126 | transaction = property(contextmanager(transaction)) 127 | del contextmanager # from the body of this class 128 | 129 | @property 130 | def node(self): 131 | if not hasattr(self, '__nodes'): 132 | self.__nodes = Nodes(self) 133 | return self.__nodes 134 | 135 | # Syntax sugar 136 | nodes = node 137 | 138 | @property 139 | def relationship(self): 140 | if not hasattr(self, '__relationships'): 141 | self.__relationships = Relationships(self) 142 | return self.__relationships 143 | 144 | # Syntax sugar 145 | relationships = relationship 146 | 147 | @property 148 | def reference_node(self): 149 | return self.getReferenceNode() 150 | 151 | def traversal(self): 152 | ''' This is deprecated, please use cypher instead.''' 153 | return Traversal.description() 154 | 155 | def query(self, query, **params): 156 | return self._cypher_engine.execute(query, **params) 157 | 158 | def prepare_query(self, query): 159 | ''' This is deprecated, Cypher internally caches query plans now, 160 | use queries with parameters to take full advantage of this. 161 | ''' 162 | return self._cypher_engine.prepare(query) 163 | 164 | @property 165 | def _cypher_engine(self): 166 | if not hasattr(self, '__cached_cypher_engine'): 167 | self.__cached_cypher_engine = CypherEngine(self) 168 | return self.__cached_cypher_engine 169 | 170 | -------------------------------------------------------------------------------- /src/main/python/neo4j/__main__.py: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- mode: Python; coding: utf-8 -*- 3 | 4 | # Copyright (c) 2002-2013 "Neo Technology," 5 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 6 | # 7 | # This file is part of Neo4j. 8 | # 9 | # Neo4j is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program. If not, see . 21 | 22 | """:" 23 | # Locate (good enough) absolute path for the Neo4j library 24 | FILE=$0 25 | while [ -L "$FILE" ]; do 26 | FILE=$(readlink $FILE) 27 | done 28 | DIR=$(cd `dirname $FILE`; pwd) 29 | FILE=$(basename $FILE) 30 | if [ "__main__.py" != "$FILE" ]; then 31 | echo Neo4j library has been obscured >&2 32 | exit -1 33 | fi 34 | while [ -L "$DIR" ]; do 35 | DIR=$(readlink $DIR) 36 | done 37 | if [ "neo4j" != "$(basename $DIR)" ]; then 38 | echo Neo4j library has been obscured >&2 39 | exit -1 40 | fi 41 | DIR=$(dirname $DIR) 42 | while [ -L "$DIR" ]; do 43 | DIR=$(readlink $DIR) 44 | done 45 | 46 | # Set up the Python path 47 | if [ -z "$PYTHONPATH" ]; then 48 | PYTHONPATH="$DIR" 49 | else 50 | PYTHONPATH="$DIR:$PYTHONPATH" 51 | fi 52 | export PYTHONPATH 53 | 54 | BIN=$(dirname $(dirname $DIR))/bin 55 | if [ -d "$BIN" -a -f "$BIN/classpath" -a -x "$BIN/classpath" ]; then 56 | CLASSPATH=$($BIN/classpath) 57 | export CLASSPATH 58 | fi 59 | 60 | # Launch the __main__ 61 | python "$DIR/neo4j/$FILE" "$@" 62 | # Exit with same exit code 63 | exit $? 64 | ":""" 65 | USAGE = """USAGE: neo4j [ 96 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 |

Packages

157 | Note: package statistics are not computed recursively, they only sum up all of its testsuites numbers. 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | Failure 175 | Error 176 | 177 | 178 | 179 | 180 | 181 | 182 | 187 | 188 | 189 | 190 | 191 |
183 | 184 | 185 | 186 |
192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 |

Package

206 | 207 | 208 | 209 | 210 | 211 | 212 |
213 | Back to top 214 |

215 |

216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 |

TestCase

225 | 226 | 227 | 228 | 232 | 233 | 234 | 235 | 236 | 237 | 238 |
239 | 245 |

246 | 247 | Back to top 248 | 249 | 250 | 251 | 252 |

Summary

253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | Failure 270 | Error 271 | 272 | 273 | 274 | 275 | 276 | 281 | 286 | 287 | 288 |
TestsFailuresErrorsSuccess rateTime
277 | 278 | 279 | 280 | 282 | 283 | 284 | 285 |
289 | 290 | 291 | 294 | 295 |
292 | Note: failures are anticipated and checked for with assertions while errors are unanticipated. 293 |
296 |
297 | 298 | 302 | 303 | cur = TestCases['.'] = new Array(); 304 | 305 | 306 | cur[''] = ''; 307 | 308 | 309 | 310 | 311 | 312 |

313 | 314 | 315 | 316 | 317 | 318 |
Designed for use with JUnit and Ant.
319 |
320 |
321 | 322 | 323 | 324 | Name 325 | Tests 326 | Errors 327 | Failures 328 | Time(s) 329 | 330 | 331 | 332 | 333 | 334 | 335 | Name 336 | Tests 337 | Errors 338 | Failures 339 | Time(s) 340 | Time Stamp 341 | Host 342 | 343 | 344 | 345 | 346 | 347 | 348 | Name 349 | Status 350 | Type 351 | Time(s) 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | Failure 363 | Error 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | Error 387 | 388 | 389 | 390 | 391 | 392 | Failure 393 | 394 | 395 | 396 | Error 397 | 398 | 399 | 400 | Success 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | stderr 413 | 414 | 415 | 416 | stdout 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | N/A 435 | 436 | 437 | 438 | 439 | 440 | 441 |

442 | 443 | 444 | 445 |
446 | 447 | 448 |
449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 |
468 |       
469 |     
470 |
471 | 472 | 473 | 477 | 478 | 479 | 480 | 481 | 482 |
483 | 484 | 485 | 486 |
487 | 488 | 489 | 490 |
491 |
492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | -------------------------------------------------------------------------------- /src/test/python/core.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import tempfile, os 25 | 26 | class GraphTest(unit_tests.GraphDatabaseTest): 27 | 28 | def test_create_db(self): 29 | folder_to_put_db_in = tempfile.mkdtemp() 30 | try: 31 | # START SNIPPET: creatingDatabase 32 | from neo4j import GraphDatabase 33 | 34 | # Create db 35 | db = GraphDatabase(folder_to_put_db_in) 36 | 37 | # Always shut down your database 38 | db.shutdown() 39 | # END SNIPPET: creatingDatabase 40 | finally: 41 | if os.path.exists(folder_to_put_db_in): 42 | import shutil 43 | shutil.rmtree(folder_to_put_db_in) 44 | 45 | def test_create_configured_db(self): 46 | folder_to_put_db_in = tempfile.mkdtemp() 47 | try: 48 | # START SNIPPET: creatingConfiguredDatabase 49 | from neo4j import GraphDatabase 50 | 51 | # Example configuration parameters 52 | db = GraphDatabase(folder_to_put_db_in, string_block_size=200, array_block_size=240) 53 | 54 | db.shutdown() 55 | # END SNIPPET: creatingConfiguredDatabase 56 | finally: 57 | if os.path.exists(folder_to_put_db_in): 58 | import shutil 59 | shutil.rmtree(folder_to_put_db_in) 60 | 61 | def test_with_statement_transactions(self): 62 | db = self.graphdb 63 | # START SNIPPET: withBasedTransactions 64 | # Start a transaction 65 | with db.transaction: 66 | # This is inside the transactional 67 | # context. All work done here 68 | # will either entirely succeed, 69 | # or no changes will be applied at all. 70 | 71 | # Create a node 72 | node = db.node() 73 | 74 | # Give it a name 75 | node['name'] = 'Cat Stevens' 76 | 77 | # The transaction is automatically 78 | # commited when you exit the with 79 | # block. 80 | # END SNIPPET: withBasedTransactions 81 | self.assertNotEqual(node, None) 82 | 83 | def test_create_node(self): 84 | with self.graphdb.transaction: 85 | node = self.graphdb.node() 86 | self.assertNotEqual(node, None) 87 | 88 | def test_delete_node(self): 89 | db = self.graphdb 90 | # START SNIPPET: deleteNode 91 | with db.transaction: 92 | node = db.node() 93 | node.delete() 94 | # END SNIPPET: deleteNode 95 | 96 | try: 97 | self.graphdb.node[node.id] 98 | self.assertEqual(True,False) 99 | except Exception, e: 100 | self.assertTrue(isinstance(e, KeyError)) 101 | 102 | def test_delete_node_by_id(self): 103 | db = self.graphdb 104 | with db.transaction: 105 | node = db.node() 106 | some_node_id = node.id 107 | 108 | # START SNIPPET: deleteByIdNode 109 | with db.transaction: 110 | del db.node[some_node_id] 111 | # END SNIPPET: deleteByIdNode 112 | 113 | try: 114 | self.graphdb.node[node.id] 115 | self.assertEqual(True,False) 116 | except Exception, e: 117 | self.assertTrue(isinstance(e, KeyError)) 118 | 119 | def test_create_node_with_properties(self): 120 | db = self.graphdb 121 | # START SNIPPET: createNode 122 | with db.transaction: 123 | # Create a node 124 | thomas = db.node(name='Thomas Anderson', age=42) 125 | # END SNIPPET: createNode 126 | self.assertNotEqual(thomas, None) 127 | self.assertEquals(thomas['name'], 'Thomas Anderson') 128 | self.assertEquals(thomas['age'], 42) 129 | 130 | def test_properties(self): 131 | db = self.graphdb 132 | with db.transaction: 133 | node_or_rel = db.node() 134 | 135 | 136 | # START SNIPPET: setProperties 137 | with db.transaction: 138 | node_or_rel['name'] = 'Thomas Anderson' 139 | node_or_rel['age'] = 42 140 | node_or_rel['favourite_numbers'] = [1,2,3] 141 | node_or_rel['favourite_words'] = ['banana','blue'] 142 | # END SNIPPET: setProperties 143 | 144 | 145 | # START SNIPPET: programaticSetProperties 146 | with db.transaction: 147 | node_or_rel.set('name', 'Thomas Anderson') 148 | # END SNIPPET: programaticSetProperties 149 | 150 | 151 | # START SNIPPET: getProperties 152 | numbers = node_or_rel['favourite_numbers'] 153 | # END SNIPPET: getProperties 154 | 155 | 156 | # START SNIPPET: programaticGetProperties 157 | numbers = node_or_rel.get_property('favourite_numbers') 158 | 159 | # With default value 160 | value = node_or_rel.get_property('some_property', 'defaultvalue') 161 | # END SNIPPET: programaticGetProperties 162 | 163 | self.assertEqual(value, 'defaultvalue') 164 | 165 | 166 | # START SNIPPET: deleteProperties 167 | with db.transaction: 168 | del node_or_rel['favourite_numbers'] 169 | # END SNIPPET: deleteProperties 170 | del node_or_rel['favourite_words'] 171 | 172 | 173 | # START SNIPPET: loopProperties 174 | # Loop key and value at the same time 175 | for key, value in node_or_rel.items(): 176 | pass 177 | 178 | # Loop property keys 179 | for key in node_or_rel.keys(): 180 | pass 181 | 182 | # Loop property values 183 | for value in node_or_rel.values(): 184 | pass 185 | # END SNIPPET: loopProperties 186 | 187 | items = list(node_or_rel.items()) 188 | self.assertEqual(len(items), 2) 189 | self.assertEqual(items[1][0],'age') 190 | self.assertEqual(items[1][1],42) 191 | 192 | keys = list(node_or_rel.keys()) 193 | self.assertEqual(len(keys), 2) 194 | self.assertEqual(keys[1],'age') 195 | 196 | values = list(node_or_rel.values()) 197 | self.assertEqual(len(values), 2) 198 | self.assertEqual(values[1],42) 199 | 200 | def test_property_types(self): 201 | with self.graphdb.transaction: 202 | n = self.graphdb.node() 203 | 204 | # Booleans 205 | n['a_bool'] = True 206 | self.assertEqual(n['a_bool'], True) 207 | self.assertEqual(type(n['a_bool']), bool) 208 | self.assertEqual(type(n.get_property('a_bool')), bool) 209 | 210 | n['a_bool'] = False 211 | self.assertEqual(n['a_bool'], False) 212 | self.assertEqual(type(n['a_bool']), bool) 213 | self.assertEqual(type(n.get_property('a_bool')), bool) 214 | 215 | # Strings 216 | n['a_string'] = 'my fancy string I made' 217 | self.assertEqual(n['a_string'], 'my fancy string I made') 218 | self.assertEqual(type(n['a_string']), unicode) 219 | 220 | # Longs 221 | n['a_long'] = 1337 222 | self.assertEqual(n['a_long'], 1337) 223 | self.assertEqual(type(n['a_long']), long) 224 | 225 | # Lists 226 | n['a_list'] = [1,2,3] 227 | self.assertEqual(n['a_list'], [1,2,3]) 228 | self.assertEqual(type(n['a_list']), list) 229 | 230 | def test_get_property_with_default(self): 231 | with self.graphdb.transaction: 232 | n = self.graphdb.node() 233 | 234 | n['a_bool'] = True 235 | self.assertEqual(n.get_property('a_bool'), True) 236 | self.assertEqual(n.get_property('a_bool', False), True) 237 | self.assertEqual(n.get_property('doesnt_exist'), None) 238 | self.assertEqual(n.get_property('doesnt_exist', False), False) 239 | 240 | 241 | def test_remove_properties(self): 242 | with self.graphdb.transaction: 243 | node = self.graphdb.node(name='Thomas Anderson', age=42) 244 | 245 | self.assertEqual(node['name'], 'Thomas Anderson') 246 | del node['name'] 247 | 248 | try: 249 | node['name'] 250 | self.assertTrue(False) 251 | except Exception, e: 252 | self.assertTrue(isinstance(e, KeyError)) 253 | 254 | def test_get_node_by_id(self): 255 | db = self.graphdb 256 | with db.transaction: 257 | node = db.node() 258 | some_node_id = node.id 259 | # START SNIPPET: getNodeById 260 | # You don't have to be in a transaction 261 | # to do read operations. 262 | a_node = db.node[some_node_id] 263 | 264 | # Ids on nodes and relationships are available via the "id" 265 | # property, eg.: 266 | node_id = a_node.id 267 | # END SNIPPET: getNodeById 268 | self.assertNotEqual(a_node, None) 269 | self.assertEqual(node_id, some_node_id) 270 | 271 | def test_get_all_nodes(self): 272 | db = self.graphdb 273 | with db.transaction: 274 | node = db.node() 275 | # START SNIPPET: getAllNodes 276 | for node in db.nodes: 277 | pass 278 | 279 | # Shorthand for iterating through 280 | # and counting all nodes 281 | number_of_nodes = len(db.nodes) 282 | # END SNIPPET: getAllNodes 283 | self.assertEqual(2, number_of_nodes) 284 | 285 | nodes = list(db.nodes) 286 | self.assertEqual(2, len(nodes)) 287 | 288 | def test_get_all_relationships(self): 289 | db = self.graphdb 290 | with db.transaction: 291 | node = db.node() 292 | db.reference_node.Knows(node) 293 | node.Knows(db.reference_node) 294 | node.Knows(node) 295 | # START SNIPPET: getAllRelationships 296 | for rel in db.relationships: 297 | pass 298 | 299 | # Shorthand for iterating through 300 | # and counting all relationships 301 | number_of_rels = len(db.relationships) 302 | # END SNIPPET: getAllRelationships 303 | self.assertEqual(3, number_of_rels) 304 | 305 | rels = list(db.relationships) 306 | self.assertEqual(3, len(rels)) 307 | 308 | def test_get_reference_node(self): 309 | db = self.graphdb 310 | # START SNIPPET: getReferenceNode 311 | reference = db.reference_node 312 | # END SNIPPET: getReferenceNode 313 | self.assertNotEqual(reference, None) 314 | 315 | def test_can_create_relationship(self): 316 | db = self.graphdb 317 | 318 | # START SNIPPET: createRelationship 319 | with db.transaction: 320 | # Nodes to create a relationship between 321 | steven = self.graphdb.node(name='Steve Brook') 322 | poplar_bluff = self.graphdb.node(name='Poplar Bluff') 323 | 324 | # Create a relationship of type "mayor_of" 325 | relationship = steven.mayor_of(poplar_bluff, since="12th of July 2012") 326 | 327 | # Or, to create relationship types with names 328 | # that would not be possible with the above 329 | # method. 330 | steven.relationships.create('mayor_of', poplar_bluff, since="12th of July 2012") 331 | # END SNIPPET: createRelationship 332 | secondrel = poplar_bluff.likes(steven, message="buh") 333 | 334 | message = '' 335 | for rel in steven.mayor_of: 336 | message += "%s %s %s" % ( 337 | rel.start['name'], 338 | rel['since'], 339 | rel.end['name'], 340 | ) 341 | self.assertEquals(message, "Steve Brook 12th of July 2012 Poplar BluffSteve Brook 12th of July 2012 Poplar Bluff") 342 | 343 | a_node = steven 344 | # START SNIPPET: accessingRelationships 345 | # All relationships on a node 346 | for rel in a_node.relationships: 347 | pass 348 | 349 | # Incoming relationships 350 | for rel in a_node.relationships.incoming: 351 | pass 352 | 353 | # Outgoing relationships 354 | for rel in a_node.relationships.outgoing: 355 | pass 356 | 357 | # Relationships of a specific type 358 | for rel in a_node.mayor_of: 359 | pass 360 | 361 | # Incoming relationships of a specific type 362 | for rel in a_node.mayor_of.incoming: 363 | pass 364 | 365 | # Outgoing relationships of a specific type 366 | for rel in a_node.mayor_of.outgoing: 367 | pass 368 | # END SNIPPET: accessingRelationships 369 | 370 | self.assertEquals(len(steven.relationships), 3) 371 | self.assertEquals(len(steven.relationships.incoming), 1) 372 | self.assertEquals(len(steven.relationships.outgoing), 2) 373 | 374 | self.assertEquals(len(steven.likes), 1) 375 | self.assertEquals(len(steven.likes.incoming), 1) 376 | self.assertEquals(len(steven.likes.outgoing), 0) 377 | 378 | def test_relationship_attributes(self): 379 | db = self.graphdb 380 | 381 | with db.transaction: 382 | source = self.graphdb.node() 383 | target = self.graphdb.node() 384 | 385 | # Create a relationship of type "related_to" 386 | relationship = source.related_to(target) 387 | # START SNIPPET: relationshipAttributes 388 | relationship_type = relationship.type 389 | 390 | start_node = relationship.start 391 | end_node = relationship.end 392 | # END SNIPPET: relationshipAttributes 393 | 394 | rel = relationship 395 | self.assertEquals(rel.type.name(), 'related_to') 396 | self.assertEquals(rel.start, source) 397 | self.assertEquals(rel.end, target) 398 | 399 | def test_get_relationship_by_id(self): 400 | db = self.graphdb 401 | with db.transaction: 402 | source = self.graphdb.node() 403 | target = self.graphdb.node() 404 | rel = source.Knows(target) 405 | a_relationship_id = rel.id 406 | # START SNIPPET: getRelationshipById 407 | the_relationship = db.relationship[a_relationship_id] 408 | # END SNIPPET: getRelationshipById 409 | self.assertNotEqual(the_relationship, None) 410 | 411 | def test_delete_relationship(self): 412 | db = self.graphdb 413 | # START SNIPPET: deleteRelationship 414 | with db.transaction: 415 | # Create a relationship 416 | source = db.node() 417 | target = db.node() 418 | rel = source.Knows(target) 419 | 420 | # Delete it 421 | rel.delete() 422 | # END SNIPPET: deleteRelationship 423 | 424 | try: 425 | self.graphdb.relationship[rel.id] 426 | self.assertTrue(False) 427 | except Exception, e: 428 | self.assertTrue(isinstance(e, KeyError)) 429 | 430 | def test_delete_relationship_by_id(self): 431 | 432 | db = self.graphdb 433 | 434 | with db.transaction: 435 | node1 = self.graphdb.node() 436 | node2 = self.graphdb.node() 437 | rel = node1.Knows(node2) 438 | 439 | some_relationship_id = rel.id 440 | # START SNIPPET: deleteByIdRelationship 441 | with db.transaction: 442 | del db.relationship[some_relationship_id] 443 | # END SNIPPET: deleteByIdRelationship 444 | 445 | try: 446 | self.graphdb.relationship[rel.id] 447 | self.assertTrue(False) 448 | except Exception, e: 449 | self.assertTrue(isinstance(e, KeyError)) 450 | 451 | if __name__ == '__main__': 452 | unit_tests.unittest.main() 453 | -------------------------------------------------------------------------------- /src/test/python/cypher.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import tempfile, os 25 | 26 | class CypherTest(unit_tests.GraphDatabaseTest): 27 | 28 | def test_simple_query(self): 29 | db = self.graphdb 30 | 31 | # START SNIPPET: basicCypherQuery 32 | result = db.query("START n=node(0) RETURN n") 33 | # END SNIPPET: basicCypherQuery 34 | 35 | # START SNIPPET: getCypherResultColumn 36 | root_node = "START n=node(0) RETURN n" 37 | 38 | # Fetch an iterator for the "n" column 39 | column = db.query(root_node)['n'] 40 | 41 | for cell in column: 42 | node = cell 43 | 44 | # Coumns support "single": 45 | column = db.query(root_node)['n'] 46 | node = column.single 47 | # END SNIPPET: getCypherResultColumn 48 | 49 | self.assertEquals(0, node.id) 50 | 51 | # START SNIPPET: iterateCypherResult 52 | root_node = "START n=node(0) RETURN n" 53 | 54 | # Iterate through all result rows 55 | for row in db.query(root_node): 56 | node = row['n'] 57 | 58 | # We know it's a single result, 59 | # so we could have done this as well 60 | node = db.query(root_node).single['n'] 61 | # END SNIPPET: iterateCypherResult 62 | self.assertEquals(0, node.id) 63 | 64 | def test_list_columns(self): 65 | db = self.graphdb 66 | 67 | # START SNIPPET: listCypherResultColumns 68 | result = db.query("START n=node(0) RETURN n,count(n)") 69 | 70 | # Get a list of the column names 71 | columns = result.keys() 72 | # END SNIPPET: listCypherResultColumns 73 | 74 | self.assertEquals(columns[0], 'n') 75 | self.assertEquals(columns[1], 'count(n)') 76 | 77 | def test_parameterized_query(self): 78 | db = self.graphdb 79 | 80 | # START SNIPPET: parameterizedCypherQuery 81 | result = db.query("START n=node({id}) RETURN n",id=0) 82 | 83 | node = result.single['n'] 84 | # END SNIPPET: parameterizedCypherQuery 85 | 86 | self.assertEquals(0, node.id) 87 | 88 | def test_resultset_to_list(self): 89 | db = self.graphdb 90 | 91 | result = db.query("START n=node({id}) RETURN n",id=0) 92 | 93 | rows = list(result) 94 | 95 | self.assertEquals(1, len(rows)) 96 | self.assertEquals(0, rows[0]['n'].id) 97 | 98 | def test_prepared_queries(self): 99 | db = self.graphdb 100 | 101 | # START SNIPPET: preparedCypherQuery 102 | get_node_by_id = db.prepare_query("START n=node({id}) RETURN n") 103 | 104 | result = db.query(get_node_by_id, id=0) 105 | 106 | node = result.single['n'] 107 | # END SNIPPET: preparedCypherQuery 108 | 109 | self.assertEquals(0, node.id) 110 | 111 | def test_aggregate_queries(self): 112 | db = self.graphdb 113 | 114 | with db.transaction: 115 | result = db.query('''CREATE p=node-[:Depends_on]->port<-[:Has]-parent1<-[:Has]-parent2, 116 | port<-[:Has]-parent3 117 | RETURN node,p''') 118 | for row in result: 119 | node = row['node'] 120 | 121 | result = db.query(''' 122 | START n=node({node}) 123 | MATCH p=n-[:Depends_on]->port<-[:Has]-parent 124 | RETURN COLLECT(p) AS end_points''',node=node) 125 | 126 | 127 | end_points = result['end_points'].single 128 | 129 | # Should be able to iterate across them 130 | count = 0 131 | for path in end_points: 132 | count += 1 133 | 134 | # Should have been two of them 135 | self.assertEquals(count, 2) 136 | 137 | 138 | if __name__ == '__main__': 139 | unit_tests.unittest.main() 140 | -------------------------------------------------------------------------------- /src/test/python/examples.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import tempfile, os 25 | 26 | class ExamplesTest(unit_tests.GraphDatabaseTest): 27 | 28 | def test_hello_world(self): 29 | folder_to_put_db_in = tempfile.mkdtemp() 30 | try: 31 | # START SNIPPET: helloworld 32 | from neo4j import GraphDatabase 33 | 34 | # Create a database 35 | db = GraphDatabase(folder_to_put_db_in) 36 | 37 | # All write operations happen in a transaction 38 | with db.transaction: 39 | firstNode = db.node(name='Hello') 40 | secondNode = db.node(name='world!') 41 | 42 | # Create a relationship with type 'knows' 43 | relationship = firstNode.knows(secondNode, name='graphy') 44 | 45 | # Read operations can happen anywhere 46 | message = ' '.join([firstNode['name'], relationship['name'], secondNode['name']]) 47 | 48 | print message 49 | 50 | # Delete the data 51 | with db.transaction: 52 | firstNode.knows.single.delete() 53 | firstNode.delete() 54 | secondNode.delete() 55 | 56 | # Always shut down your database when your application exits 57 | db.shutdown() 58 | # END SNIPPET: helloworld 59 | finally: 60 | if os.path.exists(folder_to_put_db_in): 61 | import shutil 62 | shutil.rmtree(folder_to_put_db_in) 63 | 64 | self.assertEqual(message, 'Hello graphy world!') 65 | 66 | def test_invoice_app(self): 67 | folder_to_put_db_in = tempfile.mkdtemp() 68 | try: 69 | # START SNIPPET: invoiceapp-setup 70 | from neo4j import GraphDatabase, INCOMING, Evaluation 71 | 72 | # Create a database 73 | db = GraphDatabase(folder_to_put_db_in) 74 | 75 | # All write operations happen in a transaction 76 | with db.transaction: 77 | 78 | # A node to connect customers to 79 | customers = db.node() 80 | 81 | # A node to connect invoices to 82 | invoices = db.node() 83 | 84 | # Connected to the reference node, so 85 | # that we can always find them. 86 | db.reference_node.CUSTOMERS(customers) 87 | db.reference_node.INVOICES(invoices) 88 | 89 | # An index, helps us rapidly look up customers 90 | customer_idx = db.node.indexes.create('customers') 91 | # END SNIPPET: invoiceapp-setup 92 | 93 | # START SNIPPET: invoiceapp-domainlogic-create 94 | def create_customer(name): 95 | with db.transaction: 96 | customer = db.node(name=name) 97 | customer.INSTANCE_OF(customers) 98 | 99 | # Index the customer by name 100 | customer_idx['name'][name] = customer 101 | return customer 102 | 103 | def create_invoice(customer, amount): 104 | with db.transaction: 105 | invoice = db.node(amount=amount) 106 | invoice.INSTANCE_OF(invoices) 107 | 108 | invoice.SENT_TO(customer) 109 | return customer 110 | # END SNIPPET: invoiceapp-domainlogic-create 111 | 112 | # START SNIPPET: invoiceapp-domainlogic-get-by-idx 113 | def get_customer(name): 114 | return customer_idx['name'][name].single 115 | # END SNIPPET: invoiceapp-domainlogic-get-by-idx 116 | 117 | # START SNIPPET: invoiceapp-domainlogic-get-by-cypher 118 | def get_invoices_with_amount_over(customer, min_sum): 119 | # Find all invoices over a given sum for a given customer. 120 | # Note that we return an iterator over the "invoice" column 121 | # in the result (['invoice']). 122 | return db.query('''START customer=node({customer_id}) 123 | MATCH invoice-[:SENT_TO]->customer 124 | WHERE has(invoice.amount) and invoice.amount >= {min_sum} 125 | RETURN invoice''', 126 | customer_id = customer.id, min_sum = min_sum)['invoice'] 127 | # END SNIPPET: invoiceapp-domainlogic-get-by-cypher 128 | 129 | # START SNIPPET: invoiceapp-create-and-search 130 | for name in ['Acme Inc.', 'Example Ltd.']: 131 | create_customer(name) 132 | 133 | # Loop through customers 134 | for relationship in customers.INSTANCE_OF: 135 | customer = relationship.start 136 | for i in range(1,12): 137 | create_invoice(customer, 100 * i) 138 | 139 | # Finding large invoices 140 | large_invoices = get_invoices_with_amount_over(get_customer('Acme Inc.'), 500) 141 | 142 | # Getting all invoices per customer: 143 | for relationship in get_customer('Acme Inc.').SENT_TO.incoming: 144 | invoice = relationship.start 145 | # END SNIPPET: invoiceapp-create-and-search 146 | 147 | self.assertEqual(len(list(large_invoices)), 7) 148 | db.shutdown() 149 | finally: 150 | if os.path.exists(folder_to_put_db_in): 151 | import shutil 152 | shutil.rmtree(folder_to_put_db_in) 153 | 154 | if __name__ == '__main__': 155 | unit_tests.unittest.main() 156 | -------------------------------------------------------------------------------- /src/test/python/index.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import neo4j 25 | from neo4j import Direction, Evaluation, Uniqueness 26 | 27 | class IndexTest(unit_tests.GraphDatabaseTest): 28 | 29 | def test_index_exists(self): 30 | db = self.graphdb 31 | 32 | with db.transaction: 33 | db.relationship.indexes.create('my_rels') 34 | db.node.indexes.create('my_nodes') 35 | 36 | with db.transaction: 37 | # Create a relationship index 38 | self.assertTrue(db.relationship.indexes.exists('my_rels')) 39 | self.assertFalse(db.relationship.indexes.exists('asd')) 40 | 41 | # START SNIPPET: checkIfIndexExists 42 | exists = db.node.indexes.exists('my_nodes') 43 | # END SNIPPET: checkIfIndexExists 44 | self.assertTrue(exists) 45 | self.assertFalse(db.node.indexes.exists('asd')) 46 | 47 | def test_create_fulltext_index(self): 48 | db = self.graphdb 49 | 50 | # START SNIPPET: createIndex 51 | with db.transaction: 52 | # Create a relationship index 53 | rel_idx = db.relationship.indexes.create('my_rels') 54 | 55 | # Create a node index, passing optional 56 | # arguments to the index provider. 57 | # In this case, enable full-text indexing. 58 | node_idx = db.node.indexes.create('my_nodes', type='fulltext') 59 | # END SNIPPET: createIndex 60 | # START SNIPPET: getIndex 61 | with db.transaction: 62 | node_idx = db.node.indexes.get('my_nodes') 63 | 64 | rel_idx = db.relationship.indexes.get('my_rels') 65 | # END SNIPPET: getIndex 66 | # START SNIPPET: deleteIndex 67 | with db.transaction: 68 | node_idx = db.node.indexes.get('my_nodes') 69 | node_idx.delete() 70 | 71 | rel_idx = db.relationship.indexes.get('my_rels') 72 | rel_idx.delete() 73 | # END SNIPPET: deleteIndex 74 | idx = node_idx = db.node.indexes.create('test', type='fulltext') 75 | idx['akey']['A name of some kind.'] = self.graphdb.node() 76 | 77 | it = idx.query("akey:name") 78 | 79 | self.assertTrue(len(it), 1) 80 | it.close() 81 | 82 | def test_index_node(self): 83 | db = self.graphdb 84 | # START SNIPPET: addToIndex 85 | with db.transaction: 86 | # Indexing nodes 87 | a_node = db.node() 88 | node_idx = db.node.indexes.create('my_nodes') 89 | 90 | # Add the node to the index 91 | node_idx['akey']['avalue'] = a_node 92 | 93 | # Indexing relationships 94 | a_relationship = a_node.knows(db.node()) 95 | rel_idx = db.relationship.indexes.create('my_rels') 96 | 97 | # Add the relationship to the index 98 | rel_idx['akey']['avalue'] = a_relationship 99 | # END SNIPPET: addToIndex 100 | 101 | ret = list(node_idx['akey']['avalue'])[0] 102 | self.assertEqual(ret.id, a_node.id) 103 | 104 | 105 | def test_remove_node_index(self): 106 | with self.graphdb.transaction: 107 | n = self.graphdb.node() 108 | 109 | idx1 = self.graphdb.node.indexes.create('test1') 110 | idx1['akey']['avalue'] = n 111 | 112 | idx1.delete() 113 | 114 | try: 115 | self.graphdb.node.indexes.get('test1') 116 | self.assertTrue(False) 117 | except Exception, e: 118 | self.assertTrue(isinstance(e, ValueError)) 119 | 120 | def test_query_for_node(self): 121 | with self.graphdb.transaction: 122 | n = self.graphdb.node() 123 | 124 | idx = self.graphdb.node.indexes.create('test') 125 | idx['akey']['avalue'] = n 126 | 127 | ret = list(idx.query('akey:avalue'))[0] 128 | self.assertEqual(ret.id, n.id) 129 | 130 | def test_get_first_index_hit(self): 131 | with self.graphdb.transaction: 132 | 133 | idx = self.graphdb.node.indexes.create('test') 134 | for x in range(50): 135 | idx['akey']['avalue'] = self.graphdb.node(name=x) 136 | 137 | hits = iter(idx['akey']['avalue']) 138 | node = hits.next() 139 | hits.close() 140 | 141 | self.assertEquals(0,node['name']) 142 | 143 | def test_iterate_index(self): 144 | with self.graphdb.transaction: 145 | 146 | idx = self.graphdb.node.indexes.create('test') 147 | for x in range(50): 148 | idx['akey']['avalue'] = self.graphdb.node(name=x) 149 | 150 | hits = [] 151 | for hit in idx['akey']['avalue']: 152 | hits.append(hit) 153 | 154 | self.assertEquals(50,len(hits)) 155 | 156 | def test_slice_query_result(self): 157 | with self.graphdb.transaction: 158 | 159 | idx = self.graphdb.node.indexes.create('test') 160 | for x in range(50): 161 | idx['akey']['avalue'] = self.graphdb.node(name=x) 162 | 163 | 164 | # START SNIPPET: query 165 | hits = idx.query('akey:avalue') 166 | for item in hits: 167 | pass 168 | 169 | # Always close index results when you are 170 | # done, to free up resources. 171 | hits.close() 172 | # END SNIPPET: query 173 | 174 | hits = idx.query('akey:avalue') 175 | self.assertEqual(len(list(hits[:10])), 10) 176 | hits.close() 177 | 178 | def test_slice_get_result(self): 179 | with self.graphdb.transaction: 180 | 181 | idx = self.graphdb.node.indexes.create('test') 182 | for x in range(50): 183 | idx['akey']['avalue'] = self.graphdb.node() 184 | 185 | it = idx['akey']['avalue'] 186 | 187 | self.assertTrue(len(list(it[:10])), 10) 188 | it.close() 189 | 190 | # START SNIPPET: directLookup 191 | hits = idx['akey']['avalue'] 192 | for item in hits: 193 | pass 194 | 195 | # Always close index results when you are 196 | # done, to free up resources. 197 | hits.close() 198 | # END SNIPPET: directLookup 199 | 200 | def test_get_first_result(self): 201 | with self.graphdb.transaction: 202 | 203 | idx = self.graphdb.node.indexes.create('test') 204 | for x in range(50): 205 | idx['akey']['avalue'] = self.graphdb.node() 206 | 207 | it = idx['akey']['avalue'] 208 | 209 | self.assertTrue(it[0] is not None) 210 | it.close() 211 | 212 | def test_remove_node_from_index(self): 213 | with self.graphdb.transaction: 214 | n = self.graphdb.node() 215 | 216 | idx = self.graphdb.node.indexes.create('test') 217 | self._index_and_remove_item(idx, n) 218 | 219 | def test_remove_relationship_from_index(self): 220 | with self.graphdb.transaction: 221 | n = self.graphdb.node() 222 | r = n.Knows(self.graphdb.node()) 223 | idx = self.graphdb.relationship.indexes.create('test') 224 | self._index_and_remove_item(idx, r) 225 | 226 | def _index_and_remove_item(self, idx, item): 227 | with self.graphdb.transaction: 228 | idx['akey']['avalue'] = item 229 | idx['akey']['bvalue'] = item 230 | idx['bkey']['avalue'] = item 231 | 232 | self.assertEqual(len(list(idx['akey']['avalue'])), 1) 233 | self.assertEqual(len(list(idx['akey']['bvalue'])), 1) 234 | self.assertEqual(len(list(idx['bkey']['avalue'])), 1) 235 | 236 | del idx['akey']['avalue'][item] 237 | 238 | self.assertEqual(len(list(idx['akey']['avalue'])), 0) 239 | self.assertEqual(len(list(idx['akey']['bvalue'])), 1) 240 | self.assertEqual(len(list(idx['bkey']['avalue'])), 1) 241 | 242 | del idx['akey'][item] 243 | 244 | self.assertEqual(len(list(idx['akey']['avalue'])), 0) 245 | self.assertEqual(len(list(idx['akey']['bvalue'])), 0) 246 | self.assertEqual(len(list(idx['bkey']['avalue'])), 1) 247 | 248 | del idx[item] 249 | 250 | self.assertEqual(len(list(idx['akey']['avalue'])), 0) 251 | self.assertEqual(len(list(idx['akey']['bvalue'])), 0) 252 | self.assertEqual(len(list(idx['bkey']['avalue'])), 0) 253 | 254 | # START SNIPPET: removeFromIndex 255 | # Remove specific key/value/item triplet 256 | del idx['akey']['avalue'][item] 257 | 258 | # Remove all instances under a certain 259 | # key 260 | del idx['akey'][item] 261 | 262 | # Remove all instances all together 263 | del idx[item] 264 | # END SNIPPET: removeFromIndex 265 | 266 | if __name__ == '__main__': 267 | unit_tests.unittest.main() 268 | -------------------------------------------------------------------------------- /src/test/python/junit_xml.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | """Support for writing JUnit XML test results for the unit tests 22 | 23 | The base of this code is borrowed from the Jython project. 24 | """ 25 | 26 | import os 27 | import re 28 | import sys 29 | import time 30 | import traceback 31 | import unittest 32 | from StringIO import StringIO 33 | from xml.sax import saxutils 34 | 35 | # Invalid XML characters (control chars) 36 | EVIL_CHARACTERS_RE = re.compile(r"[\000-\010\013\014\016-\037]") 37 | 38 | class JUnitXMLTestRunner: 39 | """A unittest runner that writes results to a JUnit XML file in 40 | xml_dir 41 | """ 42 | 43 | def __init__(self, xml_dir): 44 | self.xml_dir = xml_dir 45 | 46 | def run(self, test): 47 | result = JUnitXMLTestResult(self.xml_dir) 48 | test(result) 49 | result.write_xml() 50 | return result 51 | 52 | 53 | class TestList(list): 54 | def __init__(self): 55 | self.errors = 0 56 | self.failures = 0 57 | self.took = 0 58 | 59 | class JUnitXMLTestResult(unittest.TestResult): 60 | """JUnit XML test result writer. 61 | 62 | The name of the file written to is determined from the full module 63 | name of the first test ran 64 | """ 65 | 66 | def __init__(self, xml_dir): 67 | unittest.TestResult.__init__(self) 68 | self.xml_dir = xml_dir 69 | 70 | # The module name of the first test ran 71 | self.tests = {} 72 | 73 | # Start time 74 | self.start = None 75 | 76 | self.old_stdout = sys.stdout 77 | self.old_stderr = sys.stderr 78 | sys.stdout = self.stdout = Tee(sys.stdout) 79 | sys.stderr = self.stderr = Tee(sys.stderr) 80 | 81 | def startTest(self, test): 82 | unittest.TestResult.startTest(self, test) 83 | self.error, self.failure = None, None 84 | self.start = time.time() 85 | 86 | def stopTest(self, test): 87 | took = time.time() - self.start 88 | unittest.TestResult.stopTest(self, test) 89 | args = [test, took, self.stdout.getvalue(), self.stderr.getvalue()] 90 | self.stdout.truncate(0) 91 | self.stderr.truncate(0) 92 | 93 | testsuite = '.'.join(test.id().split('.')[:-1]) 94 | if testsuite not in self.tests: 95 | self.tests[testsuite] = TestList() 96 | tests = self.tests[testsuite] 97 | tests.took += took 98 | 99 | if self.error: 100 | tests.errors += 1 101 | args.extend(['error', self.error]) 102 | elif self.failure: 103 | tests.failures += 1 104 | args.extend(['failure', self.failure]) 105 | tests.append(TestInfo.from_testcase(*args)) 106 | 107 | def addError(self, test, err): 108 | unittest.TestResult.addError(self, test, err) 109 | self.error = err 110 | 111 | def addFailure(self, test, err): 112 | unittest.TestResult.addFailure(self, test, err) 113 | self.failure = err 114 | 115 | def write_xml(self): 116 | if not self.tests: # No tests ran, nothing to write 117 | return 118 | 119 | stdout = self.stdout.getvalue() 120 | stderr = self.stderr.getvalue() 121 | sys.stdout = self.old_stdout 122 | sys.stderr = self.old_stderr 123 | 124 | ensure_dir(self.xml_dir) 125 | 126 | for module in self.tests: 127 | tests = self.tests[module] 128 | 129 | filename = os.path.join(self.xml_dir, 'TEST-%s.xml' % module) 130 | stream = open(filename, 'w') 131 | 132 | write_testsuite_xml(stream, len(tests), tests.errors, 133 | tests.failures, 0, module, tests.took) 134 | 135 | for info in tests: 136 | info.write_xml(stream) 137 | 138 | stream.write('\n') 139 | stream.close() 140 | 141 | 142 | class TestInfo(object): 143 | 144 | """The JUnit XML model.""" 145 | 146 | def __init__(self, name, took, type, exc_info, stdout='', stderr=''): 147 | # The name of the test 148 | self.name = name 149 | 150 | # How long it took 151 | self.took = took 152 | 153 | # Type of test: 'error', 'failure' 'skipped', or None for a success 154 | self.type = type 155 | 156 | self.stdout = stdout 157 | self.stderr = stderr 158 | 159 | if exc_info: 160 | self.exc_name = exc_name(exc_info) 161 | self.message = exc_message(exc_info) 162 | self.traceback = safe_str(''.join( 163 | traceback.format_exception(*exc_info))) 164 | else: 165 | self.exc_name = self.message = self.traceback = '' 166 | 167 | @classmethod 168 | def from_testcase(cls, testcase, took, out, err, type=None, exc_info=None): 169 | name = testcase.id().split('.')[-1] 170 | return cls(name, took, type, exc_info, out, err) 171 | 172 | def write_xml(self, stream): 173 | stream.write(' \n') 179 | return 180 | stream.write('>\n') 181 | if self.type: 182 | stream.write(' <%s type="%s" message=%s>\n' % 183 | (self.type, self.exc_name, 184 | saxutils.quoteattr(self.message), 185 | escape_cdata(self.traceback), self.type)) 186 | write_stdouterr_xml(stream, self.stdout, self.stderr) 187 | stream.write(' \n') 188 | 189 | 190 | class Tee(StringIO): 191 | 192 | """Writes data to this StringIO and a separate stream""" 193 | 194 | def __init__(self, stream): 195 | StringIO.__init__(self) 196 | self.stream = stream 197 | 198 | def write(self, data): 199 | StringIO.write(self, data) 200 | self.stream.write(data) 201 | 202 | def flush(self): 203 | StringIO.flush(self) 204 | self.stream.flush() 205 | 206 | 207 | def write_testsuite_xml(stream, tests, errors, failures, skipped, name, took): 208 | """Write the XML header ()""" 209 | stream.write('\n') 210 | stream.write('\n' % (skipped, name, 213 | took)) 214 | 215 | def write_stdouterr_xml(stream, stdout, stderr): 216 | """Write the stdout/err tags""" 217 | if stdout: 218 | stream.write(' \n' % 219 | escape_cdata(safe_str(stdout))) 220 | if stderr: 221 | stream.write(' \n' % 222 | escape_cdata(safe_str(stderr))) 223 | 224 | 225 | def ensure_dir(dir): 226 | """Ensure dir exists""" 227 | if dir.endswith('/'): dir = dir[:-1] 228 | if not os.path.exists(dir): 229 | ensure_dir(os.path.dirname(dir)) 230 | os.mkdir(dir) 231 | 232 | 233 | def exc_name(exc_info): 234 | """Determine the full name of the exception that caused exc_info""" 235 | exc = exc_info[1] 236 | name = getattr(exc.__class__, '__module__', '') 237 | if name: 238 | name += '.' 239 | return name + exc.__class__.__name__ 240 | 241 | 242 | def exc_message(exc_info): 243 | """Safely return a short message passed through safe_str describing 244 | exc_info, being careful of unicode values. 245 | """ 246 | exc = exc_info[1] 247 | if exc is None: 248 | return safe_str(exc_info[0]) 249 | if isinstance(exc, BaseException) and isinstance(exc.message, unicode): 250 | return safe_str(exc.message) 251 | try: 252 | return safe_str(str(exc)) 253 | except UnicodeEncodeError: 254 | try: 255 | val = unicode(exc) 256 | return safe_str(val) 257 | except UnicodeDecodeError: 258 | return '?' 259 | 260 | 261 | def escape_cdata(cdata): 262 | """Escape a string for an XML CDATA section""" 263 | return cdata.replace(']]>', ']]>]]>. 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import neo4j 25 | from neo4j import Direction, Evaluation, Uniqueness 26 | 27 | import threading 28 | 29 | 30 | class ThreadingTest(unit_tests.GraphDatabaseTest): 31 | 32 | def test_write_in_one_thread_read_in_another(self): 33 | db = self.graphdb 34 | 35 | with db.transaction: 36 | node = db.node(name="Bob the node") 37 | 38 | def read_method(): 39 | self.assertEquals(db.node[node.id]['name'], "Bob the node") 40 | 41 | thread = threading.Thread(target=read_method) 42 | thread.start() 43 | thread.join() 44 | 45 | def test_create_db_in_one_thread_read_index_in_another(self): 46 | db = self.graphdb 47 | 48 | with db.transaction: 49 | node = db.node(name="Bob the node") 50 | node_idx = db.node.indexes.create('my_nodes', type='fulltext') 51 | node_idx['akey']['avalue'] = node 52 | 53 | def read_method(): 54 | node_idx = db.node.indexes.get('my_nodes') 55 | node = node_idx['akey']['avalue'][0] 56 | self.assertEquals(node['name'], "Bob the node") 57 | 58 | thread = threading.Thread(target=read_method) 59 | thread.start() 60 | thread.join() 61 | 62 | if __name__ == '__main__': 63 | unit_tests.unittest.main() 64 | -------------------------------------------------------------------------------- /src/test/python/traversal.py: -------------------------------------------------------------------------------- 1 | # -*- mode: Python; coding: utf-8 -*- 2 | 3 | # Copyright (c) 2002-2013 "Neo Technology," 4 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 5 | # 6 | # This file is part of Neo4j. 7 | # 8 | # Neo4j is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import with_statement 22 | 23 | import unit_tests 24 | import neo4j 25 | from neo4j import Direction, Evaluation, Uniqueness 26 | 27 | class TraversalTest(unit_tests.GraphDatabaseTest): 28 | 29 | def create_data(self): 30 | with self.graphdb.transaction: 31 | self.source = self.graphdb.node(message='hello') 32 | target = self.graphdb.node(message='world') 33 | relationship = self.source.related_to(target, message="graphy") 34 | secondrel = target.likes(self.source, message="buh") 35 | 36 | def test_traverse_string_types(self): 37 | self.create_data() 38 | 39 | db = self.graphdb 40 | start_node = self.source 41 | 42 | # START SNIPPET: basicTraversal 43 | traverser = db.traversal()\ 44 | .relationships('related_to')\ 45 | .traverse(start_node) 46 | 47 | # The graph is traversed as 48 | # you loop through the result. 49 | for node in traverser.nodes: 50 | pass 51 | # END SNIPPET: basicTraversal 52 | 53 | self.assertEqual(len(list(traverser.nodes)), 2) 54 | 55 | # START SNIPPET: directedTraversal 56 | from neo4j import OUTGOING, INCOMING, ANY 57 | 58 | traverser = db.traversal()\ 59 | .relationships('related_to', OUTGOING)\ 60 | .traverse(start_node) 61 | # END SNIPPET: directedTraversal 62 | 63 | self.assertEqual(len(list(traverser.nodes)), 2) 64 | 65 | # START SNIPPET: multiRelationshipTraversal 66 | from neo4j import OUTGOING, INCOMING, ANY 67 | 68 | traverser = db.traversal()\ 69 | .relationships('related_to', INCOMING)\ 70 | .relationships('likes')\ 71 | .traverse(start_node) 72 | # END SNIPPET: multiRelationshipTraversal 73 | self.assertEqual(len(list(traverser.nodes)), 2) 74 | 75 | # START SNIPPET: traversalResults 76 | traverser = db.traversal()\ 77 | .relationships('related_to')\ 78 | .traverse(start_node) 79 | 80 | # Get each possible path 81 | for path in traverser: 82 | pass 83 | 84 | # Get each node 85 | for node in traverser.nodes: 86 | pass 87 | 88 | # Get each relationship 89 | for relationship in traverser.relationships: 90 | pass 91 | # END SNIPPET: traversalResults 92 | 93 | def test_traverse_programmatic_types(self): 94 | self.create_data() 95 | 96 | t = self.graphdb.traversal()\ 97 | .depthFirst()\ 98 | .relationships(Direction.ANY.related_to)\ 99 | .traverse(self.source) 100 | 101 | res = list(t.nodes) 102 | self.assertEqual(len(res), 2) 103 | 104 | def test_dynamic_evaluator(self): 105 | self.create_data() 106 | db = self.graphdb 107 | start_node = self.source 108 | 109 | # START SNIPPET: evaluators 110 | from neo4j import Evaluation 111 | 112 | # Evaluation contains the four 113 | # options that an evaluator can 114 | # return. They are: 115 | 116 | Evaluation.INCLUDE_AND_CONTINUE 117 | # Include this node in the result and 118 | # continue the traversal 119 | 120 | Evaluation.INCLUDE_AND_PRUNE 121 | # Include this node in the result, but don't 122 | # continue the traversal 123 | 124 | Evaluation.EXCLUDE_AND_CONTINUE 125 | # Exclude this node from the result, but 126 | # continue the traversal 127 | 128 | Evaluation.EXCLUDE_AND_PRUNE 129 | # Exclude this node from the result and 130 | # don't continue the traversal 131 | 132 | # An evaluator 133 | def my_evaluator(path): 134 | # Filter on end node property 135 | if path.end['message'] == 'world': 136 | return Evaluation.INCLUDE_AND_CONTINUE 137 | 138 | # Filter on last relationship type 139 | if path.last_relationship.type.name() == 'related_to': 140 | return Evaluation.INCLUDE_AND_PRUNE 141 | 142 | # You can do even more complex things here, like subtraversals. 143 | 144 | return Evaluation.EXCLUDE_AND_CONTINUE 145 | 146 | # Use the evaluator 147 | traverser = db.traversal()\ 148 | .evaluator(my_evaluator)\ 149 | .traverse(start_node) 150 | # END SNIPPET: evaluators 151 | 152 | def exclude_all(path): 153 | return Evaluation.EXCLUDE_AND_CONTINUE 154 | 155 | def include_all(path): 156 | return Evaluation.INCLUDE_AND_CONTINUE 157 | 158 | t = self.graphdb.traversal()\ 159 | .depthFirst()\ 160 | .evaluator(include_all)\ 161 | .traverse(self.source) 162 | 163 | res = list(t.nodes) 164 | self.assertEqual(len(res), 2) 165 | 166 | t = self.graphdb.traversal()\ 167 | .depthFirst()\ 168 | .evaluator(exclude_all)\ 169 | .traverse(self.source) 170 | 171 | res = list(t.nodes) 172 | self.assertEqual(len(res), 0) 173 | 174 | def test_uniqueness(self): 175 | self.create_data() 176 | db = self.graphdb 177 | start_node = self.source 178 | # START SNIPPET: uniqueness 179 | from neo4j import Uniqueness 180 | 181 | # Available options are: 182 | 183 | Uniqueness.NONE 184 | # Any position in the graph may be revisited. 185 | 186 | Uniqueness.NODE_GLOBAL 187 | # Default option 188 | # No node in the entire graph may be visited 189 | # more than once. This could potentially 190 | # consume a lot of memory since it requires 191 | # keeping an in-memory data structure 192 | # remembering all the visited nodes. 193 | 194 | Uniqueness.RELATIONSHIP_GLOBAL 195 | # No relationship in the entire graph may be 196 | # visited more than once. For the same 197 | # reasons as NODE_GLOBAL uniqueness, this 198 | # could use up a lot of memory. But since 199 | # graphs typically have a larger number of 200 | # relationships than nodes, the memory 201 | # overhead of this uniqueness level could 202 | # grow even quicker. 203 | 204 | Uniqueness.NODE_PATH 205 | # A node may not occur previously in the 206 | # path reaching up to it. 207 | 208 | Uniqueness.RELATIONSHIP_PATH 209 | # A relationship may not occur previously in 210 | # the path reaching up to it. 211 | 212 | Uniqueness.NODE_RECENT 213 | # Similar to NODE_GLOBAL uniqueness in that 214 | # there is a global collection of visited 215 | # nodes each position is checked against. 216 | # This uniqueness level does however have a 217 | # cap on how much memory it may consume in 218 | # the form of a collection that only 219 | # contains the most recently visited nodes. 220 | # The size of this collection can be 221 | # specified by providing a number as the 222 | # second argument to the 223 | # uniqueness()-method along with the 224 | # uniqueness level. 225 | 226 | Uniqueness.RELATIONSHIP_RECENT 227 | # works like NODE_RECENT uniqueness, but 228 | # with relationships instead of nodes. 229 | 230 | 231 | traverser = db.traversal()\ 232 | .uniqueness(Uniqueness.NODE_PATH)\ 233 | .traverse(start_node) 234 | # END SNIPPET: uniqueness 235 | 236 | res = list(traverser.nodes) 237 | self.assertEqual(len(res), 3) 238 | 239 | 240 | def test_ordering(self): 241 | self.create_data() 242 | db = self.graphdb 243 | start_node = self.source 244 | 245 | # START SNIPPET: ordering 246 | # Depth first traversal, this 247 | # is the default. 248 | traverser = db.traversal()\ 249 | .depthFirst()\ 250 | .traverse(self.source) 251 | 252 | # Breadth first traversal 253 | traverser = db.traversal()\ 254 | .breadthFirst()\ 255 | .traverse(start_node) 256 | # END SNIPPET: ordering 257 | 258 | res = list(traverser.nodes) 259 | self.assertEqual(len(res), 2) 260 | 261 | def test_paths(self): 262 | self.create_data() 263 | 264 | t = self.graphdb.traversal()\ 265 | .traverse(self.source) 266 | 267 | for path in t: 268 | # START SNIPPET: accessPathStartAndEndNode 269 | start_node = path.start 270 | end_node = path.end 271 | # END SNIPPET: accessPathStartAndEndNode 272 | self.assertNotEqual(start_node, None) 273 | self.assertNotEqual(end_node, None) 274 | # START SNIPPET: accessPathLastRelationship 275 | last_relationship = path.last_relationship 276 | # END SNIPPET: accessPathLastRelationship 277 | 278 | # START SNIPPET: loopThroughPath 279 | for item in path: 280 | # Item is either a Relationship, 281 | # or a Node 282 | pass 283 | 284 | for nodes in path.nodes: 285 | # All nodes in a path 286 | pass 287 | 288 | for nodes in path.relationships: 289 | # All relationships in a path 290 | pass 291 | # END SNIPPET: loopThroughPath 292 | 293 | break 294 | 295 | def test_import_decision_shortcut(self): 296 | from neo4j.traversal import INCLUDE_AND_CONTINUE, INCLUDE_AND_PRUNE, EXCLUDE_AND_CONTINUE, EXCLUDE_AND_PRUNE 297 | self.create_data() 298 | db = self.graphdb 299 | 300 | def iac(path): 301 | return INCLUDE_AND_CONTINUE 302 | 303 | def iap(path): 304 | return INCLUDE_AND_PRUNE 305 | 306 | def eac(path): 307 | return EXCLUDE_AND_CONTINUE 308 | 309 | def eap(path): 310 | return EXCLUDE_AND_PRUNE 311 | 312 | traverser = db.traversal()\ 313 | .evaluator(iac)\ 314 | .evaluator(iap)\ 315 | .evaluator(eac)\ 316 | .evaluator(eap)\ 317 | .traverse(self.source) 318 | 319 | res = list(traverser.nodes) 320 | self.assertEqual(len(res), 0) 321 | 322 | 323 | if __name__ == '__main__': 324 | unit_tests.unittest.main() 325 | -------------------------------------------------------------------------------- /src/test/python/unit_tests.py: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # -*- mode: Python; coding: utf-8 -*- 3 | 4 | # Copyright (c) 2002-2013 "Neo Technology," 5 | # Network Engine for Objects in Lund AB [http://neotechnology.com] 6 | # 7 | # This file is part of Neo4j. 8 | # 9 | # Neo4j is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program. If not, see . 21 | 22 | """:" 23 | if [ -z "$PYTHON" ]; then 24 | PYTHON=python 25 | fi 26 | 27 | # If Neo4j Python bindings are installed: use the installed ones 28 | if ! $PYTHON -c "import neo4j" &> /dev/null; then 29 | # Otherwise: set up PYTHONPATH to use the checked out source 30 | SRC=$0 31 | for (( c=3; c>0; c-- )); do 32 | while [ -L "$SRC" ]; do 33 | SRC=$(readlink $SRC) 34 | done 35 | SRC=$(cd $(dirname $SRC); pwd) 36 | done 37 | 38 | if [ -z "$PYTHONPATH" ]; then 39 | PYTHONPATH="$SRC/main/python" 40 | else 41 | PYTHONPATH="$PYTHONPATH:$SRC/main/python" 42 | fi 43 | export PYTHONPATH 44 | 45 | if [ -z "$JYTHONPATH" ]; then 46 | JYTHONPATH="$SRC/main/python" 47 | else 48 | JYTHONPATH="$JYTHONPATH:$SRC/main/python" 49 | fi 50 | export JYTHONPATH 51 | 52 | NEO4J_PYTHON_CLASSPATH=$($SRC/bin/classpath) 53 | if [ $? -ne 0 ]; then exit -1; fi 54 | export NEO4J_PYTHON_CLASSPATH 55 | fi 56 | 57 | $PYTHON $0 "$@" 58 | exit $? 59 | ":""" 60 | 61 | __all__ = () 62 | 63 | import unittest, doctest, sys, os, traceback, junit_xml 64 | 65 | if __name__ == '__main__': 66 | 67 | params = {'--classpath':None, '--junit':None} 68 | key = arg = None 69 | args = [] 70 | for arg in sys.argv: 71 | if key is not None: 72 | params[key] = arg 73 | key = None 74 | elif arg.lower() in params: 75 | key = arg.lower() 76 | else: 77 | args.append(arg) 78 | 79 | if params['--classpath']: 80 | try: 81 | import java 82 | except: 83 | os.environ['NEO4J_PYTHON_CLASSPATH'] = params['--classpath'] 84 | else: 85 | sys.path.extend(params['--classpath'].split(':')) 86 | if params['--junit']: 87 | runner = junit_xml.JUnitXMLTestRunner(params['--junit']) 88 | else: 89 | runner = None 90 | del key, arg, params 91 | 92 | modules = {} 93 | 94 | for candidate in os.listdir(os.path.dirname(os.path.abspath(__file__))): 95 | if candidate.endswith('.py'): 96 | candidate = candidate[:-3] 97 | try: 98 | exec("from %s import *" % candidate) 99 | except: 100 | modules[candidate] = traceback.format_exc() 101 | else: 102 | modules[candidate] = None 103 | 104 | class ImportTestModules(unittest.TestCase): 105 | for _module in modules: 106 | if modules[_module] is None: 107 | def _test(self): 108 | pass 109 | else: 110 | def _test(self,name=_module,failure=modules[_module]): 111 | sys.stderr.write(failure) 112 | self.fail('Failed to import test module "%s"' % name) 113 | exec("test_import_%s = _test" % _module) 114 | 115 | class TestSuiteContainer(unittest.TestCase): 116 | pass 117 | 118 | def DocTestCases(module): 119 | try: 120 | return type('doctest_%s' % module, (TestSuiteContainer,), 121 | {'__module__': module, 122 | 'test_suite': doctest.DocTestSuite(module)}) 123 | except: 124 | def suite(self,failure=traceback.format_exc()): 125 | sys.stderr.write(str(failure)) 126 | self.fail('Failed to get doctests for "%s"' % (module,)) 127 | return type('doctest_%s' % module, (unittest.TestCase,), 128 | {'__module__': module, 129 | 'test_suite': suite}) 130 | 131 | class CustomTestLoader(unittest.TestLoader): 132 | def loadTestsFromTestCase(self, testCaseClass): 133 | if issubclass(testCaseClass, TestSuiteContainer)\ 134 | and testCaseClass is not TestSuiteContainer: 135 | return testCaseClass.test_suite 136 | return unittest.TestLoader.loadTestsFromTestCase(self,testCaseClass) 137 | 138 | neo4j_doctest = DocTestCases('neo4j') 139 | 140 | params = {'argv':args, 'testLoader':CustomTestLoader()} 141 | if runner is not None: params['testRunner'] = runner 142 | unittest.main(**params) 143 | 144 | else: # imported as a module 145 | 146 | import neo4j 147 | 148 | class GraphDatabaseTest(unittest.TestCase): 149 | def setUp(self): 150 | testcase = type(self) 151 | for case in dir(testcase): 152 | if case.startswith('test_'): break 153 | else: 154 | return 155 | dirname, join = os.path.dirname, os.path.join 156 | path = dirname(dirname(dirname(dirname(os.path.abspath(__file__))))) 157 | path = join(path,'target','testdata',testcase.__module__) 158 | 159 | if os.path.exists(path): 160 | import shutil 161 | shutil.rmtree(path) 162 | path = join(path,testcase.__name__) 163 | 164 | self.graphdb = neo4j.GraphDatabase(path) 165 | def tearDown(self): 166 | graphdb = getattr(self,'graphdb',None) 167 | if graphdb is not None: 168 | graphdb.shutdown() 169 | --------------------------------------------------------------------------------