Skip to content

Commit

Permalink
use list for paths
Browse files Browse the repository at this point in the history
  • Loading branch information
Evidlo committed Jan 16, 2021
1 parent b5bf765 commit 7435b49
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 41 deletions.
10 changes: 5 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ Finding Entries

**find_entries** (title=None, username=None, password=None, url=None, notes=None, path=None, uuid=None, tags=None, string=None, group=None, recursive=True, regex=False, flags=None, history=False, first=False)

Returns entries which match all provided parameters, where ``title``, ``username``, ``password``, ``url``, ``notes``, ``path``, and ``autotype_sequence`` are strings, ``string`` is a dict, ``autotype_enabled`` is a boolean, ``uuid`` is a ``uuid.UUID`` and ``tags`` is a list of strings. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.
Returns entries which match all provided parameters, where ``title``, ``username``, ``password``, ``url``, ``notes``, and ``autotype_sequence`` are strings, ``path`` is a list, ``string`` is a dict, ``autotype_enabled`` is a boolean, ``uuid`` is a ``uuid.UUID`` and ``tags`` is a list of strings. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.

.. _XSLT style: https://www.xml.com/pub/a/2003/06/04/tr.html
.. _flags: https://www.w3.org/TR/xpath-functions/#flags

The ``path`` string is a full path to an entry (ex. ``'foobar_group/foobar_entry'``). This implies ``first=True``. All other arguments are ignored when this is given. This is useful for handling user input.
The ``path`` list is a full path to an entry (ex. ``['foobar_group', 'foobar_entry']``). This implies ``first=True``. All other arguments are ignored when this is given. This is useful for handling user input.

The ``string`` dict allows for searching custom string fields. ex. ``{'custom_field1': 'custom value', 'custom_field2': 'custom value'}``

Expand Down Expand Up @@ -112,12 +112,12 @@ Finding Groups

**find_groups** (name=None, path=None, uuid=None, notes=None, group=None, recursive=True, regex=False, flags=None, first=False)

where ``name``, ``path``, and ``notes`` are strings, ``uuid`` is a ``uuid.UUID``. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.
where ``name`` and ``notes`` are strings, ``path`` is a list, ``uuid`` is a ``uuid.UUID``. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.

.. _XSLT style: https://www.xml.com/pub/a/2003/06/04/tr.html
.. _flags: https://www.w3.org/TR/xpath-functions/#flags

The ``path`` string is a full path to a group (ex. ``'foobar_group/sub_group'``). This implies ``first=True``. All other arguments are ignored when this is given. This is useful for handling user input.
The ``path`` list is a full path to a group (ex. ``['foobar_group', 'sub_group']``). This implies ``first=True``. All other arguments are ignored when this is given. This is useful for handling user input.

The ``group`` argument determines what ``Group`` to search under, and the ``recursive`` boolean controls whether to search recursively.

Expand Down Expand Up @@ -145,7 +145,7 @@ a flattened list of all groups in the database
>>> kp.find_groups(name='foo.*', regex=True)
[Group: "foo", Group "foobar"]
>>> kp.find_groups(path='social/', regex=True)
>>> kp.find_groups(path=['social'], regex=True)
[Group: "social", Group: "social/foo_subgroup"]
>>> kp.find_groups(name='social', first=True).subgroups
Expand Down
17 changes: 7 additions & 10 deletions pykeepass/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,15 @@ def is_a_history_entry(self):
@property
def path(self):
# The root group is an orphan
if self.is_a_history_entry:
pentry = Entry(
element=self._element.getparent().getparent(),
kp=self._kp
).title
return '[History of: {}]'.format(pentry)
if self.parentgroup is None:
return None
p = self.parentgroup
ppath = ''
path = [self.title]
while p is not None and not p.is_root_group:
if p.name is not None: # dont make the root group appear
ppath = '{}/{}'.format(p.name, ppath)
path.insert(0, p.name)
p = p.parentgroup
return '{}{}'.format(ppath, self.title)
return path

def set_custom_property(self, key, value):
assert key not in reserved_keys, '{} is a reserved key'.format(key)
Expand Down Expand Up @@ -297,4 +291,7 @@ def save_history(self):
self._element.append(history)

def __str__(self):
return 'Entry: "{} ({})"'.format(self.path, self.username)
pathstr = '/'.join(self.path)
if self.is_a_history_entry:
return '[History of: {}]'.format(pathstr)
return 'Entry: "{} ({})"'.format(pathstr, self.username)
11 changes: 6 additions & 5 deletions pykeepass/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ def is_root_group(self):
def path(self):
# The root group is an orphan
if self.is_root_group or self.parentgroup is None:
return '/'
return []
p = self.parentgroup
ppath = ''
path = [self.name]
while p is not None and not p.is_root_group:
if p.name is not None: # dont make the root group appear
ppath = '{}/{}'.format(p.name, ppath)
path.insert(0, p.name)
p = p.parentgroup
return '{}{}/'.format(ppath, self.name)
return path

def append(self, entries):
if type(entries) is list:
Expand All @@ -94,4 +94,5 @@ def append(self, entries):
self._element.append(entries._element)

def __str__(self):
return 'Group: "{}"'.format(self.path)
pathstr = '/'.join(self.path)
return 'Group: "{}"'.format(pathstr)
10 changes: 4 additions & 6 deletions pykeepass/pykeepass.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,12 @@ def _find(self, prefix, keys_xp, path=None, tree=None, first=False,
first = True

xp += '/KeePassFile/Root/Group'
path = path.strip('/')
# split provided path into group and element
group_path = os.path.dirname(path)
element = os.path.basename(path)
group_path = path[:-1]
element = path[-1] if len(path) > 0 else ''
# build xpath from group_path and element
if group_path:
for group in group_path.split('/'):
xp += path_xp[regex]['group'].format(group, flags=flags)
for group in group_path:
xp += path_xp[regex]['group'].format(group, flags=flags)
if 'Entry' in prefix:
xp += path_xp[regex]['entry'].format(element, flags=flags)
elif element and 'Group' in prefix:
Expand Down
36 changes: 21 additions & 15 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ def test_find_entries_by_notes(self):
self.assertEqual('root entry notes', results.notes)

def test_find_entries_by_path(self):
results = self.kp.find_entries(path='foobar_group/group_entry')
results = self.kp.find_entries(path=['foobar_group', 'group_entry'])
self.assertIsInstance(results, Entry)
results = self.kp.find_entries(path='foobar_group/')
results = self.kp.find_entries(path=['foobar_group'])
self.assertEqual(results, None)
results = self.kp.find_entries(path='foobar_group/Group_entry', regex=True, flags='i', first=True)
results = self.kp.find_entries(path=['foobar_group', 'Group_entry'], regex=True, flags='i', first=True)
self.assertIsInstance(results, Entry)
self.assertEqual('group_entry', results.title)

Expand Down Expand Up @@ -191,7 +191,7 @@ def test_history_path(self):
hist = entry.history
self.assertTrue(len(hist) > 0)
for item in hist:
self.assertEqual(item.path, '[History of: {}]'.format(entry.title))
self.assertEqual(item.path, entry.path)

def test_history_group(self):
for title in ["root_entry", "subentry"]:
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_add_delete_move_entry(self):

sub_group = self.kp.add_group(self.kp.root_group, 'sub_group')
self.kp.move_entry(entry, sub_group)
results = self.kp.find_entries(path='sub_group/test_add_entry_title', first=True)
results = self.kp.find_entries(path=['sub_group', 'test_add_entry_title'], first=True)
self.assertEqual(results.title, entry.title)

self.kp.delete_entry(entry)
Expand Down Expand Up @@ -294,11 +294,11 @@ def test_find_groups_by_name(self):
self.assertEqual(len(results), 1)

def test_find_groups_by_path(self):
results = self.kp.find_groups_by_path('/foobar_group/subgroup/')
results = self.kp.find_groups_by_path(['foobar_group', 'subgroup'])
self.assertIsInstance(results, Group)
results = self.kp.find_groups(path='/foobar_group/subgroup/', first=True)
results = self.kp.find_groups(path=['foobar_group', 'subgroup'], first=True)
self.assertEqual(results.name, 'subgroup')
results = self.kp.find_groups(path='foobar_group/group_entry')
results = self.kp.find_groups(path=['foobar_group', 'group_entry'])
self.assertEqual(results, None)

def test_find_groups_by_uuid(self):
Expand Down Expand Up @@ -331,17 +331,17 @@ def test_add_delete_move_group(self):
base_group.notes = ''
self.assertEqual(base_group.notes, '')

results = self.kp.find_groups(path='base_group/sub_group/', first=True)
results = self.kp.find_groups(path=['base_group', 'sub_group'], first=True)
self.assertIsInstance(results, Group)
self.assertEqual(results.name, sub_group.name)
self.assertTrue(results.uuid != None)

self.kp.move_group(sub_group2, sub_group)
results = self.kp.find_groups(path='base_group/sub_group/sub_group2/', first=True)
results = self.kp.find_groups(path=['base_group', 'sub_group', 'sub_group2'], first=True)
self.assertEqual(results.name, sub_group2.name)

self.kp.delete_group(sub_group)
results = self.kp.find_groups(path='base_group/sub_group/', first=True)
results = self.kp.find_groups(path=['base_group', 'sub_group'], first=True)
self.assertIsNone(results)

# ---------- Groups representation -----------
Expand Down Expand Up @@ -433,7 +433,10 @@ def test_fields(self):
time.replace(tzinfo=tz.gettz()).astimezone(tz.gettz('UTC')))
self.assertEqual(entry.icon, icons.KEY)
self.assertEqual(entry.is_a_history_entry, False)
self.assertEqual(self.kp.find_entries(title='subentry', first=True).path, 'foobar_group/subgroup/subentry')
self.assertEqual(
self.kp.find_entries(title='subentry', first=True).path,
['foobar_group', 'subgroup', 'subentry']
)
self.assertEqual(
self.kp.find_entries(title='root_entry', first=True).history[0].group,
self.kp.root_group
Expand Down Expand Up @@ -617,7 +620,7 @@ def test_find_history_entries(self):
for item in hist:
self.assertTrue(item.is_a_history_entry)
self.assertEqual(item.group, entry.group)
self.assertEqual(item.path, '[History of: {}]'.format(entry.title))
self.assertTrue(str(item).startswith('[History of:'))

# here history items are expected
res2 = self.kp.find_entries(title=prefix + 'title', history=True)
Expand Down Expand Up @@ -677,7 +680,7 @@ def test_find_history_entries(self):
for item in hist:
self.assertTrue(item.is_a_history_entry)
self.assertEqual(item.group, entry.group)
self.assertEqual(item.path, '[History of: {}]'.format(entry.title))
self.assertTrue(str(item).startswith('[History of:'))

res2 = self.kp.find_entries(title=changed + 'title', history=True)
self.assertEqual(len(res2), 6)
Expand All @@ -689,7 +692,10 @@ def test_find_history_entries(self):
class GroupTests3(KDBX3Tests):

def test_fields(self):
self.assertEqual(self.kp.find_groups(name='subgroup2', first=True).path, 'foobar_group/subgroup/subgroup2/')
self.assertEqual(
self.kp.find_groups(name='subgroup2', first=True).path,
['foobar_group', 'subgroup', 'subgroup2']
)

def test_empty_group(self):
# test that groups are properly emptied
Expand Down

0 comments on commit 7435b49

Please sign in to comment.