Initial Commit
This commit is contained in:
commit
4dbdc7d793
18 changed files with 2384 additions and 0 deletions
15
pyqt6_scaffold/contrib/__init__.py
Normal file
15
pyqt6_scaffold/contrib/__init__.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from .backends import (
|
||||
MysqlDatabase,
|
||||
SqliteDatabase,
|
||||
PostgresqlDatabase,
|
||||
)
|
||||
from . import auth
|
||||
|
||||
__all__ = [
|
||||
"auth",
|
||||
"MysqlDatabase",
|
||||
"SqliteDatabase",
|
||||
"PostgresqlDatabase"
|
||||
]
|
||||
10
pyqt6_scaffold/contrib/auth/__init__.py
Normal file
10
pyqt6_scaffold/contrib/auth/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from .role import Role, RoleLevel
|
||||
from .database import RBACMixin
|
||||
|
||||
__all__ = [
|
||||
"Role",
|
||||
"RoleLevel",
|
||||
"RBACMixin"
|
||||
]
|
||||
45
pyqt6_scaffold/contrib/auth/database.py
Normal file
45
pyqt6_scaffold/contrib/auth/database.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from pyqt6_scaffold.core.objects import BaseUser
|
||||
|
||||
class RBACMixin:
|
||||
"""
|
||||
Role-Based Access Control mixin for AbstractDatabase subclasses.
|
||||
|
||||
Provides a can() method that checks user permissions against
|
||||
a permission table in the database. Table and column names
|
||||
are configurable as class attributes.
|
||||
|
||||
Expected table schema:
|
||||
permission_table (permission_column, level_column)
|
||||
|
||||
Example:
|
||||
permission_map (perm VARCHAR, min_level INT)
|
||||
"""
|
||||
permission_table: str = "permission_map"
|
||||
permission_column: str = "perm"
|
||||
level_column: str = "min_level"
|
||||
|
||||
def can(self, user: BaseUser, permission: str) -> bool:
|
||||
"""
|
||||
Check whether a user has the required permission level.
|
||||
|
||||
Args:
|
||||
user: A BaseUser instance with a role.level attribute.
|
||||
permission: Permission identifier to look up in the database.
|
||||
|
||||
Returns:
|
||||
True if user.role.level >= required level, False otherwise.
|
||||
"""
|
||||
with self.execute(
|
||||
f"""
|
||||
SELECT {self.level_column}
|
||||
FROM {self.permission_table}
|
||||
WHERE {self.permission_column} = {self.placeholder}
|
||||
""",
|
||||
(permission,)
|
||||
) as cursor:
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return False
|
||||
return user.role.level >= row[0]
|
||||
29
pyqt6_scaffold/contrib/auth/role.py
Normal file
29
pyqt6_scaffold/contrib/auth/role.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Role:
|
||||
"""
|
||||
Represents a user role with a hierarchical access level.
|
||||
|
||||
Attributes:
|
||||
name: Human-readable role name.
|
||||
level: Numeric access level used for permission checks.
|
||||
"""
|
||||
name: str
|
||||
level: int
|
||||
|
||||
class RoleLevel(Enum):
|
||||
"""
|
||||
Predefined access levels for common roles.
|
||||
|
||||
Use these constants when populating the permission table
|
||||
or comparing role levels in application logic.
|
||||
"""
|
||||
GUEST = 0
|
||||
CLIENT = 25
|
||||
EMPLOYEE = 50
|
||||
MANAGER = 75
|
||||
ADMIN = 100
|
||||
87
pyqt6_scaffold/contrib/backends.py
Normal file
87
pyqt6_scaffold/contrib/backends.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
import os
|
||||
from pyqt6_scaffold.core.logging import setup_logger
|
||||
from pyqt6_scaffold.core.database import AbstractDatabase
|
||||
|
||||
log = setup_logger(__name__)
|
||||
|
||||
class PostgresqlDatabase(AbstractDatabase):
|
||||
"""
|
||||
AbstractDatabase implementation for PostgreSQL via psycopg2.
|
||||
|
||||
Configuration is read from environment variables:
|
||||
PG_HOST, PG_PORT, PG_USER, PG_DATABASE, PG_PASSWORD
|
||||
|
||||
Requires:
|
||||
pip install pyqt6-scaffold[postgres]
|
||||
"""
|
||||
@property
|
||||
def placeholder(self) -> str:
|
||||
return "%s"
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
import psycopg2 as pg
|
||||
except ImportError:
|
||||
log.error("Ошибка импорта psycopg2")
|
||||
raise
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": os.getenv("PG_HOST", "127.0.0.1"),
|
||||
"port": int(os.getenv("PG_PORT", 5432)),
|
||||
"user": os.getenv("PG_USER", "postgres"),
|
||||
"database": os.getenv("PG_DATABASE", "postgres"),
|
||||
"password": os.getenv("PG_PASSWORD", "postgres")
|
||||
}
|
||||
|
||||
return pg.connect(**DB_CONFIG)
|
||||
|
||||
class MysqlDatabase(AbstractDatabase):
|
||||
"""
|
||||
AbstractDatabase implementation for MySQL via pymysql.
|
||||
|
||||
Configuration is read from environment variables:
|
||||
MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_DATABASE, MYSQL_PASSWORD
|
||||
|
||||
Requires:
|
||||
pip install pyqt6-scaffold[mysql]
|
||||
"""
|
||||
@property
|
||||
def placeholder(self) -> str:
|
||||
return "%s"
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
import pymysql as pms
|
||||
except ImportError:
|
||||
log.error("Ошибка импорта pymysql")
|
||||
raise
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": os.getenv("MYSQL_HOST", "localhost"),
|
||||
"port": int(os.getenv("MYSQL_PORT", 3306)),
|
||||
"user": os.getenv("MYSQL_USER", "root"),
|
||||
"database": os.getenv("MYSQL_DATABASE", "root"),
|
||||
"password": os.getenv("MYSQL_PASSWORD", "root")
|
||||
}
|
||||
|
||||
return pms.connect(**DB_CONFIG)
|
||||
|
||||
class SqliteDatabase(AbstractDatabase):
|
||||
"""
|
||||
AbstractDatabase implementation for SQLite via the stdlib sqlite3 module.
|
||||
|
||||
Configuration is read from environment variables:
|
||||
SQLITE_PATH (default: app.db)
|
||||
|
||||
No additional dependencies required.
|
||||
"""
|
||||
@property
|
||||
def placeholder(self) -> str:
|
||||
return "?"
|
||||
|
||||
def _connect(self):
|
||||
import sqlite3
|
||||
path = os.getenv("SQLITE_PATH", "app.db")
|
||||
return sqlite3.connect(path)
|
||||
Loading…
Add table
Add a link
Reference in a new issue