stackmate 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stackmate-0.1.0/LICENSE +21 -0
- stackmate-0.1.0/PKG-INFO +74 -0
- stackmate-0.1.0/README.md +51 -0
- stackmate-0.1.0/pyproject.toml +41 -0
- stackmate-0.1.0/setup.cfg +4 -0
- stackmate-0.1.0/stackmate/__init__.py +24 -0
- stackmate-0.1.0/stackmate/auth.py +82 -0
- stackmate-0.1.0/stackmate/db_helper.py +94 -0
- stackmate-0.1.0/stackmate/error_handler.py +73 -0
- stackmate-0.1.0/stackmate/logger.py +96 -0
- stackmate-0.1.0/stackmate/utils.py +62 -0
- stackmate-0.1.0/stackmate/validator.py +66 -0
- stackmate-0.1.0/stackmate.egg-info/PKG-INFO +74 -0
- stackmate-0.1.0/stackmate.egg-info/SOURCES.txt +21 -0
- stackmate-0.1.0/stackmate.egg-info/dependency_links.txt +1 -0
- stackmate-0.1.0/stackmate.egg-info/requires.txt +9 -0
- stackmate-0.1.0/stackmate.egg-info/top_level.txt +1 -0
- stackmate-0.1.0/tests/test_auth.py +69 -0
- stackmate-0.1.0/tests/test_db_helper.py +73 -0
- stackmate-0.1.0/tests/test_error_handler.py +71 -0
- stackmate-0.1.0/tests/test_logger.py +58 -0
- stackmate-0.1.0/tests/test_utils.py +59 -0
- stackmate-0.1.0/tests/test_validator.py +64 -0
stackmate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sudhakar K
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
stackmate-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackmate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Full-Stack Development Toolkit for Python developers.
|
|
5
|
+
Author-email: Sudhakar K <sudhakark4227@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: PyJWT>=2.8.0
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
21
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# StackMate
|
|
25
|
+
|
|
26
|
+
**StackMate** is a production-ready Full-Stack Development Toolkit for Python developers. It provides essential utilities for building robust APIs, managing authentication, handling errors, and simplifying database operations.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Logger**: Structured request/response logging with latency tracking.
|
|
31
|
+
- **Validator**: API payload validation using type hints and schema checks.
|
|
32
|
+
- **DB Helper**: Generic CRUD utilities for SQL/NoSQL databases.
|
|
33
|
+
- **Auth**: Secure JWT token management (creation and verification).
|
|
34
|
+
- **Error Handler**: Standardized API response formatting.
|
|
35
|
+
- **Utils**: Common helper functions (UUIDs, timestamps, etc.).
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install stackmate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from stackmate.logger import AppLogger
|
|
47
|
+
from stackmate.auth import AuthManager
|
|
48
|
+
|
|
49
|
+
# Initialize components
|
|
50
|
+
logger = AppLogger("my-app")
|
|
51
|
+
auth = AuthManager(secret_key="your-secret-key")
|
|
52
|
+
|
|
53
|
+
# Use them in your application
|
|
54
|
+
token = auth.create_token({"user_id": 123})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Project Structure
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
stackmate/
|
|
61
|
+
├── stackmate/ # Core library files
|
|
62
|
+
├── examples/ # Example usage scripts
|
|
63
|
+
├── tests/ # Unit tests
|
|
64
|
+
├── pyproject.toml # Build configuration
|
|
65
|
+
└── README.md # Project documentation
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Author
|
|
69
|
+
|
|
70
|
+
- **Sudhakar K** - [sudhakark4227@gmail.com](mailto:sudhakark4227@gmail.com)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# StackMate
|
|
2
|
+
|
|
3
|
+
**StackMate** is a production-ready Full-Stack Development Toolkit for Python developers. It provides essential utilities for building robust APIs, managing authentication, handling errors, and simplifying database operations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Logger**: Structured request/response logging with latency tracking.
|
|
8
|
+
- **Validator**: API payload validation using type hints and schema checks.
|
|
9
|
+
- **DB Helper**: Generic CRUD utilities for SQL/NoSQL databases.
|
|
10
|
+
- **Auth**: Secure JWT token management (creation and verification).
|
|
11
|
+
- **Error Handler**: Standardized API response formatting.
|
|
12
|
+
- **Utils**: Common helper functions (UUIDs, timestamps, etc.).
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install stackmate
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from stackmate.logger import AppLogger
|
|
24
|
+
from stackmate.auth import AuthManager
|
|
25
|
+
|
|
26
|
+
# Initialize components
|
|
27
|
+
logger = AppLogger("my-app")
|
|
28
|
+
auth = AuthManager(secret_key="your-secret-key")
|
|
29
|
+
|
|
30
|
+
# Use them in your application
|
|
31
|
+
token = auth.create_token({"user_id": 123})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Project Structure
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
stackmate/
|
|
38
|
+
├── stackmate/ # Core library files
|
|
39
|
+
├── examples/ # Example usage scripts
|
|
40
|
+
├── tests/ # Unit tests
|
|
41
|
+
├── pyproject.toml # Build configuration
|
|
42
|
+
└── README.md # Project documentation
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Author
|
|
46
|
+
|
|
47
|
+
- **Sudhakar K** - [sudhakark4227@gmail.com](mailto:sudhakark4227@gmail.com)
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stackmate"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Sudhakar K", email="sudhakark4227@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A Full-Stack Development Toolkit for Python developers."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"PyJWT>=2.8.0",
|
|
23
|
+
"pydantic>=2.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=7.0.0",
|
|
29
|
+
"pytest-cov>=4.0.0",
|
|
30
|
+
"black>=23.0.0",
|
|
31
|
+
"isort>=5.12.0",
|
|
32
|
+
"mypy>=1.0.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["stackmate*"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
python_files = "test_*.py"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StackMate: A Full-Stack Development Toolkit for Python.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__author__ = "Sudhakar K"
|
|
7
|
+
|
|
8
|
+
from stackmate.auth import AuthManager
|
|
9
|
+
from stackmate.db_helper import DBHelper
|
|
10
|
+
from stackmate.error_handler import APIResponse, ErrorCode
|
|
11
|
+
from stackmate.logger import AppLogger
|
|
12
|
+
from stackmate.utils import generate_uuid, get_timestamp
|
|
13
|
+
from stackmate.validator import PayloadValidator
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AuthManager",
|
|
17
|
+
"DBHelper",
|
|
18
|
+
"APIResponse",
|
|
19
|
+
"ErrorCode",
|
|
20
|
+
"AppLogger",
|
|
21
|
+
"generate_uuid",
|
|
22
|
+
"get_timestamp",
|
|
23
|
+
"PayloadValidator",
|
|
24
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JWT and token management utilities for StackMate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
|
|
8
|
+
import jwt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthManager:
|
|
12
|
+
"""
|
|
13
|
+
Manager for creating and verifying JWT tokens.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
secret_key: str,
|
|
19
|
+
algorithm: str = "HS256",
|
|
20
|
+
expires_delta: int = 60,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initializes the Auth Manager.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
secret_key (str): Secret key for encoding/decoding.
|
|
27
|
+
algorithm (str): JWT algorithm. Defaults to HS256.
|
|
28
|
+
expires_delta (int): Expiry time in minutes. Defaults to 60.
|
|
29
|
+
"""
|
|
30
|
+
self.secret_key = secret_key
|
|
31
|
+
self.algorithm = algorithm
|
|
32
|
+
self.expires_delta = expires_delta
|
|
33
|
+
|
|
34
|
+
def create_token(self, data: Dict[str, Any]) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Creates a JWT token.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
data (Dict): The payload to include.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: Encoded JWT token.
|
|
43
|
+
"""
|
|
44
|
+
to_encode = data.copy()
|
|
45
|
+
expire = datetime.now(timezone.utc) + timedelta(minutes=self.expires_delta)
|
|
46
|
+
to_encode.update({"exp": expire})
|
|
47
|
+
return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
|
|
48
|
+
|
|
49
|
+
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
|
50
|
+
"""
|
|
51
|
+
Verifies and decodes a JWT token.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
token (str): The JWT string.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Optional[Dict]: The decoded payload if valid, None otherwise.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
return jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
|
61
|
+
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def decode_token(self, token: str) -> Optional[Dict[str, Any]]:
|
|
65
|
+
"""
|
|
66
|
+
Decodes a token without verifying expiry (for debug/extraction).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
token (str): The JWT string.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Optional[Dict]: The decoded payload.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
return jwt.decode(
|
|
76
|
+
token,
|
|
77
|
+
self.secret_key,
|
|
78
|
+
algorithms=[self.algorithm],
|
|
79
|
+
options={"verify_exp": False},
|
|
80
|
+
)
|
|
81
|
+
except Exception:
|
|
82
|
+
return None
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database utility helpers for StackMate.
|
|
3
|
+
Simulates a generic CRUD interface.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DBHelper:
|
|
10
|
+
"""
|
|
11
|
+
Generic Database Helper for CRUD operations.
|
|
12
|
+
Acts as an abstraction layer (Mocked for demonstration).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, connection_string: str = "mock://localhost"):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the DB helper.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
connection_string (str): Database connection URI.
|
|
21
|
+
"""
|
|
22
|
+
self.connection_string = connection_string
|
|
23
|
+
# In a real app, you'd initialize a pool or client here
|
|
24
|
+
self._mock_db: List[Dict[str, Any]] = []
|
|
25
|
+
|
|
26
|
+
def create(self, table: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Inserts a new record into the "table".
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
table (str): Target table/collection name.
|
|
32
|
+
data (Dict): Record content.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dict: The created record.
|
|
36
|
+
"""
|
|
37
|
+
# Mock implementation
|
|
38
|
+
record = {"id": len(self._mock_db) + 1, "table": table, **data}
|
|
39
|
+
self._mock_db.append(record)
|
|
40
|
+
return record
|
|
41
|
+
|
|
42
|
+
def read(self, table: str, filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
43
|
+
"""
|
|
44
|
+
Retrieves records from the "table" based on filters.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
table (str): Target table name.
|
|
48
|
+
filters (Dict): Query filters.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List[Dict]: List of matching records.
|
|
52
|
+
"""
|
|
53
|
+
results = [r for r in self._mock_db if r["table"] == table]
|
|
54
|
+
if filters:
|
|
55
|
+
for key, value in filters.items():
|
|
56
|
+
results = [r for r in results if r.get(key) == value]
|
|
57
|
+
return results
|
|
58
|
+
|
|
59
|
+
def update(
|
|
60
|
+
self, table: str, record_id: int, updates: Dict[str, Any]
|
|
61
|
+
) -> Optional[Dict[str, Any]]:
|
|
62
|
+
"""
|
|
63
|
+
Updates an existing record.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
table (str): Target table name.
|
|
67
|
+
record_id (int): ID of the record to update.
|
|
68
|
+
updates (Dict): Field-value updates.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Optional[Dict]: The updated record if found.
|
|
72
|
+
"""
|
|
73
|
+
for record in self._mock_db:
|
|
74
|
+
if record["table"] == table and record["id"] == record_id:
|
|
75
|
+
record.update(updates)
|
|
76
|
+
return record
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def delete(self, table: str, record_id: int) -> bool:
|
|
80
|
+
"""
|
|
81
|
+
Deletes a record.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
table (str): Target table name.
|
|
85
|
+
record_id (int): ID of the record.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
bool: True if deleted, False if not found.
|
|
89
|
+
"""
|
|
90
|
+
for i, record in enumerate(self._mock_db):
|
|
91
|
+
if record["table"] == table and record["id"] == record_id:
|
|
92
|
+
del self._mock_db[i]
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified error handling and structured API responses for StackMate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ErrorCode(str, Enum):
|
|
10
|
+
"""Enumeration of standard error codes."""
|
|
11
|
+
|
|
12
|
+
SUCCESS = "SUCCESS"
|
|
13
|
+
VALIDATION_ERROR = "VALIDATION_ERROR"
|
|
14
|
+
AUTHENTICATION_ERROR = "AUTHENTICATION_ERROR"
|
|
15
|
+
NOT_FOUND = "NOT_FOUND"
|
|
16
|
+
DATABASE_ERROR = "DATABASE_ERROR"
|
|
17
|
+
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class APIResponse:
|
|
21
|
+
"""Class to handle structured API responses."""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def success(
|
|
25
|
+
data: Any = None,
|
|
26
|
+
message: str = "Operation successful",
|
|
27
|
+
status_code: int = 200,
|
|
28
|
+
) -> Dict[str, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Generates a successful API response.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data (Any): The payload to return.
|
|
34
|
+
message (str): A message for the user.
|
|
35
|
+
status_code (int): HTTP status code.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict[str, Any]: Structured success response.
|
|
39
|
+
"""
|
|
40
|
+
return {
|
|
41
|
+
"success": True,
|
|
42
|
+
"message": message,
|
|
43
|
+
"data": data,
|
|
44
|
+
"error_code": ErrorCode.SUCCESS,
|
|
45
|
+
"status_code": status_code,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def failure(
|
|
50
|
+
message: str,
|
|
51
|
+
error_code: ErrorCode = ErrorCode.INTERNAL_SERVER_ERROR,
|
|
52
|
+
data: Any = None,
|
|
53
|
+
status_code: int = 500,
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
Generates a failed API response.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
message (str): Error message.
|
|
60
|
+
error_code (ErrorCode): Standardized error code.
|
|
61
|
+
data (Any): Optional additional error context.
|
|
62
|
+
status_code (int): HTTP status code.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dict[str, Any]: Structured failure response.
|
|
66
|
+
"""
|
|
67
|
+
return {
|
|
68
|
+
"success": False,
|
|
69
|
+
"message": message,
|
|
70
|
+
"data": data,
|
|
71
|
+
"error_code": error_code,
|
|
72
|
+
"status_code": status_code,
|
|
73
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request/Response logging utilities for StackMate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from stackmate.utils import get_timestamp, parse_latency
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AppLogger:
|
|
14
|
+
"""
|
|
15
|
+
Standard logger for API requests and responses.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, name: str = "StackMate", level: int = logging.INFO):
|
|
19
|
+
"""
|
|
20
|
+
Initializes the logger.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
name (str): Name of the logger.
|
|
24
|
+
level (int): Logging level.
|
|
25
|
+
"""
|
|
26
|
+
self.logger = logging.getLogger(name)
|
|
27
|
+
handler = logging.StreamHandler()
|
|
28
|
+
formatter = logging.Formatter(
|
|
29
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
30
|
+
)
|
|
31
|
+
handler.setFormatter(formatter)
|
|
32
|
+
if not self.logger.handlers:
|
|
33
|
+
self.logger.addHandler(handler)
|
|
34
|
+
self.logger.setLevel(level)
|
|
35
|
+
|
|
36
|
+
def log_request(self, method: str, url: str, payload: Optional[Dict[str, Any]] = None) -> float:
|
|
37
|
+
"""
|
|
38
|
+
Logs an incoming API request.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
method (str): HTTP method (GET, POST, etc.).
|
|
42
|
+
url (str): Target URL.
|
|
43
|
+
payload (Dict): Request body.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
float: The start time of the request.
|
|
47
|
+
"""
|
|
48
|
+
start_time = time.time()
|
|
49
|
+
log_data = {
|
|
50
|
+
"timestamp": get_timestamp(),
|
|
51
|
+
"type": "REQUEST",
|
|
52
|
+
"method": method,
|
|
53
|
+
"url": url,
|
|
54
|
+
"payload": payload,
|
|
55
|
+
}
|
|
56
|
+
self.logger.info("Request: %s", json.dumps(log_data))
|
|
57
|
+
return start_time
|
|
58
|
+
|
|
59
|
+
def log_response(
|
|
60
|
+
self,
|
|
61
|
+
method: str,
|
|
62
|
+
url: str,
|
|
63
|
+
status_code: int,
|
|
64
|
+
start_time: float,
|
|
65
|
+
response_data: Optional[Any] = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Logs an API response with latency calculation.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
method (str): HTTP method.
|
|
72
|
+
url (str): Target URL.
|
|
73
|
+
status_code (int): HTTP status code.
|
|
74
|
+
start_time (float): The timestamp when the request started.
|
|
75
|
+
response_data (Any): The response content.
|
|
76
|
+
"""
|
|
77
|
+
end_time = time.time()
|
|
78
|
+
latency = parse_latency(start_time, end_time)
|
|
79
|
+
log_data = {
|
|
80
|
+
"timestamp": get_timestamp(),
|
|
81
|
+
"type": "RESPONSE",
|
|
82
|
+
"method": method,
|
|
83
|
+
"url": url,
|
|
84
|
+
"status": status_code,
|
|
85
|
+
"latency": latency,
|
|
86
|
+
"data": response_data,
|
|
87
|
+
}
|
|
88
|
+
self.logger.info("Response: %s", json.dumps(log_data))
|
|
89
|
+
|
|
90
|
+
def error(self, message: str, exc_info: bool = True) -> None:
|
|
91
|
+
"""Logs an error message."""
|
|
92
|
+
self.logger.error(message, exc_info=exc_info)
|
|
93
|
+
|
|
94
|
+
def info(self, message: str) -> None:
|
|
95
|
+
"""Logs an info message."""
|
|
96
|
+
self.logger.info(message)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Miscellaneous helper functions for StackMate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_uuid() -> str:
|
|
11
|
+
"""
|
|
12
|
+
Generates a unique UUID v4 string.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: A unique UUID.
|
|
16
|
+
"""
|
|
17
|
+
return str(uuid.uuid4())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_timestamp(utc: bool = True) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Returns the current timestamp in ISO format.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
utc (bool): Whether to use UTC time. Defaults to True.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: ISO formatted timestamp.
|
|
29
|
+
"""
|
|
30
|
+
if utc:
|
|
31
|
+
return datetime.now(timezone.utc).isoformat()
|
|
32
|
+
return datetime.now().isoformat()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def format_response_data(data: Any) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Helper to ensure data is in a dictionary format for JSON responses.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
data (Any): Input data.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dict[str, Any]: Formatted data.
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(data, dict):
|
|
46
|
+
return data
|
|
47
|
+
return {"result": data}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def parse_latency(start_time: float, end_time: float) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Calculates latency and returns it as a formatted string.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
start_time (float): Execution start time.
|
|
56
|
+
end_time (float): Execution end time.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: Latency in milliseconds (e.g., "120ms").
|
|
60
|
+
"""
|
|
61
|
+
duration = (end_time - start_time) * 1000
|
|
62
|
+
return f"{duration:.2f}ms"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API payload validation utilities for StackMate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PayloadValidator:
|
|
9
|
+
"""
|
|
10
|
+
Validates API request payloads against required fields and types.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def validate(
|
|
15
|
+
payload: Dict[str, Any],
|
|
16
|
+
schema: Dict[str, Type],
|
|
17
|
+
required_fields: Optional[List[str]] = None,
|
|
18
|
+
) -> List[str]:
|
|
19
|
+
"""
|
|
20
|
+
Validates the payload against a schema.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
payload (Dict): The data to validate.
|
|
24
|
+
schema (Dict): A mapping of field names to expected types.
|
|
25
|
+
required_fields (List): A list of fields that must be present.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List[str]: A list of error messages. Empty if valid.
|
|
29
|
+
"""
|
|
30
|
+
errors = []
|
|
31
|
+
required = required_fields or []
|
|
32
|
+
|
|
33
|
+
# Check for missing required fields
|
|
34
|
+
for field in required:
|
|
35
|
+
if field not in payload:
|
|
36
|
+
errors.append(f"Field '{field}' is required.")
|
|
37
|
+
|
|
38
|
+
# Check types for present fields
|
|
39
|
+
for field, value in payload.items():
|
|
40
|
+
if field in schema:
|
|
41
|
+
expected_type = schema[field]
|
|
42
|
+
if not isinstance(value, expected_type):
|
|
43
|
+
errors.append(
|
|
44
|
+
f"Field '{field}' must be of type {expected_type.__name__}, "
|
|
45
|
+
f"got {type(value).__name__}."
|
|
46
|
+
)
|
|
47
|
+
elif field not in schema and required_fields is not None:
|
|
48
|
+
# Optionally handle unexpected fields if schema is strict
|
|
49
|
+
# For now, we allow them unless we want strict validation
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
return errors
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def is_valid(
|
|
56
|
+
payload: Dict[str, Any],
|
|
57
|
+
schema: Dict[str, Type],
|
|
58
|
+
required_fields: Optional[List[str]] = None,
|
|
59
|
+
) -> bool:
|
|
60
|
+
"""
|
|
61
|
+
Shortcut to check if a payload is valid.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
bool: True if valid, False otherwise.
|
|
65
|
+
"""
|
|
66
|
+
return len(PayloadValidator.validate(payload, schema, required_fields)) == 0
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackmate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Full-Stack Development Toolkit for Python developers.
|
|
5
|
+
Author-email: Sudhakar K <sudhakark4227@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: PyJWT>=2.8.0
|
|
15
|
+
Requires-Dist: pydantic>=2.0.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
21
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# StackMate
|
|
25
|
+
|
|
26
|
+
**StackMate** is a production-ready Full-Stack Development Toolkit for Python developers. It provides essential utilities for building robust APIs, managing authentication, handling errors, and simplifying database operations.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Logger**: Structured request/response logging with latency tracking.
|
|
31
|
+
- **Validator**: API payload validation using type hints and schema checks.
|
|
32
|
+
- **DB Helper**: Generic CRUD utilities for SQL/NoSQL databases.
|
|
33
|
+
- **Auth**: Secure JWT token management (creation and verification).
|
|
34
|
+
- **Error Handler**: Standardized API response formatting.
|
|
35
|
+
- **Utils**: Common helper functions (UUIDs, timestamps, etc.).
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install stackmate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from stackmate.logger import AppLogger
|
|
47
|
+
from stackmate.auth import AuthManager
|
|
48
|
+
|
|
49
|
+
# Initialize components
|
|
50
|
+
logger = AppLogger("my-app")
|
|
51
|
+
auth = AuthManager(secret_key="your-secret-key")
|
|
52
|
+
|
|
53
|
+
# Use them in your application
|
|
54
|
+
token = auth.create_token({"user_id": 123})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Project Structure
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
stackmate/
|
|
61
|
+
├── stackmate/ # Core library files
|
|
62
|
+
├── examples/ # Example usage scripts
|
|
63
|
+
├── tests/ # Unit tests
|
|
64
|
+
├── pyproject.toml # Build configuration
|
|
65
|
+
└── README.md # Project documentation
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Author
|
|
69
|
+
|
|
70
|
+
- **Sudhakar K** - [sudhakark4227@gmail.com](mailto:sudhakark4227@gmail.com)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
stackmate/__init__.py
|
|
5
|
+
stackmate/auth.py
|
|
6
|
+
stackmate/db_helper.py
|
|
7
|
+
stackmate/error_handler.py
|
|
8
|
+
stackmate/logger.py
|
|
9
|
+
stackmate/utils.py
|
|
10
|
+
stackmate/validator.py
|
|
11
|
+
stackmate.egg-info/PKG-INFO
|
|
12
|
+
stackmate.egg-info/SOURCES.txt
|
|
13
|
+
stackmate.egg-info/dependency_links.txt
|
|
14
|
+
stackmate.egg-info/requires.txt
|
|
15
|
+
stackmate.egg-info/top_level.txt
|
|
16
|
+
tests/test_auth.py
|
|
17
|
+
tests/test_db_helper.py
|
|
18
|
+
tests/test_error_handler.py
|
|
19
|
+
tests/test_logger.py
|
|
20
|
+
tests/test_utils.py
|
|
21
|
+
tests/test_validator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stackmate
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.auth module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import jwt
|
|
8
|
+
import pytest
|
|
9
|
+
from stackmate.auth import AuthManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_auth_manager_initialization():
|
|
13
|
+
"""Test AuthManager initializes correctly."""
|
|
14
|
+
auth = AuthManager(secret_key="test-secret")
|
|
15
|
+
assert auth.secret_key == "test-secret"
|
|
16
|
+
assert auth.algorithm == "HS256"
|
|
17
|
+
assert auth.expires_delta == 60
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_create_token():
|
|
21
|
+
"""Test JWT token creation."""
|
|
22
|
+
auth = AuthManager(secret_key="test-secret")
|
|
23
|
+
token = auth.create_token({"user_id": 123})
|
|
24
|
+
assert isinstance(token, str)
|
|
25
|
+
assert len(token) > 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_verify_valid_token():
|
|
29
|
+
"""Test verifying a valid token."""
|
|
30
|
+
auth = AuthManager(secret_key="test-secret")
|
|
31
|
+
token = auth.create_token({"user_id": 123, "role": "admin"})
|
|
32
|
+
decoded = auth.verify_token(token)
|
|
33
|
+
assert decoded is not None
|
|
34
|
+
assert decoded["user_id"] == 123
|
|
35
|
+
assert decoded["role"] == "admin"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_verify_invalid_token():
|
|
39
|
+
"""Test verifying an invalid token."""
|
|
40
|
+
auth = AuthManager(secret_key="test-secret")
|
|
41
|
+
decoded = auth.verify_token("invalid.token.here")
|
|
42
|
+
assert decoded is None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_verify_token_wrong_secret():
|
|
46
|
+
"""Test verifying a token with wrong secret key."""
|
|
47
|
+
auth1 = AuthManager(secret_key="secret1")
|
|
48
|
+
auth2 = AuthManager(secret_key="secret2")
|
|
49
|
+
token = auth1.create_token({"user_id": 123})
|
|
50
|
+
decoded = auth2.verify_token(token)
|
|
51
|
+
assert decoded is None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_decode_token_without_verification():
|
|
55
|
+
"""Test decoding token without expiry verification."""
|
|
56
|
+
auth = AuthManager(secret_key="test-secret", expires_delta=0)
|
|
57
|
+
token = auth.create_token({"user_id": 123})
|
|
58
|
+
time.sleep(1)
|
|
59
|
+
decoded = auth.decode_token(token)
|
|
60
|
+
assert decoded is not None
|
|
61
|
+
assert decoded["user_id"] == 123
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_token_contains_expiry():
|
|
65
|
+
"""Test that created token contains expiry claim."""
|
|
66
|
+
auth = AuthManager(secret_key="test-secret")
|
|
67
|
+
token = auth.create_token({"user_id": 123})
|
|
68
|
+
decoded = auth.decode_token(token)
|
|
69
|
+
assert "exp" in decoded
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.db_helper module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from stackmate.db_helper import DBHelper
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_db_helper_initialization():
|
|
10
|
+
"""Test DBHelper initializes correctly."""
|
|
11
|
+
db = DBHelper("sqlite:///:memory:")
|
|
12
|
+
assert db.connection_string == "sqlite:///:memory:"
|
|
13
|
+
assert db._mock_db == []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_create_record():
|
|
17
|
+
"""Test creating a new record."""
|
|
18
|
+
db = DBHelper()
|
|
19
|
+
record = db.create("users", {"username": "john", "email": "john@example.com"})
|
|
20
|
+
assert record["id"] == 1
|
|
21
|
+
assert record["table"] == "users"
|
|
22
|
+
assert record["username"] == "john"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_read_all_records():
|
|
26
|
+
"""Test reading all records from a table."""
|
|
27
|
+
db = DBHelper()
|
|
28
|
+
db.create("users", {"username": "john"})
|
|
29
|
+
db.create("users", {"username": "jane"})
|
|
30
|
+
records = db.read("users")
|
|
31
|
+
assert len(records) == 2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_read_with_filters():
|
|
35
|
+
"""Test reading records with filters."""
|
|
36
|
+
db = DBHelper()
|
|
37
|
+
db.create("users", {"username": "john", "role": "admin"})
|
|
38
|
+
db.create("users", {"username": "jane", "role": "user"})
|
|
39
|
+
records = db.read("users", filters={"role": "admin"})
|
|
40
|
+
assert len(records) == 1
|
|
41
|
+
assert records[0]["username"] == "john"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_update_record():
|
|
45
|
+
"""Test updating an existing record."""
|
|
46
|
+
db = DBHelper()
|
|
47
|
+
record = db.create("users", {"username": "john"})
|
|
48
|
+
updated = db.update("users", record["id"], {"username": "john_updated"})
|
|
49
|
+
assert updated is not None
|
|
50
|
+
assert updated["username"] == "john_updated"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_update_nonexistent_record():
|
|
54
|
+
"""Test updating a record that doesn't exist."""
|
|
55
|
+
db = DBHelper()
|
|
56
|
+
result = db.update("users", 999, {"username": "ghost"})
|
|
57
|
+
assert result is None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_delete_record():
|
|
61
|
+
"""Test deleting a record."""
|
|
62
|
+
db = DBHelper()
|
|
63
|
+
record = db.create("users", {"username": "john"})
|
|
64
|
+
deleted = db.delete("users", record["id"])
|
|
65
|
+
assert deleted is True
|
|
66
|
+
assert len(db.read("users")) == 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_delete_nonexistent_record():
|
|
70
|
+
"""Test deleting a record that doesn't exist."""
|
|
71
|
+
db = DBHelper()
|
|
72
|
+
deleted = db.delete("users", 999)
|
|
73
|
+
assert deleted is False
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.error_handler module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from stackmate.error_handler import APIResponse, ErrorCode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_success_response_default():
|
|
10
|
+
"""Test success response with default values."""
|
|
11
|
+
response = APIResponse.success()
|
|
12
|
+
assert response["success"] is True
|
|
13
|
+
assert response["message"] == "Operation successful"
|
|
14
|
+
assert response["error_code"] == ErrorCode.SUCCESS
|
|
15
|
+
assert response["status_code"] == 200
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_success_response_with_data():
|
|
19
|
+
"""Test success response with custom data."""
|
|
20
|
+
data = {"user_id": 123}
|
|
21
|
+
response = APIResponse.success(data=data, message="User created")
|
|
22
|
+
assert response["success"] is True
|
|
23
|
+
assert response["data"] == data
|
|
24
|
+
assert response["message"] == "User created"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_success_response_custom_status():
|
|
28
|
+
"""Test success response with custom status code."""
|
|
29
|
+
response = APIResponse.success(status_code=201)
|
|
30
|
+
assert response["status_code"] == 201
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_failure_response_default():
|
|
34
|
+
"""Test failure response with default values."""
|
|
35
|
+
response = APIResponse.failure(message="Something went wrong")
|
|
36
|
+
assert response["success"] is False
|
|
37
|
+
assert response["message"] == "Something went wrong"
|
|
38
|
+
assert response["error_code"] == ErrorCode.INTERNAL_SERVER_ERROR
|
|
39
|
+
assert response["status_code"] == 500
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_failure_response_validation_error():
|
|
43
|
+
"""Test failure response with validation error."""
|
|
44
|
+
response = APIResponse.failure(
|
|
45
|
+
message="Invalid input",
|
|
46
|
+
error_code=ErrorCode.VALIDATION_ERROR,
|
|
47
|
+
status_code=400,
|
|
48
|
+
)
|
|
49
|
+
assert response["success"] is False
|
|
50
|
+
assert response["error_code"] == ErrorCode.VALIDATION_ERROR
|
|
51
|
+
assert response["status_code"] == 400
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_failure_response_with_data():
|
|
55
|
+
"""Test failure response with additional error data."""
|
|
56
|
+
error_data = {"field": "email", "issue": "invalid format"}
|
|
57
|
+
response = APIResponse.failure(
|
|
58
|
+
message="Validation failed",
|
|
59
|
+
error_code=ErrorCode.VALIDATION_ERROR,
|
|
60
|
+
data=error_data,
|
|
61
|
+
)
|
|
62
|
+
assert response["data"] == error_data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_error_code_enum_values():
|
|
66
|
+
"""Test ErrorCode enum contains expected values."""
|
|
67
|
+
assert ErrorCode.SUCCESS == "SUCCESS"
|
|
68
|
+
assert ErrorCode.VALIDATION_ERROR == "VALIDATION_ERROR"
|
|
69
|
+
assert ErrorCode.AUTHENTICATION_ERROR == "AUTHENTICATION_ERROR"
|
|
70
|
+
assert ErrorCode.NOT_FOUND == "NOT_FOUND"
|
|
71
|
+
assert ErrorCode.DATABASE_ERROR == "DATABASE_ERROR"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.logger module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from stackmate.logger import AppLogger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_logger_initialization():
|
|
13
|
+
"""Test logger is initialized correctly."""
|
|
14
|
+
logger = AppLogger("TestApp")
|
|
15
|
+
assert logger.logger.name == "TestApp"
|
|
16
|
+
assert logger.logger.level == logging.INFO
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_logger_custom_level():
|
|
20
|
+
"""Test logger with custom logging level."""
|
|
21
|
+
logger = AppLogger("DebugApp", level=logging.DEBUG)
|
|
22
|
+
assert logger.logger.level == logging.DEBUG
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_log_request_returns_timestamp():
|
|
26
|
+
"""Test log_request returns a start time."""
|
|
27
|
+
logger = AppLogger("RequestTest")
|
|
28
|
+
start_time = logger.log_request("GET", "/api/users")
|
|
29
|
+
assert isinstance(start_time, float)
|
|
30
|
+
assert start_time > 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_log_request_with_payload():
|
|
34
|
+
"""Test log_request with payload data."""
|
|
35
|
+
logger = AppLogger("PayloadTest")
|
|
36
|
+
payload = {"username": "test", "email": "test@example.com"}
|
|
37
|
+
start_time = logger.log_request("POST", "/api/users", payload=payload)
|
|
38
|
+
assert isinstance(start_time, float)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_log_response():
|
|
42
|
+
"""Test log_response logs correctly."""
|
|
43
|
+
logger = AppLogger("ResponseTest")
|
|
44
|
+
start_time = time.time()
|
|
45
|
+
time.sleep(0.01)
|
|
46
|
+
logger.log_response("GET", "/api/users", 200, start_time, {"users": []})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_logger_info_method():
|
|
50
|
+
"""Test info logging method."""
|
|
51
|
+
logger = AppLogger("InfoTest")
|
|
52
|
+
logger.info("This is an info message")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_logger_error_method():
|
|
56
|
+
"""Test error logging method."""
|
|
57
|
+
logger = AppLogger("ErrorTest")
|
|
58
|
+
logger.error("This is an error message", exc_info=False)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.utils module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from stackmate.utils import (
|
|
7
|
+
format_response_data,
|
|
8
|
+
generate_uuid,
|
|
9
|
+
get_timestamp,
|
|
10
|
+
parse_latency,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_generate_uuid():
|
|
15
|
+
"""Test UUID generation returns a valid UUID string."""
|
|
16
|
+
uuid1 = generate_uuid()
|
|
17
|
+
uuid2 = generate_uuid()
|
|
18
|
+
assert isinstance(uuid1, str)
|
|
19
|
+
assert len(uuid1) == 36
|
|
20
|
+
assert uuid1 != uuid2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_get_timestamp_utc():
|
|
24
|
+
"""Test UTC timestamp generation."""
|
|
25
|
+
timestamp = get_timestamp(utc=True)
|
|
26
|
+
assert isinstance(timestamp, str)
|
|
27
|
+
assert "T" in timestamp
|
|
28
|
+
assert "+" in timestamp or "Z" in timestamp
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_get_timestamp_local():
|
|
32
|
+
"""Test local timestamp generation."""
|
|
33
|
+
timestamp = get_timestamp(utc=False)
|
|
34
|
+
assert isinstance(timestamp, str)
|
|
35
|
+
assert "T" in timestamp
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_format_response_data_dict():
|
|
39
|
+
"""Test formatting when data is already a dict."""
|
|
40
|
+
data = {"key": "value"}
|
|
41
|
+
result = format_response_data(data)
|
|
42
|
+
assert result == data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_format_response_data_non_dict():
|
|
46
|
+
"""Test formatting when data is not a dict."""
|
|
47
|
+
data = "simple string"
|
|
48
|
+
result = format_response_data(data)
|
|
49
|
+
assert result == {"result": data}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_parse_latency():
|
|
53
|
+
"""Test latency calculation."""
|
|
54
|
+
start = 1.0
|
|
55
|
+
end = 1.12
|
|
56
|
+
latency = parse_latency(start, end)
|
|
57
|
+
assert isinstance(latency, str)
|
|
58
|
+
assert "ms" in latency
|
|
59
|
+
assert "120.00ms" == latency
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for stackmate.validator module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from stackmate.validator import PayloadValidator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_validate_success():
|
|
10
|
+
"""Test validation passes with correct payload."""
|
|
11
|
+
payload = {"username": "john", "age": 30}
|
|
12
|
+
schema = {"username": str, "age": int}
|
|
13
|
+
errors = PayloadValidator.validate(payload, schema, required_fields=["username", "age"])
|
|
14
|
+
assert errors == []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_validate_missing_required_field():
|
|
18
|
+
"""Test validation fails when required field is missing."""
|
|
19
|
+
payload = {"username": "john"}
|
|
20
|
+
schema = {"username": str, "age": int}
|
|
21
|
+
errors = PayloadValidator.validate(payload, schema, required_fields=["username", "age"])
|
|
22
|
+
assert len(errors) == 1
|
|
23
|
+
assert "age" in errors[0]
|
|
24
|
+
assert "required" in errors[0]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_validate_wrong_type():
|
|
28
|
+
"""Test validation fails when field has wrong type."""
|
|
29
|
+
payload = {"username": "john", "age": "thirty"}
|
|
30
|
+
schema = {"username": str, "age": int}
|
|
31
|
+
errors = PayloadValidator.validate(payload, schema, required_fields=["username", "age"])
|
|
32
|
+
assert len(errors) == 1
|
|
33
|
+
assert "age" in errors[0]
|
|
34
|
+
assert "int" in errors[0]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_validate_multiple_errors():
|
|
38
|
+
"""Test validation returns multiple errors."""
|
|
39
|
+
payload = {"age": "thirty"}
|
|
40
|
+
schema = {"username": str, "age": int}
|
|
41
|
+
errors = PayloadValidator.validate(payload, schema, required_fields=["username", "age"])
|
|
42
|
+
assert len(errors) == 2
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_is_valid_true():
|
|
46
|
+
"""Test is_valid returns True for valid payload."""
|
|
47
|
+
payload = {"username": "john", "email": "john@example.com"}
|
|
48
|
+
schema = {"username": str, "email": str}
|
|
49
|
+
assert PayloadValidator.is_valid(payload, schema, required_fields=["username"]) is True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_is_valid_false():
|
|
53
|
+
"""Test is_valid returns False for invalid payload."""
|
|
54
|
+
payload = {"username": 123}
|
|
55
|
+
schema = {"username": str}
|
|
56
|
+
assert PayloadValidator.is_valid(payload, schema, required_fields=["username"]) is False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_validate_optional_fields():
|
|
60
|
+
"""Test validation allows optional fields."""
|
|
61
|
+
payload = {"username": "john"}
|
|
62
|
+
schema = {"username": str, "age": int}
|
|
63
|
+
errors = PayloadValidator.validate(payload, schema, required_fields=["username"])
|
|
64
|
+
assert errors == []
|