Skip to content

Commit 433f457

Browse files
Smart Passwords Library (smartpasslib) v2.1.0
feat: add update functionality for smart password metadata Add `SmartPassword.update()` and `SmartPasswordManager.update_smart_password()` methods to modify description and length of existing smart passwords without affecting cryptographic integrity. Public keys remain unchanged, ensuring backward compatibility within v2.0.x/v2.1.x series. - Add comprehensive test coverage for new functionality - Update README.md This allows users to modify service descriptions and password lengths without recreating password entries, improving usability while maintaining security.
1 parent d52e51f commit 433f457

File tree

8 files changed

+347
-43
lines changed

8 files changed

+347
-43
lines changed

README.md

Lines changed: 207 additions & 39 deletions
Large diffs are not rendered by default.

data/images/cov.png

-4.36 KB
Loading

data/requirements-dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest
2+
pytest-cov

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "smartpasslib"
7-
version = "2.0.0"
8-
description = "Cross-platform library for generating smart passwords."
7+
version = "2.1.0"
8+
description = "Smart Passwords Library: Cryptographic password generation and management without storage. Generate passwords from secrets, verify knowledge without exposure, manage metadata securely."
99
authors = [
1010
{name = "Alexander Suvorov", email = "smartlegiondev@gmail.com"}
1111
]

smartpasslib/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (©) 2025, Alexander Suvorov. All rights reserved.
2-
"""Smart Password Library - Cross-platform library for generating smart passwords."""
2+
"""Smart Passwords Library: Cryptographic password generation and management without storage.
3+
Generate passwords from secrets, verify knowledge without exposure, manage metadata securely."""
34
from smartpasslib.factories.smart_password_factory import SmartPasswordFactory
45
from smartpasslib.generators.base import BasePasswordGenerator
56
from smartpasslib.generators.hash import HashGenerator
@@ -11,7 +12,7 @@
1112
from smartpasslib.managers.smart_password_manager import SmartPasswordManager
1213
from smartpasslib.masters.smart_password_master import SmartPasswordMaster
1314
from smartpasslib.smart_passwords.smart_password import SmartPassword
14-
__version__ = '2.0.0'
15+
__version__ = '2.1.0'
1516
__author__ = 'A.A. Suvorov'
1617
__all__ = [
1718
"SmartPasswordMaster",

smartpasslib/managers/smart_password_manager.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ def get_smart_password(self, public_key: str) -> Optional[SmartPassword]:
112112
"""
113113
return self.smart_passwords.get(public_key)
114114

115+
def update_smart_password(self, public_key: str, description: str = None, length: int = None) -> bool:
116+
"""
117+
Update metadata of an existing smart password.
118+
119+
Args:
120+
public_key: Public key of the password to update
121+
description: New description (optional)
122+
length: New password length (optional)
123+
124+
Returns:
125+
bool: True if update was successful, False if password not found
126+
127+
Raises:
128+
ValueError: If length is provided and less than 1
129+
"""
130+
password = self.get_smart_password(public_key)
131+
if password is None:
132+
return False
133+
134+
password.update(description=description, length=length)
135+
self._write_data()
136+
return True
137+
115138
def delete_smart_password(self, public_key: str):
116139
"""
117140
Delete smart password metadata by public key.

smartpasslib/smart_passwords/smart_password.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ def length(self) -> int:
5252
"""
5353
return self._length
5454

55+
def update(self, description: str = None, length: int = None) -> None:
56+
"""
57+
Update password metadata (description and/or length).
58+
59+
Args:
60+
description: New service/account description (optional)
61+
length: New password length (optional, must be >= 1)
62+
63+
Raises:
64+
ValueError: If length is provided and less than 1
65+
"""
66+
if description is not None:
67+
self._description = description
68+
69+
if length is not None:
70+
if length < 1:
71+
raise ValueError("Password length must be at least 1 character")
72+
self._length = length
73+
5574
def to_dict(self) -> Dict[str, str | int]:
5675
"""
5776
Convert to dictionary for serialization.

tests/test_managers/test_manager.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Copyright (©) 2025, Alexander Suvorov. All rights reserved.
22
import pytest
3+
4+
from smartpasslib import SmartPassword
35
from smartpasslib.managers.smart_password_manager import SmartPasswordManager
46

57

@@ -88,3 +90,92 @@ def test_check_public_key(self, test_secret):
8890
assert SmartPasswordManager.check_public_key(test_secret, public_key) is True
8991
assert SmartPasswordManager.check_public_key("wrong_secret", public_key) is False
9092
assert SmartPasswordManager.check_public_key(test_secret, "wrong_key") is False
93+
94+
def test_update_smart_password(self, temp_file, test_password):
95+
manager = SmartPasswordManager(filename=temp_file)
96+
manager.add_smart_password(test_password)
97+
98+
assert manager.update_smart_password(test_password.public_key, description="Updated Description") is True
99+
updated = manager.get_smart_password(test_password.public_key)
100+
assert updated.description == "Updated Description"
101+
assert updated.length == test_password.length # Length unchanged
102+
103+
assert manager.update_smart_password(test_password.public_key, length=20) is True
104+
updated = manager.get_smart_password(test_password.public_key)
105+
assert updated.description == "Updated Description" # Description unchanged
106+
assert updated.length == 20
107+
108+
assert manager.update_smart_password(
109+
test_password.public_key,
110+
description="Final Description",
111+
length=16
112+
) is True
113+
updated = manager.get_smart_password(test_password.public_key)
114+
assert updated.description == "Final Description"
115+
assert updated.length == 16
116+
117+
manager2 = SmartPasswordManager(filename=temp_file)
118+
reloaded = manager2.get_smart_password(test_password.public_key)
119+
assert reloaded.description == "Final Description"
120+
assert reloaded.length == 16
121+
122+
def test_update_nonexistent_password(self, temp_file):
123+
manager = SmartPasswordManager(filename=temp_file)
124+
assert manager.update_smart_password("nonexistent_key", description="Test") is False
125+
126+
def test_update_invalid_length(self, temp_file, test_password):
127+
manager = SmartPasswordManager(filename=temp_file)
128+
manager.add_smart_password(test_password)
129+
130+
with pytest.raises(ValueError, match="Password length must be at least 1 character"):
131+
manager.update_smart_password(test_password.public_key, length=0)
132+
133+
original = manager.get_smart_password(test_password.public_key)
134+
assert original.length == test_password.length
135+
assert original.description == test_password.description
136+
137+
def test_update_no_changes(self, temp_file, test_password):
138+
manager = SmartPasswordManager(filename=temp_file)
139+
manager.add_smart_password(test_password)
140+
141+
assert manager.update_smart_password(test_password.public_key) is True
142+
143+
password = manager.get_smart_password(test_password.public_key)
144+
assert password.description == test_password.description
145+
assert password.length == test_password.length
146+
147+
def test_smart_password_update_method(self):
148+
password = SmartPassword(
149+
public_key="test_key",
150+
description="Original Description",
151+
length=12
152+
)
153+
154+
password.update(description="New Description")
155+
assert password.description == "New Description"
156+
assert password.length == 12
157+
158+
password.update(length=20)
159+
assert password.description == "New Description"
160+
assert password.length == 20
161+
162+
password.update(description="Final Description", length=16)
163+
assert password.description == "Final Description"
164+
assert password.length == 16
165+
166+
with pytest.raises(ValueError, match="Password length must be at least 1 character"):
167+
password.update(length=0)
168+
169+
def test_smart_password_update_preserves_public_key(self, temp_file, test_password):
170+
manager = SmartPasswordManager(filename=temp_file)
171+
manager.add_smart_password(test_password)
172+
173+
original_public_key = test_password.public_key
174+
manager.update_smart_password(
175+
test_password.public_key,
176+
description="Updated",
177+
length=20
178+
)
179+
180+
updated = manager.get_smart_password(test_password.public_key)
181+
assert updated.public_key == original_public_key # Public key unchanged

0 commit comments

Comments
 (0)