Skip to content

Commit 34d6abf

Browse files
author
Evidlo
committed
merge master
2 parents a27b040 + 2b051d9 commit 34d6abf

34 files changed

Lines changed: 1751 additions & 1016 deletions

.github/workflows/ci.yaml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,40 @@ on: [push, pull_request, workflow_dispatch]
44

55
jobs:
66
tests:
7-
runs-on: ubuntu-latest
7+
name: ${{ matrix.os }}-latest / ${{ matrix.python-version }}
8+
runs-on: ${{ matrix.os }}-latest
89
strategy:
9-
matrix:
10-
python-version: [3.6, 3.7, 3.8, 3.9]
1110
fail-fast: false
11+
matrix:
12+
os:
13+
- ubuntu
14+
# - windows
15+
# - macos
16+
python-version:
17+
# - "3.6" # not supported in ubuntu-latest (22.04)
18+
# - "3.7" # not supported in ubuntu-latest ()
19+
- "3.8"
20+
- "3.9"
21+
- "3.10"
22+
- "3.11"
23+
- "3.12"
24+
1225
steps:
13-
- uses: actions/checkout@v2
26+
- uses: actions/checkout@v4
1427

1528
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v2
29+
uses: actions/setup-python@v5
1730
with:
1831
python-version: ${{ matrix.python-version }}
32+
cache: pip
33+
allow-prereleases: true
1934

2035
- name: Install dependencies
2136
run: |
2237
sudo apt update
2338
sudo apt-get install -y libxml2-dev libxmlsec1-dev
2439
python -m pip install --upgrade pip
25-
pip install -r requirements.txt
40+
pip install .[test]
2641
2742
- name: Run tests
2843
run: |

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ dist/
44
*.egg-info/
55
build/
66
*.xml
7+
Pipfile
78
Pipfile.lock
89
*.kdbx
910
*.kdbx.out
11+
.idea
12+
.venv*
13+
TMPNOTES
14+
docs/

CHANGELOG.rst

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
1-
4.0.4 -
1+
4.1.1 - 2025-03-04
2+
------------------
3+
- fixed #410 - support empty string as password
4+
5+
4.1.0 - 2024-06-26
6+
------------------
7+
- merged #389 - add PyKeePass.database_name and database_description
8+
- merged #392, fixed #390 - fix pkg_resources dependency issue
9+
- fixed #391 - Entry.tags returns empty list instead of None
10+
- fixed #395 - set 'encoding' attribute when exporting as XML
11+
- fixed #383 - parse datetimes using isoformat instead of strptime
12+
13+
4.0.7 - 2024-02-29
14+
------------------
15+
- fixed #359 - PyKeePass has `decrypt` kwarg for accessing header info
16+
- merged #347 - added Entry.index and Entry.move for moving entries
17+
- merged #367 - added Entry.autotype_window setter
18+
- merged #364 - allow filename/keyfile to be file-like objects
19+
- merged #371 - drop dateutil dependency
20+
- merged #348 - switch to pyproject.toml
21+
22+
4.0.6 - 2023-08-22
23+
------------------
24+
- fixed #350 - fixed all Python 2 deprecation FIXMEs (e.g. future, )
25+
26+
4.0.5 - 2023-06-05
27+
------------------
28+
- fixed #344 - AttributeError when accessing Times with None value
29+
- use __hash__ when evaluating equality
30+
- use mtime/uuid for HistoryEntry hashing
31+
32+
4.0.4 - 2023-05-23
233
------------------
334
- fixed #314 - correctly handle binaries with no data
435
- fixed #265 - check for keepass signature
536
- fixed #319 - support pathlib for filename/keyfile
637
- fixed #194 - added 'protected' arg to _set_string_field
738
- use official icon names from KeePass source and deprecate old icons
839
- added Entry.is_custom_property_protected()
40+
- fixed #338 - allow comma entry tag separator
941

1042
4.0.3 - 2022-06-21
1143
------------------
12-
- add otp support
13-
- add debug_setup() function
44+
- added otp support
45+
- added debug_setup() function
1446

1547
4.0.2 - 2022-05-21
1648
------------------

Makefile

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,50 @@
1-
version := $(shell python -c "exec(open('pykeepass/version.py').read());print(__version__)")
1+
.ONESHELL:
2+
.SHELLFLAGS = -ec
3+
.SILENT:
4+
version := $(shell python -c "import tomllib;print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
25

36
.PHONY: dist
47
dist:
5-
python setup.py sdist bdist_wheel
8+
python -m build
69

7-
.PHONY: pypi
8-
pypi: dist
9-
twine upload dist/pykeepass-$(version).tar.gz
10+
.PHONY: release
11+
release: lock dist
12+
# check that changelog is updated. only look at first 3 parts of semver
13+
version=$(version)
14+
stripped=$$(echo $${version} | cut -d . -f -3 | cut -d '-' -f 1)
15+
if ! grep $${stripped} CHANGELOG.rst
16+
then
17+
echo "Changelog doesn't seem to be updated! Quitting..."
18+
exit 1
19+
fi
20+
# generate release notes from changelog
21+
awk "BEGIN{p=0}; /^$${stripped}/{next}; /---/{p=1;next}; /^$$/{exit}; p {print}" CHANGELOG.rst > TMPNOTES
22+
# make github and pypi release
23+
gh release create --latest --verify-tag v$(version) dist/pykeepass-$(version)* -F TMPNOTES
24+
twine upload -u __token__ dist/pykeepass-$(version)*
25+
26+
.PHONY: lock
27+
lock:
28+
# run tests then make a requirements.txt lockfile
29+
rm -rf .venv_lock
30+
virtualenv .venv_lock
31+
. .venv_lock/bin/activate
32+
pip install .[test]
33+
python tests/tests.py
34+
pip freeze > requirements.txt
35+
36+
.PHONY: tag
37+
tag:
38+
# tag git commit
39+
git add requirements.txt
40+
git add pyproject.toml
41+
git add CHANGELOG.rst
42+
git commit -m "bump version" --allow-empty
43+
git tag -a v$(version) -m "version $(version)"
44+
git push --tags
45+
git push
1046

1147
.PHONY: docs
1248
docs:
13-
lazydocs pykeepass --overview-file README.md
49+
pdoc -o docs --docformat google --no-search pykeepass '!pykeepass.icons' --footer-text "pykeepass ${version}"
1450
ghp-import -f -p -b docs docs

README.md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# pykeepass
2+
3+
<a href="https://github.com/libkeepass/pykeepass/actions/workflows/ci.yaml"><img src="https://github.com/libkeepass/pykeepass/actions/workflows/ci.yaml/badge.svg"/></a>
4+
<a href="https://libkeepass.github.io/pykeepass"><img src="https://readthedocs.org/projects/pykeepass/badge/?version=latest"/></a>
5+
<a href="https://matrix.to/#/%23pykeepass:matrix.org"><img src="https://img.shields.io/badge/chat-%23pykeepass-green"/></a>
6+
7+
This library allows you to write entries to a KeePass database.
8+
9+
Come chat at [#pykeepass:matrix.org](https://matrix.to/#/%23pykeepass:matrix.org) on Matrix.
10+
11+
# Installation
12+
13+
``` bash
14+
sudo apt install python3-lxml
15+
pip install pykeepass
16+
```
17+
18+
# Quickstart
19+
20+
General database manipulation
21+
22+
``` python
23+
from pykeepass import PyKeePass
24+
25+
# load database
26+
>>> kp = PyKeePass('db.kdbx', password='somePassw0rd')
27+
28+
# get all entries
29+
>>> kp.entries
30+
[Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)", ...]
31+
32+
# find any group by its name
33+
>>> group = kp.find_groups(name='social', first=True)
34+
35+
# get the entries in a group
36+
>>> group.entries
37+
[Entry: "social/facebook (myusername)", Entry: "social/twitter (myusername)"]
38+
39+
# find any entry by its title
40+
>>> entry = kp.find_entries(title='facebook', first=True)
41+
42+
# retrieve the associated password and OTP information
43+
>>> entry.password
44+
's3cure_p455w0rd'
45+
>>> entry.otp
46+
otpauth://totp/test:lkj?secret=TEST%3D%3D%3D%3D&period=30&digits=6&issuer=test
47+
48+
# update an entry
49+
>>> entry.notes = 'primary facebook account'
50+
51+
# create a new group
52+
>>> group = kp.add_group(kp.root_group, 'email')
53+
54+
# create a new entry
55+
>>> kp.add_entry(group, 'gmail', 'myusername', 'myPassw0rdXX')
56+
Entry: "email/gmail (myusername)"
57+
58+
# save database
59+
>>> kp.save()
60+
```
61+
62+
Finding and manipulating entries
63+
64+
``` python
65+
# add a new entry to the Root group
66+
>>> kp.add_entry(kp.root_group, 'testing', 'foo_user', 'passw0rd')
67+
Entry: "testing (foo_user)"
68+
69+
# add a new entry to the social group
70+
>>> group = kp.find_groups(name='social', first=True)
71+
>>> entry = kp.add_entry(group, 'testing', 'foo_user', 'passw0rd')
72+
Entry: "testing (foo_user)"
73+
74+
# save the database
75+
>>> kp.save()
76+
77+
# delete an entry
78+
>>> kp.delete_entry(entry)
79+
80+
# move an entry
81+
>>> kp.move_entry(entry, kp.root_group)
82+
83+
# save the database
84+
>>> kp.save()
85+
86+
# change creation time
87+
>>> from datetime import datetime, timezone
88+
>>> entry.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)
89+
90+
# update modification or access time
91+
>>> entry.touch(modify=True)
92+
93+
# save entry history
94+
>>> entry.save_history()
95+
```
96+
97+
Finding and manipulating groups
98+
99+
``` python
100+
>>> kp.groups
101+
[Group: "foo", Group "foobar", Group: "social", Group: "social/foo_subgroup"]
102+
103+
>>> kp.find_groups(name='foo', first=True)
104+
Group: "foo"
105+
106+
>>> kp.find_groups(name='foo.*', regex=True)
107+
[Group: "foo", Group "foobar"]
108+
109+
>>> kp.find_groups(path=['social'], regex=True)
110+
[Group: "social", Group: "social/foo_subgroup"]
111+
112+
>>> kp.find_groups(name='social', first=True).subgroups
113+
[Group: "social/foo_subgroup"]
114+
115+
>>> kp.root_group
116+
Group: "/"
117+
118+
# add a new group to the Root group
119+
>>> group = kp.add_group(kp.root_group, 'social')
120+
121+
# add a new group to the social group
122+
>>> group2 = kp.add_group(group, 'gmail')
123+
Group: "social/gmail"
124+
125+
# save the database
126+
>>> kp.save()
127+
128+
# delete a group
129+
>>> kp.delete_group(group)
130+
131+
# move a group
132+
>>> kp.move_group(group2, kp.root_group)
133+
134+
# save the database
135+
>>> kp.save()
136+
137+
# change creation time
138+
>>> from datetime import datetime, timezone
139+
>>> group.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)
140+
141+
# update modification or access time
142+
>>> group.touch(modify=True)
143+
```
144+
145+
Attachments
146+
147+
``` python
148+
>>> e = kp.add_entry(kp.root_group, title='foo', username='', password='')
149+
150+
# add attachment data to the db
151+
>>> binary_id = kp.add_binary(b'Hello world')
152+
153+
>>> kp.binaries
154+
[b'Hello world']
155+
156+
# add attachment reference to entry
157+
>>> a = e.add_attachment(binary_id, 'hello.txt')
158+
>>> a
159+
Attachment: 'hello.txt' -> 0
160+
161+
# access attachments
162+
>>> a
163+
Attachment: 'hello.txt' -> 0
164+
>>> a.id
165+
0
166+
>>> a.filename
167+
'hello.txt'
168+
>>> a.data
169+
b'Hello world'
170+
>>> e.attachments
171+
[Attachment: 'hello.txt' -> 0]
172+
173+
# list all attachments in the database
174+
>>> kp.attachments
175+
[Attachment: 'hello.txt' -> 0]
176+
177+
# search attachments
178+
>>> kp.find_attachments(filename='hello.txt')
179+
[Attachment: 'hello.txt** -> 0]
180+
181+
# delete attachment reference
182+
>>> e.delete_attachment(a)
183+
184+
# or, delete both attachment reference and binary
185+
>>> kp.delete_binary(binary_id**
186+
```
187+
188+
OTP codes
189+
190+
``` python
191+
# find an entry which has otp attribute
192+
>>> e = kp.find_entries(otp='.*', regex=True, first=True)
193+
>>> import pyotp
194+
>>> pyotp.parse_uri(e.otp).now()
195+
799270
196+
```
197+
198+
199+
# Tests and Debugging
200+
201+
Run tests with `python tests/tests.py` or `python tests/tests.py SomeSpecificTest`
202+
203+
Enable debugging when doing tests in console:
204+
205+
``` python
206+
>>> from pykeepass.pykeepass import debug_setup
207+
>>> debug_setup()
208+
>>> kp.entries[0]
209+
DEBUG:pykeepass.pykeepass:xpath query: //Entry
210+
DEBUG:pykeepass.pykeepass:xpath query: (ancestor::Group)[last()]
211+
DEBUG:pykeepass.pykeepass:xpath query: (ancestor::Group)[last()]
212+
DEBUG:pykeepass.pykeepass:xpath query: String/Key[text()="Title"]/../Value
213+
DEBUG:pykeepass.pykeepass:xpath query: String/Key[text()="UserName"]/../Value
214+
Entry: "root_entry (foobar_user)"
215+
```
216+

0 commit comments

Comments
 (0)