Skip to content

Commit

Permalink
Merge pull request #848 from neo4j-contrib/rc/5.4.2
Browse files Browse the repository at this point in the history
Rc/5.4.2
  • Loading branch information
mariusconjeaud authored Dec 16, 2024
2 parents 25b0249 + 3c7c701 commit f6d87a9
Show file tree
Hide file tree
Showing 36 changed files with 1,823 additions and 991 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e '.[dev,pandas,numpy]'
pip install -e '.[dev,extras]'
- name: Test with pytest
env:
AURA_TEST_DB_USER: ${{ secrets.AURA_TEST_DB_USER }}
AURA_TEST_DB_PASSWORD: ${{ secrets.AURA_TEST_DB_PASSWORD }}
AURA_TEST_DB_HOSTNAME: ${{ secrets.AURA_TEST_DB_HOSTNAME }}
run: |
pytest --cov=neomodel --cov-report=html:coverage_report
- name: Install neo4j-rust-ext and verify it is installed
run: |
pip install -e '.[rust-driver-ext]'
pip list | grep neo4j-rust-ext || exit 1
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
6 changes: 6 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 5.4.2 2024-12
* Add support for Neo4j Rust driver extension : pip install neomodel[rust-driver-ext]
* Add initial_context parameter to subqueries
* NodeNameResolver can call self to reference top-level node
* Housekeeping : implementing mypy for static typing

Version 5.4.1 2024-11
* Add support for Cypher parallel runtime
* Add options for intermediate_transform : distinct, include_in_return, use a prop as source
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ Install from pypi (recommended):

$ pip install neomodel ($ source dev # To install all things needed in a Python3 venv)

# Neomodel has some optional dependencies (including Shapely), to install these use:
# neomodel can use the Rust extension to the Neo4j driver for faster transport, to install use:

$ pip install neomodel['extras']
$ pip install neomodel[rust-driver-ext]

# neomodel has some optional dependencies (Shapely, pandas, numpy), to install these use:

$ pip install neomodel[extras, rust-driver-ext]

To install from github:

Expand Down
14 changes: 10 additions & 4 deletions doc/source/advanced_query_operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ As discussed in the note above, this is for example useful when you need to orde

Options for `intermediate_transform` *variables* are:

- `source`: `string`or `Resolver` - the variable to use as source for the transformation. Works with resolvers (see below).
- `source`: `string` or `Resolver` - the variable to use as source for the transformation. Works with resolvers (see below).
- `source_prop`: `string` - optionally, a property of the source variable to use as source for the transformation.
- `include_in_return`: `bool` - whether to include the variable in the return statement. Defaults to False.

Expand Down Expand Up @@ -95,7 +95,7 @@ Subqueries
The `subquery` method allows you to perform a `Cypher subquery <https://neo4j.com/docs/cypher-manual/current/subqueries/call-subquery/>`_ inside your query. This allows you to perform operations in isolation to the rest of your query::

from neomodel.sync_match import Collect, Last

# This will create a CALL{} subquery
# And return a variable named supps usable in the rest of your query
Coffee.nodes.filter(name="Espresso")
Expand All @@ -106,12 +106,18 @@ The `subquery` method allows you to perform a `Cypher subquery <https://neo4j.co
)
.annotate(supps=Last(Collect("suppliers"))),
["supps"],
[NodeNameResolver("self")]
)

Options for `subquery` calls are:

- `return_set`: list of `string` - the subquery variables that should be included in the outer query result
- `initial_context`: optional list of `string` or `Resolver` - the outer query variables that will be injected at the begining of the subquery

.. note::
Notice the subquery starts with Coffee.nodes ; neomodel will use this to know it needs to inject the source "coffee" variable generated by the outer query into the subquery. This means only Espresso coffee nodes will be considered in the subquery.
In the example above, we reference `self` to be included in the initial context. It will actually inject the outer variable corresponding to `Coffee` node.

We know this is confusing to read, but have not found a better wat to do this yet. If you have any suggestions, please let us know.
We know this is confusing to read, but have not found a better way to do this yet. If you have any suggestions, please let us know.

Helpers
-------
Expand Down
2 changes: 1 addition & 1 deletion doc/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Adjust driver configuration - these options are only available for this connecti
config.MAX_TRANSACTION_RETRY_TIME = 30.0 # default
config.RESOLVER = None # default
config.TRUST = neo4j.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES # default
config.USER_AGENT = neomodel/v5.4.1 # default
config.USER_AGENT = neomodel/v5.4.2 # default

Setting the database name, if different from the default one::

Expand Down
2 changes: 1 addition & 1 deletion neomodel/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "5.4.1"
__version__ = "5.4.2"
31 changes: 20 additions & 11 deletions neomodel/async_/cardinality.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
from typing import TYPE_CHECKING, Any, Optional

from neomodel.async_.relationship_manager import ( # pylint:disable=unused-import
AsyncRelationshipManager,
AsyncZeroOrMore,
)
from neomodel.exceptions import AttemptedCardinalityViolation, CardinalityViolation

if TYPE_CHECKING:
from neomodel import AsyncStructuredNode, AsyncStructuredRel


class AsyncZeroOrOne(AsyncRelationshipManager):
"""A relationship to zero or one node."""

description = "zero or one relationship"

async def single(self):
async def single(self) -> Optional["AsyncStructuredNode"]:
"""
Return the associated node.
Expand All @@ -23,11 +28,13 @@ async def single(self):
raise CardinalityViolation(self, len(nodes))
return None

async def all(self):
async def all(self) -> list["AsyncStructuredNode"]:
node = await self.single()
return [node] if node else []

async def connect(self, node, properties=None):
async def connect(
self, node: "AsyncStructuredNode", properties: Optional[dict[str, Any]] = None
) -> "AsyncStructuredRel":
"""
Connect to a node.
Expand All @@ -49,7 +56,7 @@ class AsyncOneOrMore(AsyncRelationshipManager):

description = "one or more relationships"

async def single(self):
async def single(self) -> "AsyncStructuredNode":
"""
Fetch one of the related nodes
Expand All @@ -60,7 +67,7 @@ async def single(self):
return nodes[0]
raise CardinalityViolation(self, "none")

async def all(self):
async def all(self) -> list["AsyncStructuredNode"]:
"""
Returns all related nodes.
Expand All @@ -71,7 +78,7 @@ async def all(self):
return nodes
raise CardinalityViolation(self, "none")

async def disconnect(self, node):
async def disconnect(self, node: "AsyncStructuredNode") -> None:
"""
Disconnect node
:param node:
Expand All @@ -89,7 +96,7 @@ class AsyncOne(AsyncRelationshipManager):

description = "one relationship"

async def single(self):
async def single(self) -> "AsyncStructuredNode":
"""
Return the associated node.
Expand All @@ -102,25 +109,27 @@ async def single(self):
raise CardinalityViolation(self, len(nodes))
raise CardinalityViolation(self, "none")

async def all(self):
async def all(self) -> list["AsyncStructuredNode"]:
"""
Return single node in an array
:return: [node]
"""
return [await self.single()]

async def disconnect(self, node):
async def disconnect(self, node: "AsyncStructuredNode") -> None:
raise AttemptedCardinalityViolation(
"Cardinality one, cannot disconnect use reconnect."
)

async def disconnect_all(self):
async def disconnect_all(self) -> None:
raise AttemptedCardinalityViolation(
"Cardinality one, cannot disconnect_all use reconnect."
)

async def connect(self, node, properties=None):
async def connect(
self, node: "AsyncStructuredNode", properties: Optional[dict[str, Any]] = None
) -> "AsyncStructuredRel":
"""
Connect a node
Expand Down
Loading

0 comments on commit f6d87a9

Please sign in to comment.