Skip to content

Commit 0454f16

Browse files
committed
feat: configuring password generator
1 parent c723a7a commit 0454f16

6 files changed

Lines changed: 96 additions & 5 deletions

File tree

app/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from fastapi import FastAPI
2-
from app.routes import json_tools, uuid_tools, base64_tools, time_tools
2+
from app.routes import json_tools, uuid_tools, base64_tools, time_tools, password_tools
33

44
app = FastAPI(title="Developer Toolkit API")
55

66
app.include_router(json_tools.router)
77
app.include_router(uuid_tools.router)
88
app.include_router(base64_tools.router)
99
app.include_router(time_tools.router)
10+
app.include_router(password_tools.router)
1011

1112
@app.get("/")
1213
def read_root():

app/routes/password_tools.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from fastapi import APIRouter, Query, HTTPException
2+
from pydantic import BaseModel, Field
3+
import random
4+
import string
5+
6+
router = APIRouter(prefix="/tools/password", tags=["Password Tools"])
7+
8+
# Response model for password generation
9+
class PasswordResponse(BaseModel):
10+
password: str = Field(
11+
example="a9B!7xTq@1l#",
12+
description="A randomly generated password"
13+
)
14+
15+
@router.get(
16+
"/generate",
17+
response_model=PasswordResponse,
18+
summary="Generate a secure random password",
19+
response_description="Returns a randomly generated password based on query parameters"
20+
)
21+
def generate_password(
22+
length: int = Query(12, ge=4, le=128, description="Length of the password"),
23+
include_symbols: bool = Query(True, description="Include special characters"),
24+
include_numbers: bool = Query(True, description="Include digits"),
25+
include_uppercase: bool = Query(True, description="Include uppercase letters"),
26+
include_lowercase: bool = Query(True, description="Include lowercase letters")
27+
):
28+
"""
29+
Generate a secure, random password.
30+
31+
You can customize:
32+
- Length
33+
- Whether to include symbols, numbers, uppercase, and lowercase characters
34+
35+
Returns:
36+
A JSON object containing the generated password.
37+
"""
38+
character_pool = ""
39+
40+
if include_lowercase:
41+
character_pool += string.ascii_lowercase
42+
if include_uppercase:
43+
character_pool += string.ascii_uppercase
44+
if include_numbers:
45+
character_pool += string.digits
46+
if include_symbols:
47+
character_pool += string.punctuation
48+
49+
if not character_pool:
50+
raise HTTPException(
51+
status_code=400,
52+
detail="At least one character set must be selected."
53+
)
54+
55+
password = ''.join(random.choices(character_pool, k=length))
56+
return {"password": password}
57+

app/routes/time_tools.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Optional
44
from typing import Union
55
from datetime import datetime
6+
from datetime import timezone
67

78

89
router = APIRouter(prefix="/tools/time", tags=["Time Tools"])
@@ -48,11 +49,11 @@ def convert_time(data: TimeConversionInput):
4849
"""
4950
try:
5051
if data.timestamp is not None:
51-
dt = datetime.fromtimestamp(data.timestamp)
52+
dt = datetime.utcfromtimestamp(data.timestamp)
5253
return {"date_string": dt.isoformat()}
5354

5455
elif data.date_string is not None:
55-
dt = datetime.fromisoformat(data.date_string)
56+
dt = datetime.fromisoformat(data.date_string).replace(tzinfo=timezone.utc)
5657
return {"timestamp": int(dt.timestamp())}
5758

5859
else:

tests/test_json_tools.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ def test_prettify_invalid_json():
1818
json={"content": "{name: Jameelah"} # Invalid JSON
1919
)
2020
assert response.status_code == 400
21-
assert response.json()["detail"] == "Invalid JSON input."
21+
assert response.json()["detail"] == "Invalid JSON string."
22+
2223

tests/test_password_tools.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from fastapi.testclient import TestClient
2+
from app.main import app
3+
4+
client = TestClient(app)
5+
6+
def test_generate_password_default():
7+
response = client.get("/tools/password/generate")
8+
assert response.status_code == 200
9+
data = response.json()
10+
assert "password" in data
11+
assert len(data["password"]) == 12 # Default length
12+
13+
def test_generate_password_custom_length():
14+
response = client.get("/tools/password/generate?length=20")
15+
assert response.status_code == 200
16+
assert len(response.json()["password"]) == 20
17+
18+
def test_generate_password_without_symbols():
19+
response = client.get("/tools/password/generate?include_symbols=false")
20+
assert response.status_code == 200
21+
password = response.json()["password"]
22+
assert all(char.isalnum() or char in string.ascii_letters + string.digits for char in password)
23+
24+
def test_generate_password_with_all_sets_false():
25+
response = client.get(
26+
"/tools/password/generate?include_symbols=false&include_numbers=false&include_uppercase=false&include_lowercase=false"
27+
)
28+
assert response.status_code == 400
29+
assert response.json()["detail"] == "At least one character set must be selected."
30+

tests/test_time_tools.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ def test_convert_timestamp_to_date():
1212
def test_convert_date_to_timestamp():
1313
response = client.post("/tools/time/convert", json={"date_string": "2021-01-01T00:00:00"})
1414
assert response.status_code == 200
15-
assert response.json()["timestamp"] == 1609459200
15+
assert response.json()["timestamp"] == 1609459200 # This assumes UTC!
16+
1617

0 commit comments

Comments
 (0)