starspring 0.1.0__py3-none-any.whl

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,82 @@
1
+ """
2
+ Exception hierarchy for StarSpring framework
3
+
4
+ Provides Spring Boot-style exception classes with automatic HTTP status mapping.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any
8
+
9
+
10
+ class StarSpringException(Exception):
11
+ """Base exception for all StarSpring framework exceptions"""
12
+
13
+ def __init__(
14
+ self,
15
+ message: str,
16
+ status_code: int = 500,
17
+ details: Optional[Dict[str, Any]] = None
18
+ ):
19
+ super().__init__(message)
20
+ self.message = message
21
+ self.status_code = status_code
22
+ self.details = details or {}
23
+
24
+ def to_dict(self) -> Dict[str, Any]:
25
+ """Convert exception to dictionary for JSON response"""
26
+ result = {
27
+ "error": self.__class__.__name__,
28
+ "message": self.message,
29
+ "status": self.status_code,
30
+ }
31
+ if self.details:
32
+ result["details"] = self.details
33
+ return result
34
+
35
+
36
+ class BadRequestException(StarSpringException):
37
+ """Exception for 400 Bad Request errors"""
38
+
39
+ def __init__(self, message: str = "Bad Request", details: Optional[Dict[str, Any]] = None):
40
+ super().__init__(message, status_code=400, details=details)
41
+
42
+
43
+ class UnauthorizedException(StarSpringException):
44
+ """Exception for 401 Unauthorized errors"""
45
+
46
+ def __init__(self, message: str = "Unauthorized", details: Optional[Dict[str, Any]] = None):
47
+ super().__init__(message, status_code=401, details=details)
48
+
49
+
50
+ class ForbiddenException(StarSpringException):
51
+ """Exception for 403 Forbidden errors"""
52
+
53
+ def __init__(self, message: str = "Forbidden", details: Optional[Dict[str, Any]] = None):
54
+ super().__init__(message, status_code=403, details=details)
55
+
56
+
57
+ class NotFoundException(StarSpringException):
58
+ """Exception for 404 Not Found errors"""
59
+
60
+ def __init__(self, message: str = "Resource Not Found", details: Optional[Dict[str, Any]] = None):
61
+ super().__init__(message, status_code=404, details=details)
62
+
63
+
64
+ class ConflictException(StarSpringException):
65
+ """Exception for 409 Conflict errors"""
66
+
67
+ def __init__(self, message: str = "Conflict", details: Optional[Dict[str, Any]] = None):
68
+ super().__init__(message, status_code=409, details=details)
69
+
70
+
71
+ class ValidationException(StarSpringException):
72
+ """Exception for validation errors"""
73
+
74
+ def __init__(self, message: str = "Validation Failed", details: Optional[Dict[str, Any]] = None):
75
+ super().__init__(message, status_code=422, details=details)
76
+
77
+
78
+ class InternalServerException(StarSpringException):
79
+ """Exception for 500 Internal Server errors"""
80
+
81
+ def __init__(self, message: str = "Internal Server Error", details: Optional[Dict[str, Any]] = None):
82
+ super().__init__(message, status_code=500, details=details)
@@ -0,0 +1,147 @@
1
+ """
2
+ Response utilities similar to Spring Boot's ResponseEntity
3
+
4
+ Provides structured response classes for API endpoints.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any, Generic, TypeVar
8
+ from enum import Enum
9
+ from pydantic import BaseModel
10
+
11
+
12
+ T = TypeVar('T')
13
+
14
+
15
+ class HttpStatus(Enum):
16
+ """HTTP status codes"""
17
+ OK = 200
18
+ CREATED = 201
19
+ ACCEPTED = 202
20
+ NO_CONTENT = 204
21
+ BAD_REQUEST = 400
22
+ UNAUTHORIZED = 401
23
+ FORBIDDEN = 403
24
+ NOT_FOUND = 404
25
+ CONFLICT = 409
26
+ UNPROCESSABLE_ENTITY = 422
27
+ INTERNAL_SERVER_ERROR = 500
28
+
29
+
30
+ class ApiResponse(BaseModel, Generic[T]):
31
+ """
32
+ Generic API response wrapper
33
+
34
+ Example:
35
+ return ApiResponse(
36
+ success=True,
37
+ data=user,
38
+ message="User created successfully"
39
+ )
40
+ """
41
+ success: bool
42
+ data: Optional[T] = None
43
+ message: Optional[str] = None
44
+ errors: Optional[Dict[str, Any]] = None
45
+
46
+ class Config:
47
+ arbitrary_types_allowed = True
48
+
49
+
50
+ class ResponseEntity(Generic[T]):
51
+ """
52
+ Response entity similar to Spring Boot's ResponseEntity
53
+
54
+ Provides fluent API for building HTTP responses with status codes and headers.
55
+
56
+ Example:
57
+ return ResponseEntity.ok(user)
58
+ return ResponseEntity.created(user).header("Location", "/api/users/1")
59
+ return ResponseEntity.not_found().body({"error": "User not found"})
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ body: Optional[T] = None,
65
+ status: int = 200,
66
+ headers: Optional[Dict[str, str]] = None
67
+ ):
68
+ self.body = body
69
+ self.status = status
70
+ self.headers = headers or {}
71
+
72
+ def header(self, key: str, value: str) -> 'ResponseEntity[T]':
73
+ """Add a header to the response"""
74
+ self.headers[key] = value
75
+ return self
76
+
77
+ def to_starlette_response(self):
78
+ """Convert to Starlette JSONResponse"""
79
+ from starlette.responses import JSONResponse
80
+
81
+ # Handle Pydantic models
82
+ if hasattr(self.body, 'model_dump'):
83
+ content = self.body.model_dump()
84
+ elif hasattr(self.body, 'dict'):
85
+ content = self.body.dict()
86
+ elif isinstance(self.body, list):
87
+ content = [
88
+ item.model_dump() if hasattr(item, 'model_dump')
89
+ else item.dict() if hasattr(item, 'dict')
90
+ else item
91
+ for item in self.body
92
+ ]
93
+ else:
94
+ content = self.body
95
+
96
+ return JSONResponse(
97
+ content=content,
98
+ status_code=self.status,
99
+ headers=self.headers
100
+ )
101
+
102
+ # Static factory methods for common responses
103
+
104
+ @staticmethod
105
+ def ok(body: Optional[T] = None) -> 'ResponseEntity[T]':
106
+ """Create a 200 OK response"""
107
+ return ResponseEntity(body=body, status=HttpStatus.OK.value)
108
+
109
+ @staticmethod
110
+ def created(body: Optional[T] = None) -> 'ResponseEntity[T]':
111
+ """Create a 201 Created response"""
112
+ return ResponseEntity(body=body, status=HttpStatus.CREATED.value)
113
+
114
+ @staticmethod
115
+ def accepted(body: Optional[T] = None) -> 'ResponseEntity[T]':
116
+ """Create a 202 Accepted response"""
117
+ return ResponseEntity(body=body, status=HttpStatus.ACCEPTED.value)
118
+
119
+ @staticmethod
120
+ def no_content() -> 'ResponseEntity[None]':
121
+ """Create a 204 No Content response"""
122
+ return ResponseEntity(body=None, status=HttpStatus.NO_CONTENT.value)
123
+
124
+ @staticmethod
125
+ def bad_request(body: Optional[T] = None) -> 'ResponseEntity[T]':
126
+ """Create a 400 Bad Request response"""
127
+ return ResponseEntity(body=body, status=HttpStatus.BAD_REQUEST.value)
128
+
129
+ @staticmethod
130
+ def unauthorized(body: Optional[T] = None) -> 'ResponseEntity[T]':
131
+ """Create a 401 Unauthorized response"""
132
+ return ResponseEntity(body=body, status=HttpStatus.UNAUTHORIZED.value)
133
+
134
+ @staticmethod
135
+ def forbidden(body: Optional[T] = None) -> 'ResponseEntity[T]':
136
+ """Create a 403 Forbidden response"""
137
+ return ResponseEntity(body=body, status=HttpStatus.FORBIDDEN.value)
138
+
139
+ @staticmethod
140
+ def not_found(body: Optional[T] = None) -> 'ResponseEntity[T]':
141
+ """Create a 404 Not Found response"""
142
+ return ResponseEntity(body=body, status=HttpStatus.NOT_FOUND.value)
143
+
144
+ @staticmethod
145
+ def status(status_code: int, body: Optional[T] = None) -> 'ResponseEntity[T]':
146
+ """Create a response with custom status code"""
147
+ return ResponseEntity(body=body, status=status_code)
@@ -0,0 +1,47 @@
1
+ """
2
+ Data package initialization
3
+ """
4
+
5
+ from starspring.data.repository import Repository, CrudRepository, StarRepository
6
+ from starspring.data.entity import (
7
+ Entity,
8
+ Column,
9
+ Id,
10
+ GeneratedValue,
11
+ ManyToOne,
12
+ OneToMany,
13
+ ManyToMany,
14
+ BaseEntity,
15
+ GenerationType,
16
+ ColumnMetadata,
17
+ RelationshipMetadata,
18
+ EntityMetadata
19
+ )
20
+ from starspring.data.orm_gateway import ORMGateway, SQLAlchemyGateway, get_orm_gateway, set_orm_gateway
21
+ from starspring.data.transaction import Transactional
22
+ from starspring.data.query_builder import QueryMethodParser, SQLQueryGenerator
23
+
24
+ __all__ = [
25
+ 'Repository',
26
+ 'CrudRepository',
27
+ 'StarRepository',
28
+ 'Entity',
29
+ 'Column',
30
+ 'Id',
31
+ 'GeneratedValue',
32
+ 'ManyToOne',
33
+ 'OneToMany',
34
+ 'ManyToMany',
35
+ 'BaseEntity',
36
+ 'GenerationType',
37
+ 'ColumnMetadata',
38
+ 'RelationshipMetadata',
39
+ 'EntityMetadata',
40
+ 'ORMGateway',
41
+ 'SQLAlchemyGateway',
42
+ 'get_orm_gateway',
43
+ 'set_orm_gateway',
44
+ 'Transactional',
45
+ 'QueryMethodParser',
46
+ 'SQLQueryGenerator',
47
+ ]
@@ -0,0 +1,113 @@
1
+ """
2
+ Database configuration module
3
+
4
+ Provides database connection configuration for MySQL, PostgreSQL, and SQLite.
5
+ """
6
+
7
+ from typing import Optional
8
+ from starspring.config.properties import get_properties
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class DatabaseConfig:
15
+ """
16
+ Database configuration
17
+
18
+ Loads database settings from application properties and creates
19
+ SQLAlchemy engine and session factory.
20
+ """
21
+
22
+ def __init__(self):
23
+ """Initialize database configuration from properties"""
24
+ try:
25
+ from sqlalchemy import create_engine
26
+ from sqlalchemy.orm import sessionmaker
27
+ except ImportError:
28
+ raise ImportError(
29
+ "SQLAlchemy is required for database support. "
30
+ "Install it with: pip install sqlalchemy"
31
+ )
32
+
33
+ # Load configuration
34
+ props = get_properties()
35
+
36
+ self.url = props.get("database.url")
37
+ if not self.url:
38
+ raise ValueError(
39
+ "Database URL not configured. "
40
+ "Add 'database.url' to application.yaml"
41
+ )
42
+
43
+ self.pool_size = props.get_int("database.pool_size", 10)
44
+ self.max_overflow = props.get_int("database.max_overflow", 20)
45
+ self.echo = props.get_bool("database.echo", False)
46
+ self.pool_pre_ping = props.get_bool("database.pool_pre_ping", True)
47
+
48
+ # Create engine
49
+ logger.info(f"Initializing database: {self._mask_password(self.url)}")
50
+
51
+ self.engine = create_engine(
52
+ self.url,
53
+ pool_size=self.pool_size,
54
+ max_overflow=self.max_overflow,
55
+ echo=self.echo,
56
+ pool_pre_ping=self.pool_pre_ping
57
+ )
58
+
59
+ # Create session factory
60
+ self.SessionFactory = sessionmaker(bind=self.engine)
61
+
62
+ logger.info("Database initialized successfully")
63
+
64
+ def _mask_password(self, url: str) -> str:
65
+ """Mask password in database URL for logging"""
66
+ if '@' in url and '://' in url:
67
+ protocol, rest = url.split('://', 1)
68
+ if '@' in rest:
69
+ credentials, host = rest.split('@', 1)
70
+ if ':' in credentials:
71
+ user, _ = credentials.split(':', 1)
72
+ return f"{protocol}://{user}:****@{host}"
73
+ return url
74
+
75
+ def create_tables(self, base):
76
+ """
77
+ Create all tables defined in the Base metadata
78
+
79
+ Args:
80
+ base: SQLAlchemy declarative base
81
+ """
82
+ logger.info("Creating database tables...")
83
+ base.metadata.create_all(self.engine)
84
+ logger.info("Database tables created")
85
+
86
+ def drop_tables(self, base):
87
+ """
88
+ Drop all tables defined in the Base metadata
89
+
90
+ Args:
91
+ base: SQLAlchemy declarative base
92
+ """
93
+ logger.warning("Dropping all database tables...")
94
+ base.metadata.drop_all(self.engine)
95
+ logger.info("Database tables dropped")
96
+
97
+
98
+ # Global database config instance
99
+ _database_config: Optional[DatabaseConfig] = None
100
+
101
+
102
+ def get_database_config() -> DatabaseConfig:
103
+ """Get the global database configuration instance"""
104
+ global _database_config
105
+ if _database_config is None:
106
+ _database_config = DatabaseConfig()
107
+ return _database_config
108
+
109
+
110
+ def set_database_config(config: DatabaseConfig):
111
+ """Set the global database configuration instance"""
112
+ global _database_config
113
+ _database_config = config