elric-cli 1.0.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 (39) hide show
  1. elric_cli/__init__.py +1 -0
  2. elric_cli/app.py +24 -0
  3. elric_cli/commands/.gitkeep +0 -0
  4. elric_cli/commands/__init__.py +0 -0
  5. elric_cli/commands/apikey.py +134 -0
  6. elric_cli/commands/make.py +266 -0
  7. elric_cli/commands/migrate.py +69 -0
  8. elric_cli/commands/project.py +72 -0
  9. elric_cli/commands/route.py +49 -0
  10. elric_cli/commands/serve.py +33 -0
  11. elric_cli/install.sh +92 -0
  12. elric_cli/stubs/.gitkeep +0 -0
  13. elric_cli/stubs/agent.stub.py +23 -0
  14. elric_cli/stubs/agent_chat.stub.py +62 -0
  15. elric_cli/stubs/agent_planner.stub.py +82 -0
  16. elric_cli/stubs/agent_react.stub.py +83 -0
  17. elric_cli/stubs/agent_simple.stub.py +32 -0
  18. elric_cli/stubs/agent_streaming.stub.py +59 -0
  19. elric_cli/stubs/agent_tool.stub.py +63 -0
  20. elric_cli/stubs/chain.stub.py +23 -0
  21. elric_cli/stubs/controller.stub.py +30 -0
  22. elric_cli/stubs/event.stub.py +18 -0
  23. elric_cli/stubs/exception.stub.py +21 -0
  24. elric_cli/stubs/job.stub.py +24 -0
  25. elric_cli/stubs/listener.stub.py +23 -0
  26. elric_cli/stubs/middleware.stub.py +32 -0
  27. elric_cli/stubs/migration.stub.py +26 -0
  28. elric_cli/stubs/model.stub.py +18 -0
  29. elric_cli/stubs/route.stub.py +33 -0
  30. elric_cli/stubs/schema.stub.py +32 -0
  31. elric_cli/stubs/test.stub.py +21 -0
  32. elric_cli/stubs/tool.stub.py +24 -0
  33. elric_cli/uninstall.sh +64 -0
  34. elric_cli/utils.py +181 -0
  35. elric_cli-1.0.0.dist-info/METADATA +98 -0
  36. elric_cli-1.0.0.dist-info/RECORD +39 -0
  37. elric_cli-1.0.0.dist-info/WHEEL +4 -0
  38. elric_cli-1.0.0.dist-info/entry_points.txt +2 -0
  39. elric_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
elric_cli/install.sh ADDED
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+
3
+ # Elric Framework - Installation Script
4
+ # This script installs the 'elric' command globally on your system
5
+
6
+ set -e
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Get the absolute path of the CLI repository root
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
18
+
19
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
20
+ echo -e "${BLUE} Elric Framework - Installation${NC}"
21
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
22
+ echo ""
23
+
24
+ # Check if uv is installed
25
+ if ! command -v uv &> /dev/null; then
26
+ echo -e "${RED}✗ Error: 'uv' is not installed${NC}"
27
+ echo -e "${YELLOW} Please install uv first: https://github.com/astral-sh/uv${NC}"
28
+ echo -e "${YELLOW} Quick install: curl -LsSf https://astral.sh/uv/install.sh | sh${NC}"
29
+ exit 1
30
+ fi
31
+
32
+ echo -e "${GREEN}✓ Found uv package manager${NC}"
33
+
34
+ # Determine installation directory
35
+ if [ -w "/usr/local/bin" ]; then
36
+ INSTALL_DIR="/usr/local/bin"
37
+ elif [ -w "$HOME/.local/bin" ]; then
38
+ INSTALL_DIR="$HOME/.local/bin"
39
+ mkdir -p "$INSTALL_DIR"
40
+ else
41
+ INSTALL_DIR="$HOME/.local/bin"
42
+ mkdir -p "$INSTALL_DIR"
43
+ fi
44
+
45
+ echo -e "${BLUE}→ Installation directory: ${INSTALL_DIR}${NC}"
46
+
47
+ # Create wrapper script
48
+ WRAPPER_SCRIPT="${INSTALL_DIR}/elric"
49
+
50
+ echo -e "${BLUE}→ Creating wrapper script...${NC}"
51
+
52
+ cat > "$WRAPPER_SCRIPT" << EOF
53
+ #!/bin/bash
54
+ # Elric Framework CLI Wrapper
55
+ # Auto-generated by install.sh
56
+
57
+ PROJECT_DIR="${PROJECT_DIR}"
58
+
59
+ # Run elric from the CLI project while preserving current working directory
60
+ uv run --project "\${PROJECT_DIR}" elric "\$@"
61
+ EOF
62
+
63
+ # Make wrapper executable
64
+ chmod +x "$WRAPPER_SCRIPT"
65
+
66
+ echo -e "${GREEN}✓ Wrapper script created${NC}"
67
+
68
+ # Check if installation directory is in PATH
69
+ if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then
70
+ echo ""
71
+ echo -e "${YELLOW}⚠ Warning: ${INSTALL_DIR} is not in your PATH${NC}"
72
+ echo -e "${YELLOW} Add this line to your ~/.zshrc or ~/.bashrc:${NC}"
73
+ echo -e "${BLUE} export PATH=\"${INSTALL_DIR}:\$PATH\"${NC}"
74
+ echo ""
75
+ echo -e "${YELLOW} Then reload your shell:${NC}"
76
+ echo -e "${BLUE} source ~/.zshrc # or source ~/.bashrc${NC}"
77
+ echo ""
78
+ fi
79
+
80
+ echo ""
81
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
82
+ echo -e "${GREEN} ✓ Installation completed successfully!${NC}"
83
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
84
+ echo ""
85
+ echo -e "${BLUE} You can now use the 'elric' command from anywhere:${NC}"
86
+ echo -e "${GREEN} elric --help${NC}"
87
+ echo -e "${GREEN} elric make agent MyAgent${NC}"
88
+ echo -e "${GREEN} elric serve${NC}"
89
+ echo ""
90
+ echo -e "${BLUE} Project directory: ${PROJECT_DIR}${NC}"
91
+ echo -e "${BLUE} Wrapper installed: ${WRAPPER_SCRIPT}${NC}"
92
+ echo ""
File without changes
@@ -0,0 +1,23 @@
1
+ from typing import Any
2
+
3
+ from app.ai.agents.base_agent import BaseAgent
4
+
5
+
6
+ class {{ class_name }}(BaseAgent):
7
+ """{{ class_name }} agent implementation."""
8
+
9
+ def __init__(self):
10
+ super().__init__(name="{{ snake_name }}")
11
+
12
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
13
+ """
14
+ Execute the agent logic.
15
+
16
+ Args:
17
+ input_data: Input data for the agent
18
+
19
+ Returns:
20
+ dict: Agent execution result
21
+ """
22
+ # TODO: Implement agent logic here
23
+ return {"status": "success", "message": "{{ class_name }} executed"}
@@ -0,0 +1,62 @@
1
+ from typing import Any
2
+
3
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
4
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
5
+
6
+ from app.ai.agents.base_agent import BaseAgent
7
+ from {{ llm_import }}
8
+
9
+
10
+ class {{ class_name }}(BaseAgent):
11
+ """{{ class_name }} - Chat agent with conversation memory."""
12
+
13
+ def __init__(self, model: str = "{{ model_name }}", system_prompt: str = "You are a helpful AI assistant."):
14
+ super().__init__(name="{{ snake_name }}")
15
+ self.llm = {{ llm_class }}(model=model)
16
+ self.system_prompt = system_prompt
17
+ self.chat_history: list = []
18
+
19
+ self.prompt = ChatPromptTemplate.from_messages([
20
+ ("system", "{system_prompt}"),
21
+ MessagesPlaceholder(variable_name="chat_history"),
22
+ ("human", "{input}")
23
+ ])
24
+
25
+ self.chain = self.prompt | self.llm
26
+
27
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
28
+ """
29
+ Execute the chat agent logic with conversation memory.
30
+
31
+ Args:
32
+ input_data: Input data containing 'message' and optional 'clear_history'
33
+
34
+ Returns:
35
+ dict: Agent execution result with response and chat history
36
+ """
37
+ message = input_data.get("message", "")
38
+ clear_history = input_data.get("clear_history", False)
39
+
40
+ if clear_history:
41
+ self.chat_history = []
42
+
43
+ response = await self.chain.ainvoke({
44
+ "system_prompt": self.system_prompt,
45
+ "chat_history": self.chat_history,
46
+ "input": message
47
+ })
48
+
49
+ # Update chat history
50
+ self.chat_history.append(HumanMessage(content=message))
51
+ self.chat_history.append(AIMessage(content=response.content))
52
+
53
+ return {
54
+ "status": "success",
55
+ "response": response.content,
56
+ "model": self.llm.model_name,
57
+ "history_length": len(self.chat_history)
58
+ }
59
+
60
+ def clear_history(self):
61
+ """Clear the conversation history."""
62
+ self.chat_history = []
@@ -0,0 +1,82 @@
1
+ from typing import Any
2
+
3
+ from langchain.chains import LLMChain
4
+ from langchain_core.prompts import ChatPromptTemplate
5
+
6
+ from app.ai.agents.base_agent import BaseAgent
7
+ from {{ llm_import }}
8
+
9
+
10
+ class {{ class_name }}(BaseAgent):
11
+ """{{ class_name }} - Planner agent for complex task decomposition."""
12
+
13
+ def __init__(self, model: str = "{{ model_name }}"):
14
+ super().__init__(name="{{ snake_name }}")
15
+ self.llm = {{ llm_class }}(model=model, temperature=0.7)
16
+
17
+ # Planning prompt
18
+ self.planning_prompt = ChatPromptTemplate.from_messages([
19
+ ("system", """You are an expert task planner. Break down complex tasks into clear, actionable steps.
20
+ For each step, provide:
21
+ 1. Step number
22
+ 2. Action to take
23
+ 3. Expected outcome
24
+ 4. Dependencies (if any)
25
+
26
+ Format your response as a structured plan."""),
27
+ ("human", "Task: {task}\n\nCreate a detailed plan to accomplish this task.")
28
+ ])
29
+
30
+ # Execution prompt
31
+ self.execution_prompt = ChatPromptTemplate.from_messages([
32
+ ("system", "You are executing step {step_number} of a plan: {step_description}"),
33
+ ("human", "{input}")
34
+ ])
35
+
36
+ self.planning_chain = self.planning_prompt | self.llm
37
+ self.execution_chain = self.execution_prompt | self.llm
38
+
39
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
40
+ """
41
+ Execute the planner agent logic.
42
+
43
+ Args:
44
+ input_data: Input data containing 'task' and optional 'execute_plan'
45
+
46
+ Returns:
47
+ dict: Agent execution result with plan and optional execution results
48
+ """
49
+ task = input_data.get("task", "")
50
+ execute_plan = input_data.get("execute_plan", False)
51
+
52
+ # Generate plan
53
+ plan_response = await self.planning_chain.ainvoke({"task": task})
54
+ plan = plan_response.content
55
+
56
+ result = {
57
+ "status": "success",
58
+ "plan": plan,
59
+ "model": self.llm.model_name,
60
+ "task": task
61
+ }
62
+
63
+ # Optionally execute the plan
64
+ if execute_plan:
65
+ execution_results = []
66
+ # TODO: Parse plan and execute each step
67
+ # This is a placeholder for plan execution logic
68
+ result["execution_results"] = execution_results
69
+ result["executed"] = True
70
+ else:
71
+ result["executed"] = False
72
+
73
+ return result
74
+
75
+ async def execute_step(self, step_number: int, step_description: str, input_data: str) -> str:
76
+ """Execute a single step of the plan."""
77
+ response = await self.execution_chain.ainvoke({
78
+ "step_number": step_number,
79
+ "step_description": step_description,
80
+ "input": input_data
81
+ })
82
+ return response.content
@@ -0,0 +1,83 @@
1
+ from typing import Any
2
+
3
+ from langchain.agents import AgentExecutor, create_react_agent
4
+ from langchain_core.prompts import PromptTemplate
5
+
6
+ from app.ai.agents.base_agent import BaseAgent
7
+ from {{ llm_import }}
8
+
9
+
10
+ class {{ class_name }}(BaseAgent):
11
+ """{{ class_name }} - ReAct (Reasoning + Acting) agent implementation."""
12
+
13
+ def __init__(self, model: str = "{{ model_name }}", tools: list = None):
14
+ super().__init__(name="{{ snake_name }}")
15
+ self.llm = {{ llm_class }}(model=model)
16
+ self.tools = tools or []
17
+
18
+ # ReAct prompt template
19
+ self.prompt = PromptTemplate.from_template(
20
+ """Answer the following questions as best you can. You have access to the following tools:
21
+
22
+ {tools}
23
+
24
+ Use the following format:
25
+
26
+ Question: the input question you must answer
27
+ Thought: you should always think about what to do
28
+ Action: the action to take, should be one of [{tool_names}]
29
+ Action Input: the input to the action
30
+ Observation: the result of the action
31
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
32
+ Thought: I now know the final answer
33
+ Final Answer: the final answer to the original input question
34
+
35
+ Begin!
36
+
37
+ Question: {input}
38
+ Thought: {agent_scratchpad}"""
39
+ )
40
+
41
+ if self.tools:
42
+ agent = create_react_agent(self.llm, self.tools, self.prompt)
43
+ self.agent_executor = AgentExecutor(
44
+ agent=agent,
45
+ tools=self.tools,
46
+ verbose=True,
47
+ max_iterations=5,
48
+ handle_parsing_errors=True
49
+ )
50
+ else:
51
+ self.agent_executor = None
52
+
53
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
54
+ """
55
+ Execute the ReAct agent logic with reasoning and acting.
56
+
57
+ Args:
58
+ input_data: Input data containing 'input' and optional 'max_iterations'
59
+
60
+ Returns:
61
+ dict: Agent execution result with reasoning steps
62
+ """
63
+ user_input = input_data.get("input", "")
64
+ max_iterations = input_data.get("max_iterations", 5)
65
+
66
+ if not self.agent_executor:
67
+ return {
68
+ "status": "error",
69
+ "message": "No tools configured for this ReAct agent",
70
+ "model": self.llm.model_name
71
+ }
72
+
73
+ self.agent_executor.max_iterations = max_iterations
74
+
75
+ result = await self.agent_executor.ainvoke({"input": user_input})
76
+
77
+ return {
78
+ "status": "success",
79
+ "response": result.get("output", ""),
80
+ "model": self.llm.model_name,
81
+ "tools_available": len(self.tools),
82
+ "reasoning_steps": result.get("intermediate_steps", [])
83
+ }
@@ -0,0 +1,32 @@
1
+ from typing import Any
2
+
3
+ from app.ai.agents.base_agent import BaseAgent
4
+ from {{ llm_import }}
5
+
6
+
7
+ class {{ class_name }}(BaseAgent):
8
+ """{{ class_name }} - Simple agent implementation."""
9
+
10
+ def __init__(self, model: str = "{{ model_name }}"):
11
+ super().__init__(name="{{ snake_name }}")
12
+ self.llm = {{ llm_class }}(model=model)
13
+
14
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
15
+ """
16
+ Execute the agent logic.
17
+
18
+ Args:
19
+ input_data: Input data for the agent
20
+
21
+ Returns:
22
+ dict: Agent execution result
23
+ """
24
+ # TODO: Implement agent logic here
25
+ prompt = input_data.get("prompt", "")
26
+ response = await self.llm.ainvoke(prompt)
27
+
28
+ return {
29
+ "status": "success",
30
+ "response": response.content,
31
+ "model": self.llm.model_name
32
+ }
@@ -0,0 +1,59 @@
1
+ from typing import Any, AsyncIterator
2
+
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+
5
+ from app.ai.agents.base_agent import BaseAgent
6
+ from {{ llm_import }}
7
+
8
+
9
+ class {{ class_name }}(BaseAgent):
10
+ """{{ class_name }} - Streaming agent for real-time responses."""
11
+
12
+ def __init__(self, model: str = "{{ model_name }}"):
13
+ super().__init__(name="{{ snake_name }}")
14
+ self.llm = {{ llm_class }}(model=model, streaming=True)
15
+
16
+ self.prompt = ChatPromptTemplate.from_messages([
17
+ ("system", "You are a helpful AI assistant."),
18
+ ("human", "{input}")
19
+ ])
20
+
21
+ self.chain = self.prompt | self.llm
22
+
23
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
24
+ """
25
+ Execute the agent logic with streaming disabled (use stream() for streaming).
26
+
27
+ Args:
28
+ input_data: Input data for the agent
29
+
30
+ Returns:
31
+ dict: Agent execution result
32
+ """
33
+ user_input = input_data.get("input", "")
34
+
35
+ response = await self.chain.ainvoke({"input": user_input})
36
+
37
+ return {
38
+ "status": "success",
39
+ "response": response.content,
40
+ "model": self.llm.model_name
41
+ }
42
+
43
+ async def stream(self, input_data: dict[str, Any]) -> AsyncIterator[str]:
44
+ """
45
+ Stream the agent response in real-time.
46
+
47
+ Args:
48
+ input_data: Input data containing 'input'
49
+
50
+ Yields:
51
+ str: Chunks of the response as they are generated
52
+ """
53
+ user_input = input_data.get("input", "")
54
+
55
+ async for chunk in self.chain.astream({"input": user_input}):
56
+ if hasattr(chunk, 'content'):
57
+ yield chunk.content
58
+ else:
59
+ yield str(chunk)
@@ -0,0 +1,63 @@
1
+ from typing import Any
2
+
3
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
4
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
5
+
6
+ from app.ai.agents.base_agent import BaseAgent
7
+ from {{ llm_import }}
8
+
9
+
10
+ class {{ class_name }}(BaseAgent):
11
+ """{{ class_name }} - Tool-using agent implementation."""
12
+
13
+ def __init__(self, model: str = "{{ model_name }}", tools: list = None):
14
+ super().__init__(name="{{ snake_name }}")
15
+ self.llm = {{ llm_class }}(model=model)
16
+ self.tools = tools or []
17
+
18
+ self.prompt = ChatPromptTemplate.from_messages([
19
+ ("system", "You are a helpful AI assistant with access to tools. Use them when needed."),
20
+ ("human", "{input}"),
21
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
22
+ ])
23
+
24
+ if self.tools:
25
+ agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
26
+ self.agent_executor = AgentExecutor(agent=agent, tools=self.tools, verbose=True)
27
+ else:
28
+ self.agent_executor = None
29
+
30
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
31
+ """
32
+ Execute the tool agent logic.
33
+
34
+ Args:
35
+ input_data: Input data containing 'input' and optional 'tools'
36
+
37
+ Returns:
38
+ dict: Agent execution result with tool usage information
39
+ """
40
+ user_input = input_data.get("input", "")
41
+
42
+ if not self.agent_executor:
43
+ return {
44
+ "status": "error",
45
+ "message": "No tools configured for this agent",
46
+ "model": self.llm.model_name
47
+ }
48
+
49
+ result = await self.agent_executor.ainvoke({"input": user_input})
50
+
51
+ return {
52
+ "status": "success",
53
+ "response": result.get("output", ""),
54
+ "model": self.llm.model_name,
55
+ "tools_used": len(self.tools)
56
+ }
57
+
58
+ def add_tool(self, tool):
59
+ """Add a tool to the agent."""
60
+ self.tools.append(tool)
61
+ # Recreate agent executor with new tools
62
+ agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
63
+ self.agent_executor = AgentExecutor(agent=agent, tools=self.tools, verbose=True)
@@ -0,0 +1,23 @@
1
+ from typing import Any
2
+
3
+ from app.ai.chains.base_chain import BaseChain
4
+
5
+
6
+ class {{ class_name }}(BaseChain):
7
+ """{{ class_name }} chain implementation."""
8
+
9
+ def __init__(self):
10
+ super().__init__(name="{{ snake_name }}")
11
+
12
+ async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
13
+ """
14
+ Execute the chain logic.
15
+
16
+ Args:
17
+ input_data: Input data for the chain
18
+
19
+ Returns:
20
+ dict: Chain execution result
21
+ """
22
+ # TODO: Implement chain logic here
23
+ return {"status": "success", "message": "{{ class_name }} executed"}
@@ -0,0 +1,30 @@
1
+ from typing import Any
2
+
3
+
4
+ class {{ class_name }}:
5
+ """{{ class_name }} controller for business logic."""
6
+
7
+ async def index(self) -> dict[str, Any]:
8
+ """List all items."""
9
+ # TODO: Implement list logic
10
+ return {"data": [], "total": 0}
11
+
12
+ async def show(self, id: str) -> dict[str, Any]:
13
+ """Get a specific item by ID."""
14
+ # TODO: Implement get logic
15
+ return {"id": id, "data": {}}
16
+
17
+ async def create(self, data: dict[str, Any]) -> dict[str, Any]:
18
+ """Create a new item."""
19
+ # TODO: Implement create logic
20
+ return {"id": "new_id", "data": data}
21
+
22
+ async def update(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
23
+ """Update an item by ID."""
24
+ # TODO: Implement update logic
25
+ return {"id": id, "data": data}
26
+
27
+ async def delete(self, id: str) -> dict[str, Any]:
28
+ """Delete an item by ID."""
29
+ # TODO: Implement delete logic
30
+ return {"id": id, "deleted": True}
@@ -0,0 +1,18 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+
5
+ class {{ class_name }}:
6
+ """{{ class_name }} event."""
7
+
8
+ def __init__(self, data: dict[str, Any]):
9
+ self.data = data
10
+ self.timestamp = datetime.utcnow()
11
+
12
+ def to_dict(self) -> dict[str, Any]:
13
+ """Convert event to dictionary."""
14
+ return {
15
+ "event": "{{ snake_name }}",
16
+ "data": self.data,
17
+ "timestamp": self.timestamp.isoformat(),
18
+ }
@@ -0,0 +1,21 @@
1
+ from typing import Any, Optional
2
+
3
+ from app.exceptions.base import ElricException
4
+
5
+
6
+ class {{ class_name }}(ElricException):
7
+ """{{ class_name }} exception."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str = "{{ class_name }} error occurred",
12
+ error_code: str = "{{ snake_name }}_error",
13
+ status_code: int = 500,
14
+ details: Optional[dict[str, Any]] = None,
15
+ ):
16
+ super().__init__(
17
+ message=message,
18
+ status_code=status_code,
19
+ error_code=error_code,
20
+ details=details,
21
+ )
@@ -0,0 +1,24 @@
1
+ import structlog
2
+
3
+ logger = structlog.get_logger()
4
+
5
+
6
+ class {{ class_name }}:
7
+ """{{ class_name }} background job."""
8
+
9
+ async def execute(self, *args, **kwargs):
10
+ """
11
+ Execute the job logic.
12
+
13
+ Args:
14
+ *args: Positional arguments
15
+ **kwargs: Keyword arguments
16
+ """
17
+ logger.info("job.started", job="{{ snake_name }}")
18
+
19
+ try:
20
+ # TODO: Implement job logic here
21
+ logger.info("job.completed", job="{{ snake_name }}")
22
+ except Exception as e:
23
+ logger.error("job.failed", job="{{ snake_name }}", error=str(e))
24
+ raise
@@ -0,0 +1,23 @@
1
+ import structlog
2
+
3
+ logger = structlog.get_logger()
4
+
5
+
6
+ class {{ class_name }}:
7
+ """{{ class_name }} event listener."""
8
+
9
+ async def handle(self, event):
10
+ """
11
+ Handle the event.
12
+
13
+ Args:
14
+ event: The event to handle
15
+ """
16
+ logger.info("listener.handling", listener="{{ snake_name }}", event=event.to_dict())
17
+
18
+ try:
19
+ # TODO: Implement event handling logic here
20
+ logger.info("listener.handled", listener="{{ snake_name }}")
21
+ except Exception as e:
22
+ logger.error("listener.failed", listener="{{ snake_name }}", error=str(e))
23
+ raise
@@ -0,0 +1,32 @@
1
+ import structlog
2
+ from starlette.middleware.base import BaseHTTPMiddleware
3
+ from starlette.requests import Request
4
+
5
+ logger = structlog.get_logger()
6
+
7
+
8
+ class {{ class_name }}(BaseHTTPMiddleware):
9
+ """{{ class_name }} middleware."""
10
+
11
+ async def dispatch(self, request: Request, call_next):
12
+ """
13
+ Process the request.
14
+
15
+ Args:
16
+ request: The incoming request
17
+ call_next: The next middleware/route handler
18
+
19
+ Returns:
20
+ Response from the next handler
21
+ """
22
+ logger.debug("middleware.before", middleware="{{ snake_name }}", path=request.url.path)
23
+
24
+ # TODO: Add pre-processing logic here
25
+
26
+ response = await call_next(request)
27
+
28
+ # TODO: Add post-processing logic here
29
+
30
+ logger.debug("middleware.after", middleware="{{ snake_name }}", status_code=response.status_code)
31
+
32
+ return response