Skip to content

Commit e57c944

Browse files
pushing changes
1 parent 022f66b commit e57c944

File tree

14 files changed

+1167
-38
lines changed

14 files changed

+1167
-38
lines changed

build/lib/commands/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
3+
"""
4+
Get a list of valid Python files to import as modules.
5+
Uses the location of __file__ to identify the folder.
6+
"""
7+
8+
list_of_files = [file for file in os.listdir(os.path.dirname(__file__))
9+
# Get the list of files in the directory
10+
if not file.startswith('-')
11+
# Exclude files starting with '-'
12+
and not file.startswith('.')
13+
# Exclude hidden files starting with '.'
14+
and file.endswith('.py')
15+
# Include only files ending with '.py'
16+
and os.path.isfile(os.path.join(os.path.dirname(__file__), file))]
17+
# Check if it's a file
18+
19+
module_list = []
20+
21+
# Drop the .py extension from the file list and populate the module_list
22+
for name in list_of_files:
23+
module_list.append(os.path.splitext(name)[0])
24+
# Remove the '.py' extension and add to module_list
25+
26+
# Use the generated module_list for wildcard imports
27+
__all__ = module_list
28+
# Expose all modules for wildcard imports

build/lib/commands/cache.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import click
2+
from commands import logger
3+
4+
@click.group(help="A group of commands for managing credentials and tokens.")
5+
def cache():
6+
"""
7+
A group of commands for managing credentials and tokens.
8+
"""
9+
pass
10+
11+
@click.command(help="Add credentials to the cache.")
12+
def add_credential():
13+
pass
14+
15+
@click.command(help="Update an existing credential.")
16+
def reset_credential():
17+
pass
18+
19+
@click.command(help="Delete an existing credential.")
20+
def delete_credential():
21+
pass
22+
23+
@click.command(help="Add a token.")
24+
def add_token():
25+
pass
26+
27+
@click.command(help="Delete a token.")
28+
def delete_token():
29+
pass
30+
31+
@click.command(help="View all existing credentials.")
32+
def view_credentials():
33+
pass
34+
35+
@click.command(help="View all existing tokens.")
36+
def view_tokens():
37+
pass
38+
39+
@click.command(help="Delete a specific credential.")
40+
def clear_credentials():
41+
pass
42+
43+
@click.command(help="Delete a speficic token.")
44+
def clear_tokens():
45+
pass
46+
47+
# ===== COMMANDS ===============
48+
49+
# Add commands to cache group
50+
cache.add_command(add_credential)
51+
cache.add_command(reset_credential)
52+
cache.add_command(delete_credential)
53+
cache.add_command(add_token)
54+
cache.add_command(delete_token)
55+
cache.add_command(view_credentials)
56+
cache.add_command(view_tokens)
57+
cache.add_command(clear_credentials)
58+
cache.add_command(clear_tokens)
59+
60+
if __name__ == "__main__":
61+
cache()

build/lib/commands/calculator.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import click
2+
3+
@click.command(help="A simple calculator.\n\n*Example:*\n`calculator --num1 10 --num2 5 --operation +`")
4+
@click.option("--num1", prompt="Prompt first number", type=int, help="The first number.")
5+
@click.option("--num2", prompt="Prompt second number", type=int, help="The second number.")
6+
@click.option("--operation", prompt="Input operator", type=click.Choice(["+", "-", "*", "/"], case_sensitive=False), help="Promopt operation: + - * /")
7+
def calculator(num1, num2, operation):
8+
"""Perform a simple arithmetic operation."""
9+
if operation == "+":
10+
result = num1 + num2
11+
elif operation == "-":
12+
result = num1 - num2
13+
elif operation == "*":
14+
result = num1 * num2
15+
elif operation == "/":
16+
# check for vidion by zero
17+
if num2 == 0:
18+
click.echo("Error: division by zero is not allowed.")
19+
return
20+
result = num1 / num2
21+
22+
click.echo(f"{num1} {operation} {num2} = {result}")
23+
24+
if __name__ == "__main__":
25+
calculator()

build/lib/commands/expense.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import click
2+
import requests
3+
4+
"""
5+
SCRIPT REQUIRES expense_api.py to run with univorn.
6+
Notes:
7+
expose_value=False --> option will not show up as an actual option when the user invokes the command.
8+
"""
9+
10+
# base url for your fastapi server
11+
BASE_URL = "http://127.0.0.1:8000"
12+
13+
@click.group(help="A group of commands for the expense tracker app.")
14+
@click.option("--warning", is_eager=True, help="WARNING: fastapi/expense_api.py needs to run on uvicorn server before running this script.", expose_value=False)
15+
def expense():
16+
"""
17+
command group for managing expenses.
18+
"""
19+
pass
20+
21+
# ==========
22+
23+
@click.command()
24+
@click.option("-a", "--amount", type=float, required=True, help="Amount of the expense.")
25+
@click.option("-c", "--category", type=str, required=True, help="Category of the expense.")
26+
@click.option("-d", "--date", type=click.DateTime(formats=["%d-%m-%y"]), required=True, help="Date of the expense (format: DD-MM-YY).")
27+
@click.option("-n", "--note", type=str, help="Optional notes for the expense.")
28+
def add_expense(amount, category, date, note):
29+
"""
30+
add a new expense.
31+
"""
32+
# send a POST request to the server to add a new expense
33+
response = requests.post(f"{BASE_URL}/expenses/", json={
34+
"amount": amount,
35+
"category": category,
36+
"date": date.date().isoformat(),
37+
"note": note or ""
38+
})
39+
40+
# check if the response status code indicates success (201 Created)
41+
if response.status_code == 201:
42+
result = response.json()
43+
# output the added expense details in a formatted manner
44+
click.echo("\nExpense Added Successfully:")
45+
click.echo("=" * 30)
46+
click.echo(f"ID : {result['id']}")
47+
click.echo(f"Amount : ${amount:.2f}")
48+
click.echo(f"Category: {category}")
49+
click.echo(f"Date : {date.date()}")
50+
click.echo(f"Note : {note or 'No notes provided'}")
51+
click.echo("=" * 30)
52+
# handle bad request errors (400 Bad Request)
53+
elif response.status_code == 400:
54+
click.echo(f"Bad request: {response.json().get('detail', 'Invalid data provided')}")
55+
else:
56+
# handle other errors
57+
click.echo(f"Error adding expense: {response.status_code} - {response.text}")
58+
59+
# ==========
60+
61+
@click.command()
62+
@click.option("-s", "--start-date", type=click.DateTime(formats=["%d-%m-%y"]), help="Start date for viewing expenses (format: DD-MM-YY).")
63+
@click.option("-e", "--end-date", type=click.DateTime(formats=["%d-%m-%y"]), help="End date for viewing expenses (format: DD-MM-YY).")
64+
def view_expenses(start_date, end_date):
65+
"""
66+
view expenses within a date range.
67+
"""
68+
# prepare query parameters for the GET request
69+
params = {
70+
"start_date": start_date.date().isoformat() if start_date else None,
71+
"end_date": end_date.date().isoformat() if end_date else None
72+
}
73+
74+
# remove any parameters that are None to avoid sending unnecessary parameters
75+
params = {k: v for k, v in params.items() if v is not None}
76+
77+
# send a GET request to the server to retrieve expenses
78+
response = requests.get(f"{BASE_URL}/expenses/", params=params)
79+
80+
# check if the response status code indicates success (200 OK)
81+
if response.status_code == 200:
82+
expenses = response.json()
83+
if not expenses:
84+
click.echo("No expenses found for the given dates.")
85+
else:
86+
# output the retrieved expenses in a formatted manner
87+
click.echo("\nExpenses Found:")
88+
click.echo("=" * 30)
89+
for exp in expenses:
90+
click.echo(f"ID : {exp['id']}")
91+
click.echo(f"Amount : ${exp['amount']:.2f}")
92+
click.echo(f"Category: {exp['category']}")
93+
click.echo(f"Date : {exp['date']}")
94+
click.echo(f"Note : {exp['note'] or 'No notes provided'}")
95+
click.echo("-" * 30)
96+
97+
# handle bad request errors (400 Bad Request)
98+
elif response.status_code == 400:
99+
click.echo(f"Bad request: {response.json().get('detail', 'Invalid date format')}")
100+
else:
101+
# handle other errors
102+
click.echo(f"Error retrieving expenses: {response.status_code} - {response.text}")
103+
104+
# ==========
105+
106+
@click.command()
107+
@click.option("--id", type=int, required=True, help="ID of the expense to delete.")
108+
def delete_expense(id):
109+
"""
110+
delete an expense by its id.
111+
"""
112+
# send a DELETE request to the server to remove the expense with the given ID
113+
response = requests.delete(f"{BASE_URL}/expenses/{id}")
114+
115+
# check if the response status code indicates success (204 No Content)
116+
if response.status_code == 204:
117+
click.echo(f"Deleted expense with ID: {id}")
118+
119+
# handle not found errors (404 Not Found)
120+
elif response.status_code == 404:
121+
click.echo(f"Expense with ID {id} not found.")
122+
else:
123+
# handle other errors
124+
click.echo(f"Error deleting expense: {response.status_code} - {response.text}")
125+
126+
# ==========
127+
128+
@click.command()
129+
@click.option("-s", "--start-date", type=click.DateTime(formats=["%d-%m-%y"]), help="Start date for the expense report (format: DD-MM-YY).")
130+
@click.option("-e", "--end-date", type=click.DateTime(formats=["%d-%m-%y"]), help="End date for the expense report (format: DD-MM-YY).")
131+
def generate_report(start_date, end_date):
132+
"""
133+
generate an expense report within a date range.
134+
"""
135+
# prepare query parameters for the GET request
136+
params = {
137+
"start_date": start_date.date().isoformat() if start_date else None,
138+
"end_date": end_date.date().isoformat() if end_date else None
139+
}
140+
141+
# remove any parameters that are None to avoid sending unnecessary parameters
142+
params = {k: v for k, v in params.items() if v is not None}
143+
144+
# send a GET request to the server to generate the expense report
145+
response = requests.get(f"{BASE_URL}/report/", params=params)
146+
147+
# check if the response status code indicates success (200 OK)
148+
if response.status_code == 200:
149+
report = response.json()
150+
total_amount = report.get("total_amount", 0)
151+
click.echo(f"Total expenses from {start_date.date() if start_date else 'start'} to {end_date.date() if end_date else 'end'}: ${total_amount:.2f}")
152+
153+
# handle bad request errors (400 Bad Request)
154+
elif response.status_code == 400:
155+
click.echo(f"Bad request: {response.json().get('detail', 'Invalid date format')}")
156+
else:
157+
# handle other errors
158+
click.echo(f"Error generating report: {response.status_code} - {response.text}")
159+
160+
# ==========
161+
162+
# add the sub-commands to the "expense" group
163+
expense.add_command(add_expense)
164+
expense.add_command(view_expenses)
165+
expense.add_command(delete_expense)
166+
expense.add_command(generate_report)
167+
168+
# if this script runs directly, invoke the "expense" group
169+
if __name__ == "__main__":
170+
expense()

build/lib/commands/gen_pass.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import click
2+
import secrets
3+
import string
4+
from commands import logger
5+
6+
# ====================
7+
8+
@click.group(help="A group of commands for password generator.")
9+
def gen_pass():
10+
"""
11+
A group of commands for the password generator.
12+
This function serves as the entry point for the Click group.
13+
"""
14+
pass
15+
16+
# ====================
17+
18+
@click.command("gen")
19+
@click.option("-l", "--length", type=int, required=True, help="Length of password (minimum: 8).")
20+
@click.option("-c", "--include-cap", is_flag=True, help="Include uppercase letters in the password.")
21+
@click.option("-n", "--include-num", is_flag=True, help="Include numbers in the password.")
22+
@click.option("-s", "--include-sp", is_flag=True, help="Include special characters in the password.")
23+
def generate_pass(length, include_cap, include_num, include_sp):
24+
"""
25+
Generate a random password based on specified criteria.
26+
27+
Parameters:
28+
- length (int): The length of the password to be generated. Must be at least 8.
29+
- include_cap (bool): Flag to include uppercase letters in the password.
30+
- include_num (bool): Flag to include numbers in the password.
31+
- include_sp (bool): Flag to include special characters in the password.
32+
"""
33+
34+
# Check if the length is less than the minimum required
35+
if length < 8:
36+
logger.error("Password length must be at least 8.")
37+
return
38+
39+
# Initialise the alphabet with lowercase letters
40+
alphabet = string.ascii_lowercase
41+
42+
# Include uppercase letters if the flag is set
43+
if include_cap:
44+
# Append uppercase letters
45+
alphabet += string.ascii_uppercase
46+
47+
# Include numbers if the flag is set
48+
if include_num:
49+
alphabet += string.digits # Append digits
50+
51+
# Include special characters if the flag is set
52+
if include_sp:
53+
alphabet += string.punctuation # Append special characters
54+
55+
# Ensure at least one character type is included
56+
if len(alphabet) == 0:
57+
logger.error("At least one character type must be included.")
58+
return
59+
60+
# Generate the password by selecting random characters from the alphabet
61+
password = "".join(secrets.choice(alphabet) for _ in range(length))
62+
63+
# Output the generated password
64+
logger.success("Password successfully generated:")
65+
# Print the password to the console
66+
click.echo(f"{password}")
67+
68+
# ====================
69+
70+
# Add the generate_pass command to the gen_pass group
71+
gen_pass.add_command(generate_pass)
72+
73+
# ====================
74+
75+
if __name__ == "__main__":
76+
gen_pass()

0 commit comments

Comments
 (0)