This commit is contained in:
commit
615835d57d
52
.github/workflows/ci.yml
vendored
Normal file
52
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
name: Python CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
neo4j:
|
||||||
|
image: neo4j:3.5
|
||||||
|
ports:
|
||||||
|
- 7687:7687 # Bolt port
|
||||||
|
- 7474:7474 # Web
|
||||||
|
options: >
|
||||||
|
--env NEO4J_AUTH=neo4j/testpassword
|
||||||
|
--env NEO4J_dbms_memory_heap_initial__size=512m
|
||||||
|
--env NEO4J_dbms_memory_heap_max__size=1G
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
- name: Wait for Neo4j to be ready
|
||||||
|
run: |
|
||||||
|
echo "Waiting for Neo4j to start..."
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
env:
|
||||||
|
NEO4J_HTTP_PORT: 7474
|
||||||
|
NEO4J_BOLT_PORT: 7687
|
||||||
|
NEO4J_HOSTNAME: localhost
|
||||||
|
NEO4J_USER: neo4j
|
||||||
|
NEO4J_PASSWORD: testpassword
|
||||||
|
run: |
|
||||||
|
# Ensure we can connect to Neo4j
|
||||||
|
curl -I http://localhost:7474
|
||||||
|
poetry run python -m unittest discover -s tests
|
97
.gitignore
vendored
Normal file
97
.gitignore
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.nox
|
||||||
|
/.nox
|
||||||
|
.mypy_cache
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# IPython Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# PyCharm project settings
|
||||||
|
.idea
|
||||||
|
|
33
.gitlab-ci.yml
Normal file
33
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
image: python:3
|
||||||
|
|
||||||
|
variables:
|
||||||
|
NEO4J_AUTH: neo4j/testing
|
||||||
|
|
||||||
|
services:
|
||||||
|
- neo4j:4.4
|
||||||
|
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- python -V
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
script:
|
||||||
|
- python setup.py testing
|
||||||
|
- python setup.py test
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
|
script:
|
||||||
|
- pip install -U twine build
|
||||||
|
- python -m build
|
||||||
|
- twine upload dist/* --verbose
|
203
LICENSE
Normal file
203
LICENSE
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
116
README.md
Normal file
116
README.md
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# python-norduniclient
|
||||||
|
[![PyPI](https://img.shields.io/pypi/v/norduniclient.svg)](https://pypi.python.org/pypi/norduniclient)
|
||||||
|
|
||||||
|
|
||||||
|
Neo4j database client for NORDUnet network inventory
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
poetry shell
|
||||||
|
poetry install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
Add the following environment variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NEO4J_HTTP_PORT=7474
|
||||||
|
NEO4J_BOLT_PORT=7687
|
||||||
|
NEO4J_HOSTNAME=xx
|
||||||
|
NEO4J_USER=xx
|
||||||
|
NEO4J_PASSWORD=xx
|
||||||
|
```
|
||||||
|
|
||||||
|
and run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry run python -m unittest discover
|
||||||
|
```
|
||||||
|
|
||||||
|
or save the environment variables in a local file `.env` and run it with [dotenvx](https://dotenvx.com/) as follows:
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotenvx run -- poetry run python -m unittest discover
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nox -rs tests
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nox -rs tests_dotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install norduniclient
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
python3 -m pip install --index-url https://platform.sunet.se/api/packages/benedith/pypi/simple/ --extra-index-url https://pypi.org/simple/ norduniclient
|
||||||
|
|
||||||
|
python3 -m pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ jolieprinter
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
import norduniclient as nc
|
||||||
|
|
||||||
|
NODE_META_TYPE_CHOICES = zip(nc.META_TYPES, nc.META_TYPES)
|
||||||
|
|
||||||
|
print("nc.META_TYPES=", nc.META_TYPES)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Poetry guide
|
||||||
|
|
||||||
|
```
|
||||||
|
poetry run python [operation]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add depenency
|
||||||
|
|
||||||
|
#### Add a new lib
|
||||||
|
```bash
|
||||||
|
poetry add <library>
|
||||||
|
```
|
||||||
|
#### Remove a lib
|
||||||
|
```bash
|
||||||
|
poetry remove <library>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Get venv path
|
||||||
|
```bash
|
||||||
|
poetry run which python
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Show dependencies
|
||||||
|
```bash
|
||||||
|
poetry show
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
poetry run pip list
|
||||||
|
```
|
||||||
|
|
||||||
|
#### List configuratiom
|
||||||
|
```bash
|
||||||
|
poetry config --list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry config repositories.pypi https://upload.pypi.org/legacy/
|
||||||
|
poetry config pypi-token.pypi [token]
|
||||||
|
poetry publish --build --repository pypi
|
||||||
|
poetry publish --build --repository testpypi
|
||||||
|
```
|
44
norduniclient-package.md
Normal file
44
norduniclient-package.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# python-norduniclient
|
||||||
|
[![PyPI](https://img.shields.io/pypi/v/norduniclient.svg)](https://pypi.python.org/pypi/norduniclient)
|
||||||
|
|
||||||
|
|
||||||
|
Neo4j database client for NORDUnet network inventory
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
poetry shell
|
||||||
|
poetry install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
Add the following environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NEO4J_HTTP_PORT=7474
|
||||||
|
NEO4J_BOLT_PORT=7687
|
||||||
|
NEO4J_HOSTNAME=xx
|
||||||
|
NEO4J_USER=xx
|
||||||
|
NEO4J_PASSWORD=xx
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
poetry run python -m unittest discover
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install norduniclient
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```python
|
||||||
|
import norduniclient as nc
|
||||||
|
|
||||||
|
NODE_META_TYPE_CHOICES = zip(nc.META_TYPES, nc.META_TYPES)
|
||||||
|
|
||||||
|
print("nc.META_TYPES=", nc.META_TYPES)
|
||||||
|
```
|
15
norduniclient/__init__.py
Normal file
15
norduniclient/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from norduniclient.core import *
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
# Init as singleton for easy use in Django
|
||||||
|
# You can use it like this:
|
||||||
|
# from norduniclient import graphdb as db
|
||||||
|
# get_node(db.manager, 'node_id*)
|
||||||
|
graphdb = GraphDB.get_instance()
|
||||||
|
|
||||||
|
neo4jdb = graphdb.manager # Works as the old neo4jdb
|
56
norduniclient/contextmanager.py
Normal file
56
norduniclient/contextmanager.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from norduniclient.core import get_db_driver
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
class Neo4jDBSessionManager:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Every new connection is a transaction. To minimize new connection overhead for many reads we try to reuse a single
|
||||||
|
connection. If this seem like a bad idea some kind of connection pool might work better.
|
||||||
|
|
||||||
|
Neo4jDBSessionManager.session()
|
||||||
|
|
||||||
|
Neo4jDBSessionManager.transaction()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, uri, username=None, password=None, encrypted=True, max_pool_size=50):
|
||||||
|
self.uri = uri
|
||||||
|
self.driver = get_db_driver(uri, username, password, encrypted, max_pool_size)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _session(self):
|
||||||
|
session = self.driver.session()
|
||||||
|
try:
|
||||||
|
yield session
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
session = property(_session)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _transaction(self):
|
||||||
|
session = self.driver.session()
|
||||||
|
transaction = session.begin_transaction()
|
||||||
|
try:
|
||||||
|
yield transaction
|
||||||
|
except Exception as e:
|
||||||
|
transaction.success = False
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
transaction.success = True
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
transaction = property(_transaction)
|
713
norduniclient/core.py
Normal file
713
norduniclient/core.py
Normal file
|
@ -0,0 +1,713 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# core.py
|
||||||
|
#
|
||||||
|
# Copyright 2016 Johan Lundberg <lundberg@nordu.net>
|
||||||
|
#
|
||||||
|
|
||||||
|
# This started as an extension to the Neo4j REST client made by Versae, continued
|
||||||
|
# as an extension for the official Neo4j python bindings when they were released
|
||||||
|
# (Neo4j 1.5, python-embedded).
|
||||||
|
#
|
||||||
|
# After the release of neo4j 3.0 and the bolt protocol we replaced neo4jdb-python with
|
||||||
|
# the official Neo4j driver.
|
||||||
|
#
|
||||||
|
# The goal is to make it easier to add and retrieve data from a Neo4j database
|
||||||
|
# according to the NORDUnet Network Inventory data model.
|
||||||
|
#
|
||||||
|
# More information about NORDUnet Network Inventory:
|
||||||
|
# https://portal.nordu.net/display/NI/
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from neo4j import GraphDatabase
|
||||||
|
from neo4j.exceptions import DatabaseError, ClientError
|
||||||
|
from neo4j.api import basic_auth
|
||||||
|
from norduniclient import exceptions
|
||||||
|
from norduniclient import models
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Load Django settings
|
||||||
|
NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD = None, None, None
|
||||||
|
MAX_POOL_SIZE = 50
|
||||||
|
ENCRYPTED = False
|
||||||
|
try:
|
||||||
|
from django.conf import settings as django_settings
|
||||||
|
try:
|
||||||
|
# Mandatory Django settings for quick init
|
||||||
|
NEO4J_URI = django_settings.NEO4J_RESOURCE_URI
|
||||||
|
NEO4J_USERNAME = django_settings.NEO4J_USERNAME
|
||||||
|
NEO4J_PASSWORD = django_settings.NEO4J_PASSWORD
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
# Optional Django settings for quick init
|
||||||
|
try:
|
||||||
|
MAX_POOL_SIZE = django_settings.NEO4J_MAX_POOL_SIZE
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ENCRYPTED = django_settings.NEO4J_ENCRYPTED
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ImportError:
|
||||||
|
logger.info('Starting up without a Django environment.')
|
||||||
|
logger.info('Initial: norduniclient.neo4jdb == None.')
|
||||||
|
logger.info('Use norduniclient.init_db to open a database connection.')
|
||||||
|
|
||||||
|
|
||||||
|
META_TYPES = ['Physical', 'Logical', 'Relation', 'Location']
|
||||||
|
|
||||||
|
|
||||||
|
class GraphDB(object):
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
_manager = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = cls()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._manager = self.manager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manager(self):
|
||||||
|
if self._manager is None:
|
||||||
|
try:
|
||||||
|
self._manager = init_db()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Could not create manager: {}'.format(e))
|
||||||
|
self._manager = None
|
||||||
|
return self._manager
|
||||||
|
|
||||||
|
@manager.setter
|
||||||
|
def manager(self, manager):
|
||||||
|
self._manager = manager
|
||||||
|
|
||||||
|
|
||||||
|
def init_db(uri=NEO4J_URI, username=NEO4J_USERNAME, password=NEO4J_PASSWORD, encrypted=ENCRYPTED,
|
||||||
|
max_pool_size=MAX_POOL_SIZE):
|
||||||
|
if uri:
|
||||||
|
try:
|
||||||
|
from norduniclient.contextmanager import Neo4jDBSessionManager
|
||||||
|
manager = Neo4jDBSessionManager(uri=uri, username=username, password=password, encrypted=encrypted,
|
||||||
|
max_pool_size=max_pool_size)
|
||||||
|
try:
|
||||||
|
with manager.session as s:
|
||||||
|
s.run('CREATE CONSTRAINT ON (n:Node) ASSERT n.handle_id IS UNIQUE')
|
||||||
|
except ClientError as e:
|
||||||
|
if e.title == 'EquivalentSchemaRuleAlreadyExists':
|
||||||
|
logger.info('Unique constraint already exists')
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Could not create constraints for Neo4j database: {!s}'.format(uri))
|
||||||
|
raise e
|
||||||
|
try:
|
||||||
|
create_index(manager, 'name')
|
||||||
|
except ClientError as e:
|
||||||
|
if e.title == 'EquivalentSchemaRuleAlreadyExists':
|
||||||
|
logger.info('Index already exists')
|
||||||
|
else:
|
||||||
|
logger.error('Could not create index for Neo4j database: {!s}'.format(uri))
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Could not create index for Neo4j database: {!s}'.format(uri))
|
||||||
|
raise e
|
||||||
|
return manager
|
||||||
|
except DatabaseError as e:
|
||||||
|
logger.warning('Could not connect to Neo4j database: {!s}'.format(uri))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_driver(uri, username=None, password=None, encrypted=True, max_pool_size=50, trust=0):
|
||||||
|
"""
|
||||||
|
:param uri: Bolt uri
|
||||||
|
:type uri: str
|
||||||
|
:param username: Neo4j username
|
||||||
|
:type username: str
|
||||||
|
:param password: Neo4j password
|
||||||
|
:type password: str
|
||||||
|
:param encrypted: Use TLS
|
||||||
|
:type encrypted: Boolean
|
||||||
|
:param max_pool_size: Maximum number of idle sessions
|
||||||
|
:type max_pool_size: Integer
|
||||||
|
:param trust: Trust cert on first use (0) or do not accept unknown cert (1)
|
||||||
|
:type trust: Integer
|
||||||
|
:return: Neo4j driver
|
||||||
|
:rtype: neo4j.session.Driver
|
||||||
|
"""
|
||||||
|
|
||||||
|
return GraphDatabase.driver(uri, auth=basic_auth(username, password), encrypted=encrypted,
|
||||||
|
max_connection_pool_size=max_pool_size, trust='TRUST_ALL_CERTIFICATES')
|
||||||
|
|
||||||
|
|
||||||
|
def query_to_dict(manager, query, **kwargs):
|
||||||
|
d = {}
|
||||||
|
with manager.session as s:
|
||||||
|
result = s.run(query, kwargs)
|
||||||
|
for record in result:
|
||||||
|
for key, value in record.items():
|
||||||
|
d[key] = value
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def query_to_list(manager, query, **kwargs):
|
||||||
|
out = []
|
||||||
|
with manager.session as s:
|
||||||
|
result = s.run(query, kwargs)
|
||||||
|
for record in result:
|
||||||
|
d = {}
|
||||||
|
for key, value in record.items():
|
||||||
|
d[key] = value
|
||||||
|
out.append(d)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def query_to_iterator(manager, query, **kwargs):
|
||||||
|
with manager.session as s:
|
||||||
|
result = s.run(query, kwargs)
|
||||||
|
for record in result:
|
||||||
|
d = {}
|
||||||
|
for key, value in record.items():
|
||||||
|
d[key] = value
|
||||||
|
yield d
|
||||||
|
|
||||||
|
|
||||||
|
def neo4j_entity_to_dict(node):
|
||||||
|
return {k: v for k, v in node.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def create_node(manager, name, meta_type_label, type_label, handle_id):
|
||||||
|
"""
|
||||||
|
Creates a node with the mandatory attributes name and handle_id also sets type label.
|
||||||
|
|
||||||
|
:param manager: Manager to handle sessions and transactions
|
||||||
|
:param name: Node name
|
||||||
|
:param meta_type_label: Node meta type
|
||||||
|
:param type_label: Node label
|
||||||
|
:param handle_id: Unique id
|
||||||
|
|
||||||
|
:type manager: norduniclient.contextmanager.Neo4jDBSessionManager
|
||||||
|
:type name: str|unicode
|
||||||
|
:type meta_type_label: str|unicode
|
||||||
|
:type type_label: str|unicode
|
||||||
|
:type handle_id: str|unicode
|
||||||
|
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
if meta_type_label not in META_TYPES:
|
||||||
|
raise exceptions.MetaLabelNamingError(meta_type_label)
|
||||||
|
q = """
|
||||||
|
CREATE (n:Node:%s:%s { name: $name, handle_id: $handle_id})
|
||||||
|
RETURN n
|
||||||
|
""" % (meta_type_label, type_label)
|
||||||
|
with manager.session as s:
|
||||||
|
return neo4j_entity_to_dict(s.run(q, {'name': name, 'handle_id': handle_id}).single()['n'])
|
||||||
|
|
||||||
|
|
||||||
|
def get_node(manager, handle_id):
|
||||||
|
"""
|
||||||
|
:param manager: Manager to handle sessions and transactions
|
||||||
|
:param handle_id: Unique id
|
||||||
|
|
||||||
|
:type manager: norduniclient.contextmanager.Neo4jDBSessionManager
|
||||||
|
:type handle_id: str|unicode
|
||||||
|
|
||||||
|
:rtype: dict|neo4j.types.Node
|
||||||
|
"""
|
||||||
|
q = 'MATCH (n:Node { handle_id: $handle_id }) RETURN n'
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
result = s.run(q, {'handle_id': handle_id}).single()
|
||||||
|
if result:
|
||||||
|
return neo4j_entity_to_dict(result['n'])
|
||||||
|
raise exceptions.NodeNotFound(manager, handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_bundle(manager, handle_id=None, node=None):
|
||||||
|
"""
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param handle_id: Unique id
|
||||||
|
:type handle_id: str|unicode
|
||||||
|
:param node: Node object
|
||||||
|
:type node: neo4j.types.Node
|
||||||
|
:return: dict
|
||||||
|
"""
|
||||||
|
if not node:
|
||||||
|
q = 'MATCH (n:Node { handle_id: $handle_id }) RETURN n'
|
||||||
|
with manager.session as s:
|
||||||
|
result = s.run(q, {'handle_id': handle_id}).single()
|
||||||
|
if not result:
|
||||||
|
raise exceptions.NodeNotFound(manager, handle_id)
|
||||||
|
node = result['n']
|
||||||
|
d = {
|
||||||
|
'data': neo4j_entity_to_dict(node)
|
||||||
|
}
|
||||||
|
labels = list(node.labels)
|
||||||
|
labels.remove('Node') # All nodes have this label for indexing
|
||||||
|
for label in labels:
|
||||||
|
if label in META_TYPES:
|
||||||
|
d['meta_type'] = label
|
||||||
|
labels.remove(label)
|
||||||
|
d['labels'] = labels
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def delete_node(manager, handle_id):
|
||||||
|
"""
|
||||||
|
Deletes the node and all its relationships.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param handle_id: Unique id
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})
|
||||||
|
OPTIONAL MATCH (n)-[r]-()
|
||||||
|
DELETE n,r
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
s.run(q, {'handle_id': handle_id})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_relationship(manager, relationship_id):
|
||||||
|
"""
|
||||||
|
:param manager: Manager to handle sessions and transactions
|
||||||
|
:param relationship_id: Unique id
|
||||||
|
|
||||||
|
:type manager: norduniclient.contextmanager.Neo4jDBSessionManager
|
||||||
|
:type relationship_id: int
|
||||||
|
|
||||||
|
:rtype int|neo4j.types.Relationship
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH ()-[r]->()
|
||||||
|
WHERE ID(r) = $relationship_id
|
||||||
|
RETURN r
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
record = s.run(q, {'relationship_id': int(relationship_id)}).single()
|
||||||
|
if record:
|
||||||
|
return neo4j_entity_to_dict(record['r'])
|
||||||
|
raise exceptions.RelationshipNotFound(manager, int(relationship_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get_relationship_bundle(manager, relationship_id=None):
|
||||||
|
"""
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param relationship_id: Internal Neo4j id
|
||||||
|
|
||||||
|
:type relationship_id: int
|
||||||
|
|
||||||
|
:rtype: dictionary
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH (start)-[r]->(end)
|
||||||
|
WHERE ID(r) = $relationship_id
|
||||||
|
RETURN start, r, end
|
||||||
|
"""
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
record = s.run(q, {'relationship_id': int(relationship_id)}).single()
|
||||||
|
if record is None:
|
||||||
|
raise exceptions.RelationshipNotFound(manager, int(relationship_id))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': record['r'].type,
|
||||||
|
'id': int(relationship_id),
|
||||||
|
'data': neo4j_entity_to_dict(record['r']),
|
||||||
|
'start': neo4j_entity_to_dict(record['start']),
|
||||||
|
'end': neo4j_entity_to_dict(record['end']),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_relationship(manager, relationship_id):
|
||||||
|
"""
|
||||||
|
Deletes the relationship.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param relationship_id: Internal Neo4j relationship id
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH ()-[r]->()
|
||||||
|
WHERE ID(r) = $relationship_id
|
||||||
|
DELETE r
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
s.run(q, {'relationship_id': int(relationship_id)})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_meta_type(manager, handle_id):
|
||||||
|
"""
|
||||||
|
Returns the meta type of the supplied node as a string.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param handle_id: Unique id
|
||||||
|
:return: string
|
||||||
|
"""
|
||||||
|
node = get_node_bundle(manager=manager, handle_id=handle_id)
|
||||||
|
if 'meta_type' not in node:
|
||||||
|
raise exceptions.NoMetaLabelFound(handle_id)
|
||||||
|
return node['meta_type']
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Try out elasticsearch
|
||||||
|
def get_nodes_by_value(manager, value, prop, node_type='Node'):
|
||||||
|
"""
|
||||||
|
Traverses all nodes or nodes of specified label and compares the property/properties of the node
|
||||||
|
with the supplied string.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param value: Value to search for
|
||||||
|
:param prop: Which property to look for value in
|
||||||
|
:param node_type:
|
||||||
|
|
||||||
|
:type value: str|list|bool|int
|
||||||
|
:type prop: str
|
||||||
|
:type node_type: str
|
||||||
|
:return: dicts
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
WHERE n.{prop} = $value
|
||||||
|
RETURN distinct n
|
||||||
|
""".format(label=node_type, prop=prop)
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q, {'value': value}):
|
||||||
|
yield neo4j_entity_to_dict(result['n'])
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_by_type(manager, node_type):
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
RETURN distinct n
|
||||||
|
""".format(label=node_type)
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q):
|
||||||
|
yield neo4j_entity_to_dict(result['n'])
|
||||||
|
|
||||||
|
|
||||||
|
def search_nodes_by_value(manager, value, prop=None, node_type='Node'):
|
||||||
|
"""
|
||||||
|
Traverses all nodes or nodes of specified label and fuzzy compares the property/properties of the node
|
||||||
|
with the supplied string.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param value: Value to search for
|
||||||
|
:param prop: Which property to look for value in
|
||||||
|
:param node_type:
|
||||||
|
|
||||||
|
:type value: str
|
||||||
|
:type prop: str
|
||||||
|
:type node_type: str
|
||||||
|
:return: dicts
|
||||||
|
"""
|
||||||
|
if prop:
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
WHERE n.{prop} =~ "(?i).*{value}.*" OR any(x IN n.{prop} WHERE x =~ "(?i).*{value}.*")
|
||||||
|
RETURN distinct n
|
||||||
|
""".format(label=node_type, prop=prop, value=value)
|
||||||
|
else:
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
WITH n, keys(n) as props
|
||||||
|
WHERE any(prop in props WHERE n[prop] =~ "(?i).*{value}.*") OR
|
||||||
|
any(prop in props WHERE any(x IN n[prop] WHERE x =~ "(?i).*{value}.*"))
|
||||||
|
RETURN distinct n
|
||||||
|
""".format(label=node_type, value=value)
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q):
|
||||||
|
yield result['n']
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Try out elasticsearch
|
||||||
|
def get_nodes_by_type(manager, node_type):
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
RETURN n
|
||||||
|
""".format(label=node_type)
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q):
|
||||||
|
yield result['n']
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Try out elasticsearch
|
||||||
|
def get_nodes_by_name(manager, name):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {name: $name})
|
||||||
|
RETURN n
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q, {'name': name}):
|
||||||
|
yield result['n']
|
||||||
|
|
||||||
|
|
||||||
|
def create_index(manager, prop, node_type='Node'):
|
||||||
|
"""
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param prop: Property to index
|
||||||
|
:param node_type: Label to create index on
|
||||||
|
|
||||||
|
:type manager: Neo4jDBSessionManager
|
||||||
|
:type prop: str
|
||||||
|
:type node_type: str
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
s.run('CREATE INDEX ON :{node_type}({prop})'.format(node_type=node_type, prop=prop))
|
||||||
|
|
||||||
|
|
||||||
|
def get_indexed_node(manager, prop, value, node_type='Node', lookup_func='CONTAINS'):
|
||||||
|
"""
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param prop: Indexed property
|
||||||
|
:param value: Indexed value
|
||||||
|
:param node_type: Label used for index
|
||||||
|
:param lookup_func: STARTS WITH | CONTAINS | ENDS WITH
|
||||||
|
|
||||||
|
:type manager: Neo4jDBSessionManager
|
||||||
|
:type prop: str
|
||||||
|
:type value: str
|
||||||
|
:type node_type: str
|
||||||
|
:type lookup_func: str
|
||||||
|
|
||||||
|
:return: Dict or Node object
|
||||||
|
:rtype: dict|Node
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH (n:{label})
|
||||||
|
WHERE toLower(n.{prop}) {lookup_func} toLower($value)
|
||||||
|
RETURN n
|
||||||
|
""".format(label=node_type, prop=prop, lookup_func=lookup_func)
|
||||||
|
with manager.session as s:
|
||||||
|
for result in s.run(q, {'value': value}):
|
||||||
|
yield neo4j_entity_to_dict(result['n'])
|
||||||
|
|
||||||
|
|
||||||
|
def get_unique_node_by_name(manager, node_name, node_type):
|
||||||
|
"""
|
||||||
|
Returns the node if the node is unique for name and type or None.
|
||||||
|
|
||||||
|
:param manager: Neo4jDBSessionManager
|
||||||
|
:param node_name: string
|
||||||
|
:param node_type: str|unicode
|
||||||
|
:return: norduniclient node model or None
|
||||||
|
"""
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node { name: $name })
|
||||||
|
WHERE $label IN labels(n)
|
||||||
|
RETURN n.handle_id as handle_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
result = list(s.run(q, {'name': node_name, 'label': node_type}))
|
||||||
|
|
||||||
|
if result:
|
||||||
|
if len(result) == 1:
|
||||||
|
return get_node_model(manager, result[0]['handle_id'])
|
||||||
|
raise exceptions.MultipleNodesReturned(node_name, node_type)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _create_relationship(manager, handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
:param manager: Context manager to handle transactions
|
||||||
|
:param handle_id: Node handle id
|
||||||
|
:param other_handle_id: Other node handle id
|
||||||
|
:param rel_type: Relationship type
|
||||||
|
|
||||||
|
:type manager: Neo4jDBSessionManager
|
||||||
|
:type handle_id: str|unicode
|
||||||
|
:type other_handle_id: str|unicode
|
||||||
|
:type rel_type: str|unicode
|
||||||
|
|
||||||
|
:rtype: int relationship_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
q = """
|
||||||
|
MATCH (a:Node {handle_id: $start}),(b:Node {handle_id: $end})
|
||||||
|
CREATE (a)-[r:%s]->(b)
|
||||||
|
RETURN r
|
||||||
|
""" % rel_type
|
||||||
|
|
||||||
|
with manager.session as s:
|
||||||
|
return s.run(q, {'start': handle_id, 'end': other_handle_id}).single()['r'].id
|
||||||
|
|
||||||
|
|
||||||
|
def create_location_relationship(manager, location_handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
Makes relationship between the two nodes and returns the relationship.
|
||||||
|
If a relationship is not possible NoRelationshipPossible exception is
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
other_meta_type = get_node_meta_type(manager, other_handle_id)
|
||||||
|
if other_meta_type == 'Location' and rel_type == 'Has':
|
||||||
|
return _create_relationship(manager, location_handle_id, other_handle_id, rel_type)
|
||||||
|
raise exceptions.NoRelationshipPossible(location_handle_id, 'Location', other_handle_id, other_meta_type, rel_type)
|
||||||
|
|
||||||
|
|
||||||
|
def create_logical_relationship(manager, logical_handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
Makes relationship between the two nodes and returns the relationship.
|
||||||
|
If a relationship is not possible NoRelationshipPossible exception is
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
other_meta_type = get_node_meta_type(manager, other_handle_id)
|
||||||
|
if rel_type == 'Depends_on':
|
||||||
|
if other_meta_type == 'Logical' or other_meta_type == 'Physical':
|
||||||
|
return _create_relationship(manager, logical_handle_id, other_handle_id, rel_type)
|
||||||
|
elif rel_type == 'Part_of':
|
||||||
|
if other_meta_type == 'Physical':
|
||||||
|
return _create_relationship(manager, logical_handle_id, other_handle_id, rel_type)
|
||||||
|
raise exceptions.NoRelationshipPossible(logical_handle_id, 'Logical', other_handle_id, other_meta_type, rel_type)
|
||||||
|
|
||||||
|
|
||||||
|
def create_relation_relationship(manager, relation_handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
Makes relationship between the two nodes and returns the relationship.
|
||||||
|
If a relationship is not possible NoRelationshipPossible exception is
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
other_meta_type = get_node_meta_type(manager, other_handle_id)
|
||||||
|
if other_meta_type == 'Logical':
|
||||||
|
if rel_type in ['Uses', 'Provides']:
|
||||||
|
return _create_relationship(manager, relation_handle_id, other_handle_id, rel_type)
|
||||||
|
elif other_meta_type == 'Location' and rel_type == 'Responsible_for':
|
||||||
|
return _create_relationship(manager, relation_handle_id, other_handle_id, rel_type)
|
||||||
|
elif other_meta_type == 'Physical':
|
||||||
|
if rel_type in ['Owns', 'Provides']:
|
||||||
|
return _create_relationship(manager, relation_handle_id, other_handle_id, rel_type)
|
||||||
|
raise exceptions.NoRelationshipPossible(relation_handle_id, 'Relation', other_handle_id, other_meta_type, rel_type)
|
||||||
|
|
||||||
|
|
||||||
|
def create_physical_relationship(manager, physical_handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
Makes relationship between the two nodes and returns the relationship.
|
||||||
|
If a relationship is not possible NoRelationshipPossible exception is
|
||||||
|
raised.
|
||||||
|
"""
|
||||||
|
other_meta_type = get_node_meta_type(manager, other_handle_id)
|
||||||
|
if other_meta_type == 'Physical':
|
||||||
|
if rel_type == 'Has' or rel_type == 'Connected_to':
|
||||||
|
return _create_relationship(manager, physical_handle_id, other_handle_id, rel_type)
|
||||||
|
elif other_meta_type == 'Location' and rel_type == 'Located_in':
|
||||||
|
return _create_relationship(manager, physical_handle_id, other_handle_id, rel_type)
|
||||||
|
raise exceptions.NoRelationshipPossible(physical_handle_id, 'Physical', other_handle_id, other_meta_type, rel_type)
|
||||||
|
|
||||||
|
|
||||||
|
def create_relationship(manager, handle_id, other_handle_id, rel_type):
|
||||||
|
"""
|
||||||
|
Makes a relationship from node to other_node depending on which
|
||||||
|
meta_type the nodes are. Returns the relationship or raises
|
||||||
|
NoRelationshipPossible exception.
|
||||||
|
"""
|
||||||
|
meta_type = get_node_meta_type(manager, handle_id)
|
||||||
|
if meta_type == 'Location':
|
||||||
|
return create_location_relationship(manager, handle_id, other_handle_id, rel_type)
|
||||||
|
elif meta_type == 'Logical':
|
||||||
|
return create_logical_relationship(manager, handle_id, other_handle_id, rel_type)
|
||||||
|
elif meta_type == 'Relation':
|
||||||
|
return create_relation_relationship(manager, handle_id, other_handle_id, rel_type)
|
||||||
|
elif meta_type == 'Physical':
|
||||||
|
return create_physical_relationship(manager, handle_id, other_handle_id, rel_type)
|
||||||
|
other_meta_type = get_node_meta_type(manager, other_handle_id)
|
||||||
|
raise exceptions.NoRelationshipPossible(handle_id, meta_type, other_handle_id, other_meta_type, rel_type)
|
||||||
|
|
||||||
|
|
||||||
|
def get_relationships(manager, handle_id1, handle_id2, rel_type=None):
|
||||||
|
"""
|
||||||
|
Takes a start and an end node with an optional relationship
|
||||||
|
type.
|
||||||
|
Returns the relationships between the nodes or an empty list.
|
||||||
|
"""
|
||||||
|
if rel_type:
|
||||||
|
q = """
|
||||||
|
MATCH (a:Node {{handle_id: $handle_id1}})-[r:{rel_type}]-(b:Node {{handle_id: $handle_id2}})
|
||||||
|
RETURN collect(r) as relationships
|
||||||
|
""".format(rel_type=rel_type)
|
||||||
|
else:
|
||||||
|
q = """
|
||||||
|
MATCH (a:Node {handle_id: $handle_id1})-[r]-(b:Node {handle_id: $handle_id2})
|
||||||
|
RETURN collect(r) as relationships
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
return s.run(q, {'handle_id1': handle_id1, 'handle_id2': handle_id2}).single()['relationships']
|
||||||
|
|
||||||
|
|
||||||
|
def set_node_properties(manager, handle_id, new_properties):
|
||||||
|
new_properties['handle_id'] = handle_id # Make sure the handle_id can't be changed
|
||||||
|
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $props.handle_id})
|
||||||
|
SET n = $props
|
||||||
|
RETURN n
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
return neo4j_entity_to_dict(s.run(q, {'handle_id': handle_id, 'props': new_properties}).single()['n'])
|
||||||
|
|
||||||
|
|
||||||
|
def set_relationship_properties(manager, relationship_id, new_properties):
|
||||||
|
|
||||||
|
q = """
|
||||||
|
MATCH ()-[r]->()
|
||||||
|
WHERE ID(r) = $relationship_id
|
||||||
|
SET r = $props
|
||||||
|
RETURN r
|
||||||
|
"""
|
||||||
|
with manager.session as s:
|
||||||
|
return s.run(q, {'relationship_id': int(relationship_id), 'props': new_properties}).single()
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_model(manager, handle_id=None, node=None):
|
||||||
|
"""
|
||||||
|
:param manager: Context manager to handle transactions
|
||||||
|
:type manager: Neo4jDBSessionManager
|
||||||
|
:param handle_id: Nodes handle id
|
||||||
|
:type handle_id: str|unicode
|
||||||
|
:param node: Node object
|
||||||
|
:type node: neo4j.types.Node
|
||||||
|
:return: Node model
|
||||||
|
:rtype: models.BaseNodeModel or sub class of models.BaseNodeModel
|
||||||
|
"""
|
||||||
|
bundle = get_node_bundle(manager, handle_id, node)
|
||||||
|
for label in bundle.get('labels'):
|
||||||
|
try:
|
||||||
|
classname = '{meta_type}{base}Model'.format(meta_type=bundle.get('meta_type'), base=label).replace('_', '')
|
||||||
|
return getattr(models, classname)(manager).load(bundle)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
for label in bundle.get('labels'):
|
||||||
|
try:
|
||||||
|
classname = '{base}Model'.format(base=label).replace('_', '')
|
||||||
|
return getattr(models, classname)(manager).load(bundle)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
classname = '{base}Model'.format(base=bundle.get('meta_type'))
|
||||||
|
return getattr(models, classname)(manager).load(bundle)
|
||||||
|
except AttributeError:
|
||||||
|
return models.BaseNodeModel(manager).load(bundle)
|
||||||
|
|
||||||
|
|
||||||
|
def get_relationship_model(manager, relationship_id):
|
||||||
|
"""
|
||||||
|
:param manager: Context manager to handle transactions
|
||||||
|
:type manager: Neo4jDBSessionManager
|
||||||
|
:param relationship_id: Internal Neo4j relationship id
|
||||||
|
:type relationship_id: int
|
||||||
|
:return: Relationship model
|
||||||
|
:rtype: models.BaseRelationshipModel
|
||||||
|
"""
|
||||||
|
bundle = get_relationship_bundle(manager, relationship_id)
|
||||||
|
return models.BaseRelationshipModel(manager).load(bundle)
|
116
norduniclient/exceptions.py
Normal file
116
norduniclient/exceptions.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Created on Thu Oct 13 16:36:31 2011
|
||||||
|
|
||||||
|
@author: lundberg
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
|
||||||
|
class NoRelationshipPossible(Exception):
|
||||||
|
"""
|
||||||
|
Exception that explains why the nodes relationship was not possible.
|
||||||
|
"""
|
||||||
|
def __init__(self, handle_id1, meta_type1, handle_id2, meta_type2, relationship_type):
|
||||||
|
self.handle_id1 = handle_id1
|
||||||
|
self.handle_id2 = handle_id2
|
||||||
|
self.meta_type1 = meta_type1
|
||||||
|
self.meta_type2 = meta_type2
|
||||||
|
self.relationship_type = relationship_type
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
node1_str = '{meta_type} Node ({handle_id})'.format(meta_type=self.meta_type1, handle_id=self.handle_id1)
|
||||||
|
node2_str = '{meta_type} Node ({handle_id})'.format(meta_type=self.meta_type2, handle_id=self.handle_id2)
|
||||||
|
return '%s %s %s is not possible.' % (node1_str, self.relationship_type, node2_str)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaLabelNamingError(Exception):
|
||||||
|
"""
|
||||||
|
Exception that explains that meta labels must have special names defined
|
||||||
|
in create_node().
|
||||||
|
"""
|
||||||
|
def __init__(self, name):
|
||||||
|
self.error = 'A meta label can not be named {name}.'.format(name=name)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.error
|
||||||
|
|
||||||
|
|
||||||
|
class NoMetaLabelFound(Exception):
|
||||||
|
"""
|
||||||
|
All nodes need a meta type to function correctly in the NOCLook model. This
|
||||||
|
exception should be raised if the nodes meta node can't be found.
|
||||||
|
"""
|
||||||
|
def __init__(self, handle_id):
|
||||||
|
self.handle_id = handle_id
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Node with handle_id {handle_id} has no meta label.'.format(handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class UniqueNodeError(Exception):
|
||||||
|
"""
|
||||||
|
Should be raised when the user tries to create a new node that should be
|
||||||
|
unique for that node_name and node_type.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, handle_id, node_type):
|
||||||
|
self.name = name
|
||||||
|
self.handle_id = handle_id
|
||||||
|
self.node_type = node_type
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'A node named {name} with node type {type} already exists. Handle ID: {id}'.format(name=self.name,
|
||||||
|
type=self.node_type,
|
||||||
|
id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleNodesReturned(Exception):
|
||||||
|
"""
|
||||||
|
If a user requests an unique node, by name and type, and multiple nodes are returned
|
||||||
|
this exception should be raised.
|
||||||
|
"""
|
||||||
|
def __init__(self, node_name, node_type):
|
||||||
|
self.node_name = node_name
|
||||||
|
self.node_type = node_type
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Multiple nodes of name %s and type %s was returned.' % (self.node_name,
|
||||||
|
self.node_type)
|
||||||
|
|
||||||
|
|
||||||
|
class BadProperties(Exception):
|
||||||
|
"""
|
||||||
|
If a user tries to set node or relationship properties that are not Numeric values,
|
||||||
|
String values or Boolean values.
|
||||||
|
"""
|
||||||
|
def __init__(self, properties):
|
||||||
|
self.properties = properties
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '''Tried to set {properties} as properties.
|
||||||
|
Only numeric values, string values or boolean values are allowed'''.format(properties=self.properties)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeNotFound(Exception):
|
||||||
|
"""
|
||||||
|
The provided handle_id did not match any node in the graph database.
|
||||||
|
"""
|
||||||
|
def __init__(self, manager, handle_id):
|
||||||
|
self.message = '{handle_id} did not match a node in database at {db}.'.format(handle_id=handle_id,
|
||||||
|
db=manager.uri)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
class RelationshipNotFound(Exception):
|
||||||
|
"""
|
||||||
|
The provided handle_id did not match any node in the graph database.
|
||||||
|
"""
|
||||||
|
def __init__(self, manager, relationship_id):
|
||||||
|
self.message = '{relationship_id} did not match a relationship in database at {db}.'.format(
|
||||||
|
relationship_id=relationship_id, db=manager.uri)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
43
norduniclient/helpers.py
Normal file
43
norduniclient/helpers.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
def update_item_properties(item_properties, new_properties):
|
||||||
|
for key, value in new_properties.items():
|
||||||
|
if value or value == 0:
|
||||||
|
item_properties[key] = value
|
||||||
|
elif key in item_properties.keys():
|
||||||
|
del item_properties[key]
|
||||||
|
return item_properties
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Does this helper make any sense?
|
||||||
|
def merge_properties(item_properties, prop_name, merge_value):
|
||||||
|
"""
|
||||||
|
Tries to figure out which type of property value that should be merged and
|
||||||
|
invoke the right function.
|
||||||
|
Returns new properties if the merge was successful otherwise False.
|
||||||
|
"""
|
||||||
|
existing_value = item_properties.get(prop_name, None)
|
||||||
|
if not existing_value: # A node without existing values for the property
|
||||||
|
item_properties[prop_name] = merge_value
|
||||||
|
else:
|
||||||
|
if type(merge_value) is int or type(merge_value) is str:
|
||||||
|
item_properties[prop_name] = existing_value + merge_value
|
||||||
|
elif type(merge_value) is list:
|
||||||
|
item_properties[prop_name] = merge_list(existing_value, merge_value)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return item_properties
|
||||||
|
|
||||||
|
|
||||||
|
def merge_list(existing_value, new_value):
|
||||||
|
"""
|
||||||
|
Takes the name of a property, a list of new property values and the existing
|
||||||
|
node values.
|
||||||
|
Returns the merged properties.
|
||||||
|
"""
|
||||||
|
new_set = set(existing_value + new_value)
|
||||||
|
return list(new_set)
|
889
norduniclient/models.py
Normal file
889
norduniclient/models.py
Normal file
|
@ -0,0 +1,889 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from functools import total_ordering
|
||||||
|
from collections import defaultdict
|
||||||
|
try:
|
||||||
|
# Python 2
|
||||||
|
import core
|
||||||
|
except ImportError: # Fix circular import in python 2 vs python 3
|
||||||
|
# Python 3
|
||||||
|
from norduniclient import core
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class BaseRelationshipModel(object):
|
||||||
|
|
||||||
|
def __init__(self, manager):
|
||||||
|
self.manager = manager
|
||||||
|
self.id = None
|
||||||
|
self.type = None
|
||||||
|
self.data = None
|
||||||
|
self.start = None
|
||||||
|
self.end = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return u'({start})-[{id}:{type}{data}]->({end}) in database {db}.'.format(
|
||||||
|
start=self.start['handle_id'], type=self.type, id=self.id, data=self.data, end=self.end['handle_id'],
|
||||||
|
db=self.manager.uri
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.id == other.id
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.id < other.id
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return u'<{c} id:{id} in {db}>'.format(c=self.__class__.__name__, id=self.id, db=self.manager.uri)
|
||||||
|
|
||||||
|
def load(self, relationship_bundle):
|
||||||
|
self.id = relationship_bundle.get('id')
|
||||||
|
self.type = relationship_bundle.get('type')
|
||||||
|
self.data = relationship_bundle.get('data', {})
|
||||||
|
self.start = relationship_bundle.get('start')
|
||||||
|
self.end = relationship_bundle.get('end')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
core.delete_relationship(self.manager, self.id)
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class BaseNodeModel(object):
|
||||||
|
|
||||||
|
def __init__(self, manager):
|
||||||
|
self.manager = manager
|
||||||
|
self.meta_type = None
|
||||||
|
self.labels = None
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
labels = ':'.join(self.labels)
|
||||||
|
return u'(node:{meta_type}:{labels} {data}) in database {db}.'.format(
|
||||||
|
meta_type=self.meta_type, labels=labels, data=self.data, db=self.manager.uri
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.handle_id == other.handle_id
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.handle_id < other.handle_id
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return u'<{c} handle_id:{handle_id} in {db}>'.format(c=self.__class__.__name__, handle_id=self.handle_id,
|
||||||
|
db=self.manager.uri)
|
||||||
|
|
||||||
|
def _get_handle_id(self):
|
||||||
|
return self.data.get('handle_id')
|
||||||
|
handle_id = property(_get_handle_id)
|
||||||
|
|
||||||
|
def _incoming(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r]-(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
incoming = property(_incoming)
|
||||||
|
|
||||||
|
def _outgoing(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r]->(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
outgoing = property(_outgoing)
|
||||||
|
|
||||||
|
def _relationships(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r]-(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
relationships = property(_relationships)
|
||||||
|
|
||||||
|
def _basic_read_query_to_dict(self, query, **kwargs):
|
||||||
|
d = defaultdict(list)
|
||||||
|
with self.manager.session as s:
|
||||||
|
kwargs['handle_id'] = self.handle_id
|
||||||
|
result = s.run(query, kwargs)
|
||||||
|
for record in result:
|
||||||
|
relationship = record['r']
|
||||||
|
node = record['node']
|
||||||
|
key = relationship.type
|
||||||
|
if 'key' in record.keys():
|
||||||
|
key = record['key']
|
||||||
|
d[key].append({
|
||||||
|
'relationship_id': relationship.id,
|
||||||
|
'relationship': relationship,
|
||||||
|
'node': core.get_node_model(self.manager, node=node)
|
||||||
|
})
|
||||||
|
d.default_factory = None
|
||||||
|
return d
|
||||||
|
|
||||||
|
def _basic_write_query_to_dict(self, query, **kwargs):
|
||||||
|
d = defaultdict(list)
|
||||||
|
with self.manager.session as s:
|
||||||
|
kwargs['handle_id'] = self.handle_id
|
||||||
|
result = s.run(query, kwargs)
|
||||||
|
for record in result:
|
||||||
|
created = record['created']
|
||||||
|
relationship = record['r']
|
||||||
|
node = record['node']
|
||||||
|
key = relationship.type
|
||||||
|
if 'key' in record.keys():
|
||||||
|
key = record['key']
|
||||||
|
d[key].append({
|
||||||
|
'created': created,
|
||||||
|
'relationship_id': relationship.id,
|
||||||
|
'relationship': relationship,
|
||||||
|
'node': core.get_node_model(self.manager, node=node)
|
||||||
|
})
|
||||||
|
d.default_factory = None
|
||||||
|
return d
|
||||||
|
|
||||||
|
def load(self, node_bundle):
|
||||||
|
self.meta_type = node_bundle.get('meta_type')
|
||||||
|
self.labels = node_bundle.get('labels')
|
||||||
|
self.data = node_bundle.get('data')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_label(self, label):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {{handle_id: $handle_id}})
|
||||||
|
SET n:{label}
|
||||||
|
RETURN n
|
||||||
|
""".format(label=label)
|
||||||
|
with self.manager.session as s:
|
||||||
|
node = s.run(q, {'handle_id': self.handle_id}).single()['n']
|
||||||
|
return self.reload(node=node)
|
||||||
|
|
||||||
|
def remove_label(self, label):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {{handle_id: $handle_id}})
|
||||||
|
REMOVE n:{label}
|
||||||
|
RETURN n
|
||||||
|
""".format(label=label)
|
||||||
|
with self.manager.session as s:
|
||||||
|
node = s.run(q, {'handle_id': self.handle_id}).single()['n']
|
||||||
|
return self.reload(node=node)
|
||||||
|
|
||||||
|
def change_meta_type(self, meta_type):
|
||||||
|
if meta_type not in core.META_TYPES:
|
||||||
|
raise core.exceptions.MetaLabelNamingError(meta_type)
|
||||||
|
if meta_type == self.meta_type:
|
||||||
|
return self
|
||||||
|
model = self.remove_label(self.meta_type)
|
||||||
|
return model.add_label(meta_type)
|
||||||
|
|
||||||
|
def switch_type(self, old_type, new_type):
|
||||||
|
if old_type == new_type:
|
||||||
|
return self
|
||||||
|
model = self.remove_label(old_type)
|
||||||
|
return model.add_label(new_type)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
core.delete_node(self.manager, self.handle_id)
|
||||||
|
|
||||||
|
def reload(self, node=None):
|
||||||
|
return core.get_node_model(self.manager, self.handle_id, node=node)
|
||||||
|
|
||||||
|
|
||||||
|
class CommonQueries(BaseNodeModel):
|
||||||
|
|
||||||
|
def get_location_path(self):
|
||||||
|
return {'location_path': []}
|
||||||
|
|
||||||
|
def get_placement_path(self):
|
||||||
|
return {'placement_path': []}
|
||||||
|
|
||||||
|
def get_location(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_child_form_data(self, node_type):
|
||||||
|
type_filter = ''
|
||||||
|
if node_type:
|
||||||
|
type_filter = 'and (child):{node_type}'.format(node_type=node_type)
|
||||||
|
q = """
|
||||||
|
MATCH (parent:Node {{handle_id:$handle_id}})
|
||||||
|
MATCH (parent)--(child)
|
||||||
|
WHERE (parent)-[:Has]->(child) or (parent)<-[:Located_in|Part_of]-(child) {type_filter}
|
||||||
|
RETURN child.handle_id as handle_id, labels(child) as labels, child.name as name,
|
||||||
|
child.description as description
|
||||||
|
""".format(type_filter=type_filter)
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_relations(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Owns|Uses|Provides|Responsible_for]-(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_dependencies(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Depends_on]->(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_dependents(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Depends_on]-(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_dependent_as_types(self):
|
||||||
|
q = """
|
||||||
|
MATCH (node:Node {handle_id: $handle_id})
|
||||||
|
OPTIONAL MATCH (node)<-[:Depends_on]-(d)
|
||||||
|
WITH node, collect(DISTINCT d) as direct
|
||||||
|
OPTIONAL MATCH (node)<-[:Part_of|Depends_on*1..20]-(dep)
|
||||||
|
OPTIONAL MATCH (node)-[:Depends_on]->(p:Port)<-[:Part_of|Depends_on*1..20]-(port_deps)
|
||||||
|
WITH direct, collect(DISTINCT dep) + collect(DISTINCT port_deps) as deps
|
||||||
|
WITH direct, deps, [n in deps WHERE n:Service] as services
|
||||||
|
WITH direct, deps, services, [n in deps WHERE n:Optical_Path] as paths
|
||||||
|
WITH direct, deps, services, paths, [n in deps WHERE n:Optical_Multiplex_Section] as oms
|
||||||
|
WITH direct, deps, services, paths, oms, [n in deps WHERE n:Optical_Link] as links
|
||||||
|
RETURN direct, services, paths, oms, links
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_dependencies_as_types(self):
|
||||||
|
q = """
|
||||||
|
MATCH (node:Node {handle_id: $handle_id})
|
||||||
|
OPTIONAL MATCH (node)-[:Depends_on]->(d)
|
||||||
|
WITH node, collect(DISTINCT d) as direct
|
||||||
|
MATCH (node)-[:Depends_on*1..20]->(dep)
|
||||||
|
WITH node, direct, collect(DISTINCT dep) as deps
|
||||||
|
WITH node, direct, deps, [n in deps WHERE n:Service] as services
|
||||||
|
WITH node, direct, deps, services, [n in deps WHERE n:Optical_Path] as paths
|
||||||
|
WITH node, direct, deps, services, paths, [n in deps WHERE n:Optical_Multiplex_Section] as oms
|
||||||
|
WITH node, direct, deps, services, paths, oms, [n in deps WHERE n:Optical_Link] as links
|
||||||
|
WITH node, direct, services, paths, oms, links
|
||||||
|
OPTIONAL MATCH (node)-[:Depends_on*1..20]->()-[:Connected_to*1..50]-(cable)
|
||||||
|
RETURN direct, services, paths, oms, links, [n in collect(DISTINCT cable) WHERE n:Cable] as cables
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_ports(self):
|
||||||
|
q = """
|
||||||
|
MATCH (node:Node {handle_id: $handle_id})-[r:Connected_to|Depends_on]-(port:Port)
|
||||||
|
WITH port, r
|
||||||
|
OPTIONAL MATCH p=(port)<-[:Has*1..]-(parent)
|
||||||
|
RETURN port, r as relationship, LAST(nodes(p)) as parent
|
||||||
|
ORDER BY parent.name
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class LogicalModel(CommonQueries):
|
||||||
|
|
||||||
|
def get_part_of(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Part_of]->(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def set_user(self, user_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (user:Node {handle_id: $user_handle_id})
|
||||||
|
WITH n, user, NOT EXISTS((n)<-[:Uses]-(user)) as created
|
||||||
|
MERGE (n)<-[r:Uses]-(user)
|
||||||
|
RETURN created, r, user as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, user_handle_id=user_handle_id)
|
||||||
|
|
||||||
|
def set_provider(self, provider_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (provider:Node {handle_id: $provider_handle_id})
|
||||||
|
WITH n, provider, NOT EXISTS((n)<-[:Provides]-(provider)) as created
|
||||||
|
MERGE (n)<-[r:Provides]-(provider)
|
||||||
|
RETURN created, r, provider as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, provider_handle_id=provider_handle_id)
|
||||||
|
|
||||||
|
def set_dependency(self, dependency_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (dependency:Node {handle_id: $dependency_handle_id})
|
||||||
|
WITH n, dependency, NOT EXISTS((n)-[:Depends_on]->(dependency)) as created
|
||||||
|
MERGE (n)-[r:Depends_on]->(dependency)
|
||||||
|
RETURN created, r, dependency as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, dependency_handle_id=dependency_handle_id)
|
||||||
|
|
||||||
|
# Logical versions of physical things can't have physical connections
|
||||||
|
def get_connections(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# TODO: Create a method that complains if any relationships that breaks the model exists
|
||||||
|
|
||||||
|
|
||||||
|
class PhysicalModel(CommonQueries):
|
||||||
|
|
||||||
|
def get_location(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Located_in]->(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_location_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[:Located_in]->(r)
|
||||||
|
MATCH p=()-[:Has*0..20]->(r)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as location_path
|
||||||
|
RETURN location_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_placement_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[:Has]-(parent)
|
||||||
|
OPTIONAL MATCH p=()-[:Has*0..20]->(parent)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as placement_path
|
||||||
|
RETURN placement_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def set_owner(self, owner_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (owner:Node {handle_id: $owner_handle_id})
|
||||||
|
WITH n, owner, NOT EXISTS((n)<-[:Owns]-(owner)) as created
|
||||||
|
MERGE (n)<-[r:Owns]-(owner)
|
||||||
|
RETURN created, r, owner as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, owner_handle_id=owner_handle_id)
|
||||||
|
|
||||||
|
def set_provider(self, provider_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (provider:Node {handle_id: $provider_handle_id})
|
||||||
|
WITH n, provider, NOT EXISTS((n)<-[:Provides]-(provider)) as created
|
||||||
|
MERGE (n)<-[r:Provides]-(provider)
|
||||||
|
RETURN created, r, provider as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, provider_handle_id=provider_handle_id)
|
||||||
|
|
||||||
|
def set_location(self, location_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (location:Node {handle_id: $location_handle_id})
|
||||||
|
WITH n, location, NOT EXISTS((n)-[:Located_in]->(location)) as created
|
||||||
|
MERGE (n)-[r:Located_in]->(location)
|
||||||
|
RETURN created, r, location as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, location_handle_id=location_handle_id)
|
||||||
|
|
||||||
|
def get_has(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Has]->(part:Physical)
|
||||||
|
RETURN r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def set_has(self, has_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (part:Node {handle_id: $has_handle_id})
|
||||||
|
WITH n, part, NOT EXISTS((n)-[:Has]->(part)) as created
|
||||||
|
MERGE (n)-[r:Has]->(part)
|
||||||
|
RETURN created, r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, has_handle_id=has_handle_id)
|
||||||
|
|
||||||
|
def get_part_of(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Part_of]-(part:Logical)
|
||||||
|
RETURN r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def set_part_of(self, part_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (part:Node:Logical {handle_id: $part_handle_id})
|
||||||
|
WITH n, part, NOT EXISTS((n)<-[:Part_of]-(part)) as created
|
||||||
|
MERGE (n)<-[r:Part_of]-(part)
|
||||||
|
RETURN created, r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, part_handle_id=part_handle_id)
|
||||||
|
|
||||||
|
def get_parent(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Has]-(parent)
|
||||||
|
RETURN r, parent as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
# TODO: Create a method that complains if any relationships that breaks the model exists
|
||||||
|
|
||||||
|
|
||||||
|
class LocationModel(CommonQueries):
|
||||||
|
|
||||||
|
def get_location_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[:Has]-(r)
|
||||||
|
MATCH p=()-[:Has*0..20]->(r)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as location_path
|
||||||
|
RETURN location_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_parent(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Has]-(parent)
|
||||||
|
RETURN r, parent as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_located_in(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Located_in]-(node)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_has(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Has]->(node:Location)
|
||||||
|
RETURN r, node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def set_has(self, has_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (part:Node {handle_id: $has_handle_id})
|
||||||
|
WITH n, part, NOT EXISTS((n)-[:Has]->(part)) as created
|
||||||
|
MERGE (n)-[r:Has]->(part)
|
||||||
|
RETURN created, r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, has_handle_id=has_handle_id)
|
||||||
|
|
||||||
|
def set_responsible_for(self, owner_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (owner:Node {handle_id: $owner_handle_id})
|
||||||
|
WITH n, owner, NOT EXISTS((n)<-[:Responsible_for]-(owner)) as created
|
||||||
|
MERGE (n)<-[r:Responsible_for]-(owner)
|
||||||
|
RETURN created, r, owner as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, owner_handle_id=owner_handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class RelationModel(CommonQueries):
|
||||||
|
|
||||||
|
def with_same_name(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (other:Node:Relation {name: $name})
|
||||||
|
WHERE other.handle_id <> n.handle_id
|
||||||
|
RETURN COLLECT(other.handle_id) as ids
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id, name=self.data.get('name'))
|
||||||
|
|
||||||
|
def get_uses(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Uses]->(usable)
|
||||||
|
RETURN r, usable as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_provides(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Provides]->(usable)
|
||||||
|
RETURN r, usable as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_owns(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Owns]->(usable)
|
||||||
|
RETURN r, usable as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_responsible_for(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Responsible_for]->(usable)
|
||||||
|
RETURN r, usable as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
|
||||||
|
class EquipmentModel(PhysicalModel):
|
||||||
|
|
||||||
|
def get_ports(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Has]->(port:Port)
|
||||||
|
RETURN r, port as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_port(self, port_name):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Has]->(port:Port)
|
||||||
|
WHERE port.name = $port_name
|
||||||
|
RETURN r, port as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q, port_name=port_name)
|
||||||
|
|
||||||
|
def get_dependent_as_types(self):
|
||||||
|
# The + [null] is to handle both dep lists being emtpy since UNWIND gives 0 rows on unwind
|
||||||
|
q = """
|
||||||
|
MATCH (node:Node {handle_id: $handle_id})
|
||||||
|
OPTIONAL MATCH (node)<-[:Depends_on]-(d)
|
||||||
|
WITH node, collect(DISTINCT d) as direct
|
||||||
|
OPTIONAL MATCH (node)-[:Has*1..20]->()<-[:Part_of|Depends_on*1..20]-(dep)
|
||||||
|
OPTIONAL MATCH (node)-[:Has*1..20]->()<-[:Connected_to]-()-[:Connected_to]->()<-[:Depends_on*1..20]-(cable_dep)
|
||||||
|
WITH direct, collect(DISTINCT dep) + collect(DISTINCT cable_dep) + direct as coll
|
||||||
|
UNWIND coll AS x
|
||||||
|
WITH direct, collect(DISTINCT x) as deps
|
||||||
|
WITH direct, deps, [n in deps WHERE n:Service] as services
|
||||||
|
WITH direct, deps, services, [n in deps WHERE n:Optical_Path] as paths
|
||||||
|
WITH direct, deps, services, paths, [n in deps WHERE n:Optical_Multiplex_Section] as oms
|
||||||
|
WITH direct, deps, services, paths, oms, [n in deps WHERE n:Optical_Link] as links
|
||||||
|
RETURN direct, services, paths, oms, links
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_connections(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[:Has*1..10]->(porta:Port)
|
||||||
|
OPTIONAL MATCH (porta)<-[r0:Connected_to]-(cable)
|
||||||
|
OPTIONAL MATCH (cable)-[r1:Connected_to]->(portb:Port)
|
||||||
|
WHERE ID(r1) <> ID(r0)
|
||||||
|
OPTIONAL MATCH (portb)<-[:Has*1..10]-(end)
|
||||||
|
WITH porta, r0, cable, portb, r1, last(collect(end)) as end
|
||||||
|
OPTIONAL MATCH (end)-[:Located_in]->(location)
|
||||||
|
OPTIONAL MATCH (location)<-[:Has]-(site)
|
||||||
|
RETURN porta, r0, cable, r1, portb, end, location, site
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class SubEquipmentModel(PhysicalModel):
|
||||||
|
|
||||||
|
def get_location_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[:Has]-(parent)
|
||||||
|
OPTIONAL MATCH p=()-[:Has*0..20]->(r)<-[:Located_in]-()-[:Has*0..20]->(parent)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as location_path
|
||||||
|
RETURN location_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_connections(self):
|
||||||
|
q = """
|
||||||
|
MATCH (porta:Node {handle_id: $handle_id})<-[r0:Connected_to]-(cable)
|
||||||
|
OPTIONAL MATCH (porta)<-[r0:Connected_to]-(cable)-[r1:Connected_to]->(portb)
|
||||||
|
OPTIONAL MATCH (portb)<-[:Has*1..10]-(end)
|
||||||
|
WITH porta, r0, cable, portb, r1, last(collect(end)) as end
|
||||||
|
OPTIONAL MATCH (end)-[:Located_in]->(location)
|
||||||
|
OPTIONAL MATCH (location)<-[:Has]-(site)
|
||||||
|
RETURN porta, r0, cable, r1, portb, end, location, site
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class HostModel(CommonQueries):
|
||||||
|
|
||||||
|
def get_dependent_as_types(self):
|
||||||
|
q = """
|
||||||
|
MATCH (node:Node {handle_id: $handle_id})
|
||||||
|
OPTIONAL MATCH (node)<-[:Depends_on]-(d)
|
||||||
|
WITH node, [n in collect(DISTINCT d)] as direct
|
||||||
|
MATCH (node)<-[:Depends_on*1..20]-(dep)
|
||||||
|
WITH direct, collect(DISTINCT dep) as deps
|
||||||
|
WITH direct, deps, [n in deps WHERE n:Service] as services
|
||||||
|
WITH direct, deps, services, [n in deps WHERE n:Optical_Path] as paths
|
||||||
|
WITH direct, deps, services, paths, [n in deps WHERE n:Optical_Multiplex_Section] as oms
|
||||||
|
WITH direct, deps, services, paths, oms, [n in deps WHERE n:Optical_Link] as links
|
||||||
|
RETURN direct, services, paths, oms, links
|
||||||
|
"""
|
||||||
|
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class PhysicalHostModel(HostModel, EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LogicalHostModel(HostModel, LogicalModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PortModel(SubEquipmentModel):
|
||||||
|
|
||||||
|
def get_units(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Part_of]-(unit:Unit)
|
||||||
|
RETURN r, unit as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_unit(self, unit_name):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Part_of]-(unit:Unit)
|
||||||
|
WHERE unit.name = $unit_name
|
||||||
|
RETURN r, unit as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q, unit_name=unit_name)
|
||||||
|
|
||||||
|
def get_connected_to(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Connected_to]-(cable:Cable)
|
||||||
|
RETURN r, cable as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_connection_path(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Port {handle_id: $handle_id})-[:Connected_to*0..20]-(port:Port)
|
||||||
|
OPTIONAL MATCH path=(port)-[:Connected_to*]-()
|
||||||
|
WITH nodes(path) AS parts, length(path) AS len
|
||||||
|
ORDER BY len DESC
|
||||||
|
LIMIT 1
|
||||||
|
UNWIND parts AS part
|
||||||
|
OPTIONAL MATCH (part)<-[:Has*1..20]-(parent)
|
||||||
|
WHERE NOT (parent)<-[:Has]-()
|
||||||
|
RETURN part, parent
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class OpticalNodeModel(EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RouterModel(EquipmentModel):
|
||||||
|
|
||||||
|
def get_child_form_data(self, node_type=None):
|
||||||
|
if node_type:
|
||||||
|
type_filter = ':{node_type}'.format(node_type=node_type)
|
||||||
|
else:
|
||||||
|
type_filter = ':Port'
|
||||||
|
q = """
|
||||||
|
MATCH (parent:Node {{handle_id: $handle_id}})
|
||||||
|
MATCH (parent)-[:Has*]->(child{type_filter})
|
||||||
|
RETURN child.handle_id as handle_id, labels(child) as labels, child.name as name,
|
||||||
|
child.description as description
|
||||||
|
ORDER BY child.name
|
||||||
|
""".format(type_filter=type_filter)
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class PeeringPartnerModel(RelationModel):
|
||||||
|
|
||||||
|
def get_peering_groups(self):
|
||||||
|
q = """
|
||||||
|
MATCH (host:Node {handle_id: $handle_id})-[r:Uses]->(group:Peering_Group)
|
||||||
|
RETURN r, group as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
def get_peering_group(self, group_handle_id, ip_address):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Uses]->(group:Node {handle_id: $group_handle_id})
|
||||||
|
WHERE r.ip_address=$ip_address
|
||||||
|
RETURN r, group as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q, group_handle_id=group_handle_id, ip_address=ip_address)
|
||||||
|
|
||||||
|
def set_peering_group(self, group_handle_id, ip_address):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (group:Node {handle_id: $group_handle_id})
|
||||||
|
CREATE (n)-[r:Uses {ip_address:$ip_address}]->(group)
|
||||||
|
RETURN true as created, r, group as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, group_handle_id=group_handle_id, ip_address=ip_address)
|
||||||
|
|
||||||
|
|
||||||
|
class PeeringGroupModel(LogicalModel):
|
||||||
|
|
||||||
|
def get_group_dependency(self, dependency_handle_id, ip_address):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[r:Depends_on]->(dependency:Node {handle_id: $dependency_handle_id})
|
||||||
|
WHERE r.ip_address=$ip_address
|
||||||
|
RETURN r, dependency as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q, dependency_handle_id=dependency_handle_id, ip_address=ip_address)
|
||||||
|
|
||||||
|
def set_group_dependency(self, dependency_handle_id, ip_address):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (dependency:Node {handle_id: $dependency_handle_id})
|
||||||
|
CREATE (n)-[r:Depends_on {ip_address:$ip_address}]->(dependency)
|
||||||
|
RETURN true as created, r, dependency as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, dependency_handle_id=dependency_handle_id, ip_address=ip_address)
|
||||||
|
|
||||||
|
|
||||||
|
class CableModel(PhysicalModel):
|
||||||
|
|
||||||
|
def get_connected_equipment(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[rel:Connected_to]->(port)
|
||||||
|
OPTIONAL MATCH (port)<-[:Has*1..10]-(end)
|
||||||
|
WITH rel, port, last(collect(end)) as end
|
||||||
|
OPTIONAL MATCH (end)-[:Located_in]->(location)
|
||||||
|
OPTIONAL MATCH (location)<-[:Has]-(site)
|
||||||
|
RETURN id(rel) as rel_id, rel, port, end, location, site
|
||||||
|
ORDER BY end.name, port.name
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_dependent_as_types(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[:Connected_to*1..20]-(equip)
|
||||||
|
WITH DISTINCT equip
|
||||||
|
MATCH (equip)<-[:Part_of|Depends_on*1..10]-(dep)
|
||||||
|
WITH collect(DISTINCT dep) as deps
|
||||||
|
WITH deps, [n in deps WHERE n:Service] as services
|
||||||
|
WITH deps, services, [n in deps WHERE n:Optical_Path] as paths
|
||||||
|
WITH deps, services, paths, [n in deps WHERE n:Optical_Multiplex_Section] as oms
|
||||||
|
WITH deps, services, paths, oms, [n in deps WHERE n:Optical_Link] as links
|
||||||
|
RETURN services, paths, oms, links
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_services(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})
|
||||||
|
MATCH (n)-[:Connected_to*1..20]-(equip)
|
||||||
|
WITH equip
|
||||||
|
MATCH (equip)<-[:Depends_on*1..10]-(service)
|
||||||
|
WHERE service:Service
|
||||||
|
WITH distinct service
|
||||||
|
OPTIONAL MATCH (service)<-[:Uses]-(user)
|
||||||
|
RETURN service, collect(user) as users
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_connection_path(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Cable {handle_id: $handle_id})-[:Connected_to*1..10]-(port:Port)
|
||||||
|
OPTIONAL MATCH path=(port)-[:Connected_to*]-()
|
||||||
|
WITH nodes(path) AS parts, length(path) AS len
|
||||||
|
ORDER BY len DESC
|
||||||
|
LIMIT 1
|
||||||
|
UNWIND parts AS part
|
||||||
|
OPTIONAL MATCH (part)<-[:Has*1..10]-(parent)
|
||||||
|
WHERE NOT (parent)<-[:Has]-()
|
||||||
|
RETURN part, parent
|
||||||
|
"""
|
||||||
|
return core.query_to_list(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def set_connected_to(self, connected_to_handle_id):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id}), (part:Node {handle_id: $connected_to_handle_id})
|
||||||
|
WITH n, part, NOT EXISTS((n)-[:Connected_to]->(part)) as created
|
||||||
|
MERGE (n)-[r:Connected_to]->(part)
|
||||||
|
RETURN created, r, part as node
|
||||||
|
"""
|
||||||
|
return self._basic_write_query_to_dict(q, connected_to_handle_id=connected_to_handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class UnitModel(LogicalModel):
|
||||||
|
|
||||||
|
def get_placement_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[:Part_of]->(parent)
|
||||||
|
OPTIONAL MATCH p=()-[:Has*0..20]->(parent)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as placement_path
|
||||||
|
RETURN placement_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
def get_location_path(self):
|
||||||
|
# TODO: check if size(nodes(p))/size(path) in neo4j>=4.4 is equivalent to length(nodes(p))/length(path) in neo4j==3.5
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})-[:Part_of]->(parent)
|
||||||
|
OPTIONAL MATCH p=()-[:Has*0..20]->(r)<-[:Located_in]-()-[:Has*0..20]->(parent)
|
||||||
|
WITH COLLECT(nodes(p)) as paths, MAX(size(nodes(p))) AS maxLength
|
||||||
|
WITH [path IN paths WHERE size(path)=maxLength] AS longestPaths
|
||||||
|
UNWIND(longestPaths) as location_path
|
||||||
|
RETURN location_path
|
||||||
|
"""
|
||||||
|
return core.query_to_dict(self.manager, q, handle_id=self.handle_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceModel(LogicalModel):
|
||||||
|
|
||||||
|
def get_customers(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node {handle_id: $handle_id})<-[r:Owns|Uses]-(customer:Customer)
|
||||||
|
RETURN "customers" as key, r, customer as node
|
||||||
|
"""
|
||||||
|
return self._basic_read_query_to_dict(q)
|
||||||
|
|
||||||
|
|
||||||
|
class OpticalPathModel(LogicalModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpticalMultiplexSection(LogicalModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpticalLinkModel(LogicalModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalEquipmentModel(EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ODFModel(EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpticalFilterModel(EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchModel(EquipmentModel, HostModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FirewallModel(EquipmentModel, HostModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PDUModel(EquipmentModel, HostModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PICModel(SubEquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FPCModel(SubEquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerModel(RelationModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderModel(RelationModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PatchPanelModel(EquipmentModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OutletModel(EquipmentModel):
|
||||||
|
pass
|
95
norduniclient/testing.py
Normal file
95
norduniclient/testing.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import time
|
||||||
|
import atexit
|
||||||
|
from socket import error as SocketError
|
||||||
|
|
||||||
|
from norduniclient.core import init_db
|
||||||
|
import collections
|
||||||
|
collections.Callable = collections.abc.Callable
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
class Neo4jTemporaryInstance(object):
|
||||||
|
"""
|
||||||
|
Singleton to manage a temporary Neo4j instance
|
||||||
|
|
||||||
|
Use this for testing purpose only. The instance is automatically destroyed
|
||||||
|
at the end of the program.
|
||||||
|
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
_http_port = None
|
||||||
|
_bolt_port = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = cls()
|
||||||
|
atexit.register(cls._instance.shutdown)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._host = os.environ.get("NEO4J_HOSTNAME", "localhost")
|
||||||
|
self._http_port = os.environ.get("NEO4J_HTTP_PORT", 7474)
|
||||||
|
self._bolt_port = os.environ.get("NEO4J_BOLT_PORT", 7687)
|
||||||
|
self._neo4j_password = os.environ.get("NEO4J_PASSWORD", "neo4j")
|
||||||
|
self._neo4j_user = os.environ.get("NEO4J_USER", "neo4j")
|
||||||
|
|
||||||
|
for i in range(300):
|
||||||
|
time.sleep(0.5)
|
||||||
|
try:
|
||||||
|
self._db = init_db('bolt://{!s}:{!s}'.format(self.host, self.bolt_port), username=self._neo4j_user,
|
||||||
|
password=self._neo4j_password, encrypted=False)
|
||||||
|
except SocketError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.shutdown()
|
||||||
|
assert False, 'Cannot connect to the neo4j test instance'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db(self):
|
||||||
|
return self._db
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._host
|
||||||
|
|
||||||
|
@property
|
||||||
|
def http_port(self):
|
||||||
|
return self._http_port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bolt_port(self):
|
||||||
|
return self._bolt_port
|
||||||
|
|
||||||
|
def purge_db(self):
|
||||||
|
q = """
|
||||||
|
MATCH (n:Node)
|
||||||
|
OPTIONAL MATCH (n)-[r]-()
|
||||||
|
DELETE n,r
|
||||||
|
"""
|
||||||
|
with self.db.session as s:
|
||||||
|
s.run(q)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Neo4jTestCase(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Base test case that sets up a temporary Neo4j instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
neo4j_instance = Neo4jTemporaryInstance.get_instance()
|
||||||
|
neo4jdb = neo4j_instance.db
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.neo4j_instance.purge_db()
|
40
noxfile.py
Normal file
40
noxfile.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import os
|
||||||
|
import nox
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=["3.9"])
|
||||||
|
def tests(session):
|
||||||
|
# Install dev dependencies
|
||||||
|
session.install("neo4j==4.4.12")
|
||||||
|
# Set environment variables using os.environ
|
||||||
|
os.environ["NEO4J_HTTP_PORT"] = "7476"
|
||||||
|
os.environ["NEO4J_BOLT_PORT"] = "7689"
|
||||||
|
os.environ["NEO4J_HOSTNAME"] = "localhost"
|
||||||
|
os.environ["NEO4J_USER"] = "neo4j"
|
||||||
|
os.environ["NEO4J_PASSWORD"] = ""
|
||||||
|
session.run("python", "-m", "unittest", "discover", "-s", "tests")
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=["3.9"])
|
||||||
|
def tests_dotenv(session):
|
||||||
|
session.install("neo4j==4.4.12")
|
||||||
|
session.run("dotenvx", "run", "--", "python", "-m", "unittest", "discover", "-s", "tests")
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=["3.9"])
|
||||||
|
def lint(session):
|
||||||
|
session.install("flake8")
|
||||||
|
session.install("flake8-pyproject")
|
||||||
|
session.run("flake8", "norduniclient")
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=["3.9"])
|
||||||
|
def typecheck(session):
|
||||||
|
session.install("mypy")
|
||||||
|
session.run("mypy", "norduniclient")
|
||||||
|
|
||||||
|
|
||||||
|
@nox.session(python=["3.9"])
|
||||||
|
def format(session):
|
||||||
|
session.install("black")
|
||||||
|
session.run("black", "--check", "norduniclient")
|
546
poetry.lock
generated
Normal file
546
poetry.lock
generated
Normal file
|
@ -0,0 +1,546 @@
|
||||||
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argcomplete"
|
||||||
|
version = "3.5.1"
|
||||||
|
description = "Bash tab completion for argparse"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"},
|
||||||
|
{file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "24.10.0"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||||
|
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||||
|
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||||
|
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||||
|
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||||
|
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
|
||||||
|
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
|
||||||
|
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
|
||||||
|
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
|
||||||
|
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||||
|
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=8.0.0"
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
packaging = ">=22.0"
|
||||||
|
pathspec = ">=0.9.0"
|
||||||
|
platformdirs = ">=2"
|
||||||
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
|
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.10)"]
|
||||||
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfgv"
|
||||||
|
version = "3.4.0"
|
||||||
|
description = "Validate configuration and produce human readable error messages."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
|
||||||
|
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.7"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||||
|
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorlog"
|
||||||
|
version = "6.8.2"
|
||||||
|
description = "Add colours to the output of Python's logging module."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"},
|
||||||
|
{file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distlib"
|
||||||
|
version = "0.3.9"
|
||||||
|
description = "Distribution utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
|
||||||
|
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.2.2"
|
||||||
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||||
|
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest (>=6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filelock"
|
||||||
|
version = "3.16.1"
|
||||||
|
description = "A platform independent file lock."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
|
||||||
|
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
|
||||||
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
|
||||||
|
typing = ["typing-extensions (>=4.12.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flake8"
|
||||||
|
version = "7.1.1"
|
||||||
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.1"
|
||||||
|
files = [
|
||||||
|
{file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"},
|
||||||
|
{file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mccabe = ">=0.7.0,<0.8.0"
|
||||||
|
pycodestyle = ">=2.12.0,<2.13.0"
|
||||||
|
pyflakes = ">=3.2.0,<3.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "identify"
|
||||||
|
version = "2.6.1"
|
||||||
|
description = "File identification library for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
|
||||||
|
{file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
license = ["ukkonen"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "brain-dead simple config-ini parsing"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mccabe"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
|
||||||
|
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.11.2"
|
||||||
|
description = "Optional static typing for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
|
||||||
|
{file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
|
||||||
|
{file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
|
||||||
|
{file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
|
||||||
|
{file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
|
||||||
|
{file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
|
||||||
|
{file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
|
||||||
|
{file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
|
||||||
|
{file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
|
||||||
|
{file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
|
||||||
|
{file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
|
||||||
|
{file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
|
||||||
|
{file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
|
||||||
|
{file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
|
||||||
|
{file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
|
||||||
|
{file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"},
|
||||||
|
{file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"},
|
||||||
|
{file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"},
|
||||||
|
{file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"},
|
||||||
|
{file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"},
|
||||||
|
{file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"},
|
||||||
|
{file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"},
|
||||||
|
{file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"},
|
||||||
|
{file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"},
|
||||||
|
{file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"},
|
||||||
|
{file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
|
||||||
|
{file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mypy-extensions = ">=1.0.0"
|
||||||
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
|
typing-extensions = ">=4.6.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dmypy = ["psutil (>=4.0)"]
|
||||||
|
install-types = ["pip"]
|
||||||
|
mypyc = ["setuptools (>=50)"]
|
||||||
|
reports = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||||
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "neo4j"
|
||||||
|
version = "4.4.12"
|
||||||
|
description = "Neo4j Bolt driver for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "neo4j-4.4.12.tar.gz", hash = "sha256:b138271400e1ef2b89738e90ae0beb96b005f7dfedd68b17c4b85ee732d54125"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pytz = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodeenv"
|
||||||
|
version = "1.9.1"
|
||||||
|
description = "Node.js virtual environment builder"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
|
||||||
|
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nox"
|
||||||
|
version = "2024.10.9"
|
||||||
|
description = "Flexible test automation."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "nox-2024.10.9-py3-none-any.whl", hash = "sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab"},
|
||||||
|
{file = "nox-2024.10.9.tar.gz", hash = "sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
argcomplete = ">=1.9.4,<4"
|
||||||
|
colorlog = ">=2.6.1,<7"
|
||||||
|
packaging = ">=20.9"
|
||||||
|
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||||
|
virtualenv = ">=20.14.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tox-to-nox = ["jinja2", "tox"]
|
||||||
|
uv = ["uv (>=0.1.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "24.1"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||||
|
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.12.1"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||||
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.3.6"
|
||||||
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||||
|
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
|
||||||
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
|
||||||
|
type = ["mypy (>=1.11.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.5.0"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||||
|
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pre-commit"
|
||||||
|
version = "4.0.1"
|
||||||
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
|
||||||
|
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cfgv = ">=2.0.0"
|
||||||
|
identify = ">=1.0.0"
|
||||||
|
nodeenv = ">=0.11.1"
|
||||||
|
pyyaml = ">=5.1"
|
||||||
|
virtualenv = ">=20.10.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycodestyle"
|
||||||
|
version = "2.12.1"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"},
|
||||||
|
{file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyflakes"
|
||||||
|
version = "3.2.0"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
|
||||||
|
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.3.3"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
||||||
|
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||||
|
iniconfig = "*"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=1.5,<2"
|
||||||
|
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytz"
|
||||||
|
version = "2024.2"
|
||||||
|
description = "World timezone definitions, modern and historical"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
|
||||||
|
{file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.2"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
|
||||||
|
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.0.2"
|
||||||
|
description = "A lil' TOML parser"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
|
||||||
|
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.12.2"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtualenv"
|
||||||
|
version = "20.26.6"
|
||||||
|
description = "Virtual Python Environment builder"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
|
||||||
|
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
distlib = ">=0.3.7,<1"
|
||||||
|
filelock = ">=3.12.2,<4"
|
||||||
|
platformdirs = ">=3.9.1,<5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||||
|
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.9"
|
||||||
|
content-hash = "63b654a97a3f0c7cebe4798a26d1e709771484fdf521e83af3fac97216bf289f"
|
46
pyproject.toml
Normal file
46
pyproject.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "norduniclient"
|
||||||
|
version = "1.4.4"
|
||||||
|
description = "Neo4j (>=4.4.12) database client using bolt for NORDUnet network inventory"
|
||||||
|
authors = [
|
||||||
|
"Johan Lundberg <benedith@sunet.se>",
|
||||||
|
"Markus Krogh <benedith@sunet.se>",
|
||||||
|
"Benedith Mulongo <benedith@sunet.se>",
|
||||||
|
]
|
||||||
|
readme = "norduniclient-package.md"
|
||||||
|
homepage = "https://github.com/NORDUnet/python-norduniclient"
|
||||||
|
repository = "https://github.com/NORDUnet/python-norduniclient"
|
||||||
|
documentation = "https://github.com/NORDUnet/python-norduniclient"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.9"
|
||||||
|
neo4j = "4.4.12"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
black = "^24.10.0"
|
||||||
|
mypy = "^1.11.2"
|
||||||
|
pytest = "^8.3.3"
|
||||||
|
flake8 = "^7.1.1"
|
||||||
|
nox = "^2024.10.9"
|
||||||
|
pre-commit = "^4.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.flake8]
|
||||||
|
max-line-length = 125
|
||||||
|
exclude = ['.git', '__pycache__']
|
||||||
|
extend-ignore = [
|
||||||
|
# PEP 8 recommends to treat : in slices as a binary operator with the lowest priority, and to leave an equal
|
||||||
|
# amount of space on either side, except if a parameter is omitted (e.g. ham[1 + 1 :]).
|
||||||
|
# This behaviour may raise E203 whitespace before ':' warnings in style guide enforcement tools like Flake8.
|
||||||
|
# Since E203 is not PEP 8 compliant, we tell Flake8 to ignore this warning.
|
||||||
|
# https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices
|
||||||
|
"F405",
|
||||||
|
"F403"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
10
release_for_neo4j_version.sh
Executable file
10
release_for_neo4j_version.sh
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
TAG="neo4j-3.0"
|
||||||
|
|
||||||
|
git tag -d $TAG
|
||||||
|
git tag $TAG
|
||||||
|
git push -f origin $TAG
|
||||||
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
421
tests/test_core.py
Normal file
421
tests/test_core.py
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
try:
|
||||||
|
from neo4j.exceptions import ConstraintError
|
||||||
|
except ImportError:
|
||||||
|
from neo4j.v1.api import CypherError as ConstraintError # Backwards compatability with version <1.2
|
||||||
|
|
||||||
|
from norduniclient.testing import Neo4jTestCase
|
||||||
|
from norduniclient import core
|
||||||
|
from norduniclient import exceptions
|
||||||
|
from norduniclient import models
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
class CoreTests(Neo4jTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(CoreTests, self).setUp()
|
||||||
|
core.create_node(self.neo4jdb, name='Test Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='1')
|
||||||
|
core.create_node(self.neo4jdb, name='Test Node 2', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='2')
|
||||||
|
|
||||||
|
def test_create_and_get_node(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Test Node 3', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
node = core.get_node(self.neo4jdb, handle_id='3')
|
||||||
|
self.assertEqual(node.get('handle_id'), '3')
|
||||||
|
|
||||||
|
def test_create_node_existing_node_handle(self):
|
||||||
|
self.assertRaises(ConstraintError, core.create_node, self.neo4jdb, name='Test Node 1',
|
||||||
|
meta_type_label='Logical', type_label='Test_Node', handle_id='1')
|
||||||
|
|
||||||
|
def test_create_node_bad_meta_type(self):
|
||||||
|
self.assertRaises(exceptions.MetaLabelNamingError, core.create_node, self.neo4jdb, name='Test Node 1',
|
||||||
|
meta_type_label='No_Such_Label', type_label='Test_Node', handle_id='1')
|
||||||
|
|
||||||
|
def test_get_node_bundle(self):
|
||||||
|
node_bundle = core.get_node_bundle(self.neo4jdb, handle_id='1')
|
||||||
|
self.assertIsInstance(node_bundle, dict)
|
||||||
|
node_data = node_bundle.get('data')
|
||||||
|
self.assertEqual(node_data.get('handle_id'), '1')
|
||||||
|
self.assertEqual(node_bundle.get('meta_type'), 'Logical')
|
||||||
|
self.assertIsInstance(node_bundle.get('labels'), list)
|
||||||
|
self.assertIn('Test_Node', node_bundle.get('labels'))
|
||||||
|
|
||||||
|
def test_failing_get_node_bundle(self):
|
||||||
|
self.assertRaises(exceptions.NodeNotFound, core.get_node_bundle, self.neo4jdb, handle_id='3')
|
||||||
|
|
||||||
|
def test_delete_node(self):
|
||||||
|
core.delete_node(self.neo4jdb, handle_id='1')
|
||||||
|
self.assertRaises(exceptions.NodeNotFound, core.get_node, self.neo4jdb, handle_id='1')
|
||||||
|
|
||||||
|
def test_create_and_get_relationship(self):
|
||||||
|
relationship_id = core._create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2', rel_type='Tests')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
relationship = core.get_relationship_bundle(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertEqual(relationship['id'], relationship_id)
|
||||||
|
|
||||||
|
def test_failing_get_relationship(self):
|
||||||
|
self.assertRaises(exceptions.RelationshipNotFound, core.get_relationship, self.neo4jdb, relationship_id=1)
|
||||||
|
|
||||||
|
def test_get_relationship_bundle(self):
|
||||||
|
relationship_id = core._create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2', rel_type='Tests')
|
||||||
|
relationship_bundle = core.get_relationship_bundle(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertIsInstance(relationship_bundle, dict)
|
||||||
|
relationship = relationship_bundle.get('data')
|
||||||
|
self.assertIsNotNone(relationship)
|
||||||
|
self.assertEqual(relationship_bundle.get('id'), relationship_id)
|
||||||
|
self.assertEqual(relationship_bundle.get('start')['handle_id'], '1')
|
||||||
|
self.assertEqual(relationship_bundle.get('end')['handle_id'], '2')
|
||||||
|
self.assertEqual(relationship_bundle.get('type'), 'Tests')
|
||||||
|
|
||||||
|
def test_failing_get_relationship_bundle(self):
|
||||||
|
self.assertRaises(exceptions.RelationshipNotFound, core.get_relationship_bundle, self.neo4jdb,
|
||||||
|
relationship_id=1)
|
||||||
|
|
||||||
|
def test_delete_relationship(self):
|
||||||
|
relationship_id = core._create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2', rel_type='Tests')
|
||||||
|
relationship = core.get_relationship_bundle(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertEqual(relationship['id'], relationship_id)
|
||||||
|
core.delete_relationship(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertRaises(exceptions.RelationshipNotFound, core.get_relationship, self.neo4jdb,
|
||||||
|
relationship_id=relationship_id)
|
||||||
|
|
||||||
|
def test_create_location_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 2', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
relationship_id = core.create_location_relationship(self.neo4jdb, location_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Has')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
def test_failing_create_location_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 2', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_location_relationship, self.neo4jdb,
|
||||||
|
location_handle_id='3', other_handle_id='4', rel_type='Has')
|
||||||
|
|
||||||
|
def test_create_logical_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 2', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 2', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
|
||||||
|
relationship_id = core.create_logical_relationship(self.neo4jdb, logical_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Depends_on')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_logical_relationship(self.neo4jdb, logical_handle_id='3', other_handle_id='5',
|
||||||
|
rel_type='Depends_on')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_logical_relationship(self.neo4jdb, logical_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Part_of')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
def test_failing_create_logical_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 2', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 2', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_location_relationship, self.neo4jdb,
|
||||||
|
location_handle_id='3', other_handle_id='4', rel_type='Has')
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible,core.create_location_relationship, self.neo4jdb,
|
||||||
|
location_handle_id='3', other_handle_id='5', rel_type='Part_of')
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible,core.create_location_relationship, self.neo4jdb,
|
||||||
|
location_handle_id='3', other_handle_id='5', rel_type='Has')
|
||||||
|
|
||||||
|
def test_create_relation_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Relation Node 1', meta_type_label='Relation',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 1', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='6')
|
||||||
|
|
||||||
|
relationship_id = core.create_relation_relationship(self.neo4jdb, relation_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Uses')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relation_relationship(self.neo4jdb, relation_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Provides')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relation_relationship(self.neo4jdb, relation_handle_id='3', other_handle_id='5',
|
||||||
|
rel_type='Responsible_for')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relation_relationship(self.neo4jdb, relation_handle_id='3', other_handle_id='6',
|
||||||
|
rel_type='Owns')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relation_relationship(self.neo4jdb, relation_handle_id='3', other_handle_id='6',
|
||||||
|
rel_type='Provides')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
def test_failing_create_relation_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Relation Node 1', meta_type_label='Relation',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Logical Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 1', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='6')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relation_relationship, self.neo4jdb,
|
||||||
|
relation_handle_id='3', other_handle_id='5', rel_type='Uses')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relation_relationship, self.neo4jdb,
|
||||||
|
relation_handle_id='3', other_handle_id='6', rel_type='Responsible_for')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relation_relationship,
|
||||||
|
self.neo4jdb, relation_handle_id='3', other_handle_id='6', rel_type='Responsible_for')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relation_relationship, self.neo4jdb,
|
||||||
|
relation_handle_id='3', other_handle_id='5', rel_type='Owns')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relation_relationship, self.neo4jdb,
|
||||||
|
relation_handle_id='3', other_handle_id='5', rel_type='Provides')
|
||||||
|
|
||||||
|
def test_create_physical_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 1', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 2', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
|
||||||
|
relationship_id = core.create_physical_relationship(self.neo4jdb, physical_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Has')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_physical_relationship(self.neo4jdb, physical_handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Connected_to')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_physical_relationship(self.neo4jdb, physical_handle_id='3', other_handle_id='5',
|
||||||
|
rel_type='Located_in')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
def test_failing_create_physical_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 1', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 2', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_physical_relationship, self.neo4jdb,
|
||||||
|
physical_handle_id='3', other_handle_id='4', rel_type='Located_in')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_physical_relationship, self.neo4jdb,
|
||||||
|
physical_handle_id='3', other_handle_id='4', rel_type='Responsible_for')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_physical_relationship,
|
||||||
|
self.neo4jdb, physical_handle_id='3', other_handle_id='5', rel_type='Has')
|
||||||
|
|
||||||
|
def test_create_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 2', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
core.create_node(self.neo4jdb, name='Relation Node 1', meta_type_label='Relation',
|
||||||
|
type_label='Test_Node', handle_id='5')
|
||||||
|
core.create_node(self.neo4jdb, name='Physical Node 1', meta_type_label='Physical',
|
||||||
|
type_label='Test_Node', handle_id='6')
|
||||||
|
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='3', other_handle_id='4',
|
||||||
|
rel_type='Has')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='5', other_handle_id='4',
|
||||||
|
rel_type='Responsible_for')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='6', other_handle_id='4',
|
||||||
|
rel_type='Located_in')
|
||||||
|
self.assertIsInstance(relationship_id, int)
|
||||||
|
|
||||||
|
def test_failing_create_relationship(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 2', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='4')
|
||||||
|
self.assertRaises(exceptions.NoRelationshipPossible, core.create_relationship, self.neo4jdb,
|
||||||
|
handle_id='3', other_handle_id='4', rel_type='Has')
|
||||||
|
|
||||||
|
def test_get_relationships(self):
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2',
|
||||||
|
rel_type='Depends_on')
|
||||||
|
|
||||||
|
relationships = core.get_relationships(self.neo4jdb, handle_id1='1', handle_id2='2')
|
||||||
|
self.assertIn(relationship_id, [r.id for r in relationships])
|
||||||
|
|
||||||
|
relationships = core.get_relationships(self.neo4jdb, handle_id1='1', handle_id2='2', rel_type='Depends_on')
|
||||||
|
self.assertIn(relationship_id, [r.id for r in relationships])
|
||||||
|
|
||||||
|
# No relationship
|
||||||
|
core.create_node(self.neo4jdb, name='Location Node 1', meta_type_label='Location',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
relationships = core.get_relationships(self.neo4jdb, handle_id1='1', handle_id2='3')
|
||||||
|
self.assertEqual(relationships, [])
|
||||||
|
|
||||||
|
def test_set_node_properties(self):
|
||||||
|
new_properties = {'test': 'hello world'}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
node = core.get_node(self.neo4jdb, handle_id='1')
|
||||||
|
new_properties.update({'handle_id': '1'})
|
||||||
|
self.assertEqual(node['test'], new_properties['test'])
|
||||||
|
|
||||||
|
# def test_fail_set_node_properties(self):
|
||||||
|
# new_properties = {'test': set([])}
|
||||||
|
# self.assertRaises(exceptions.BadProperties, core.set_node_properties, self.neo4jdb,
|
||||||
|
# handle_id='1', new_properties=new_properties)
|
||||||
|
|
||||||
|
def test_set_relationship_properties(self):
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2',
|
||||||
|
rel_type='Depends_on')
|
||||||
|
new_properties = {'test': 'hello world'}
|
||||||
|
core.set_relationship_properties(self.neo4jdb, relationship_id=relationship_id, new_properties=new_properties)
|
||||||
|
relationship = core.get_relationship(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertEqual(relationship['test'], new_properties['test'])
|
||||||
|
|
||||||
|
# def test_fail_set_relationship_properties(self):
|
||||||
|
# relationship_id = core.create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2',
|
||||||
|
# rel_type='Depends_on')
|
||||||
|
# new_properties = {'test': set([])}
|
||||||
|
# self.assertRaises(exceptions.BadProperties, core.set_relationship_properties, self.neo4jdb,
|
||||||
|
# relationship_id=relationship_id, new_properties=new_properties)
|
||||||
|
|
||||||
|
def test_get_node_model(self):
|
||||||
|
node_model = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
self.assertIsInstance(node_model, models.LogicalModel)
|
||||||
|
|
||||||
|
def test_get_relationship_model(self):
|
||||||
|
relationship_id = core.create_relationship(self.neo4jdb, handle_id='1', other_handle_id='2',
|
||||||
|
rel_type='Depends_on')
|
||||||
|
relationship_model = core.get_relationship_model(self.neo4jdb, relationship_id=relationship_id)
|
||||||
|
self.assertIsInstance(relationship_model, models.BaseRelationshipModel)
|
||||||
|
|
||||||
|
def test_get_nodes_by_value_and_property(self):
|
||||||
|
new_properties = {'test': 'hello world'}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.get_nodes_by_value(self.neo4jdb, value='hello world', prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), 'hello world')
|
||||||
|
|
||||||
|
def test_get_nodes_by_value_and_property_list(self):
|
||||||
|
new_properties = {'test': ['hello', 'world']}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.get_nodes_by_value(self.neo4jdb, value=['hello', 'world'], prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), ['hello', 'world'])
|
||||||
|
|
||||||
|
def test_get_nodes_by_value_and_property_bool(self):
|
||||||
|
new_properties = {'test': False}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.get_nodes_by_value(self.neo4jdb, value=False, prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), False)
|
||||||
|
|
||||||
|
def test_get_nodes_by_value_and_property_int(self):
|
||||||
|
new_properties = {'test': 3}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.get_nodes_by_value(self.neo4jdb, value=3, prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), 3)
|
||||||
|
|
||||||
|
def test_search_nodes_by_value(self):
|
||||||
|
new_properties = {'test': 'hello world'}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
|
||||||
|
result = core.search_nodes_by_value(self.neo4jdb, value='world')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), 'hello world')
|
||||||
|
|
||||||
|
def test_search_nodes_by_value_and_property(self):
|
||||||
|
new_properties = {'test': 'hello world'}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.search_nodes_by_value(self.neo4jdb, value='world', prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), 'hello world')
|
||||||
|
|
||||||
|
def test_search_nodes_by_value_in_list(self):
|
||||||
|
new_properties = {'test': ['hello', 'world']}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
|
||||||
|
result = core.search_nodes_by_value(self.neo4jdb, value='hel')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), ['hello', 'world'])
|
||||||
|
|
||||||
|
def test_search_nodes_by_value_and_property_in_list(self):
|
||||||
|
new_properties = {'test': ['hello', 'world']}
|
||||||
|
core.set_node_properties(self.neo4jdb, handle_id='1', new_properties=new_properties)
|
||||||
|
result = core.search_nodes_by_value(self.neo4jdb, value='hel', prop='test')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node.get('test'), ['hello', 'world'])
|
||||||
|
|
||||||
|
def test_get_nodes_by_type(self):
|
||||||
|
result = core.get_nodes_by_type(self.neo4jdb, 'Test_Node')
|
||||||
|
|
||||||
|
for node in result:
|
||||||
|
self.assertIn('Test_Node', node.labels)
|
||||||
|
|
||||||
|
def test_get_nodes_by_name(self):
|
||||||
|
result = core.get_nodes_by_name(self.neo4jdb, 'Test Node 1')
|
||||||
|
|
||||||
|
all_results = [r for r in result]
|
||||||
|
self.assertEqual(len(all_results), 1)
|
||||||
|
node = all_results[0]
|
||||||
|
self.assertEqual(node['name'], 'Test Node 1')
|
||||||
|
|
||||||
|
def test_get_unique_node_by_name(self):
|
||||||
|
node_model = core.get_unique_node_by_name(self.neo4jdb, node_name='Test Node 1', node_type='Test_Node')
|
||||||
|
self.assertIsInstance(node_model, models.LogicalModel)
|
||||||
|
|
||||||
|
def test_failing_get_unique_node_by_name(self):
|
||||||
|
core.create_node(self.neo4jdb, name='Test Node 1', meta_type_label='Logical',
|
||||||
|
type_label='Test_Node', handle_id='3')
|
||||||
|
self.assertRaises(exceptions.MultipleNodesReturned, core.get_unique_node_by_name, self.neo4jdb,
|
||||||
|
node_name='Test Node 1', node_type='Test_Node')
|
||||||
|
|
34
tests/test_helpers.py
Normal file
34
tests/test_helpers.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from norduniclient import helpers
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
class HelpersTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_update_item_properties(self):
|
||||||
|
initial_props = {
|
||||||
|
'string': 'hello world',
|
||||||
|
'delete_me': 'byebye',
|
||||||
|
'list': ['hello', 'world'],
|
||||||
|
'int': 3
|
||||||
|
}
|
||||||
|
update_props = {
|
||||||
|
'string': 'hola el mundo',
|
||||||
|
'delete_me': '',
|
||||||
|
'list': ['hello'],
|
||||||
|
'int': 0
|
||||||
|
}
|
||||||
|
new_props = helpers.update_item_properties(initial_props, update_props)
|
||||||
|
expected_props = {
|
||||||
|
'string': 'hola el mundo',
|
||||||
|
'list': ['hello'],
|
||||||
|
'int': 0
|
||||||
|
}
|
||||||
|
self.assertEqual(new_props, expected_props)
|
||||||
|
|
||||||
|
|
726
tests/test_models.py
Normal file
726
tests/test_models.py
Normal file
|
@ -0,0 +1,726 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from norduniclient.testing import Neo4jTestCase
|
||||||
|
from norduniclient import core
|
||||||
|
from norduniclient import exceptions
|
||||||
|
from norduniclient import models
|
||||||
|
|
||||||
|
__author__ = 'lundberg'
|
||||||
|
|
||||||
|
|
||||||
|
class ModelsTests(Neo4jTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ModelsTests, self).setUp()
|
||||||
|
q1 = """
|
||||||
|
// Create nodes
|
||||||
|
CREATE (router1:Node:Physical:Router{name:'Router1', handle_id:'1'}),
|
||||||
|
(port1:Node:Physical:Port{name:'Port1', handle_id:'2'}),
|
||||||
|
(unit1:Node:Logical:Unit{name:'Unit1', handle_id:'3'}),
|
||||||
|
(port6:Node:Physical:Port{name:'Port6', handle_id:'4'}),
|
||||||
|
(unit2:Node:Logical:Unit{name:'Unit2', handle_id:'5'}),
|
||||||
|
(provider1:Node:Relation:Provider{name:'Provider1', handle_id:'6'}),
|
||||||
|
(peering_group1:Node:Logical:Peering_Group{name:'Peering Group1', handle_id:'7'}),
|
||||||
|
(peering_partner1:Node:Relation:Peering_Partner{name:'Peering Partner1', handle_id:'8'}),
|
||||||
|
(service2:Node:Logical:Service{name:'Service2', handle_id:'9'}),
|
||||||
|
(service3:Node:Logical:Service{name:'Service3', handle_id:'10'}),
|
||||||
|
(site1:Node:Location:Site{name:'Site1', handle_id:'11'}),
|
||||||
|
(rack1:Node:Location:Rack{name:'Rack1', handle_id:'12'}),
|
||||||
|
(optical_node1:Node:Physical:Optical_Node{name:'Optical Node1', handle_id:'13'}),
|
||||||
|
(port2:Node:Physical:Port{name:'Port2', handle_id:'14', description:'This is a port'}),
|
||||||
|
(rack2:Node:Location:Rack{name:'Rack2', handle_id:'15'}),
|
||||||
|
(optical_node2:Node:Physical:Optical_Node{name:'Optical Node2', handle_id:'16'}),
|
||||||
|
(port3:Node:Physical:Port{name:'Port3', handle_id:'17'}),
|
||||||
|
(site2:Node:Location:Site{name:'Site2', handle_id:'18'}),
|
||||||
|
(rack3:Node:Location:Rack{name:'Rack3', handle_id:'19'}),
|
||||||
|
(optical_path1:Node:Logical:Optical_Path{name:'Optical Path1', handle_id:'20'}),
|
||||||
|
(optical_link1:Node:Logical:Optical_Link{name:'Optical Link1', handle_id:'21'}),
|
||||||
|
(optical_link2:Node:Logical:Optical_Link{name:'Optical Link2', handle_id:'22'}),
|
||||||
|
(odf1:Node:Physical:ODF{name:'ODF1', handle_id:'23'}),
|
||||||
|
(port4:Node:Physical:Port{name:'Port4', handle_id:'24'}),
|
||||||
|
(odf2:Node:Physical:ODF{name:'ODF2', handle_id:'25'}),
|
||||||
|
(port5:Node:Physical:Port{name:'Port5', handle_id:'26'}),
|
||||||
|
(port7:Node:Physical:Port{name:'Port7', handle_id:'27'}),
|
||||||
|
(cable1:Node:Physical:Cable{name:'Cable1', handle_id:'28'}),
|
||||||
|
(cable2:Node:Physical:Cable{name:'Cable2', handle_id:'29'}),
|
||||||
|
(cable3:Node:Physical:Cable{name:'Cable3', handle_id:'30'}),
|
||||||
|
(cable4:Node:Physical:Cable{name:'Cable4', handle_id:'31'}),
|
||||||
|
(host1:Node:Physical:Host{name:'Host1', handle_id:'32'}),
|
||||||
|
(host2:Node:Logical:Host{name:'Host2', handle_id:'33'}),
|
||||||
|
(customer1:Node:Relation:Customer{name:'Customer1', handle_id:'34'}),
|
||||||
|
(customer2:Node:Relation:Customer{name:'Customer2', handle_id:'35'}),
|
||||||
|
(customer3:Node:Relation:Customer{name:'Customer3', handle_id:'36'}),
|
||||||
|
(customer4:Node:Relation:Customer{name:'Customer4', handle_id:'37'}),
|
||||||
|
(service4:Node:Logical:Service{name:'Service4', handle_id:'38'}),
|
||||||
|
(provider2:Node:Relation:Provider{name:'Provider2', handle_id:'39'}),
|
||||||
|
(port8:Node:Physical:Port{name:'Port8', handle_id:'40'}),
|
||||||
|
(rack4:Node:Location:Rack{name:'Rack4', handle_id:'41'}),
|
||||||
|
(cable5:Node:Physical:Cable{name:'Cable5', handle_id:'42'}),
|
||||||
|
(peering_group2:Node:Logical:Peering_Group{name:'Peering Group2', handle_id:'44'}),
|
||||||
|
(cable6:Node:Physical:Cable{name:'Cable6', handle_id:'45'}),
|
||||||
|
(service5:Node:Logical:Service{name:'Service5', handle_id:'46'}),
|
||||||
|
(external_equipment1:Node:Physical:External_Equipment{name:'External Equipment1', handle_id:'47'}),
|
||||||
|
|
||||||
|
// Create relationships
|
||||||
|
(router1)-[:Has]->(port1),
|
||||||
|
(unit1)-[:Part_of]->(port1),
|
||||||
|
(router1)-[:Has]->(port6),
|
||||||
|
(unit2)-[:Part_of]->(port6),
|
||||||
|
(provider1)-[:Owns]->(router1),
|
||||||
|
(provider1)-[:Provides]->(peering_group1),
|
||||||
|
(peering_partner1)-[:Uses {ip_address:'127.0.0.1'}]->(peering_group1),
|
||||||
|
(peering_group1)-[:Depends_on]->(unit1),
|
||||||
|
(site1)-[:Has]->(rack1),
|
||||||
|
(router1)-[:Located_in]->(rack1),
|
||||||
|
(provider1)-[:Responsible_for]->(rack1),
|
||||||
|
(optical_node1)-[:Has]->(port2),
|
||||||
|
(site1)-[:Has]->(rack2),
|
||||||
|
(optical_node1)-[:Located_in]->(rack2),
|
||||||
|
(optical_node2)-[:Has]->(port3),
|
||||||
|
(site2)-[:Has]->(rack3),
|
||||||
|
(optical_node2)-[:Located_in]->(rack3),
|
||||||
|
(provider1)-[:Provides]->(optical_path1),
|
||||||
|
(service2)-[:Depends_on]->(optical_path1),
|
||||||
|
(service3)-[:Depends_on]->(unit2),
|
||||||
|
(odf1)-[:Located_in]->(rack2),
|
||||||
|
(odf1)-[:Has]->(port4),
|
||||||
|
(odf2)-[:Located_in]->(rack3),
|
||||||
|
(odf2)-[:Has]->(port5),
|
||||||
|
(odf2)-[:Has]->(port7),
|
||||||
|
(port4)<-[:Connected_to]-(cable1)-[:Connected_to]->(port2),
|
||||||
|
(port5)<-[:Connected_to]-(cable2)-[:Connected_to]->(port3),
|
||||||
|
(port4)<-[:Connected_to]-(cable3)-[:Connected_to]->(port5),
|
||||||
|
(port6)<-[:Connected_to]-(cable4)-[:Connected_to]->(port7),
|
||||||
|
(port7)<-[:Connected_to]-(cable5),
|
||||||
|
(optical_link1)-[:Depends_on]->(port2),
|
||||||
|
(optical_link2)-[:Depends_on]->(port3),
|
||||||
|
(optical_link1)-[:Depends_on]->(port4),
|
||||||
|
(optical_link2)-[:Depends_on]->(port5),
|
||||||
|
(optical_path1)-[:Depends_on]->(port4),
|
||||||
|
(optical_path1)-[:Depends_on]->(port5),
|
||||||
|
(optical_path1)-[:Depends_on]->(optical_link1),
|
||||||
|
(optical_path1)-[:Depends_on]->(optical_link2),
|
||||||
|
(provider1)-[:Owns]->(host1),
|
||||||
|
(host2)-[:Depends_on]->(host1),
|
||||||
|
(customer1)-[:Uses]->(host2),
|
||||||
|
(customer2)-[:Uses]->(service2),
|
||||||
|
(customer2)-[:Uses]->(service3),
|
||||||
|
(customer3)-[:Uses]->(service3),
|
||||||
|
(service5)-[:Depends_on]->(external_equipment1)
|
||||||
|
"""
|
||||||
|
|
||||||
|
q2 = """
|
||||||
|
// Create nodes
|
||||||
|
CREATE (physical1:Node:Physical:Generic{name:'Physical1', handle_id:'101'}),
|
||||||
|
(physical2:Node:Physical:Generic{name:'Physical2', handle_id:'102', description:'This is a port'}),
|
||||||
|
(logical1:Node:Logical:Generic{name:'Logical1', handle_id:'103'}),
|
||||||
|
(physical3:Node:Physical:Generic{name:'Physical3', handle_id:'104'}),
|
||||||
|
(logical2:Node:Logical:Generic{name:'Logical2', handle_id:'105'}),
|
||||||
|
(relation1:Node:Relation:Generic{name:'Relation1', handle_id:'106'}),
|
||||||
|
(logical3:Node:Logical:Generic{name:'Logical3', handle_id:'107'}),
|
||||||
|
(relation2:Node:Relation:Generic{name:'Relation2', handle_id:'108'}),
|
||||||
|
(location1:Node:Location:Generic{name:'Location1', handle_id:'109'}),
|
||||||
|
(location2:Node:Location:Generic{name:'Location2', handle_id:'110'}),
|
||||||
|
(logical4:Node:Logical:Generic{name:'Logical4', handle_id:'111'}),
|
||||||
|
(physical4:Node:Physical:Generic{name:'Physical4', handle_id:'112', description:'This is a cable'}),
|
||||||
|
|
||||||
|
// Create relationships
|
||||||
|
(physical1)-[:Has]->(physical2),
|
||||||
|
(logical1)-[:Part_of]->(physical2),
|
||||||
|
(physical1)-[:Has]->(physical3),
|
||||||
|
(logical2)-[:Part_of]->(physical3),
|
||||||
|
(relation1)-[:Owns]->(physical1),
|
||||||
|
(relation1)-[:Provides]->(logical3),
|
||||||
|
(relation2)-[:Uses]->(logical3),
|
||||||
|
(logical3)-[:Depends_on]->(logical1),
|
||||||
|
(location1)-[:Has]->(location2),
|
||||||
|
(physical1)-[:Located_in]->(location2),
|
||||||
|
(relation1)-[:Responsible_for]->(location2),
|
||||||
|
(logical4)-[:Depends_on]->(logical3),
|
||||||
|
(physical2)<-[:Connected_to]-(physical4)-[:Connected_to]->(physical3)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Insert mocked network
|
||||||
|
with self.neo4jdb.session as s:
|
||||||
|
s.run(q1)
|
||||||
|
|
||||||
|
# Insert generic models
|
||||||
|
with self.neo4jdb.session as s:
|
||||||
|
s.run(q2)
|
||||||
|
|
||||||
|
def test_base_node_model(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
node_model_2 = core.get_node_model(self.neo4jdb, handle_id='102')
|
||||||
|
|
||||||
|
self.assertIsNotNone(str(node_model_1))
|
||||||
|
self.assertIsNotNone(repr(node_model_1))
|
||||||
|
|
||||||
|
self.assertEqual(node_model_1, node_model_1)
|
||||||
|
self.assertGreater(node_model_2, node_model_1)
|
||||||
|
self.assertLess(node_model_1, node_model_2)
|
||||||
|
|
||||||
|
self.assertEqual(node_model_1.handle_id, '101')
|
||||||
|
self.assertIn(node_model_1.meta_type, core.META_TYPES)
|
||||||
|
self.assertIsInstance(node_model_1.labels, list)
|
||||||
|
self.assertIsNotNone(node_model_1.data)
|
||||||
|
self.assertIsInstance(node_model_1.incoming, dict)
|
||||||
|
self.assertIsInstance(node_model_1.outgoing, dict)
|
||||||
|
self.assertIsInstance(node_model_1.relationships, dict)
|
||||||
|
|
||||||
|
def test_add_label(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
initial_labels = node_model_1.labels
|
||||||
|
node_model_1.add_label('Test_Label')
|
||||||
|
node_model_1 = node_model_1.reload()
|
||||||
|
new_labels = node_model_1.labels
|
||||||
|
initial_labels.append('Test_Label')
|
||||||
|
self.assertEqual(sorted(new_labels), sorted(initial_labels))
|
||||||
|
|
||||||
|
def test_remove_label(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
initial_labels = node_model_1.labels
|
||||||
|
node_model_1 = node_model_1.add_label('Test_Label')
|
||||||
|
new_labels = node_model_1.labels
|
||||||
|
expected_labels = initial_labels + ['Test_Label']
|
||||||
|
self.assertEqual(sorted(new_labels), sorted(expected_labels))
|
||||||
|
node_model_1 = node_model_1.remove_label('Test_Label')
|
||||||
|
new_labels = node_model_1.labels
|
||||||
|
self.assertEqual(sorted(new_labels), sorted(initial_labels))
|
||||||
|
|
||||||
|
def test_change_meta_type(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
self.assertEqual(node_model_1.meta_type, 'Physical')
|
||||||
|
node_model_1 = node_model_1.change_meta_type('Logical')
|
||||||
|
self.assertEqual(node_model_1.meta_type, 'Logical')
|
||||||
|
|
||||||
|
def test_switch_type(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
self.assertIn('Generic', node_model_1.labels)
|
||||||
|
node_model_1 = node_model_1.switch_type(old_type='Generic', new_type='New_Type')
|
||||||
|
self.assertNotIn('Generic', node_model_1.labels)
|
||||||
|
self.assertIn('New_Type', node_model_1.labels)
|
||||||
|
|
||||||
|
def test_delete(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
node_model_1.delete()
|
||||||
|
self.assertRaises(exceptions.NodeNotFound, core.get_node_model, self.neo4jdb, handle_id='101')
|
||||||
|
|
||||||
|
def test_base_relationship_model(self):
|
||||||
|
node_model_1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
outgoing_relationships = node_model_1.outgoing
|
||||||
|
self.assertGreater(len(outgoing_relationships), 0)
|
||||||
|
|
||||||
|
for rel_type, relationships in outgoing_relationships.items():
|
||||||
|
self.assertIsNotNone(rel_type)
|
||||||
|
for item in relationships:
|
||||||
|
relationship_model = core.get_relationship_model(self.neo4jdb, item['relationship_id'])
|
||||||
|
self.assertIsNotNone(str(relationship_model))
|
||||||
|
self.assertIsNotNone(repr(relationship_model))
|
||||||
|
self.assertIsNotNone(relationship_model.type)
|
||||||
|
self.assertIsInstance(relationship_model.id, int)
|
||||||
|
self.assertIsNotNone(relationship_model.data)
|
||||||
|
self.assertEqual(relationship_model.start['handle_id'], node_model_1.handle_id)
|
||||||
|
self.assertEqual(relationship_model.end['handle_id'], item['node'].handle_id)
|
||||||
|
|
||||||
|
def test_get_location_path(self):
|
||||||
|
# Model with location
|
||||||
|
physical1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
location_path = physical1.get_location_path()
|
||||||
|
self.assertEqual(location_path['location_path'][0]['name'], 'Location1')
|
||||||
|
self.assertEqual(location_path['location_path'][1]['name'], 'Location2')
|
||||||
|
|
||||||
|
# Model without location
|
||||||
|
relation1 = core.get_node_model(self.neo4jdb, handle_id='106')
|
||||||
|
location_path = relation1.get_location_path()
|
||||||
|
self.assertEqual(location_path['location_path'], [])
|
||||||
|
|
||||||
|
def test_get_location(self):
|
||||||
|
# Model with location
|
||||||
|
physical1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
location = physical1.get_location()
|
||||||
|
self.assertIsInstance(location['Located_in'][0]['node'], models.LocationModel)
|
||||||
|
self.assertEqual(location['Located_in'][0]['node'].data['name'], 'Location2')
|
||||||
|
self.assertIsInstance(location['Located_in'][0]['relationship_id'], int)
|
||||||
|
|
||||||
|
# Model without location
|
||||||
|
relation1 = core.get_node_model(self.neo4jdb, handle_id='106')
|
||||||
|
location = relation1.get_location()
|
||||||
|
self.assertIsNone(location.get('Located_in'))
|
||||||
|
|
||||||
|
def test_get_placement_path(self):
|
||||||
|
# Models with placement path
|
||||||
|
physical2 = core.get_node_model(self.neo4jdb, handle_id='102')
|
||||||
|
placement_path = physical2.get_placement_path()
|
||||||
|
self.assertEqual(placement_path['placement_path'][0]['name'], 'Physical1')
|
||||||
|
|
||||||
|
# Model without placement path
|
||||||
|
relation1 = core.get_node_model(self.neo4jdb, handle_id='106')
|
||||||
|
location_path = relation1.get_placement_path()
|
||||||
|
self.assertEqual(location_path['placement_path'], [])
|
||||||
|
|
||||||
|
def test_get_child_form_data(self):
|
||||||
|
physical1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
child_form_data = physical1.get_child_form_data(node_type='Generic')
|
||||||
|
for data in child_form_data:
|
||||||
|
self.assertIn(data['handle_id'], ['102', '104'])
|
||||||
|
self.assertIn(data['name'], ['Physical2', 'Physical3'])
|
||||||
|
self.assertIn(data['description'], ['This is a port', None])
|
||||||
|
self.assertEqual(data['labels'], [u'Node', u'Physical', u'Generic'])
|
||||||
|
|
||||||
|
def test_get_relations(self):
|
||||||
|
physical1 = core.get_node_model(self.neo4jdb, handle_id='101')
|
||||||
|
relations = physical1.get_relations()
|
||||||
|
self.assertEqual(physical1.meta_type, 'Physical')
|
||||||
|
self.assertIsInstance(relations['Owns'][0]['node'], models.RelationModel)
|
||||||
|
|
||||||
|
logical3 = core.get_node_model(self.neo4jdb, handle_id='107')
|
||||||
|
relations = logical3.get_relations()
|
||||||
|
self.assertEqual(logical3.meta_type, 'Logical')
|
||||||
|
self.assertIsInstance(relations['Uses'][0]['node'], models.RelationModel)
|
||||||
|
|
||||||
|
logical3 = core.get_node_model(self.neo4jdb, handle_id='107')
|
||||||
|
relations = logical3.get_relations()
|
||||||
|
self.assertIsInstance(relations['Provides'][0]['node'], models.RelationModel)
|
||||||
|
|
||||||
|
location2 = core.get_node_model(self.neo4jdb, handle_id='110')
|
||||||
|
relations = location2.get_relations()
|
||||||
|
self.assertIsInstance(relations['Responsible_for'][0]['node'], models.RelationModel)
|
||||||
|
|
||||||
|
def test_get_dependencies(self):
|
||||||
|
logical3 = core.get_node_model(self.neo4jdb, handle_id='107')
|
||||||
|
dependencies = logical3.get_dependencies()
|
||||||
|
self.assertEqual(len(dependencies['Depends_on']), 1)
|
||||||
|
self.assertEqual(dependencies['Depends_on'][0]['node'].handle_id, '103')
|
||||||
|
self.assertIsInstance(dependencies['Depends_on'][0]['node'], models.LogicalModel)
|
||||||
|
|
||||||
|
def test_get_dependents(self):
|
||||||
|
logical1 = core.get_node_model(self.neo4jdb, handle_id='103')
|
||||||
|
dependents = logical1.get_dependents()
|
||||||
|
self.assertEqual(len(dependents['Depends_on']), 1)
|
||||||
|
self.assertEqual(dependents['Depends_on'][0]['node'].handle_id, '107')
|
||||||
|
self.assertIsInstance(dependents['Depends_on'][0]['node'], models.LogicalModel)
|
||||||
|
|
||||||
|
def test_get_dependent_as_types(self):
|
||||||
|
logical1 = core.get_node_model(self.neo4jdb, handle_id='103')
|
||||||
|
dependents = logical1.get_dependent_as_types()
|
||||||
|
self.assertEqual(dependents['direct'][0]['name'], 'Logical3')
|
||||||
|
self.assertEqual(dependents['links'], [])
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertEqual(dependents['paths'], [])
|
||||||
|
self.assertEqual(dependents['services'], [])
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_port_with_unit_services(self):
|
||||||
|
port6 = core.get_node_model(self.neo4jdb, handle_id='4')
|
||||||
|
dependent = port6.get_dependent_as_types()
|
||||||
|
self.assertEqual(dependent['direct'], [])
|
||||||
|
self.assertEqual(dependent['links'], [])
|
||||||
|
self.assertEqual(dependent['oms'], [])
|
||||||
|
self.assertEqual(dependent['paths'], [])
|
||||||
|
self.assertEqual(len(dependent['services']), 1)
|
||||||
|
self.assertEqual(dependent['services'][0]['name'], 'Service3')
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_equipment(self):
|
||||||
|
external1 = core.get_node_model(self.neo4jdb, handle_id='47')
|
||||||
|
# a bit nasty just moving a port
|
||||||
|
external1.set_has('24') # port4
|
||||||
|
dependents = external1.get_dependent_as_types()
|
||||||
|
self.assertIn('Service5', [n['name'] for n in dependents['direct']])
|
||||||
|
self.assertIn('Optical Link1', [n['name'] for n in dependents['links']])
|
||||||
|
self.assertIn('Optical Link2', [n['name'] for n in dependents['links']])
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertIn('Optical Path1', [n['name'] for n in dependents['paths']])
|
||||||
|
self.assertIn('Service2', [n['name'] for n in dependents['services']])
|
||||||
|
self.assertIn('Service5', [n['name'] for n in dependents['services']])
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_equipment_only_direct(self):
|
||||||
|
external1 = core.get_node_model(self.neo4jdb, handle_id='47')
|
||||||
|
dependents = external1.get_dependent_as_types()
|
||||||
|
self.assertEqual(dependents['direct'][0]['name'], 'Service5')
|
||||||
|
self.assertEqual(dependents['links'], [])
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertEqual(dependents['paths'], [])
|
||||||
|
self.assertEqual(dependents['services'][0]['name'], 'Service5')
|
||||||
|
|
||||||
|
def test_get_dependencies_as_types(self):
|
||||||
|
logical4 = core.get_node_model(self.neo4jdb, handle_id='111')
|
||||||
|
dependencies = logical4.get_dependencies_as_types()
|
||||||
|
self.assertEqual(dependencies['direct'][0]['name'], 'Logical3')
|
||||||
|
self.assertEqual(dependencies['links'], [])
|
||||||
|
self.assertEqual(dependencies['oms'], [])
|
||||||
|
self.assertEqual(dependencies['paths'], [])
|
||||||
|
self.assertEqual(dependencies['services'], [])
|
||||||
|
|
||||||
|
def test_get_ports(self):
|
||||||
|
physical4 = core.get_node_model(self.neo4jdb, handle_id='112')
|
||||||
|
ports = physical4.get_ports()
|
||||||
|
self.assertIsInstance(ports, list)
|
||||||
|
self.assertEqual(len(ports), 0)
|
||||||
|
|
||||||
|
def test_get_part_of_logical_model(self):
|
||||||
|
unit1 = core.get_node_model(self.neo4jdb, handle_id='3')
|
||||||
|
part_of = unit1.get_part_of()
|
||||||
|
self.assertEqual(part_of['Part_of'][0]['node'].handle_id, '2')
|
||||||
|
|
||||||
|
def test_set_user_logical_model(self):
|
||||||
|
customer4 = core.get_node_model(self.neo4jdb, handle_id='37')
|
||||||
|
service4 = core.get_node_model(self.neo4jdb, handle_id='38')
|
||||||
|
|
||||||
|
result = service4.set_user(customer4.handle_id)
|
||||||
|
self.assertEqual(result['Uses'][0]['created'], True)
|
||||||
|
relations = service4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Uses']), 1)
|
||||||
|
self.assertEqual(relations['Uses'][0]['node'].handle_id, customer4.handle_id)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = service4.set_user(customer4.handle_id)
|
||||||
|
self.assertEqual(result['Uses'][0]['created'], False)
|
||||||
|
relations = service4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Uses']), 1)
|
||||||
|
|
||||||
|
def test_set_provider_logical_model(self):
|
||||||
|
provider_1 = core.get_node_model(self.neo4jdb, handle_id='6')
|
||||||
|
service4 = core.get_node_model(self.neo4jdb, handle_id='38')
|
||||||
|
|
||||||
|
result = service4.set_provider(provider_1.handle_id)
|
||||||
|
self.assertEqual(result['Provides'][0]['created'], True)
|
||||||
|
relations = service4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Provides']), 1)
|
||||||
|
self.assertEqual(relations['Provides'][0]['node'].handle_id, provider_1.handle_id)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = service4.set_provider(provider_1.handle_id)
|
||||||
|
self.assertEqual(result['Provides'][0]['created'], False)
|
||||||
|
relations = service4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Provides']), 1)
|
||||||
|
|
||||||
|
def test_set_dependency_logical_model(self):
|
||||||
|
optical_path1 = core.get_node_model(self.neo4jdb, handle_id='20')
|
||||||
|
service4 = core.get_node_model(self.neo4jdb, handle_id='38')
|
||||||
|
|
||||||
|
result = service4.set_dependency(optical_path1.handle_id)
|
||||||
|
self.assertEqual(result['Depends_on'][0]['created'], True)
|
||||||
|
relations = service4.get_dependencies()
|
||||||
|
self.assertEqual(len(relations['Depends_on']), 1)
|
||||||
|
self.assertEqual(relations['Depends_on'][0]['node'].handle_id, optical_path1.handle_id)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = service4.set_dependency(optical_path1.handle_id)
|
||||||
|
self.assertEqual(result['Depends_on'][0]['created'], False)
|
||||||
|
relations = service4.get_dependencies()
|
||||||
|
self.assertEqual(len(relations['Depends_on']), 1)
|
||||||
|
|
||||||
|
def test_get_location_physical_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
location = router1.get_location()
|
||||||
|
self.assertIsInstance(location['Located_in'][0]['node'], models.LocationModel)
|
||||||
|
self.assertEqual(location['Located_in'][0]['node'].data['name'], 'Rack1')
|
||||||
|
self.assertIsInstance(location['Located_in'][0]['relationship_id'], int)
|
||||||
|
|
||||||
|
def test_set_owner_physical_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
customer4 = core.get_node_model(self.neo4jdb, handle_id='37')
|
||||||
|
|
||||||
|
result = router1.set_owner(customer4.handle_id)
|
||||||
|
self.assertEqual(result['Owns'][0]['created'], True)
|
||||||
|
relations = router1.get_relations()
|
||||||
|
self.assertEqual(len(relations['Owns']), 2)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = router1.set_owner(customer4.handle_id)
|
||||||
|
self.assertEqual(result['Owns'][0]['created'], False)
|
||||||
|
relations = router1.get_relations()
|
||||||
|
self.assertEqual(len(relations['Owns']), 2)
|
||||||
|
|
||||||
|
def test_set_provider_physical_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
provider_2 = core.get_node_model(self.neo4jdb, handle_id='39')
|
||||||
|
|
||||||
|
result = router1.set_provider(provider_2.handle_id)
|
||||||
|
self.assertEqual(result['Provides'][0]['created'], True)
|
||||||
|
relations = router1.get_relations()
|
||||||
|
self.assertEqual(len(relations['Provides']), 1)
|
||||||
|
self.assertEqual(relations['Provides'][0]['node'].handle_id, provider_2.handle_id)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = router1.set_provider(provider_2.handle_id)
|
||||||
|
self.assertEqual(result['Provides'][0]['created'], False)
|
||||||
|
relations = router1.get_relations()
|
||||||
|
self.assertEqual(len(relations['Provides']), 1)
|
||||||
|
|
||||||
|
def test_set_location_physical_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
rack_2 = core.get_node_model(self.neo4jdb, handle_id='15')
|
||||||
|
|
||||||
|
result = router1.set_location(rack_2.handle_id)
|
||||||
|
self.assertEqual(result['Located_in'][0]['created'], True)
|
||||||
|
location = router1.get_location()
|
||||||
|
self.assertEqual(len(location['Located_in']), 2)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = router1.set_location(rack_2.handle_id)
|
||||||
|
self.assertEqual(result['Located_in'][0]['created'], False)
|
||||||
|
location = router1.get_location()
|
||||||
|
self.assertEqual(len(location['Located_in']), 2)
|
||||||
|
|
||||||
|
def test_set_and_get_has_physical_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
port8 = core.get_node_model(self.neo4jdb, handle_id='40')
|
||||||
|
|
||||||
|
result = router1.set_has(port8.handle_id)
|
||||||
|
self.assertEqual(result['Has'][0]['created'], True)
|
||||||
|
children = router1.get_has()
|
||||||
|
self.assertEqual(len(children['Has']), 3)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = router1.set_has(port8.handle_id)
|
||||||
|
self.assertEqual(result['Has'][0]['created'], False)
|
||||||
|
children = router1.get_has()
|
||||||
|
self.assertEqual(len(children['Has']), 3)
|
||||||
|
|
||||||
|
def test_set_and_get_part_of_physical_model(self):
|
||||||
|
port8 = core.get_node_model(self.neo4jdb, handle_id='40')
|
||||||
|
unit1 = core.get_node_model(self.neo4jdb, handle_id='3')
|
||||||
|
|
||||||
|
result = port8.set_part_of(unit1.handle_id)
|
||||||
|
self.assertEqual(result['Part_of'][0]['created'], True)
|
||||||
|
children = port8.get_part_of()
|
||||||
|
self.assertEqual(len(children['Part_of']), 1)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = port8.set_part_of(unit1.handle_id)
|
||||||
|
self.assertEqual(result['Part_of'][0]['created'], False)
|
||||||
|
children = port8.get_part_of()
|
||||||
|
self.assertEqual(len(children['Part_of']), 1)
|
||||||
|
|
||||||
|
def test_get_parent_physical_model(self):
|
||||||
|
port1 = core.get_node_model(self.neo4jdb, handle_id='2')
|
||||||
|
parent = port1.get_parent()
|
||||||
|
self.assertIsInstance(parent['Has'][0]['node'], models.PhysicalModel)
|
||||||
|
self.assertEqual(parent['Has'][0]['node'].data['name'], 'Router1')
|
||||||
|
self.assertIsInstance(parent['Has'][0]['relationship_id'], int)
|
||||||
|
|
||||||
|
def test_get_location_path_location_model(self):
|
||||||
|
rack_2 = core.get_node_model(self.neo4jdb, handle_id='15')
|
||||||
|
location_path = rack_2.get_location_path()
|
||||||
|
self.assertEqual(location_path['location_path'][0]['name'], 'Site1')
|
||||||
|
|
||||||
|
def test_get_parent_location_model(self):
|
||||||
|
rack_2 = core.get_node_model(self.neo4jdb, handle_id='15')
|
||||||
|
parent = rack_2.get_parent()
|
||||||
|
self.assertEqual(parent['Has'][0]['node'].data['name'], 'Site1')
|
||||||
|
|
||||||
|
def test_get_located_in_location_model(self):
|
||||||
|
rack_2 = core.get_node_model(self.neo4jdb, handle_id='15')
|
||||||
|
located_in = rack_2.get_located_in()
|
||||||
|
self.assertEqual(len(located_in['Located_in']), 2)
|
||||||
|
optical_node = [node for node in located_in['Located_in'] if node['node'].data['name'] == 'Optical Node1'][0]
|
||||||
|
self.assertIsInstance(optical_node['node'], models.PhysicalModel)
|
||||||
|
self.assertIsInstance(optical_node['relationship_id'], int)
|
||||||
|
|
||||||
|
def test_set_and_get_has_location_model(self):
|
||||||
|
site1 = core.get_node_model(self.neo4jdb, handle_id='11')
|
||||||
|
rack_4 = core.get_node_model(self.neo4jdb, handle_id='41')
|
||||||
|
|
||||||
|
result = site1.set_has(rack_4.handle_id)
|
||||||
|
self.assertEqual(result['Has'][0]['created'], True)
|
||||||
|
children = site1.get_has()
|
||||||
|
self.assertEqual(len(children['Has']), 3)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = site1.set_has(rack_4.handle_id)
|
||||||
|
self.assertEqual(result['Has'][0]['created'], False)
|
||||||
|
children = site1.get_has()
|
||||||
|
self.assertEqual(len(children['Has']), 3)
|
||||||
|
|
||||||
|
def test_set_responsible_for_location_model(self):
|
||||||
|
rack_4 = core.get_node_model(self.neo4jdb, handle_id='41')
|
||||||
|
provider_2 = core.get_node_model(self.neo4jdb, handle_id='39')
|
||||||
|
|
||||||
|
result = rack_4.set_responsible_for(provider_2.handle_id)
|
||||||
|
self.assertEqual(result['Responsible_for'][0]['created'], True)
|
||||||
|
relations = rack_4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Responsible_for']), 1)
|
||||||
|
self.assertEqual(relations['Responsible_for'][0]['node'].handle_id, provider_2.handle_id)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = rack_4.set_responsible_for(provider_2.handle_id)
|
||||||
|
self.assertEqual(result['Responsible_for'][0]['created'], False)
|
||||||
|
relations = rack_4.get_relations()
|
||||||
|
self.assertEqual(len(relations['Responsible_for']), 1)
|
||||||
|
|
||||||
|
# TODO: EquipmentModel get_ports should probably work as CommonQueries get_ports
|
||||||
|
def test_get_ports_equipment_model(self):
|
||||||
|
odf1 = core.get_node_model(self.neo4jdb, handle_id='23')
|
||||||
|
ports = odf1.get_ports()
|
||||||
|
self.assertIsInstance(ports, dict)
|
||||||
|
self.assertEqual(len(ports['Has']), 1)
|
||||||
|
for rel_type, items in ports.items():
|
||||||
|
self.assertEqual(len(items), 1)
|
||||||
|
|
||||||
|
def test_get_port_equipment_model(self):
|
||||||
|
router1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
ports = router1.get_port('Port1')
|
||||||
|
self.assertEqual(len(ports['Has']), 1)
|
||||||
|
self.assertIsInstance(ports['Has'][0]['node'], models.PortModel)
|
||||||
|
self.assertEqual(ports['Has'][0]['node'].data['name'], 'Port1')
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_equipment_model(self):
|
||||||
|
optical_node2 = core.get_node_model(self.neo4jdb, handle_id='16')
|
||||||
|
dependents = optical_node2.get_dependent_as_types()
|
||||||
|
self.assertEqual(dependents['direct'], [])
|
||||||
|
self.assertEqual(dependents['links'][0]['name'], 'Optical Link2')
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertEqual(dependents['paths'][0]['name'], 'Optical Path1')
|
||||||
|
self.assertEqual(dependents['services'][0]['name'], 'Service2')
|
||||||
|
|
||||||
|
def test_get_connections_equipment_model(self):
|
||||||
|
odf2 = core.get_node_model(self.neo4jdb, handle_id='25')
|
||||||
|
connections = odf2.get_connections()
|
||||||
|
self.assertEqual(len(connections), 4)
|
||||||
|
for connection in connections:
|
||||||
|
self.assertIsNotNone(connection['porta'])
|
||||||
|
self.assertIsNotNone(connection['cable'])
|
||||||
|
|
||||||
|
def test_get_connections_subequipment_model(self):
|
||||||
|
port4 = core.get_node_model(self.neo4jdb, handle_id='24')
|
||||||
|
connections = port4.get_connections()
|
||||||
|
self.assertEqual(len(connections), 2)
|
||||||
|
for connection in connections:
|
||||||
|
self.assertIsNotNone(connection['porta'])
|
||||||
|
self.assertIsNotNone(connection['cable'])
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_host_model(self):
|
||||||
|
host1 = core.get_node_model(self.neo4jdb, handle_id='32')
|
||||||
|
dependents = host1.get_dependent_as_types()
|
||||||
|
self.assertEqual(dependents['direct'][0]['name'], 'Host2')
|
||||||
|
self.assertEqual(dependents['links'], [])
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertEqual(dependents['paths'], [])
|
||||||
|
self.assertEqual(dependents['services'], [])
|
||||||
|
|
||||||
|
# TODO: Fix duplicates
|
||||||
|
def test_get_units_port_model(self):
|
||||||
|
port1 = core.get_node_model(self.neo4jdb, handle_id='2')
|
||||||
|
units = port1.get_units()
|
||||||
|
self.assertEqual(units['Part_of'][0]['node'].handle_id, '3')
|
||||||
|
|
||||||
|
def test_get_unit_port_model(self):
|
||||||
|
port1 = core.get_node_model(self.neo4jdb, handle_id='2')
|
||||||
|
units = port1.get_unit('Unit1')
|
||||||
|
self.assertEqual(units['Part_of'][0]['node'].handle_id, '3')
|
||||||
|
|
||||||
|
def test_get_connected_to_port_model(self):
|
||||||
|
port4 = core.get_node_model(self.neo4jdb, handle_id='24')
|
||||||
|
connected_to = port4.get_connected_to()
|
||||||
|
self.assertIn(connected_to['Connected_to'][0]['node'].handle_id, ['28', '30'])
|
||||||
|
|
||||||
|
def test_get_connection_path_port_model(self):
|
||||||
|
port4 = core.get_node_model(self.neo4jdb, handle_id='24')
|
||||||
|
connection_path = port4.get_connection_path()
|
||||||
|
self.assertEqual(len(connection_path), 7)
|
||||||
|
|
||||||
|
def test_get_child_form_data_router_model(self):
|
||||||
|
physical1 = core.get_node_model(self.neo4jdb, handle_id='1')
|
||||||
|
child_form_data = physical1.get_child_form_data(node_type='Port')
|
||||||
|
self.assertEqual(child_form_data[0]['handle_id'], '2')
|
||||||
|
self.assertEqual(child_form_data[0]['name'], 'Port1')
|
||||||
|
self.assertEqual(child_form_data[0]['description'], None)
|
||||||
|
self.assertEqual(child_form_data[0]['labels'], [u'Node', u'Physical', u'Port'])
|
||||||
|
|
||||||
|
def test_get_peering_groups_peering_partner_model(self):
|
||||||
|
peering_partner1 = core.get_node_model(self.neo4jdb, handle_id='8')
|
||||||
|
host_services = peering_partner1.get_peering_groups()
|
||||||
|
self.assertEqual(len(host_services['Uses']), 1)
|
||||||
|
self.assertIsInstance(host_services['Uses'][0]['node'], models.PeeringGroupModel)
|
||||||
|
self.assertEqual(host_services['Uses'][0]['node'].data['name'], 'Peering Group1')
|
||||||
|
self.assertEqual(host_services['Uses'][0]['relationship']['ip_address'], '127.0.0.1')
|
||||||
|
|
||||||
|
def test_set_and_get_peering_group_peering_partner_model(self):
|
||||||
|
peering_partner1 = core.get_node_model(self.neo4jdb, handle_id='8')
|
||||||
|
peering_group2 = core.get_node_model(self.neo4jdb, handle_id='44')
|
||||||
|
|
||||||
|
peering_partner1.set_peering_group(peering_group2.handle_id, ip_address='127.0.0.2')
|
||||||
|
peering_groups = peering_partner1.get_peering_group(peering_group2.handle_id, ip_address='127.0.0.2')
|
||||||
|
self.assertEqual(len(peering_groups['Uses']), 1)
|
||||||
|
|
||||||
|
# TODO: Fix duplicates
|
||||||
|
|
||||||
|
def test_set_and_get_group_dependency_peering_group_model(self):
|
||||||
|
peering_group2 = core.get_node_model(self.neo4jdb, handle_id='44')
|
||||||
|
unit2 = core.get_node_model(self.neo4jdb, handle_id='5')
|
||||||
|
|
||||||
|
peering_group2.set_group_dependency(unit2.handle_id, ip_address='127.0.0.3')
|
||||||
|
dependencies = peering_group2.get_group_dependency(unit2.handle_id, ip_address='127.0.0.3')
|
||||||
|
self.assertEqual(len(dependencies['Depends_on']), 1)
|
||||||
|
|
||||||
|
# TODO: Fix duplicates
|
||||||
|
|
||||||
|
def test_get_connected_equipment_cable_model(self):
|
||||||
|
cable1 = core.get_node_model(self.neo4jdb, handle_id='28')
|
||||||
|
connections = cable1.get_connected_equipment()
|
||||||
|
self.assertEqual(len(connections), 2)
|
||||||
|
for connection in connections:
|
||||||
|
self.assertIsNotNone(connection['port'])
|
||||||
|
self.assertIsNotNone(connection['end'])
|
||||||
|
self.assertIsNotNone(connection['location'])
|
||||||
|
self.assertIsNotNone(connection['site'])
|
||||||
|
|
||||||
|
def test_get_dependent_as_types_cable_model(self):
|
||||||
|
cable1 = core.get_node_model(self.neo4jdb, handle_id='28')
|
||||||
|
dependents = cable1.get_dependent_as_types()
|
||||||
|
|
||||||
|
for optical_link in dependents['links']:
|
||||||
|
self.assertTrue(optical_link['name'] in ['Optical Link1', 'Optical Link2'])
|
||||||
|
self.assertEqual(dependents['oms'], [])
|
||||||
|
self.assertEqual(dependents['paths'][0]['name'], 'Optical Path1')
|
||||||
|
self.assertEqual(dependents['services'][0]['name'], 'Service2')
|
||||||
|
|
||||||
|
def test_get_services_cable_model(self):
|
||||||
|
cable1 = core.get_node_model(self.neo4jdb, handle_id='28')
|
||||||
|
services = cable1.get_services()
|
||||||
|
self.assertEqual(len(services), 1)
|
||||||
|
self.assertEqual(services[0]['service']['name'], 'Service2')
|
||||||
|
self.assertIsInstance(services[0]['users'], list)
|
||||||
|
self.assertEqual(services[0]['users'][0]['name'], 'Customer2')
|
||||||
|
|
||||||
|
def test_get_connection_path_cable_model(self):
|
||||||
|
cable1 = core.get_node_model(self.neo4jdb, handle_id='28')
|
||||||
|
connection_path = cable1.get_connection_path()
|
||||||
|
self.assertEqual(len(connection_path), 7)
|
||||||
|
|
||||||
|
def test_set_connected_to_cable_model(self):
|
||||||
|
cable6 = core.get_node_model(self.neo4jdb, handle_id='45')
|
||||||
|
port7 = core.get_node_model(self.neo4jdb, handle_id='27')
|
||||||
|
|
||||||
|
result = cable6.set_connected_to(port7.handle_id)
|
||||||
|
self.assertEqual(result['Connected_to'][0]['created'], True)
|
||||||
|
relationships = cable6.relationships
|
||||||
|
self.assertEqual(len(relationships['Connected_to']), 1)
|
||||||
|
|
||||||
|
# Do not accept duplicates
|
||||||
|
result = cable6.set_connected_to(port7.handle_id)
|
||||||
|
self.assertEqual(result['Connected_to'][0]['created'], False)
|
||||||
|
relationships = cable6.relationships
|
||||||
|
self.assertEqual(len(relationships['Connected_to']), 1)
|
||||||
|
|
||||||
|
def test_get_placement_path_unit_model(self):
|
||||||
|
unit1 = core.get_node_model(self.neo4jdb, handle_id='3')
|
||||||
|
placement_path = unit1.get_placement_path()
|
||||||
|
self.assertEqual(placement_path['placement_path'][0]['name'], 'Router1')
|
||||||
|
self.assertEqual(placement_path['placement_path'][1]['name'], 'Port1')
|
||||||
|
|
||||||
|
def test_get_location_path_unit_model(self):
|
||||||
|
unit1 = core.get_node_model(self.neo4jdb, handle_id='3')
|
||||||
|
location_path = unit1.get_location_path()
|
||||||
|
self.assertEqual(location_path['location_path'][0]['name'], 'Site1')
|
||||||
|
self.assertEqual(location_path['location_path'][1]['name'], 'Rack1')
|
||||||
|
self.assertEqual(location_path['location_path'][2]['name'], 'Router1')
|
||||||
|
self.assertEqual(location_path['location_path'][3]['name'], 'Port1')
|
||||||
|
|
||||||
|
def test_get_customers_service_model(self):
|
||||||
|
service2 = core.get_node_model(self.neo4jdb, handle_id='9')
|
||||||
|
customers = service2.get_customers()
|
||||||
|
self.assertEqual(len(customers['customers']), 1)
|
||||||
|
self.assertIsInstance(customers['customers'][0]['node'], models.CustomerModel)
|
Loading…
Reference in a new issue