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.
@@ -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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,9 @@
1
+ PyJWT>=2.8.0
2
+ pydantic>=2.0.0
3
+
4
+ [dev]
5
+ pytest>=7.0.0
6
+ pytest-cov>=4.0.0
7
+ black>=23.0.0
8
+ isort>=5.12.0
9
+ mypy>=1.0.0
@@ -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 == []