Skip to content

Commit

Permalink
Add wildcard and excluding paths concept
Browse files Browse the repository at this point in the history
  • Loading branch information
aranega committed Oct 25, 2024
1 parent 8f4cf6e commit e65976f
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 7 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,17 @@ There is different kind of paths:
* creating a named rec. path is done by using the `*` or `+` operator after a name, e.g: `foo*` expresses that `foo` needs to be followed 0 or many times and `foo+` expresses that `foo` needs to be followed 1 or many times.
* **children recursive paths**: they express the recursive navigation of all "instance variable" of an object.
* creating a children rec. path is done by using `*` alone, e.g: `*` means all the "children" (the instance variable of the object/keys of the dict) and their children.
* **wildcard direct paths**: they express a direct connection between objects without naming explicitally the relationship (e.g: "any of the direct connection from this object to another")
* **excluding paths**: they express a path towards any direct relationship excluding a specific relation from them (e.g: "any of the direct connection from this object to another excluding this connection").
* creatin an excluding path is done by using `!` in front of the relationship to exclude, e.g: `!parent` means "all the direct paths excluding `parent`.

Those operators can be composed with `>`.
For examples:

* `bar>foo>*` means "`bar` then `foo` then all the children recursively"
* `*>foo` means "all the children recursively then `foo`" (if `foo` exists for each object)
* `child*>name` means "follow `child` recursively and get `name` each time"
* `!parent*` means "follow all the direct relationship from object to object recursively, but excluding `parent` each time
* ...

### Wildcards/variables
Expand Down
41 changes: 34 additions & 7 deletions iguala/paths.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# from types import LambdaType
from functools import lru_cache

from .helpers import IdentitySet, flat


Expand Down Expand Up @@ -32,6 +30,29 @@ def resolve_from(self, obj):
return flat(getattr(obj, self.path, []))


class WildcardPath(ObjectPath):
def __init__(self, excluding=None):
self.excluding = excluding or []

def resolve_from(self, obj):
direct_objects = []
try:
visit = vars(obj).items()
except TypeError:
try:
visit = {k: getattr(obj, k) for k in obj.__class__.__slots__}.items()
except AttributeError:
try:
visit = obj.items()
except AttributeError:
return []
for k, v in visit:
if k in self.excluding:
continue
direct_objects.extend(flat(v))
return direct_objects


# class LambdaPath(ObjectPath):
# def __init__(self, func):
# self.func = func
Expand Down Expand Up @@ -75,12 +96,15 @@ def _resolve_from(self, obj, seen, resolved):
if obj not in seen:
seen.add(obj)
direct_objects.extend(
flat([self._resolve_from(x, seen) for x in direct_objects])
flat([self._inner_resolve_from(x, seen) for x in direct_objects])
)
return direct_objects

def _inner_resolve_from(self, obj, seen):
return []

def resolve_from(self, obj):
res = self._resolve_from(obj, IdentitySet())
res = self._inner_resolve_from(obj, IdentitySet())
return res

@property
Expand All @@ -92,13 +116,13 @@ class NamedRecursivePath(RecursivePath):
def __init__(self, path):
self.path = path

def _resolve_from(self, obj, seen):
def _inner_resolve_from(self, obj, seen):
o = self.path.resolve_from(obj)
return super()._resolve_from(obj, seen, o)


class ChildrenRecursivePath(RecursivePath):
def _resolve_from(self, obj, seen):
def _inner_resolve_from(self, obj, seen):
direct_objects = []
try:
visit = vars(obj).items()
Expand All @@ -124,6 +148,8 @@ def as_path(s, dictkey=False):
# return LambdaPath(s)
if not isinstance(s, str):
return s.as_path()
if s == '_':
return WildcardPath()
if s == "*":
return ChildrenRecursivePath()
if isinstance(s, str):
Expand All @@ -136,5 +162,6 @@ def as_path(s, dictkey=False):
NamedRecursivePath(as_path(s[:-1], dictkey=dictkey)),
)
)

if s.startswith('!'):
return WildcardPath(excluding=[s[1:]])
return dict_cls(s)
12 changes: 12 additions & 0 deletions tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,15 @@ def test_resolve_unexisting():
p = as_path("name>unexisting")

assert p.resolve_from(obj_test) == []


def test_resolve_wildcard_path():
p = as_path("_")

assert p.resolve_from(obj_test) == [4, 8, obj_test.name, obj_test.inner, *obj_test.inner_list]


def test_resolve_named_path_excluding():
p = as_path("!inner_list")

assert p.resolve_from(obj_test) == [4, 8, obj_test.name, obj_test.inner]

0 comments on commit e65976f

Please sign in to comment.