Skip to content

Commit 0b6a418

Browse files
committed
refactor: Avoid pass-through design by manipulating the data directly in pw_command.py (and not in pw_json_client.py)
1 parent 2f64e42 commit 0b6a418

File tree

3 files changed

+79
-146
lines changed

3 files changed

+79
-146
lines changed

src/pw/pw_cli.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pyperclip
66

77
from .pw_json_client import SecretsDataJSONClient
8-
from .pw_utils import generate_random_password
8+
from .pw_utils import generate_random_password, find_key
99
from crypto.pw_encryption import SynchronousEncryption
1010

1111

@@ -18,27 +18,40 @@ def __init__(self, pw_client: SecretsDataJSONClient,
1818
self.args = args
1919

2020
def get_all_sections(self) -> List[str]:
21-
return self.pw_client.get_sections()
21+
"""Get all sections of the secrets data file (json)."""
22+
return [pw for pw in self.pw_client.pw_dict.keys()]
23+
# yield from self.pw_dict.keys()
2224

2325
def print_keys_of_section(self):
26+
"""Output all available keys of a section to the console."""
2427
if self.args.section is None:
2528
self.args.section = 'main'
26-
self.pw_client.print_keys_of_section(self.args.section)
29+
for key in self.pw_client.pw_dict[self.args.section].keys():
30+
print(key)
2731

2832
def create_section(self):
29-
return self.pw_client.create_section(self.args.section)
33+
"""Creates a new section."""
34+
35+
if self._check_existence_of_section(self.args.section):
36+
return print(f'Section {self.args.section} exists already.')
37+
38+
self.pw_client.pw_dict[self.args.section] = {}
39+
self.pw_client.save_dict_to_file()
40+
print(f'Created a new section: "{self.args.section}".')
41+
42+
def _check_existence_of_section(self, section: str):
43+
if section in self.pw_client.pw_dict:
44+
return True
3045

3146
def add_new_secrets_data(self):
32-
args = self.args
33-
3447
secrets_data = {}
3548

36-
if args.set_password:
37-
new_password = args.set_password
49+
if self.args.set_password:
50+
new_password = self.args.set_password
3851
else:
3952
new_password = generate_random_password(
40-
password_length=args.random_password_length,
41-
special_characters=args.no_special_characters)
53+
password_length=self.args.random_password_length,
54+
special_characters=self.args.no_special_characters)
4255
pyperclip.copy(new_password)
4356
encrypted_password = self.crypto.encrypt(new_password)
4457
secrets_data['password'] = encrypted_password
@@ -63,39 +76,65 @@ def add_new_secrets_data(self):
6376
encrypted_value = self.crypto.encrypt(value)
6477
secrets_data[key] = encrypted_value
6578

66-
self.pw_client.add_new_secrets_data(entity=args.new_secrets_data,
67-
secrets_data=secrets_data,
68-
section=args.section,
69-
overwrite=args.overwrite)
79+
if self.args.section is None:
80+
self.args.section = 'main'
81+
82+
if not self._check_existence_of_section(self.args.section):
83+
self.create_section()
84+
85+
if (
86+
self.args.entity in self.pw_client.pw_dict[self.args.section]
87+
and self.args.overwrite is False
88+
):
89+
print('Entity is already there. Nothing happened.')
90+
print('Use the -ow / --overwrite option to update existing secrets data.')
91+
return False
7092

93+
entity = self.args.new_secrets_data
94+
new_secrets_data = {entity: secrets_data}
95+
96+
print(f'Created new password for "{entity}".')
97+
print('')
98+
99+
self.pw_client.pw_dict[self.args.section].update(new_secrets_data)
100+
self.pw_client.save_dict_to_file()
71101
return True
72102

73103
def update_secrets_data(self):
74-
"""`pw -u <key>=<value> <entity>`"""
104+
"""`pw -u <key>=<value> (-s <section>="main") <entity>`"""
75105
k, v = self.args.update.split('=')
76106
new_data = {k: self.crypto.encrypt(v)}
77-
self.pw_client.update_secrets_data(
78-
entity=self.args.entity,
79-
section=self.args.section,
80-
new_data=new_data)
107+
108+
if self.args.section is None:
109+
self.args.section = 'main'
110+
111+
self.pw_client.pw_dict[self.args.section][self.args.entity].update(new_data)
112+
self.pw_client.save_dict_to_file()
81113

82114
def get_secrets_data(self):
83-
secrets_data = self.pw_client.get_secrets_data(
84-
entity=self.args.entity,
85-
section=self.args.section)
115+
if self.args.section is None:
116+
self.args.section = 'main'
117+
secrets_data = self.pw_client.pw_dict[self.args.section][self.args.entity]
86118
return secrets_data
87119

88120
def remove_secrets_data(self):
89121
key = self.args.remove_entity
90-
self.pw_client.remove_secrets_data(key, self.args.section)
122+
section = self.args.section or 'main'
123+
self.pw_client.pw_dict[section].pop(key)
124+
self.pw_client.save_dict_to_file()
125+
print(f'Deleted {key} from {section}')
126+
print('')
91127
return True
92128

93129
def remove_section(self):
94-
self.pw_client.remove_section(self.args.remove_section)
130+
section = self.args.remove_section
131+
self.pw_client.pw_dict.pop(section)
132+
self.pw_client.save_dict_to_file()
133+
print(f'Removed Section: "{section}"')
95134
return True
96135

97136
def find_secrets_data(self):
98-
results_as_generator = self.pw_client.find_key(self.args.find)
137+
results_as_generator = find_key(self.args.find, self.pw_client.pw_dict)
99138
results = list(results_as_generator)
100139
for result in results:
101140
print(f'Found "{result["entity"]}" in section "{result["section"]}".')

src/pw/pw_json_client.py

Lines changed: 5 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,18 @@
11
import json
22
import datetime
3-
from typing import List
4-
5-
from pw.pw_utils import get_pws_from_json_file
63

74

85
class SecretsDataJSONClient:
96
def __init__(self, creds_dir: str, creds_file_path: str):
107
self.creds_dir = creds_dir
118
self.creds_file_path = creds_file_path
12-
self.pw_dict = get_pws_from_json_file(creds_file_path)
13-
14-
def get_secrets_data(self, entity: str, section: str = None) -> dict:
15-
"""Return secrets data from self.pw_dict.
16-
17-
Args:
18-
entity (str):
19-
The name of the holder of the password, e.g. "GitHub".
20-
section (str, optional):
21-
Defaults to "main". Adjust if you want to access data from an
22-
other section.
23-
"""
24-
if section is None:
25-
section = 'main'
26-
secrets_data = self.pw_dict[section][entity]
27-
return secrets_data
28-
29-
def remove_secrets_data(self, key: str, section: str = None):
30-
"""Removes a password from the creds.json file."""
31-
section = section or 'main'
32-
self.pw_dict[section].pop(key)
33-
self.save_dict_to_file()
34-
print(f'Deleted {key} from {section}')
35-
print('')
36-
37-
def find_key(self, search_term: str) -> List[dict]:
38-
"""Iterate over all secrets and yield a secret's name that matches the search_term."""
39-
for section, secrets in self.pw_dict.items():
40-
for secret in secrets:
41-
if search_term.lower() in secret.lower():
42-
yield {'entity': secret,
43-
'section': section}
44-
45-
def add_new_secrets_data(self, entity: str, secrets_data: dict,
46-
section: 'str' = None, overwrite: bool = False) -> bool:
47-
"""Adds new secrets data to the password file.
48-
49-
If the a password already exists in the creds.json file, this method will update it.
50-
51-
Args:
52-
entity (str): The entity that you need the password for, e.g. "GitHub"
53-
secrets_data (dict):
54-
A dictionary that you want to store and that holds your
55-
secrets. Example:
56-
{'password': 'p4ssw0rd',
57-
'website': 'https://kuda.ai',
58-
'user_name': 'DataDave'}
59-
section (str, optional):
60-
You may set the section of the json file to which this
61-
password will be added to. Defaults to the section "main".
62-
overwrite (bool, optional):
63-
If set to True, an existing password will be overwritten.
64-
65-
Returns:
66-
bool: True for writing new data, False if no action.
67-
"""
9+
self.pw_dict = self.get_pws_from_json_file()
6810
self.create_backup()
6911

70-
if section is None:
71-
section = 'main'
72-
73-
if not self.check_existence_of_section(section):
74-
self.create_section(section)
75-
76-
if entity in self.pw_dict[section] and overwrite is False:
77-
print('Entity is already there. Nothing happened.')
78-
print('Use the -ow / --overwrite option to update existing secrets data.')
79-
return False
80-
81-
new_secrets_data = {entity: secrets_data}
82-
83-
print(f'Created new password for "{entity}".')
84-
print('')
85-
86-
self.pw_dict[section].update(new_secrets_data)
87-
self.save_dict_to_file()
88-
return True
89-
90-
def update_secrets_data(self, entity:str, section: str, new_data: dict):
91-
self.pw_dict[section][entity].update(new_data)
92-
self.save_dict_to_file()
12+
def get_pws_from_json_file(self):
13+
"""Loads json data into python to retrieve passwords that are stored as key value pairs."""
14+
with open(self.creds_file_path) as pws:
15+
return json.load(pws)
9316

9417
def create_backup(self):
9518
"""Create a backup of the dictionary with the passwords."""
@@ -101,35 +24,3 @@ def create_backup(self):
10124
def save_dict_to_file(self):
10225
with open(self.creds_file_path, 'w') as pw_file_json:
10326
json.dump(self.pw_dict, pw_file_json)
104-
105-
def create_section(self, section_name: str) -> None:
106-
"""Creates a new section."""
107-
if self.check_existence_of_section(section_name):
108-
return print(f'Section {section_name} exists already.')
109-
self.pw_dict[section_name] = {}
110-
self.save_dict_to_file()
111-
print(f'Created a new section: "{section_name}".')
112-
113-
def get_sections(self) -> List[str]:
114-
"""Get all sections of the secrets data file (json)."""
115-
return [pw for pw in self.pw_dict.keys()]
116-
# yield from self.pw_dict.keys()
117-
118-
def print_keys_of_section(self, section_name: str = None):
119-
"""Output all available keys of a section to the console."""
120-
for key in self.pw_dict[section_name].keys():
121-
print(key)
122-
123-
def check_existence_of_section(self, section: str):
124-
if section in self.pw_dict:
125-
return True
126-
127-
def remove_section(self, section):
128-
self.pw_dict.pop(section)
129-
self.save_dict_to_file()
130-
print(f'Removed Section: "{section}"')
131-
132-
def open_pw_file(pw_file_path, app: str = 'Sublime'):
133-
"""Open pw file with an app (default: Sublime)."""
134-
raise NotImplementedError
135-

src/pw/pw_utils.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import random
33
import string
4+
from typing import Generator
45

56

67
class HelpTexts:
@@ -32,16 +33,18 @@ def generate_random_password(special_characters=True,
3233
characters = digits + letters + punctuation
3334
random_password = ''.join(random.choice(characters) for i in range(password_length))
3435
return random_password
35-
36-
37-
def get_pws_from_json_file(file_path):
38-
"""Loads json data into python to retrieve passwords that are stored as key value pairs."""
39-
with open(file_path) as pws:
40-
return json.load(pws)
41-
36+
4237

4338
def my_exchandler(type, value, traceback):
4439
"""Set 'sys.excepthook' to myexchandler to avoid traceback.
4540
Credits: https://stackoverflow.com/questions/38598740/raising-errors-without-traceback
4641
"""
4742
print(value)
43+
44+
45+
def find_key(key: str, dictionary: dict) -> Generator[dict, None, None]:
46+
"""Iterate a dict recursively and yield all values if key matches."""
47+
for section, secrets in dictionary:
48+
for secret in secrets:
49+
if key.lower() in secret.lower():
50+
yield {'entity': secret, 'section': section}

0 commit comments

Comments
 (0)