mitsuki 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.
Files changed (67) hide show
  1. mitsuki/__init__.py +156 -0
  2. mitsuki/cli/__init__.py +3 -0
  3. mitsuki/cli/bootstrap.py +215 -0
  4. mitsuki/cli/templates/README.md.tpl +28 -0
  5. mitsuki/cli/templates/__init__.py.tpl +1 -0
  6. mitsuki/cli/templates/app.py.tpl +26 -0
  7. mitsuki/cli/templates/application-dev.yml.tpl +12 -0
  8. mitsuki/cli/templates/application-prod.yml.tpl +16 -0
  9. mitsuki/cli/templates/application-stg.yml.tpl +16 -0
  10. mitsuki/cli/templates/application.yml.tpl +35 -0
  11. mitsuki/cli/templates/controller.py.tpl +55 -0
  12. mitsuki/cli/templates/entity.py.tpl +16 -0
  13. mitsuki/cli/templates/gitignore.tpl +22 -0
  14. mitsuki/cli/templates/repository.py.tpl +46 -0
  15. mitsuki/cli/templates/service.py.tpl +37 -0
  16. mitsuki/config/__init__.py +18 -0
  17. mitsuki/config/decorators.py +157 -0
  18. mitsuki/config/defaults.yml +61 -0
  19. mitsuki/config/exceptions.py +16 -0
  20. mitsuki/config/properties.py +249 -0
  21. mitsuki/core/__init__.py +18 -0
  22. mitsuki/core/application.py +286 -0
  23. mitsuki/core/container.py +271 -0
  24. mitsuki/core/decorators.py +185 -0
  25. mitsuki/core/enums.py +137 -0
  26. mitsuki/core/logging.py +150 -0
  27. mitsuki/core/metrics.py +33 -0
  28. mitsuki/core/providers.py +61 -0
  29. mitsuki/core/scanner.py +116 -0
  30. mitsuki/core/scheduler.py +399 -0
  31. mitsuki/core/server.py +252 -0
  32. mitsuki/core/utils.py +96 -0
  33. mitsuki/data/__init__.py +115 -0
  34. mitsuki/data/adapters/__init__.py +0 -0
  35. mitsuki/data/adapters/base.py +153 -0
  36. mitsuki/data/adapters/sqlalchemy.py +621 -0
  37. mitsuki/data/entity.py +216 -0
  38. mitsuki/data/query.py +109 -0
  39. mitsuki/data/query_decorators.py +68 -0
  40. mitsuki/data/query_parser.py +203 -0
  41. mitsuki/data/repository.py +577 -0
  42. mitsuki/data/types.py +334 -0
  43. mitsuki/exceptions.py +116 -0
  44. mitsuki/openapi/__init__.py +109 -0
  45. mitsuki/openapi/decorators.py +112 -0
  46. mitsuki/openapi/generator.py +131 -0
  47. mitsuki/openapi/introspector.py +310 -0
  48. mitsuki/openapi/schemas.py +199 -0
  49. mitsuki/openapi/ui.py +182 -0
  50. mitsuki/version.py +21 -0
  51. mitsuki/web/__init__.py +56 -0
  52. mitsuki/web/controllers.py +81 -0
  53. mitsuki/web/mappings.py +315 -0
  54. mitsuki/web/multipart.py +188 -0
  55. mitsuki/web/parameter_binder.py +280 -0
  56. mitsuki/web/params.py +259 -0
  57. mitsuki/web/response.py +125 -0
  58. mitsuki/web/response_processor.py +104 -0
  59. mitsuki/web/route_builder.py +218 -0
  60. mitsuki/web/serialization.py +185 -0
  61. mitsuki/web/upload.py +96 -0
  62. mitsuki-0.1.0.dist-info/METADATA +483 -0
  63. mitsuki-0.1.0.dist-info/RECORD +67 -0
  64. mitsuki-0.1.0.dist-info/WHEEL +5 -0
  65. mitsuki-0.1.0.dist-info/entry_points.txt +2 -0
  66. mitsuki-0.1.0.dist-info/licenses/LICENSE.md +202 -0
  67. mitsuki-0.1.0.dist-info/top_level.txt +1 -0
mitsuki/__init__.py ADDED
@@ -0,0 +1,156 @@
1
+ """
2
+ Mitsuki - Spring-inspired Python web framework.
3
+ Enterprise-grade annotation-driven development for Python.
4
+ """
5
+
6
+ # Core
7
+ # Config
8
+ from mitsuki.config import Profile, Value
9
+ from mitsuki.core.application import Application, ApplicationContext
10
+ from mitsuki.core.container import DIContainer, get_container
11
+ from mitsuki.core.decorators import (
12
+ Component,
13
+ Configuration,
14
+ Provider,
15
+ Repository,
16
+ Scheduled,
17
+ Service,
18
+ )
19
+ from mitsuki.core.enums import (
20
+ ASGIMessageType,
21
+ ASGIScopeType,
22
+ DatabaseAdapter,
23
+ DatabaseDialect,
24
+ ParameterKind,
25
+ Scope,
26
+ ServerType,
27
+ SQLOperation,
28
+ )
29
+
30
+ # Logging
31
+ from mitsuki.core.logging import get_logger
32
+ from mitsuki.core.utils import get_active_profile
33
+
34
+ # Data
35
+ from mitsuki.data import (
36
+ UUID,
37
+ Column,
38
+ CrudRepository,
39
+ Entity,
40
+ Field,
41
+ Id,
42
+ Modifying,
43
+ Query,
44
+ UUIDv1,
45
+ UUIDv4,
46
+ UUIDv5,
47
+ UUIDv7,
48
+ )
49
+
50
+ # Web
51
+ from mitsuki.web.controllers import Controller, RestController, RestRouter, Router
52
+ from mitsuki.web.mappings import (
53
+ Consumes,
54
+ Delete,
55
+ DeleteMapping,
56
+ Get,
57
+ GetMapping,
58
+ Patch,
59
+ PatchMapping,
60
+ Post,
61
+ PostMapping,
62
+ Produces,
63
+ Put,
64
+ PutMapping,
65
+ RequestMapping,
66
+ )
67
+ from mitsuki.web.params import (
68
+ FormFile,
69
+ FormParam,
70
+ PathVariable,
71
+ QueryParam,
72
+ RequestBody,
73
+ RequestHeader,
74
+ RequestParam,
75
+ )
76
+ from mitsuki.web.response import JSONResponse, JsonResponse, ResponseEntity
77
+ from mitsuki.web.serialization import (
78
+ serialize_json,
79
+ serialize_json_safe,
80
+ )
81
+ from mitsuki.web.upload import UploadFile
82
+
83
+ __version__ = "0.1.0"
84
+ __all__ = [
85
+ # Core
86
+ "Application",
87
+ "ApplicationContext",
88
+ "Component",
89
+ "Service",
90
+ "Repository",
91
+ "Configuration",
92
+ "Provider",
93
+ "Scheduled",
94
+ "DIContainer",
95
+ "get_container",
96
+ "get_active_profile",
97
+ # Enums
98
+ "ServerType",
99
+ "DatabaseAdapter",
100
+ "DatabaseDialect",
101
+ "SQLOperation",
102
+ "ASGIMessageType",
103
+ "ASGIScopeType",
104
+ "ParameterKind",
105
+ "Scope",
106
+ # Logging
107
+ "get_logger",
108
+ # Web
109
+ "Controller",
110
+ "RestController",
111
+ "Router",
112
+ "RestRouter",
113
+ "GetMapping",
114
+ "PostMapping",
115
+ "PutMapping",
116
+ "DeleteMapping",
117
+ "PatchMapping",
118
+ "RequestMapping",
119
+ "Produces",
120
+ "Consumes",
121
+ "Get",
122
+ "Post",
123
+ "Put",
124
+ "Delete",
125
+ "Patch",
126
+ "PathVariable",
127
+ "QueryParam",
128
+ "RequestParam",
129
+ "RequestBody",
130
+ "RequestHeader",
131
+ "FormFile",
132
+ "FormParam",
133
+ "UploadFile",
134
+ "ResponseEntity",
135
+ "JsonResponse",
136
+ "JSONResponse",
137
+ # Data
138
+ "Entity",
139
+ "CrudRepository",
140
+ "Id",
141
+ "Column",
142
+ "Field",
143
+ "UUID",
144
+ "UUIDv1",
145
+ "UUIDv4",
146
+ "UUIDv5",
147
+ "UUIDv7",
148
+ "Query",
149
+ "Modifying",
150
+ # Config
151
+ "Value",
152
+ "Profile",
153
+ # Serialization
154
+ "serialize_json",
155
+ "serialize_json_safe",
156
+ ]
@@ -0,0 +1,3 @@
1
+ from mitsuki.cli.bootstrap import main
2
+
3
+ __all__ = ["main"]
@@ -0,0 +1,215 @@
1
+ from pathlib import Path
2
+
3
+ import click
4
+
5
+ from mitsuki.core.enums import DatabaseDialect
6
+ from mitsuki.core.logging import get_logger
7
+
8
+ logger = get_logger()
9
+
10
+
11
+ def read_template(template_name: str) -> str:
12
+ """Read a template file from the templates directory."""
13
+ template_dir = Path(__file__).parent / "templates"
14
+ template_path = template_dir / template_name
15
+ return template_path.read_text()
16
+
17
+
18
+ def create_directory(path: Path) -> None:
19
+ """Create a directory if it doesn't exist."""
20
+ path.mkdir(parents=True, exist_ok=True)
21
+ logger.info(f"Created directory: {path}")
22
+
23
+
24
+ def write_file(path: Path, content: str) -> None:
25
+ """Write content to a file."""
26
+ path.write_text(content)
27
+ logger.info(f"Created file: {path}")
28
+
29
+
30
+ def create_domain_files(app_dir: Path, app_name: str, domain_name: str) -> None:
31
+ """Create entity, repository, service, and controller for a domain."""
32
+ domain_lower = domain_name.lower()
33
+
34
+ # Entity
35
+ entity_content = read_template("entity.py.tpl").replace(
36
+ "{{DOMAIN_NAME}}", domain_name
37
+ )
38
+ write_file(app_dir / "domain" / f"{domain_lower}.py", entity_content)
39
+
40
+ # Repository
41
+ repo_content = (
42
+ read_template("repository.py.tpl")
43
+ .replace("{{DOMAIN_NAME}}", domain_name)
44
+ .replace("{{domain_name}}", domain_lower)
45
+ )
46
+ write_file(app_dir / "repository" / f"{domain_lower}_repository.py", repo_content)
47
+
48
+ # Service
49
+ service_content = (
50
+ read_template("service.py.tpl")
51
+ .replace("{{DOMAIN_NAME}}", domain_name)
52
+ .replace("{{domain_name}}", domain_lower)
53
+ )
54
+ write_file(app_dir / "service" / f"{domain_lower}_service.py", service_content)
55
+
56
+ # Controller
57
+ controller_content = (
58
+ read_template("controller.py.tpl")
59
+ .replace("{{DOMAIN_NAME}}", domain_name)
60
+ .replace("{{domain_name}}", domain_lower)
61
+ )
62
+ write_file(
63
+ app_dir / "controller" / f"{domain_lower}_controller.py", controller_content
64
+ )
65
+
66
+
67
+ @click.group()
68
+ def cli():
69
+ pass
70
+
71
+
72
+ @cli.command()
73
+ def init():
74
+ """Bootstrap a new Mitsuki application."""
75
+
76
+ # Application name
77
+ app_name = click.prompt("Application name")
78
+ app_name = app_name.lower().replace("-", "_").replace(" ", "_")
79
+
80
+ # Description
81
+ description = click.prompt("Description (optional)", default="", show_default=False)
82
+
83
+ # Database type
84
+ db_type = click.prompt(
85
+ "Database type",
86
+ type=click.Choice(["sqlite", "postgresql", "mysql"], case_sensitive=False),
87
+ default="sqlite",
88
+ )
89
+
90
+ # Starter domain
91
+ create_domain = click.confirm(
92
+ "Create starter domain (i.e. models/object definitions)?", default=True
93
+ )
94
+
95
+ domains = []
96
+ if create_domain:
97
+ domain_name = click.prompt("Domain name (e.g., User, Product)")
98
+ domains.append(domain_name)
99
+
100
+ # Additional domains
101
+ while click.confirm("Add another domain object?", default=False):
102
+ domain_name = click.prompt("Domain name")
103
+ domains.append(domain_name)
104
+
105
+ # Create project structure
106
+ project_root = Path.cwd() / app_name
107
+ src_dir = project_root / "src"
108
+ app_dir = src_dir / app_name
109
+
110
+ if project_root.exists():
111
+ click.echo(f"Error: Directory {app_name} already exists")
112
+ return
113
+
114
+ # Create directories
115
+ create_directory(project_root)
116
+ create_directory(src_dir)
117
+ create_directory(app_dir)
118
+
119
+ subdirs = ["domain", "repository", "service", "controller"]
120
+ init_content = read_template("__init__.py.tpl").replace(
121
+ "{{MODULE_DESCRIPTION}}", description or f"{app_name} module"
122
+ )
123
+
124
+ for subdir in subdirs:
125
+ create_directory(app_dir / subdir)
126
+ write_file(app_dir / subdir / "__init__.py", init_content)
127
+
128
+ write_file(app_dir / "__init__.py", init_content)
129
+
130
+ # Create app.py with controller imports
131
+ controller_imports = ""
132
+ if domains:
133
+ imports = []
134
+ for domain_name in domains:
135
+ domain_lower = domain_name.lower()
136
+ imports.append(
137
+ f"from {app_name}.controller.{domain_lower}_controller import {domain_name}Controller"
138
+ )
139
+ controller_imports = "\n" + "\n".join(imports)
140
+
141
+ app_content = read_template("app.py.tpl").replace(
142
+ "{{CONTROLLER_IMPORT}}", controller_imports
143
+ )
144
+ write_file(src_dir / "app.py", app_content)
145
+
146
+ # Create domain files
147
+ for domain_name in domains:
148
+ create_domain_files(app_dir, app_name, domain_name)
149
+
150
+ # Create configuration files
151
+ db_url_map = {
152
+ "sqlite": f"sqlite:///{app_name}.db",
153
+ "postgresql": f"postgresql://localhost/{app_name}",
154
+ "mysql": f"mysql://localhost/{app_name}",
155
+ }
156
+ db_url = db_url_map[db_type]
157
+
158
+ app_yml = read_template("application.yml.tpl").replace("{{app_name}}", app_name)
159
+ app_yml = app_yml.replace("sqlite:///{{app_name}}.db", db_url)
160
+ write_file(project_root / "application.yml", app_yml)
161
+
162
+ for env in ["dev", "stg", "prod"]:
163
+ env_yml = read_template(f"application-{env}.yml.tpl").replace(
164
+ "{{app_name}}", app_name
165
+ )
166
+ if db_type == DatabaseDialect.SQLITE:
167
+ env_yml = env_yml.replace(
168
+ "postgresql://localhost/{{app_name}}", f"sqlite:///{app_name}.db"
169
+ )
170
+ elif db_type == DatabaseDialect.MYSQL:
171
+ env_yml = env_yml.replace("postgresql://localhost/", "mysql://localhost/")
172
+ write_file(project_root / f"application-{env}.yml", env_yml)
173
+
174
+ # Create README
175
+ app_title = app_name.replace("_", " ").title()
176
+ domain_section = ""
177
+
178
+ if domains:
179
+ endpoints = []
180
+ for domain_name in domains:
181
+ domain_lower = domain_name.lower()
182
+ endpoints.append(f"""
183
+ - `GET /api/{domain_lower}` - List all {domain_lower}s
184
+ - `GET /api/{domain_lower}/{{id}}` - Get {domain_lower} by ID
185
+ - `POST /api/{domain_lower}` - Create new {domain_lower}
186
+ - `PUT /api/{domain_lower}/{{id}}` - Update {domain_lower}
187
+ - `DELETE /api/{domain_lower}/{{id}}` - Delete {domain_lower}""")
188
+
189
+ domain_section = "\n## API Endpoints\n" + "\n".join(endpoints) + "\n"
190
+
191
+ readme = (
192
+ read_template("README.md.tpl")
193
+ .replace("{{APP_TITLE}}", app_title)
194
+ .replace("{{app_name}}", app_name)
195
+ .replace("{{DOMAIN_SECTION}}", domain_section)
196
+ )
197
+ write_file(project_root / "README.md", readme)
198
+
199
+ # Create .gitignore
200
+ gitignore = read_template("gitignore.tpl")
201
+ write_file(project_root / ".gitignore", gitignore)
202
+
203
+ logger.info(f"\nSuccessfully created Mitsuki application: {app_name}")
204
+ logger.info("\nTo get started:")
205
+ logger.info(f" cd {app_name}")
206
+ logger.info(" MITSUKI_PROFILE=development python src/app.py")
207
+
208
+
209
+ def main():
210
+ cli()
211
+
212
+
213
+ if __name__ == "__main__":
214
+ # For running the bootstrap script as a script
215
+ cli()
@@ -0,0 +1,28 @@
1
+ # {{APP_TITLE}}
2
+
3
+ Mitsuki application generated with the Mitsuki CLI.
4
+ {{DOMAIN_SECTION}}
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ pip install mitsuki
9
+ MITSUKI_PROFILE=development python src/app.py
10
+ ```
11
+
12
+ The server will start on http://127.0.0.1:8000
13
+
14
+ ## Project Structure
15
+
16
+ ```
17
+ src/
18
+ {{app_name}}/
19
+ domain/ # @Entity classes
20
+ repository/ # @CrudRepository classes
21
+ service/ # @Service classes
22
+ controller/ # @RestController classes
23
+ app.py
24
+ application.yml
25
+ application-dev.yml
26
+ application-stg.yml
27
+ application-prod.yml
28
+ ```
@@ -0,0 +1 @@
1
+ """{{MODULE_DESCRIPTION}}"""
@@ -0,0 +1,26 @@
1
+ """
2
+ Main application entry point.
3
+ """
4
+ import os
5
+ from mitsuki import Application, Value, get_logger{{CONTROLLER_IMPORT}}
6
+
7
+
8
+ @Application
9
+ class App:
10
+ """
11
+ Mitsuki application.
12
+
13
+ Configuration is loaded from application.yml and can be overridden
14
+ with environment variables (MITSUKI_SERVER_PORT, etc.)
15
+ """
16
+ port: int = Value("${server.port:8000}")
17
+ host: str = Value("${server.host:127.0.0.1}")
18
+
19
+
20
+ if __name__ == "__main__":
21
+ # Get active profile from environment
22
+ profile = os.getenv("MITSUKI_PROFILE", "development")
23
+ logger = get_logger()
24
+ logger.info(f"Starting Mitsuki application with profile: {profile}")
25
+
26
+ App.run()
@@ -0,0 +1,12 @@
1
+ # Development Configuration
2
+
3
+ database:
4
+ url: sqlite:///dev.db
5
+ echo: true
6
+
7
+ logging:
8
+ level: DEBUG
9
+ sqlalchemy: true
10
+
11
+ app:
12
+ debug: true
@@ -0,0 +1,16 @@
1
+ # Production Configuration
2
+
3
+ database:
4
+ url: postgresql://localhost/{{app_name}}_production
5
+ echo: false
6
+ pool:
7
+ enabled: true
8
+ size: 50
9
+ max_overflow: 100
10
+
11
+ logging:
12
+ level: WARNING
13
+ sqlalchemy: false
14
+
15
+ app:
16
+ debug: false
@@ -0,0 +1,16 @@
1
+ # Staging Configuration
2
+
3
+ database:
4
+ url: postgresql://localhost/{{app_name}}_staging
5
+ echo: false
6
+ pool:
7
+ enabled: true
8
+ size: 10
9
+ max_overflow: 20
10
+
11
+ logging:
12
+ level: INFO
13
+ sqlalchemy: false
14
+
15
+ app:
16
+ debug: false
@@ -0,0 +1,35 @@
1
+ # Mitsuki Application Configuration
2
+
3
+ server:
4
+ host: 0.0.0.0
5
+ port: 8000
6
+ type: granian # Options: uvicorn, granian
7
+ workers: 1 # Number of worker processes (Currently supported on Granian only)
8
+ access_log: true # Enable/disable access logging
9
+ ignore_trailing_slash: true
10
+ # timeout: 60 # Request timeout in seconds (optional, uses server defaults if not set)
11
+ # max_body_size: 10485760 # Max request body size in bytes (default: 10MB)
12
+ # cors:
13
+ # enabled: false
14
+ # allowed_origins:
15
+ # - "*"
16
+
17
+ database:
18
+ url: sqlite:///app.db
19
+ adapter: sqlalchemy
20
+ echo: false # SQLAlchemy logs
21
+ pool:
22
+ enabled: true
23
+ size: 10
24
+ max_overflow: 20
25
+ timeout: 30
26
+ recycle: 3600
27
+
28
+ logging:
29
+ level: INFO
30
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
31
+ sqlalchemy: false
32
+
33
+ app:
34
+ name: {{APP_NAME}}
35
+ debug: false
@@ -0,0 +1,55 @@
1
+ """
2
+ {{DOMAIN_NAME}}Controller - REST API endpoints for {{DOMAIN_NAME}}.
3
+ """
4
+ import uuid
5
+ from typing import List
6
+ from mitsuki import RestController, GetMapping, PostMapping, DeleteMapping, QueryParam
7
+ from ..service.{{domain_name}}_service import {{DOMAIN_NAME}}Service
8
+
9
+
10
+ @RestController("/api/{{domain_name}}s")
11
+ class {{DOMAIN_NAME}}Controller:
12
+ """REST API controller for {{DOMAIN_NAME}} resources."""
13
+
14
+ def __init__(self, service: {{DOMAIN_NAME}}Service):
15
+ self.service = service
16
+
17
+ @GetMapping("")
18
+ async def list_all(
19
+ self,
20
+ page: int = QueryParam(default=0),
21
+ size: int = QueryParam(default=100)
22
+ ) -> List[dict]:
23
+ """GET /api/{{domain_name}}s?page=0&size=100"""
24
+ entities = await self.service.get_all(page=page, size=size)
25
+ return [self._to_dict(e) for e in entities]
26
+
27
+ @GetMapping("/{id}")
28
+ async def get_by_id(self, id: str) -> dict:
29
+ """GET /api/{{domain_name}}s/123e4567-e89b-12d3-a456-426614174000"""
30
+ entity = await self.service.get_by_id(uuid.UUID(id))
31
+ if not entity:
32
+ return {"error": "Not found"}, 404
33
+ return self._to_dict(entity)
34
+
35
+ @PostMapping("")
36
+ async def create(self, body: dict) -> dict:
37
+ """POST /api/{{domain_name}}s"""
38
+ entity = await self.service.create()
39
+ return self._to_dict(entity), 201
40
+
41
+ @DeleteMapping("/{id}")
42
+ async def delete(self, id: str) -> dict:
43
+ """DELETE /api/{{domain_name}}s/123e4567-e89b-12d3-a456-426614174000"""
44
+ deleted = await self.service.delete(uuid.UUID(id))
45
+ if not deleted:
46
+ return {"error": "Not found"}, 404
47
+ return {"success": True}
48
+
49
+ def _to_dict(self, entity) -> dict:
50
+ """Convert entity to dict for JSON response"""
51
+ return {
52
+ "id": str(entity.id),
53
+ "created_at": entity.created_at.isoformat() if entity.created_at else None,
54
+ "updated_at": entity.updated_at.isoformat() if entity.updated_at else None,
55
+ }
@@ -0,0 +1,16 @@
1
+ """
2
+ {{DOMAIN_NAME}} entity definition.
3
+ """
4
+ import uuid
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from mitsuki import Entity, UUIDv7, Field
8
+
9
+
10
+ @Entity()
11
+ @dataclass
12
+ class {{DOMAIN_NAME}}:
13
+ """{{DOMAIN_NAME}} domain object."""
14
+ id: uuid.UUID = UUIDv7()
15
+ created_at: datetime = Field(update_on_create=True)
16
+ updated_at: datetime = Field(update_on_save=True)
@@ -0,0 +1,22 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ .Python
6
+ env/
7
+ venv/
8
+ *.egg-info/
9
+
10
+ # Mitsuki
11
+ *.db
12
+ *.sqlite
13
+
14
+ # IDE
15
+ .vscode/
16
+ .idea/
17
+
18
+ # OS
19
+ .DS_Store
20
+
21
+ # Environment
22
+ .env
@@ -0,0 +1,46 @@
1
+ """
2
+ {{DOMAIN_NAME}}Repository - Data access layer for {{DOMAIN_NAME}}.
3
+ """
4
+ from typing import Optional, List
5
+ from mitsuki import CrudRepository, Query, Modifying
6
+ from ..domain.{{domain_name}} import {{DOMAIN_NAME}}
7
+
8
+
9
+ @CrudRepository(entity={{DOMAIN_NAME}})
10
+ class {{DOMAIN_NAME}}Repository:
11
+ """
12
+ Repository for {{DOMAIN_NAME}} entities.
13
+
14
+ Auto-implemented CRUD methods (you can just call these as-is):
15
+ - save(entity) - Create or update
16
+ - find_by_id(id) - Find by primary key
17
+ - find_all(page, size, sort_by, sort_desc) - Paginated query
18
+ - delete(entity) - Delete entity
19
+ - delete_by_id(id) - Delete by ID
20
+ - count() - Count all entities
21
+ - exists_by_id(id) - Check existence
22
+
23
+ Auto-generated query methods (define signature only):
24
+ Examples:
25
+ async def find_by_name(self, name: str) -> Optional[{{DOMAIN_NAME}}]: ...
26
+ async def find_by_status(self, status: str) -> List[{{DOMAIN_NAME}}]: ...
27
+ async def find_by_created_at_greater_than(self, created_at) -> List[{{DOMAIN_NAME}}]: ...
28
+
29
+ Custom @Query methods:
30
+ @Query("SELECT e FROM {{DOMAIN_NAME}} e WHERE e.status = :status ORDER BY e.created_at DESC")
31
+ async def find_active_sorted(self, status: str) -> List[{{DOMAIN_NAME}}]: ...
32
+
33
+ Custom @Modifying queries (UPDATE/DELETE):
34
+ @Modifying
35
+ @Query("UPDATE {{DOMAIN_NAME}} SET status = :status WHERE id = :id")
36
+ async def update_status(self, id: int, status: str) -> int: ...
37
+
38
+ Custom methods with direct database access:
39
+ async def custom_complex_query(self, param: str) -> List[dict]:
40
+ async with self.connection as conn:
41
+ result = await conn.execute("SELECT * FROM table WHERE field = :param", {"param": param})
42
+ return [dict(row) for row in result]
43
+ """
44
+
45
+ # Add your custom query methods here
46
+ pass
@@ -0,0 +1,37 @@
1
+ """
2
+ {{DOMAIN_NAME}}Service - Business logic for {{DOMAIN_NAME}}.
3
+ """
4
+ import uuid
5
+ from typing import List, Optional
6
+ from mitsuki import Service
7
+ from ..domain.{{domain_name}} import {{DOMAIN_NAME}}
8
+ from ..repository.{{domain_name}}_repository import {{DOMAIN_NAME}}Repository
9
+
10
+
11
+ @Service()
12
+ class {{DOMAIN_NAME}}Service:
13
+ """Service layer for {{DOMAIN_NAME}} business logic."""
14
+
15
+ def __init__(self, repo: {{DOMAIN_NAME}}Repository):
16
+ self.repo = repo
17
+
18
+ async def get_all(self, page: int = 0, size: int = 100) -> List[{{DOMAIN_NAME}}]:
19
+ """Get all {{domain_name}}s with pagination"""
20
+ return await self.repo.find_all(page=page, size=size)
21
+
22
+ async def get_by_id(self, id: uuid.UUID) -> Optional[{{DOMAIN_NAME}}]:
23
+ """Get {{domain_name}} by ID"""
24
+ return await self.repo.find_by_id(id)
25
+
26
+ async def create(self) -> {{DOMAIN_NAME}}:
27
+ """Create new {{domain_name}}"""
28
+ entity = {{DOMAIN_NAME}}(id=None)
29
+ return await self.repo.save(entity)
30
+
31
+ async def delete(self, id: uuid.UUID) -> bool:
32
+ """Delete {{domain_name}} by ID"""
33
+ exists = await self.repo.exists_by_id(id)
34
+ if not exists:
35
+ return False
36
+ await self.repo.delete_by_id(id)
37
+ return True