updated to mysql-connector-python
This commit is contained in:
1 parent
65691a0aee
commit
4bc9b87317
6 files changed
+80
-68
No files matched your search
@@ -6,41 +6,41 @@ Tables 9.3–9.5 (STUDENT, GUARDIAN, ATTENDANCE).
|
|||||||
|
|
||||||
## What it does on startup
|
## What it does on startup
|
||||||
|
|
||||||
1. Connects to the MySQL server at `10.0.1.40:3306`.
|
1. Connects to the MySQL server at `10.10.0.160:3306`.
|
||||||
2. Verifies the `student_db` database exists — creates it if not.
|
2. Verifies the `student_db` database exists — creates it if not.
|
||||||
3. Verifies the `GUARDIAN`, `STUDENT`, and `ATTENDANCE` tables exist
|
3. Verifies the `GUARDIAN`, `STUDENT`, and `ATTENDANCE` tables exist
|
||||||
(in that order, since STUDENT references GUARDIAN and ATTENDANCE
|
(in that order, since STUDENT references GUARDIAN and ATTENDANCE
|
||||||
references STUDENT) — creates any that are missing, with the exact
|
references STUDENT) — creates any that are missing, with the exact
|
||||||
data types and constraints from the schema (primary keys, foreign
|
data types and constraints from the schema (primary keys, foreign
|
||||||
keys, `NOT NULL`, `UNIQUE` on `GPhone`, and the composite primary key
|
keys, `NOT NULL`, `UNIQUE` on `GPhone`, and the composite primary key
|
||||||
`(AttendanceDate, RollNumber)` on ATTENDANCE).
|
`(AttendanceDate, RollNumber)` on ATTENDANCE).
|
||||||
4. Launches an interactive menu for Create / Read / Update / Delete.
|
4. Launches an interactive menu for Create / Read / Update / Delete.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Install the one dependency:
|
1. Install the one dependency:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Set your MySQL credentials as environment variables (the host/port/
|
2. Set your MySQL credentials as environment variables (the host/port/
|
||||||
database default to the assignment's values already):
|
database default to the assignment's values already):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export DB_USER=root
|
export DB_USER=root
|
||||||
export DB_PASSWORD=your_password_here
|
export DB_PASSWORD=your_password_here
|
||||||
# optional overrides — these already default correctly:
|
# optional overrides — these already default correctly:
|
||||||
# export DB_HOST=10.0.1.40
|
# export DB_HOST=10.10.0.160
|
||||||
# export DB_PORT=3306
|
# export DB_PORT=3306
|
||||||
# export DB_NAME=student_db
|
# export DB_NAME=student_db
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run it:
|
3. Run it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 main.py
|
python3 main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
@@ -72,12 +72,6 @@ cascades and removes their attendance records automatically.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Uses [PyMySQL](https://pymysql.readthedocs.io/), a pure-Python MySQL
|
- Uses [mysql-connector-python](https://dev.mysql.com/doc/connector-python/en/), Oracle's official pure-Python MySQL driver — no system libraries (like `libmysqlclient`) required.
|
||||||
client — no system libraries (like `libmysqlclient`) required.
|
- All input is validated against the schema's constraints (max lengths, digit counts, date format, P/A status) before hitting the database, and database-level errors (duplicate keys, foreign key violations) are caught and shown as friendly messages rather than crashing.
|
||||||
- All input is validated against the schema's constraints (max lengths,
|
- Tested end-to-end (connect → create DB/tables → full CRUD → constraint error handling → cascade delete) against a local MySQL/MariaDB instance before delivery.
|
||||||
digit counts, date format, P/A status) before hitting the database,
|
|
||||||
and database-level errors (duplicate keys, foreign key violations)
|
|
||||||
are caught and shown as friendly messages rather than crashing.
|
|
||||||
- Tested end-to-end (connect → create DB/tables → full CRUD → constraint
|
|
||||||
error handling → cascade delete) against a local MySQL/MariaDB
|
|
||||||
instance before delivery.
|
|
||||||
@@ -5,12 +5,17 @@ Connection settings are read from environment variables first, falling back
|
|||||||
to the defaults below (which match the assignment's requirements). This
|
to the defaults below (which match the assignment's requirements). This
|
||||||
means you can run the app as-is, or override any value like:
|
means you can run the app as-is, or override any value like:
|
||||||
|
|
||||||
DB_HOST=10.0.1.40 DB_USER=root DB_PASSWORD=secret python3 main.py
|
DB_HOST=10.10.0.160 DB_USER=root DB_PASSWORD=secret python3 main.py
|
||||||
|
|
||||||
|
# on windows
|
||||||
|
$env:DB_USER="mysql_user"
|
||||||
|
$env:DB_PASSWORD="mysql_password"
|
||||||
|
python3 main.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DB_HOST = os.environ.get("DB_HOST", "10.0.1.40")
|
DB_HOST = os.environ.get("DB_HOST", "10.10.0.160")
|
||||||
DB_PORT = int(os.environ.get("DB_PORT", "3306"))
|
DB_PORT = int(os.environ.get("DB_PORT", "3306"))
|
||||||
DB_USER = os.environ.get("DB_USER", "root")
|
DB_USER = os.environ.get("DB_USER", "root")
|
||||||
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")
|
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"""
|
"""
|
||||||
CRUD operations for GUARDIAN, STUDENT and ATTENDANCE.
|
CRUD operations for GUARDIAN, STUDENT and ATTENDANCE.
|
||||||
|
|
||||||
Every function takes an open pymysql connection as its first argument and
|
Every function takes an open mysql-connector-python connection as its first
|
||||||
manages its own commit/rollback. All functions return plain data
|
argument and manages its own commit/rollback. All functions return plain
|
||||||
(dicts/lists/bool) so this module has no dependency on the UI layer.
|
data (dicts/lists/bool) so this module has no dependency on the UI layer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pymysql
|
import mysql.connector
|
||||||
|
|
||||||
|
|
||||||
class CrudError(Exception):
|
class CrudError(Exception):
|
||||||
@@ -20,33 +20,34 @@ def _run(conn, sql, params=None, fetch=None):
|
|||||||
fetch: None (no fetch), 'one', or 'all'
|
fetch: None (no fetch), 'one', or 'all'
|
||||||
Translates common MySQL errors into CrudError with a friendly message.
|
Translates common MySQL errors into CrudError with a friendly message.
|
||||||
"""
|
"""
|
||||||
|
cur = conn.cursor(dictionary=True)
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
cur.execute(sql, params or ())
|
||||||
cur.execute(sql, params or ())
|
result = None
|
||||||
result = None
|
if fetch == "one":
|
||||||
if fetch == "one":
|
result = cur.fetchone()
|
||||||
result = cur.fetchone()
|
elif fetch == "all":
|
||||||
elif fetch == "all":
|
result = cur.fetchall()
|
||||||
result = cur.fetchall()
|
conn.commit()
|
||||||
conn.commit()
|
return result if fetch else cur.rowcount
|
||||||
return result if fetch else cur.rowcount
|
except mysql.connector.errors.IntegrityError as e:
|
||||||
except pymysql.err.IntegrityError as e:
|
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
code = e.args[0] if e.args else None
|
if e.errno == 1062:
|
||||||
if code == 1062:
|
|
||||||
raise CrudError("A record with that primary/unique key already exists.")
|
raise CrudError("A record with that primary/unique key already exists.")
|
||||||
if code in (1451, 1452):
|
if e.errno in (1451, 1452):
|
||||||
raise CrudError(
|
raise CrudError(
|
||||||
"That operation violates a foreign key constraint "
|
"That operation violates a foreign key constraint "
|
||||||
"(referenced record missing, or still referenced by other rows)."
|
"(referenced record missing, or still referenced by other rows)."
|
||||||
)
|
)
|
||||||
raise CrudError(f"Database integrity error: {e}")
|
raise CrudError(f"Database integrity error: {e}")
|
||||||
except pymysql.err.DataError as e:
|
except mysql.connector.errors.DataError as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
raise CrudError(f"Invalid data for one or more fields: {e}")
|
raise CrudError(f"Invalid data for one or more fields: {e}")
|
||||||
except pymysql.err.MySQLError as e:
|
except mysql.connector.Error as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
raise CrudError(f"Database error: {e}")
|
raise CrudError(f"Database error: {e}")
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- GUARDIAN --
|
# ---------------------------------------------------------------- GUARDIAN --
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ Schema notes:
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pymysql
|
import mysql.connector
|
||||||
import pymysql.cursors
|
from mysql.connector import errorcode
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
@@ -61,20 +61,25 @@ TABLE_ORDER = ["GUARDIAN", "STUDENT", "ATTENDANCE"]
|
|||||||
def connect_server(database=None):
|
def connect_server(database=None):
|
||||||
"""Connect to the MySQL server (optionally selecting a database)."""
|
"""Connect to the MySQL server (optionally selecting a database)."""
|
||||||
try:
|
try:
|
||||||
conn = pymysql.connect(
|
conn = mysql.connector.connect(
|
||||||
host=config.DB_HOST,
|
host=config.DB_HOST,
|
||||||
port=config.DB_PORT,
|
port=config.DB_PORT,
|
||||||
user=config.DB_USER,
|
user=config.DB_USER,
|
||||||
password=config.DB_PASSWORD,
|
password=config.DB_PASSWORD,
|
||||||
database=database,
|
database=database,
|
||||||
connect_timeout=config.DB_CONNECT_TIMEOUT,
|
connection_timeout=config.DB_CONNECT_TIMEOUT,
|
||||||
cursorclass=pymysql.cursors.DictCursor,
|
|
||||||
autocommit=False,
|
autocommit=False,
|
||||||
)
|
)
|
||||||
return conn
|
return conn
|
||||||
except pymysql.err.OperationalError as e:
|
except mysql.connector.Error as e:
|
||||||
print(f"\n[ERROR] Could not connect to MySQL server at "
|
if e.errno == errorcode.ER_ACCESS_DENIED_ERROR:
|
||||||
f"{config.DB_HOST}:{config.DB_PORT} as user '{config.DB_USER}'.")
|
print(f"\n[ERROR] Access denied for user '{config.DB_USER}' — check "
|
||||||
|
f"DB_USER/DB_PASSWORD.")
|
||||||
|
elif database and e.errno == errorcode.ER_BAD_DB_ERROR:
|
||||||
|
print(f"\n[ERROR] Database '{database}' does not exist.")
|
||||||
|
else:
|
||||||
|
print(f"\n[ERROR] Could not connect to MySQL server at "
|
||||||
|
f"{config.DB_HOST}:{config.DB_PORT} as user '{config.DB_USER}'.")
|
||||||
print(f" Details: {e}")
|
print(f" Details: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@@ -83,7 +88,8 @@ def ensure_database():
|
|||||||
"""Verify the student_db database exists; create it if it does not."""
|
"""Verify the student_db database exists; create it if it does not."""
|
||||||
conn = connect_server(database=None)
|
conn = connect_server(database=None)
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cur:
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
cur.execute("SHOW DATABASES LIKE %s", (config.DB_NAME,))
|
cur.execute("SHOW DATABASES LIKE %s", (config.DB_NAME,))
|
||||||
exists = cur.fetchone() is not None
|
exists = cur.fetchone() is not None
|
||||||
if exists:
|
if exists:
|
||||||
@@ -96,23 +102,29 @@ def ensure_database():
|
|||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
print(f"[OK] Database '{config.DB_NAME}' created.")
|
print(f"[OK] Database '{config.DB_NAME}' created.")
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def ensure_tables(conn):
|
def ensure_tables(conn):
|
||||||
"""Verify each required table exists; create any that are missing."""
|
"""Verify each required table exists; create any that are missing."""
|
||||||
with conn.cursor() as cur:
|
cur = conn.cursor()
|
||||||
cur.execute("SHOW TABLES")
|
cur.execute("SHOW TABLES")
|
||||||
existing = {row[f"Tables_in_{config.DB_NAME}"] for row in cur.fetchall()}
|
existing = {row[0] for row in cur.fetchall()}
|
||||||
|
cur.close()
|
||||||
|
|
||||||
for table in TABLE_ORDER:
|
for table in TABLE_ORDER:
|
||||||
if table in existing:
|
if table in existing:
|
||||||
print(f"[OK] Table '{table}' found.")
|
print(f"[OK] Table '{table}' found.")
|
||||||
else:
|
else:
|
||||||
print(f"[..] Table '{table}' not found. Creating it...")
|
print(f"[..] Table '{table}' not found. Creating it...")
|
||||||
with conn.cursor() as cur:
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
cur.execute(TABLE_DEFINITIONS[table])
|
cur.execute(TABLE_DEFINITIONS[table])
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
conn.commit()
|
conn.commit()
|
||||||
print(f"[OK] Table '{table}' created.")
|
print(f"[OK] Table '{table}' created.")
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Student Management System
|
Student Management System
|
||||||
==========================
|
==========================
|
||||||
Terminal CRUD application for STUDENT / GUARDIAN / ATTENDANCE, backed by
|
Terminal CRUD application for STUDENT / GUARDIAN / ATTENDANCE, backed by
|
||||||
MySQL on 10.0.1.40:3306 (student_db).
|
MySQL on 10.10.0.160:3306 (student_db).
|
||||||
|
|
||||||
On startup this:
|
On startup this:
|
||||||
1. Connects to the MySQL server.
|
1. Connects to the MySQL server.
|
||||||
@@ -17,7 +17,7 @@ Run:
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pymysql
|
import mysql.connector
|
||||||
|
|
||||||
import crud
|
import crud
|
||||||
import db
|
import db
|
||||||
@@ -326,7 +326,7 @@ def main():
|
|||||||
main_menu(conn)
|
main_menu(conn)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n\nInterrupted. Exiting.")
|
print("\n\nInterrupted. Exiting.")
|
||||||
except pymysql.err.MySQLError as e:
|
except mysql.connector.Error as e:
|
||||||
print(f"\n[ERROR] Lost database connection: {e}")
|
print(f"\n[ERROR] Lost database connection: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
PyMySQL>=1.1.0
|
mysql-connector-python>=9.0.0
|
||||||
Reference in new issue
Block a user