-
Notifications
You must be signed in to change notification settings - Fork 681
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c245957
commit f3b51a0
Showing
5 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# CRUD CLI with 🐘 PostgreSQL | ||
|
||
Well, this is a simple CRUD CLI with PostgreSQL. I made this project to help others learn more about PostgreSQL driver for Python and how to create beautiful and interactive CLI's with Python. | ||
|
||
Well it is basically a book mark manager, you can add, edit, delete and list your bookmarks. | ||
|
||
It is called the Query DB btw. | ||
|
||
## What you can learn | ||
|
||
I highly recommend you try and read through the code. | ||
As a beginner, it is far easier to read code that is more like what you would write yourself, and this is a good example of that. | ||
It doesn't use any advanced Python features, and it doesn't use any advanced SQL features either. | ||
|
||
So, no classes, confusing context managers, or anything like that. | ||
Just plain old functions and SQL. | ||
|
||
So, if you are a beginner, you can learn: | ||
|
||
- Asking better input from the user | ||
- Using PostgreSQL with Python | ||
- Creating beautiful and interactive CLI's with Python | ||
- Create, Read, Update and Delete (CRUD) operations with PostgreSQL | ||
|
||
If you find this project useful, please give it's parent a star. | ||
|
||
## Screenshot | ||
|
||
![Screenshot](assets/screenshot.png) | ||
|
||
## Features | ||
|
||
1. Easy to use | ||
2. Interactive | ||
3. Beautiful | ||
4. No need to remember SQL commands | ||
5. Fast and lightweight | ||
|
||
## Prerequisites | ||
|
||
- Python 3.6 or higher | ||
- PostgreSQL 9.5 or higher | ||
|
||
### Python dependencies | ||
|
||
- psycopg2 | ||
- rich | ||
|
||
These dependencies are already in the `requirements.txt` file. | ||
|
||
### Create a database | ||
|
||
We use postgresql as our database, so you need to create a database with any name and enter the credentials in the `creds.json` file. | ||
|
||
```bash | ||
createdb db_name | ||
``` | ||
|
||
## How to use | ||
|
||
First, you need to install the dependencies: | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
Then, you need to create a `creds.json` file with the following info: | ||
|
||
```json | ||
{ | ||
"username": "postgres", | ||
"password": "password", | ||
"host": "127.0.0.1", | ||
"port": "5432", | ||
"database": "test" | ||
} | ||
``` | ||
|
||
After that, you can run the CLI with: | ||
|
||
```bash | ||
python main.py | ||
``` | ||
|
||
It's done! Now you can use the CLI. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"username": "joshi", | ||
"password": "NoobScience", | ||
"host": "127.0.0.1", | ||
"port": "5432", | ||
"database": "learn_sql" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# Author: NoobScience | ||
# filename: db.py | ||
# License: MIT | ||
# 3rd party Modules Used: | ||
# Rich | ||
# psycopg2 | ||
|
||
# Import the necessary modules | ||
# Rich for formatting | ||
from rich.console import Console | ||
from rich.table import Table | ||
from rich import print | ||
from rich.text import Text | ||
from rich.panel import Panel | ||
from rich.prompt import Prompt, Confirm | ||
# For working with postgres | ||
import psycopg2 | ||
from psycopg2 import Error | ||
import json | ||
import string | ||
import random | ||
console = Console() | ||
|
||
try: | ||
# Connect to an existing database | ||
try: | ||
# Get the secrets | ||
file = open("creds.json", "r") | ||
stuff = file.read() | ||
secrets = json.loads(stuff) | ||
# We are using the secrets to connect to the database | ||
# Although this is very convenient, it is not secure | ||
# If you are building something serious, use a more secure method | ||
# Like a .env file | ||
# The creds.json is only being used to make the code more readable | ||
connection = psycopg2.connect(user=str(secrets["username"]), | ||
password=str(secrets["password"]), | ||
host=str(secrets["host"]), | ||
port=str(secrets["port"]), | ||
database=str(secrets["database"])) | ||
|
||
# Get the info of the connection using the get_dsn_parameters() method | ||
info = connection.get_dsn_parameters() | ||
except: | ||
console.print( | ||
":red_circle:Sorry, something went wrong! Maybe a wrong credential? Check stuff.json's info correctly") | ||
|
||
# Define a new cursor object by calling the cursor() method | ||
# This is the object we use to interact with the database | ||
# Think of it as a pointer to a specific place in the database | ||
cursor = connection.cursor() | ||
|
||
print( | ||
f'{info["user"]} is connected to PostgreSQL server on {info["host"]}:{info["port"]}') | ||
|
||
# Generate hash for assigning to a entry | ||
def hash_gen(): | ||
lower = string.ascii_lowercase | ||
upper = string.ascii_uppercase | ||
digits = string.digits | ||
whole = lower + upper + digits | ||
hash_string = random.sample(whole, 8) | ||
hash = "".join(hash_string) | ||
return hash | ||
|
||
# Check if a table exits, if not create one | ||
# We have the following columns in the table | ||
# name, url, author, hash, date | ||
# The name, url, author have to be filled | ||
# Rest is auto generated by the program and the database | ||
def check_table(): | ||
cursor.execute(""" | ||
CREATE TABLE IF NOT EXISTS dbview( | ||
name TEXT NOT NULL, | ||
url TEXT NOT NULL, | ||
author TEXT, | ||
hash TEXT NOT NULL, | ||
date TIMESTAMP | ||
); | ||
""") | ||
console.log(":green_circle: Connected to Table :man: dbview") | ||
|
||
# Insert a new entry into the table | ||
# The hash is auto generated | ||
def insert(name, url, author): | ||
cursor.execute(f""" | ||
INSERT INTO dbview (name, url, author, hash, date) VALUES ('{name}', '{url}', '{author}', '{str(hash_gen())}', CURRENT_TIMESTAMP) | ||
""") | ||
search("name", name) | ||
|
||
# View the table using a rich table | ||
def table(): | ||
cursor.execute("SELECT * from dbview") | ||
records = cursor.fetchall() | ||
try: | ||
table = Table(title=Panel( | ||
f'Query DB [red]{info["host"]}:{info["port"]}'), show_header=True, header_style="bold green") | ||
table.add_column("Name", style="purple", width=20) | ||
table.add_column("Url", style="yellow") | ||
table.add_column("Author", style="cyan") | ||
table.add_column("Hash", style="dim") | ||
table.add_column("Date", no_wrap=True, style="dim blue") | ||
for record in records: | ||
table.add_row( | ||
record[0], | ||
record[1], | ||
record[2], | ||
record[3], | ||
str(record[4]) | ||
) | ||
console.print(table) | ||
except: | ||
# Some terminals don't support rich | ||
print("Name\t\t | URL\t\t | Channel\t\t | Hash\t\t | Date") | ||
for record in records: | ||
print( | ||
f'{record[0]}\t\t | {record[1]}\t\t | {record[2]}\t\t | {record[3]}\t\t| {str(record[4])}\t\t') | ||
|
||
# Edit a entry in the table | ||
def edit(key, value): | ||
console.print( | ||
"Enter The New Values to Update in the form of a name url channel") | ||
new_record = str(Prompt.ask("Enter Here: ")) | ||
new_records = new_record.split() | ||
# Execute the query to update the table | ||
cursor.execute( | ||
f"UPDATE dbview SET name='{new_records[0]}', url='{new_records[1]}', author='{new_records[2]}' WHERE {key}='{value}'") | ||
console.log(":green_circle: Successfully Updated Database") | ||
table() | ||
|
||
# Get the hash of a entry | ||
def hash(): | ||
console.print("Enter A Key and Value to get Hash") | ||
record_key = str(Prompt.ask("Enter a Key", choices=[ | ||
"name", "url", "author", "hash", "date"], default="name")) | ||
record_value = str(Prompt.ask("Enter a Value")) | ||
cursor.execute( | ||
f"SELECT * from dnview WHERE {record_key}='{record_value}'") | ||
records = cursor.fetchone() | ||
console.print(f"The Hash is {records[3]}") | ||
|
||
# Delete a entry from the table | ||
def delete(key, value): | ||
table() | ||
cursor.execute(f"DELETE from dbview WHERE {key}='{value}'") | ||
console.log(":green_circle: Successfully Updated Database") | ||
table() | ||
|
||
# Drop the table | ||
def drop(): | ||
console.print( | ||
"Are you sure you want to Drop the database? This will delete the whole table") | ||
confirmation = Confirm.ask("Are you sure") | ||
if confirmation: | ||
cursor.execute("DROP table dbview;") | ||
console.print( | ||
"Dropped Table dbview: Restart the application to start a new main table") | ||
else: | ||
console.print("Skipped the drop operation") | ||
|
||
# Search the table for a entry | ||
# We provide a lot of options to search | ||
# You can search by name, url, author, hash | ||
# This makes it easy to search for a entry | ||
def search(key, value): | ||
cursor.execute(f"SELECT * from dbview WHERE {key}='{value}'") | ||
records = cursor.fetchall() | ||
table = Table(title=Panel( | ||
f'Query DB [red]{info["host"]}:{info["port"]}'), show_header=True, header_style="bold green") | ||
table.add_column("Name", style="purple", width=20) | ||
table.add_column("Url", style="yellow") | ||
table.add_column("Channel", style="cyan") | ||
table.add_column("Hash", style="dim") | ||
table.add_column("Date", no_wrap=True, style="dim blue") | ||
for record in records: | ||
table.add_row( | ||
record[0], | ||
record[1], | ||
record[2], | ||
record[3], | ||
str(record[4]) | ||
) | ||
console.print(table) | ||
loop = 1 | ||
# 12 is a rather arbitrary number | ||
# I know. But after a while, you don't want the program to run forever | ||
while loop != 12: | ||
check_table() | ||
# Just the normal CRUD operations | ||
action = str(Prompt.ask("Enter your name", choices=[ | ||
"view", "new", "edit", "delete", "drop", "quit", "hash"], default="view")) | ||
if action == "view": | ||
table() | ||
if action == "new": | ||
name = Prompt.ask("Enter the Name") | ||
url = Prompt.ask("Enter The URL") | ||
author = Prompt.ask("Enter The Author") | ||
insert(name, url, author) | ||
if action == "delete": | ||
record_key = str(Prompt.ask("Enter the key", choices=[ | ||
"name", "url", "author", "hash", "date"], default="name")) | ||
record_value = str(Prompt.ask("Enter the Value")) | ||
delete(record_key, record_value) | ||
if action == "drop": | ||
drop() | ||
if action == "edit": | ||
record_key = str(Prompt.ask("Enter the key", choices=[ | ||
"name", "url", "author", "hash", "date"], default="name")) | ||
record_value = str(Prompt.ask("Enter the Value")) | ||
edit(record_key, record_value) | ||
if action == "hash": | ||
hash() | ||
if action == "quit": | ||
loop = 12 | ||
|
||
# If something goes wrong, we catch the error | ||
except (Exception, Error) as error: | ||
print("Error while connecting to PostgreSQL", error) | ||
# We finally close the connection | ||
finally: | ||
if (connection): | ||
connection.commit() | ||
cursor.close() | ||
connection.close() | ||
console.print(":red_heart: Thanks for using db.py") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
rich==13.5.3 | ||
psycopg2==2.9.7 |