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,151 @@
1
+ """
2
+ Schema generator for automatic table creation
3
+
4
+ Generates database schema from entity metadata, similar to Spring Boot's ddl-auto feature.
5
+ """
6
+
7
+ from typing import Type, List
8
+ from sqlalchemy import text
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class SchemaGenerator:
15
+ """
16
+ Generates database schema from entity metadata
17
+
18
+ Similar to Spring Boot's Hibernate ddl-auto feature.
19
+ """
20
+
21
+ def __init__(self, session):
22
+ """
23
+ Initialize schema generator
24
+
25
+ Args:
26
+ session: SQLAlchemy session
27
+ """
28
+ self.session = session
29
+
30
+ def create_tables_from_entities(self, entity_classes: List[Type]) -> None:
31
+ """
32
+ Create database tables from entity classes
33
+
34
+ Args:
35
+ entity_classes: List of entity classes with @Entity decorator
36
+ """
37
+ logger.info("Creating database tables from entities...")
38
+
39
+ for entity_class in entity_classes:
40
+ if not hasattr(entity_class, '_entity_metadata'):
41
+ logger.warning(f"Skipping {entity_class.__name__} - not an entity")
42
+ continue
43
+
44
+ try:
45
+ self._create_table_for_entity(entity_class)
46
+ except Exception as e:
47
+ logger.error(f"Failed to create table for {entity_class.__name__}: {e}")
48
+ raise
49
+
50
+ self.session.commit()
51
+ logger.info(f"Successfully created {len(entity_classes)} tables")
52
+
53
+ def _create_table_for_entity(self, entity_class: Type) -> None:
54
+ """
55
+ Create a single table from entity metadata
56
+
57
+ Args:
58
+ entity_class: Entity class with metadata
59
+ """
60
+ metadata = entity_class._entity_metadata
61
+ table_name = metadata.table_name
62
+
63
+ # Check if table already exists
64
+ check_sql = f"""
65
+ SELECT name FROM sqlite_master
66
+ WHERE type='table' AND name='{table_name}'
67
+ """
68
+ result = self.session.execute(text(check_sql))
69
+ if result.fetchone():
70
+ logger.debug(f"Table '{table_name}' already exists, skipping")
71
+ return
72
+
73
+ # Build CREATE TABLE statement
74
+ columns = []
75
+
76
+ for field_name, col_meta in metadata.columns.items():
77
+ col_def = self._build_column_definition(field_name, col_meta)
78
+ columns.append(col_def)
79
+
80
+ columns_sql = ",\n ".join(columns)
81
+ create_sql = f"""
82
+ CREATE TABLE IF NOT EXISTS {table_name} (
83
+ {columns_sql}
84
+ )
85
+ """
86
+
87
+ logger.info(f"Creating table: {table_name}")
88
+ self.session.execute(text(create_sql))
89
+
90
+ def _build_column_definition(self, field_name: str, col_meta) -> str:
91
+ """
92
+ Build column definition SQL
93
+
94
+ Args:
95
+ field_name: Field name
96
+ col_meta: Column metadata
97
+
98
+ Returns:
99
+ Column definition SQL
100
+ """
101
+ from datetime import datetime
102
+
103
+ # Determine SQL type
104
+ col_type = col_meta.type
105
+ if col_type == int:
106
+ sql_type = "INTEGER"
107
+ elif col_type == str:
108
+ length = col_meta.length or 255
109
+ sql_type = f"TEXT" # SQLite uses TEXT for all strings
110
+ elif col_type == bool:
111
+ sql_type = "BOOLEAN"
112
+ elif col_type == datetime:
113
+ sql_type = "DATETIME"
114
+ elif col_type == float:
115
+ sql_type = "REAL"
116
+ else:
117
+ sql_type = "TEXT" # Default fallback
118
+
119
+ # Build column definition
120
+ parts = [field_name, sql_type]
121
+
122
+ # Primary key
123
+ if col_meta.primary_key:
124
+ parts.append("PRIMARY KEY")
125
+
126
+ # Auto increment
127
+ if col_meta.auto_increment:
128
+ parts.append("AUTOINCREMENT")
129
+
130
+ # Nullable
131
+ if not col_meta.nullable:
132
+ parts.append("NOT NULL")
133
+
134
+ # Unique
135
+ if col_meta.unique:
136
+ parts.append("UNIQUE")
137
+
138
+ # Default value
139
+ if col_meta.default is not None and not callable(col_meta.default):
140
+ if isinstance(col_meta.default, bool):
141
+ default_val = "1" if col_meta.default else "0"
142
+ elif isinstance(col_meta.default, str):
143
+ default_val = f"'{col_meta.default}'"
144
+ elif isinstance(col_meta.default, (int, float)):
145
+ default_val = str(col_meta.default)
146
+ else:
147
+ # Skip complex defaults
148
+ return " ".join(parts)
149
+ parts.append(f"DEFAULT {default_val}")
150
+
151
+ return " ".join(parts)
@@ -0,0 +1,58 @@
1
+ """
2
+ Transaction management
3
+
4
+ Provides declarative transaction support.
5
+ """
6
+
7
+ from typing import Callable
8
+ from functools import wraps
9
+ from starspring.data.orm_gateway import get_orm_gateway
10
+
11
+
12
+ def Transactional(func: Callable) -> Callable:
13
+ """
14
+ Mark a method as transactional
15
+
16
+ Similar to Spring Boot's @Transactional annotation.
17
+ Automatically commits on success and rolls back on exception.
18
+
19
+ Example:
20
+ @Service
21
+ class UserService:
22
+ @Transactional
23
+ async def create_user(self, user: User):
24
+ # Operations here are wrapped in a transaction
25
+ return await self.user_repository.save(user)
26
+ """
27
+ @wraps(func)
28
+ async def async_wrapper(*args, **kwargs):
29
+ gateway = get_orm_gateway()
30
+
31
+ try:
32
+ gateway.begin_transaction()
33
+ result = await func(*args, **kwargs)
34
+ gateway.commit()
35
+ return result
36
+ except Exception as e:
37
+ gateway.rollback()
38
+ raise e
39
+
40
+ @wraps(func)
41
+ def sync_wrapper(*args, **kwargs):
42
+ gateway = get_orm_gateway()
43
+
44
+ try:
45
+ gateway.begin_transaction()
46
+ result = func(*args, **kwargs)
47
+ gateway.commit()
48
+ return result
49
+ except Exception as e:
50
+ gateway.rollback()
51
+ raise e
52
+
53
+ # Return appropriate wrapper based on function type
54
+ import inspect
55
+ if inspect.iscoroutinefunction(func):
56
+ return async_wrapper
57
+ else:
58
+ return sync_wrapper
@@ -0,0 +1 @@
1
+ """Decorator components for routing, DI, and configuration"""
@@ -0,0 +1,179 @@
1
+ """
2
+ Component decorators for dependency injection
3
+
4
+ Provides Spring Boot-style component annotations like @Controller, @Service, etc.
5
+ """
6
+
7
+ from typing import Optional, Type, TypeVar, Callable
8
+ from functools import wraps
9
+ from starspring.core.context import get_application_context, BeanScope
10
+
11
+
12
+ T = TypeVar('T')
13
+
14
+
15
+ def Controller(prefix: str = "") -> Callable[[Type[T]], Type[T]]:
16
+ """
17
+ Mark a class as a REST controller
18
+
19
+ Similar to Spring Boot's @RestController annotation.
20
+
21
+ Args:
22
+ prefix: URL prefix for all routes in this controller
23
+
24
+ Example:
25
+ @Controller("/api/users")
26
+ class UserController:
27
+ pass
28
+ """
29
+ def decorator(cls: Type[T]) -> Type[T]:
30
+ # Store metadata on the class
31
+ cls._is_controller = True # type: ignore
32
+ cls._controller_prefix = prefix # type: ignore
33
+ cls._routes = [] # type: ignore
34
+
35
+ # Register in application context
36
+ context = get_application_context()
37
+ context.register_bean(cls, scope=BeanScope.SINGLETON)
38
+
39
+ return cls
40
+
41
+ return decorator
42
+
43
+
44
+ def Service(cls: Optional[Type[T]] = None, *, name: Optional[str] = None) -> Type[T]:
45
+ """
46
+ Mark a class as a service component
47
+
48
+ Similar to Spring Boot's @Service annotation.
49
+
50
+ Example:
51
+ @Service
52
+ class UserService:
53
+ pass
54
+ """
55
+ def decorator(target_cls: Type[T]) -> Type[T]:
56
+ # Store metadata
57
+ target_cls._is_service = True # type: ignore
58
+
59
+ # Register in application context
60
+ context = get_application_context()
61
+ context.register_bean(target_cls, scope=BeanScope.SINGLETON, name=name)
62
+
63
+ return target_cls
64
+
65
+ # Handle both @Service and @Service() syntax
66
+ if cls is None:
67
+ return decorator # type: ignore
68
+ else:
69
+ return decorator(cls)
70
+
71
+
72
+ def Component(cls: Optional[Type[T]] = None, *, name: Optional[str] = None) -> Type[T]:
73
+ """
74
+ Mark a class as a generic component
75
+
76
+ Similar to Spring Boot's @Component annotation.
77
+
78
+ Example:
79
+ @Component
80
+ class EmailSender:
81
+ pass
82
+ """
83
+ def decorator(target_cls: Type[T]) -> Type[T]:
84
+ # Store metadata
85
+ target_cls._is_component = True # type: ignore
86
+
87
+ # Register in application context
88
+ context = get_application_context()
89
+ context.register_bean(target_cls, scope=BeanScope.SINGLETON, name=name)
90
+
91
+ return target_cls
92
+
93
+ # Handle both @Component and @Component() syntax
94
+ if cls is None:
95
+ return decorator # type: ignore
96
+ else:
97
+ return decorator(cls)
98
+
99
+
100
+ def Repository(cls: Optional[Type[T]] = None, *, name: Optional[str] = None) -> Type[T]:
101
+ """
102
+ Mark a class as a repository component
103
+
104
+ Similar to Spring Boot's @Repository annotation.
105
+
106
+ Example:
107
+ @Repository
108
+ class UserRepository:
109
+ pass
110
+ """
111
+ def decorator(target_cls: Type[T]) -> Type[T]:
112
+ # Store metadata
113
+ target_cls._is_repository = True # type: ignore
114
+
115
+ # Register in application context
116
+ context = get_application_context()
117
+ context.register_bean(target_cls, scope=BeanScope.SINGLETON, name=name)
118
+
119
+ return target_cls
120
+
121
+ # Handle both @Repository and @Repository() syntax
122
+ if cls is None:
123
+ return decorator # type: ignore
124
+ else:
125
+ return decorator(cls)
126
+
127
+
128
+ def TemplateController(prefix: str = "") -> Callable[[Type[T]], Type[T]]:
129
+ """
130
+ Mark a class as a template controller
131
+
132
+ Similar to Spring MVC's @Controller annotation.
133
+ Template controllers return ModelAndView objects for HTML responses.
134
+
135
+ Args:
136
+ prefix: URL prefix for all routes in this controller
137
+
138
+ Example:
139
+ @TemplateController("/")
140
+ class HomeController:
141
+ @GetMapping("/")
142
+ def index(self) -> ModelAndView:
143
+ return ModelAndView("index.html", {"title": "Home"})
144
+ """
145
+ def decorator(cls: Type[T]) -> Type[T]:
146
+ # Store metadata on the class
147
+ cls._is_controller = True # type: ignore
148
+ cls._is_template_controller = True # type: ignore
149
+ cls._controller_prefix = prefix # type: ignore
150
+ cls._routes = [] # type: ignore
151
+
152
+ # Register in application context
153
+ context = get_application_context()
154
+ context.register_bean(cls, scope=BeanScope.SINGLETON)
155
+
156
+ return cls
157
+
158
+ return decorator
159
+
160
+
161
+ def Autowired(func: Callable) -> Callable:
162
+ """
163
+ Mark a method for dependency injection
164
+
165
+ Similar to Spring Boot's @Autowired annotation.
166
+ This is mainly for documentation purposes as dependency injection
167
+ happens automatically in the constructor.
168
+
169
+ Example:
170
+ @Autowired
171
+ def __init__(self, user_service: UserService):
172
+ self.user_service = user_service
173
+ """
174
+ @wraps(func)
175
+ def wrapper(*args, **kwargs):
176
+ return func(*args, **kwargs)
177
+
178
+ wrapper._is_autowired = True # type: ignore
179
+ return wrapper
@@ -0,0 +1,102 @@
1
+ """
2
+ Configuration decorators
3
+
4
+ Provides Spring Boot-style configuration annotations.
5
+ """
6
+
7
+ from typing import Callable, Optional, Type, TypeVar, Any
8
+ from functools import wraps
9
+ from starspring.core.context import get_application_context
10
+
11
+
12
+ T = TypeVar('T')
13
+
14
+
15
+ def Configuration(cls: Type[T]) -> Type[T]:
16
+ """
17
+ Mark a class as a configuration class
18
+
19
+ Similar to Spring Boot's @Configuration annotation.
20
+ Configuration classes can contain @Bean methods.
21
+
22
+ Example:
23
+ @Configuration
24
+ class AppConfig:
25
+ @Bean
26
+ def database(self):
27
+ return Database()
28
+ """
29
+ cls._is_configuration = True # type: ignore
30
+
31
+ # Process @Bean methods
32
+ context = get_application_context()
33
+ instance = cls()
34
+
35
+ for attr_name in dir(cls):
36
+ attr = getattr(cls, attr_name)
37
+ if callable(attr) and hasattr(attr, '_is_bean'):
38
+ # Call the bean factory method
39
+ bean_instance = attr(instance)
40
+ bean_type = type(bean_instance)
41
+ bean_name = getattr(attr, '_bean_name', None)
42
+
43
+ # Register the bean
44
+ context.register_bean(
45
+ bean_type,
46
+ factory=lambda: attr(instance),
47
+ name=bean_name
48
+ )
49
+
50
+ return cls
51
+
52
+
53
+ def Bean(func: Optional[Callable] = None, *, name: Optional[str] = None) -> Callable:
54
+ """
55
+ Mark a method as a bean factory method
56
+
57
+ Similar to Spring Boot's @Bean annotation.
58
+ Must be used within a @Configuration class.
59
+
60
+ Args:
61
+ name: Optional custom name for the bean
62
+
63
+ Example:
64
+ @Configuration
65
+ class AppConfig:
66
+ @Bean
67
+ def user_service(self):
68
+ return UserService()
69
+ """
70
+ def decorator(target_func: Callable) -> Callable:
71
+ target_func._is_bean = True # type: ignore
72
+ target_func._bean_name = name # type: ignore
73
+ return target_func
74
+
75
+ # Handle both @Bean and @Bean() syntax
76
+ if func is None:
77
+ return decorator
78
+ else:
79
+ return decorator(func)
80
+
81
+
82
+ def Value(key: str, default: Any = None) -> Any:
83
+ """
84
+ Inject a configuration value
85
+
86
+ Similar to Spring Boot's @Value annotation.
87
+
88
+ Args:
89
+ key: Configuration key to inject
90
+ default: Default value if key not found
91
+
92
+ Example:
93
+ class MyService:
94
+ def __init__(self):
95
+ self.api_key = Value("api.key", "default-key")
96
+
97
+ Note: This is a simplified implementation. In a real application,
98
+ this would integrate with the configuration system.
99
+ """
100
+ # This is a placeholder - actual implementation would read from config
101
+ from starspring.config.properties import get_property
102
+ return get_property(key, default)