aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cleberg <hello@cleberg.net>2025-04-05 13:36:49 -0500
committerGitHub <noreply@github.com>2025-04-05 13:36:49 -0500
commitc8617ce8aecb40ccf1d13c02dcd7d7e74e79b56b (patch)
treecf901030197768b2aa21d2d3fcf0426948a94111
parent1fc982100a13de26bfca3419d1a6c5a65acad3d2 (diff)
downloadyoshi-cli-c8617ce8aecb40ccf1d13c02dcd7d7e74e79b56b.tar.gz
yoshi-cli-c8617ce8aecb40ccf1d13c02dcd7d7e74e79b56b.tar.bz2
yoshi-cli-c8617ce8aecb40ccf1d13c02dcd7d7e74e79b56b.zip
migrate from pylint to ruff-action (#4)
* migrate from pylint to ruff-action * temporarily disable push param * Commit from GitHub Actions (Pylint) * re-enable push param --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
-rw-r--r--.github/workflows/pylint.yml26
-rw-r--r--yoshi/__main__.py1
-rw-r--r--yoshi/account.py24
-rw-r--r--yoshi/cli.py90
-rw-r--r--yoshi/crypto.py12
-rw-r--r--yoshi/database.py60
-rw-r--r--yoshi/process.py105
7 files changed, 169 insertions, 149 deletions
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index 6f918b6..caef813 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -1,23 +1,37 @@
name: Pylint
-on: [push]
+on:
+ push:
+ paths:
+ - '**.py'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.8", "3.9", "3.10"]
+ python-version: ["3.x"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
+ ref: ${{ github.event.pull_request.head.ref }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pylint cryptography prettytable
- - name: Analysing the code with pylint
+ pip install cryptography prettytable
+ - name: Install Ruff
+ uses: astral-sh/ruff-action@v3.2.2
+ - name: Ruff Actions
run: |
- pylint -d R0801 $(git ls-files '*.py')
+ ruff check --fix
+ ruff format
+ - name: Add and Commit
+ uses: EndBug/add-and-commit@v9
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ default_author: github_actions
+ pathspec_error_handling: ignore \ No newline at end of file
diff --git a/yoshi/__main__.py b/yoshi/__main__.py
index b7e6c6f..a94bac4 100644
--- a/yoshi/__main__.py
+++ b/yoshi/__main__.py
@@ -4,4 +4,5 @@ Entry point for the yoshi cli app.
if __name__ == "__main__":
from yoshi.cli import yoshi
+
yoshi()
diff --git a/yoshi/account.py b/yoshi/account.py
index fb42008..e92c304 100644
--- a/yoshi/account.py
+++ b/yoshi/account.py
@@ -11,9 +11,14 @@ from yoshi import database
class Account:
"""Represents a login account."""
- def __init__(self, uuid: str, application: str, #pylint: disable=R0913,R0917
- username: str, #pylint: disable=R0913,R0917
- password: str, url: str) -> None: #pylint: disable=R0913,R0917
+ def __init__(
+ self,
+ uuid: str,
+ application: str, # pylint: disable=R0913,R0917
+ username: str, # pylint: disable=R0913,R0917
+ password: str,
+ url: str,
+ ) -> None: # pylint: disable=R0913,R0917
self.uuid = uuid
self.application = application
self.username = username
@@ -22,16 +27,17 @@ class Account:
def display_account(self) -> None:
"""Print the account details."""
- print('ID:', self.uuid)
- print('Application:', self.application)
- print('Username:', self.username)
- print('Password:', self.password)
- print('URL:', self.url)
+ print("ID:", self.uuid)
+ print("Application:", self.application)
+ print("Username:", self.username)
+ print("Password:", self.password)
+ print("URL:", self.url)
def save_account(self) -> None:
"""Save the account details to the database."""
database.add_account(
- self.uuid, self.application, self.username, self.password, self.url)
+ self.uuid, self.application, self.username, self.password, self.url
+ )
def delete_account(self) -> bool:
"""Delete the account from the database.
diff --git a/yoshi/cli.py b/yoshi/cli.py
index e140098..2e9ce49 100644
--- a/yoshi/cli.py
+++ b/yoshi/cli.py
@@ -7,91 +7,76 @@ It imports the required modules and sets up a parser with basic options for demo
import argparse
from yoshi import crypto, database, process
+
def yoshi():
"""
CLI entry point logic, used to parse user inputs.
"""
parser = argparse.ArgumentParser(
- description='Manage your username and passwords via a convenient CLI vault.'
+ description="Manage your username and passwords via a convenient CLI vault."
)
# Top-level arguments
group_one = parser.add_mutually_exclusive_group()
group_one.add_argument(
- '-n', '--new',
- help='Create a new account.',
- action='store_true'
+ "-n", "--new", help="Create a new account.", action="store_true"
)
group_one.add_argument(
- '-l', '--list',
- help='List all saved accounts.',
- action='store_true'
+ "-l", "--list", help="List all saved accounts.", action="store_true"
)
group_one.add_argument(
- '-e', '--edit',
- help='Edit a saved account.',
- action='store_true'
+ "-e", "--edit", help="Edit a saved account.", action="store_true"
)
group_one.add_argument(
- '-d', '--delete',
- help='Delete a saved account.',
- action='store_true'
+ "-d", "--delete", help="Delete a saved account.", action="store_true"
)
group_one.add_argument(
- '--purge',
+ "--purge",
help=(
- 'Purge all accounts and delete the vault. '
- '(Caution: this will irreversibly destroy your data.)'
+ "Purge all accounts and delete the vault. "
+ "(Caution: this will irreversibly destroy your data.)"
),
- action='store_true'
- )
- group_one.add_argument(
- '--encrypt',
- help='Encrypt the vault.',
- action='store_true'
- )
- group_one.add_argument(
- '--decrypt',
- help='Decrypt the vault.',
- action='store_true'
+ action="store_true",
)
+ group_one.add_argument("--encrypt", help="Encrypt the vault.", action="store_true")
+ group_one.add_argument("--decrypt", help="Decrypt the vault.", action="store_true")
# Encryption flags
group_two = parser.add_mutually_exclusive_group()
group_two.add_argument(
- '-g', '--generate',
- help=(
- 'When using the --encrypt option, generate a new encryption key.'
- ),
- action='store_true'
+ "-g",
+ "--generate",
+ help=("When using the --encrypt option, generate a new encryption key."),
+ action="store_true",
)
group_two.add_argument(
- '-k', '--keyfile',
- help='Path to existing key file.',
- action='store',
+ "-k",
+ "--keyfile",
+ help="Path to existing key file.",
+ action="store",
nargs=1,
- type=str
+ type=str,
)
# Edit flags
group_three = parser.add_argument_group()
group_three.add_argument(
- '-u', '--uuid',
- help=(
- 'When using the --edit or --delete options, provide the account UUID.'
- ),
- action='store',
+ "-u",
+ "--uuid",
+ help=("When using the --edit or --delete options, provide the account UUID."),
+ action="store",
nargs=1,
- type=str
+ type=str,
)
group_three.add_argument(
- '-f', '--field',
+ "-f",
+ "--field",
help=(
- 'When using the --edit option, specify the field to edit (integer index).'
+ "When using the --edit option, specify the field to edit (integer index)."
),
- action='store',
+ action="store",
nargs=1,
- type=int
+ type=int,
)
args = parser.parse_args()
@@ -100,22 +85,22 @@ def yoshi():
if args.keyfile:
key = crypto.load_key(args.keyfile[0])
else:
- key = input('Please enter your decryption key: ')
+ key = input("Please enter your decryption key: ")
crypto.decrypt(key)
elif args.encrypt:
if args.generate:
key = crypto.generate_key()
print(
- 'WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT '
- 'YOUR DATA WITHOUT IT!'
+ "WRITE THIS KEY DOWN SOMEWHERE SAFE. YOU WILL NOT BE ABLE TO DECRYPT "
+ "YOUR DATA WITHOUT IT!"
)
print(key.decode())
- print('\n')
+ print("\n")
else:
if args.keyfile:
key = crypto.load_key(args.keyfile[0])
else:
- key = input('Please enter your encryption key: ')
+ key = input("Please enter your encryption key: ")
crypto.encrypt(key)
elif database.check_table():
if args.new:
@@ -130,8 +115,9 @@ def yoshi():
process.purge_accounts()
else:
raise TypeError(
- 'Please specify a command or use the --help flag for more information.'
+ "Please specify a command or use the --help flag for more information."
)
+
if __name__ == "__main__":
yoshi()
diff --git a/yoshi/crypto.py b/yoshi/crypto.py
index 9b0a423..d85fbe9 100644
--- a/yoshi/crypto.py
+++ b/yoshi/crypto.py
@@ -6,7 +6,7 @@ It allows for secure encryption and decryption of data using a secret key.
from cryptography.fernet import Fernet
-VAULT_FILE = 'vault.sqlite'
+VAULT_FILE = "vault.sqlite"
def generate_key() -> bytes:
@@ -21,25 +21,25 @@ def load_key(key_file: str) -> bytes:
Args:
key_file (str): Path to the key file.
"""
- with open(key_file, 'rb') as key:
+ with open(key_file, "rb") as key:
return key.read()
def encrypt(key: bytes, filename: str = VAULT_FILE) -> None:
"""Encrypts the data in the specified file using the provided key."""
f = Fernet(key)
- with open(filename, 'rb') as vault:
+ with open(filename, "rb") as vault:
data = vault.read()
encrypted_data = f.encrypt(data)
- with open(filename, 'wb') as vault:
+ with open(filename, "wb") as vault:
vault.write(encrypted_data)
def decrypt(key: bytes, filename: str = VAULT_FILE) -> None:
"""Decrypts the data in the specified file using the provided key."""
f = Fernet(key)
- with open(filename, 'rb') as vault:
+ with open(filename, "rb") as vault:
encrypted_data = vault.read()
decrypted_data = f.decrypt(encrypted_data)
- with open(filename, 'wb') as vault:
+ with open(filename, "wb") as vault:
vault.write(decrypted_data)
diff --git a/yoshi/database.py b/yoshi/database.py
index e1e2e78..4591955 100644
--- a/yoshi/database.py
+++ b/yoshi/database.py
@@ -7,8 +7,8 @@ import sqlite3
import sys
import os
-VAULT_DECRYPTED = 'vault.sqlite'
-VAULT_ENCRYPTED = 'vault.sqlite.aes'
+VAULT_DECRYPTED = "vault.sqlite"
+VAULT_ENCRYPTED = "vault.sqlite.aes"
def create_table() -> None:
@@ -16,8 +16,8 @@ def create_table() -> None:
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
cursor.execute(
- ''' CREATE TABLE IF NOT EXISTS accounts (uuid text, application text,
- username text, password text, url text) '''
+ """ CREATE TABLE IF NOT EXISTS accounts (uuid text, application text,
+ username text, password text, url text) """
)
db_connection.commit()
db_connection.close()
@@ -29,17 +29,18 @@ def check_table() -> bool:
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
cursor.execute(
- ''' SELECT count(name) FROM sqlite_master WHERE type='table'
- AND name='accounts' '''
+ """ SELECT count(name) FROM sqlite_master WHERE type='table'
+ AND name='accounts' """
)
if cursor.fetchone()[0] != 1:
user_choice = input(
- 'Password vault does not exist. Would you like to create it now? (y/n): ')
- if user_choice.lower() == 'y':
+ "Password vault does not exist. Would you like to create it now? (y/n): "
+ )
+ if user_choice.lower() == "y":
create_table()
check = True
else:
- sys.exit('Program aborted upon user request.')
+ sys.exit("Program aborted upon user request.")
else:
check = True
db_connection.commit()
@@ -47,17 +48,22 @@ def check_table() -> bool:
return check
-def add_account(uuid: str, application: str, username: str, password: str,
- url: str) -> None:
+def add_account(
+ uuid: str, application: str, username: str, password: str, url: str
+) -> None:
"""Add a new account within the vault database."""
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
cursor.execute(
- ''' INSERT INTO accounts VALUES (:uuid,:application,:username,
- :password,:url) ''', {
- 'uuid': uuid, 'application': application, 'username': username,
- 'password': password, 'url': url
- }
+ """ INSERT INTO accounts VALUES (:uuid,:application,:username,
+ :password,:url) """,
+ {
+ "uuid": uuid,
+ "application": application,
+ "username": username,
+ "password": password,
+ "url": url,
+ },
)
db_connection.commit()
db_connection.close()
@@ -67,9 +73,7 @@ def delete_account(uuid: str) -> None:
"""Delete an account within the vault database by its unique ID."""
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
- cursor.execute(
- ''' DELETE FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}
- )
+ cursor.execute(""" DELETE FROM accounts WHERE uuid = :uuid """, {"uuid": uuid})
db_connection.commit()
db_connection.close()
@@ -78,9 +82,7 @@ def find_account(uuid: str) -> list:
"""Find an account within the vault database by its unique ID."""
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
- cursor.execute(
- ''' SELECT * FROM accounts WHERE uuid = :uuid ''', {'uuid': uuid}
- )
+ cursor.execute(""" SELECT * FROM accounts WHERE uuid = :uuid """, {"uuid": uuid})
account = cursor.fetchall()
db_connection.close()
return account
@@ -90,7 +92,7 @@ def find_accounts() -> list:
"""Return all accounts stored within the vault database."""
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
- cursor.execute(''' SELECT * FROM accounts ''')
+ cursor.execute(""" SELECT * FROM accounts """)
accounts = cursor.fetchall()
db_connection.close()
return accounts
@@ -99,14 +101,14 @@ def find_accounts() -> list:
def update_account(field_name: str, new_value: str, uuid: str) -> None:
"""Update an account within the vault database by its unique ID."""
queries = {
- 'application': 'UPDATE accounts SET application = :new_value WHERE uuid = :uuid',
- 'username': 'UPDATE accounts SET username = :new_value WHERE uuid = :uuid',
- 'password': 'UPDATE accounts SET password = :new_value WHERE uuid = :uuid',
- 'url': 'UPDATE accounts SET url = :new_value WHERE uuid = :uuid'
+ "application": "UPDATE accounts SET application = :new_value WHERE uuid = :uuid",
+ "username": "UPDATE accounts SET username = :new_value WHERE uuid = :uuid",
+ "password": "UPDATE accounts SET password = :new_value WHERE uuid = :uuid",
+ "url": "UPDATE accounts SET url = :new_value WHERE uuid = :uuid",
}
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
- cursor.execute(queries[field_name], {'new_value': new_value, 'uuid': uuid})
+ cursor.execute(queries[field_name], {"new_value": new_value, "uuid": uuid})
db_connection.commit()
db_connection.close()
@@ -115,7 +117,7 @@ def purge_table() -> None:
"""Purge the 'accounts' table within the vault database."""
db_connection = sqlite3.connect(VAULT_DECRYPTED)
cursor = db_connection.cursor()
- cursor.execute(''' DROP TABLE accounts ''')
+ cursor.execute(""" DROP TABLE accounts """)
db_connection.commit()
db_connection.close()
diff --git a/yoshi/process.py b/yoshi/process.py
index f09c036..ed9f456 100644
--- a/yoshi/process.py
+++ b/yoshi/process.py
@@ -32,6 +32,7 @@ from yoshi.account import Account
from yoshi import database
from yoshi.wordlist import WORDLIST
+
def generate_characters(n: int) -> list:
"""
Generates a list of n random characters from the set of ASCII letters,
@@ -61,7 +62,7 @@ def shuffle_characters(characters: list) -> str:
str: A string representation of the shuffled characters
"""
random.shuffle(characters)
- character_string = ''.join(characters)
+ character_string = "".join(characters)
return character_string
@@ -96,7 +97,7 @@ def list_accounts() -> None:
None
"""
accounts = database.find_accounts()
- t = PrettyTable(['UUID', 'Application', 'Username', 'Password', 'URL'])
+ t = PrettyTable(["UUID", "Application", "Username", "Password", "URL"])
for account in accounts:
t.add_row([account[0], account[1], account[2], account[3], account[4]])
print(t)
@@ -113,13 +114,15 @@ def delete_account(account_uuid: str) -> None:
None
"""
account_record = database.find_account(account_uuid)
- account = Account(account_record[0][0],
- account_record[0][1],
- account_record[0][2],
- account_record[0][3],
- account_record[0][4])
+ account = Account(
+ account_record[0][0],
+ account_record[0][1],
+ account_record[0][2],
+ account_record[0][3],
+ account_record[0][4],
+ )
if account.delete_account():
- print('Account successfully deleted.')
+ print("Account successfully deleted.")
def purge_accounts() -> None:
@@ -130,12 +133,15 @@ def purge_accounts() -> None:
None
"""
check = input(
- '''Are you absolutely sure you want to delete your password vault?
- This action is irreversible. (y/n): ''')
- if check.lower() == 'y':
+ """Are you absolutely sure you want to delete your password vault?
+ This action is irreversible. (y/n): """
+ )
+ if check.lower() == "y":
database.purge_table()
database.purge_database()
- print('The password vault has been purged. You may now exit or create a new one.')
+ print(
+ "The password vault has been purged. You may now exit or create a new one."
+ )
def create_account() -> None:
@@ -145,46 +151,50 @@ def create_account() -> None:
Returns:
None
"""
- application_string = input('Please enter a name for this account: ')
- username_string = input('Please enter your username for this account: ')
- url_string = input('(Optional) Please enter a URL for this account: ')
+ application_string = input("Please enter a name for this account: ")
+ username_string = input("Please enter your username for this account: ")
+ url_string = input("(Optional) Please enter a URL for this account: ")
password_type = input(
- '''Do you want a random character password (p), an XKCD-style passphrase
-(x), or a custom password (c)? (p|x|c): '''
+ """Do you want a random character password (p), an XKCD-style passphrase
+(x), or a custom password (c)? (p|x|c): """
)
- if password_type not in ['p', 'x', 'c']:
- print('Error: Invalid choice. Please choose p, x, or c.')
+ if password_type not in ["p", "x", "c"]:
+ print("Error: Invalid choice. Please choose p, x, or c.")
return
- if password_type == 'x':
+ if password_type == "x":
password_length = int(
- input('Please enter number of words to include (min. 2): ')
+ input("Please enter number of words to include (min. 2): ")
)
if password_length < 3:
- print('Error: Your passphrase length must be at least 3 words.')
+ print("Error: Your passphrase length must be at least 3 words.")
return
password_separator = input(
- 'Please enter your desired separator symbol (_,-, ~, etc.): '
+ "Please enter your desired separator symbol (_,-, ~, etc.): "
)
password_string = generate_passphrase(password_length, password_separator)
- elif password_type == 'p':
+ elif password_type == "p":
password_length = int(
- input('Please enter your desired password length (min. 8): ')
+ input("Please enter your desired password length (min. 8): ")
)
if password_length < 8:
- print('Error: Your password length must be at least 8 characters.')
+ print("Error: Your password length must be at least 8 characters.")
return
password_characters = generate_characters(password_length)
password_string = shuffle_characters(password_characters)
else:
- password_string = input('Please enter your desired password: ')
-
- account = Account(str(uuid.uuid4()), application_string,
- username_string, password_string, url_string)
+ password_string = input("Please enter your desired password: ")
+
+ account = Account(
+ str(uuid.uuid4()),
+ application_string,
+ username_string,
+ password_string,
+ url_string,
+ )
account.save_account()
- print('Account saved to the vault. Use `--list` to see all saved accounts.')
-
+ print("Account saved to the vault. Use `--list` to see all saved accounts.")
def edit_account(account_uuid: str, edit_parameter: int) -> None:
@@ -197,29 +207,30 @@ def edit_account(account_uuid: str, edit_parameter: int) -> None:
Valid values are 1 for application name, 2 for username,
3 for password, and 4 for URL.
"""
- field_name, new_value = ''
+ field_name, new_value = ""
if edit_parameter == 1:
- field_name = 'application'
- new_value = input('Please enter your desired Application name: ')
+ field_name = "application"
+ new_value = input("Please enter your desired Application name: ")
elif edit_parameter == 2:
- field_name = 'username'
- new_value = input('Please enter your desired username: ')
+ field_name = "username"
+ new_value = input("Please enter your desired username: ")
elif edit_parameter == 3:
- field_name = 'password'
+ field_name = "password"
type_check = input(
- 'Do you want a new random password or to enter a custom password? '
- '(random/custom): ').lower()
- if type_check == 'random':
- password_length = int(input('Please enter your desired password length: '))
+ "Do you want a new random password or to enter a custom password? "
+ "(random/custom): "
+ ).lower()
+ if type_check == "random":
+ password_length = int(input("Please enter your desired password length: "))
if password_length < 8:
- print('Error: Your password length must be at least 8 characters.')
+ print("Error: Your password length must be at least 8 characters.")
else:
password_characters = generate_characters(password_length)
new_value = shuffle_characters(password_characters)
else:
- new_value = input('Please enter your desired password: ')
+ new_value = input("Please enter your desired password: ")
elif edit_parameter == 4:
- field_name = 'url'
- new_value = input('Please enter your desired URL: ')
+ field_name = "url"
+ new_value = input("Please enter your desired URL: ")
database.update_account(field_name, new_value, account_uuid)
- print('Account successfully updated.')
+ print("Account successfully updated.")