forge-core-di 0.2.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.
- forge_core_di-0.2.0/.gitignore +9 -0
- forge_core_di-0.2.0/CHANGELOG.md +47 -0
- forge_core_di-0.2.0/CONTRIBUTING.md +21 -0
- forge_core_di-0.2.0/LICENSE +9 -0
- forge_core_di-0.2.0/PKG-INFO +58 -0
- forge_core_di-0.2.0/README.md +38 -0
- forge_core_di-0.2.0/daftar_test.txt +22 -0
- forge_core_di-0.2.0/examples/automation.py +75 -0
- forge_core_di-0.2.0/examples/event_driven.py +54 -0
- forge_core_di-0.2.0/examples/trading_bot.py +92 -0
- forge_core_di-0.2.0/grep_circular.txt +0 -0
- forge_core_di-0.2.0/grep_scope.txt +63 -0
- forge_core_di-0.2.0/pyproject.toml +54 -0
- forge_core_di-0.2.0/semua_kode.txt +1577 -0
- forge_core_di-0.2.0/src/forgecore/__init__.py +88 -0
- forge_core_di-0.2.0/src/forgecore/application/__init__.py +7 -0
- forge_core_di-0.2.0/src/forgecore/application/app.py +94 -0
- forge_core_di-0.2.0/src/forgecore/application/metadata.py +11 -0
- forge_core_di-0.2.0/src/forgecore/config/__init__.py +5 -0
- forge_core_di-0.2.0/src/forgecore/config/config.py +23 -0
- forge_core_di-0.2.0/src/forgecore/context/__init__.py +5 -0
- forge_core_di-0.2.0/src/forgecore/context/context.py +23 -0
- forge_core_di-0.2.0/src/forgecore/core/__init__.py +31 -0
- forge_core_di-0.2.0/src/forgecore/core/constants.py +7 -0
- forge_core_di-0.2.0/src/forgecore/core/exceptions.py +18 -0
- forge_core_di-0.2.0/src/forgecore/core/types.py +4 -0
- forge_core_di-0.2.0/src/forgecore/core/version.py +3 -0
- forge_core_di-0.2.0/src/forgecore/lifecycle/__init__.py +7 -0
- forge_core_di-0.2.0/src/forgecore/lifecycle/manager.py +40 -0
- forge_core_di-0.2.0/src/forgecore/lifecycle/state.py +11 -0
- forge_core_di-0.2.0/src/forgecore/logging/__init__.py +5 -0
- forge_core_di-0.2.0/src/forgecore/logging/logger.py +23 -0
- forge_core_di-0.2.0/src/forgecore/plugins/__init__.py +7 -0
- forge_core_di-0.2.0/src/forgecore/plugins/manager.py +31 -0
- forge_core_di-0.2.0/src/forgecore/plugins/plugin.py +21 -0
- forge_core_di-0.2.0/src/forgecore/providers/__init__.py +5 -0
- forge_core_di-0.2.0/src/forgecore/providers/provider.py +14 -0
- forge_core_di-0.2.0/src/forgecore/registry/__init__.py +7 -0
- forge_core_di-0.2.0/src/forgecore/registry/exceptions.py +2 -0
- forge_core_di-0.2.0/src/forgecore/registry/registry.py +31 -0
- forge_core_di-0.2.0/src/forgecore/runtime/__init__.py +9 -0
- forge_core_di-0.2.0/src/forgecore/runtime/bus.py +50 -0
- forge_core_di-0.2.0/src/forgecore/runtime/event.py +10 -0
- forge_core_di-0.2.0/src/forgecore/runtime/exceptions.py +2 -0
- forge_core_di-0.2.0/src/forgecore/services/__init__.py +20 -0
- forge_core_di-0.2.0/src/forgecore/services/autowire.py +27 -0
- forge_core_di-0.2.0/src/forgecore/services/container.py +103 -0
- forge_core_di-0.2.0/src/forgecore/services/descriptor.py +16 -0
- forge_core_di-0.2.0/src/forgecore/services/exceptions.py +6 -0
- forge_core_di-0.2.0/src/forgecore/services/factory.py +22 -0
- forge_core_di-0.2.0/src/forgecore/services/resolver.py +165 -0
- forge_core_di-0.2.0/src/forgecore/services/scope.py +7 -0
- forge_core_di-0.2.0/src/forgecore/services/scope_context.py +31 -0
- forge_core_di-0.2.0/src/forgecore/utils/__init__.py +9 -0
- forge_core_di-0.2.0/src/forgecore/utils/inspect.py +7 -0
- forge_core_di-0.2.0/src/forgecore/utils/system.py +15 -0
- forge_core_di-0.2.0/src/forgecore/validation/__init__.py +15 -0
- forge_core_di-0.2.0/src/forgecore/validation/validators.py +21 -0
- forge_core_di-0.2.0/src/forgecore/version.py +1 -0
- forge_core_di-0.2.0/struktur_proyek.txt +66 -0
- forge_core_di-0.2.0/tests/test_app_events.py +37 -0
- forge_core_di-0.2.0/tests/test_application.py +87 -0
- forge_core_di-0.2.0/tests/test_autowire.py +63 -0
- forge_core_di-0.2.0/tests/test_binding.py +17 -0
- forge_core_di-0.2.0/tests/test_binding_resolver.py +19 -0
- forge_core_di-0.2.0/tests/test_circular.py +50 -0
- forge_core_di-0.2.0/tests/test_config.py +12 -0
- forge_core_di-0.2.0/tests/test_context.py +12 -0
- forge_core_di-0.2.0/tests/test_descriptor.py +27 -0
- forge_core_di-0.2.0/tests/test_factory.py +56 -0
- forge_core_di-0.2.0/tests/test_lifecycle.py +61 -0
- forge_core_di-0.2.0/tests/test_metadata.py +36 -0
- forge_core_di-0.2.0/tests/test_optional_injection.py +59 -0
- forge_core_di-0.2.0/tests/test_plugin.py +27 -0
- forge_core_di-0.2.0/tests/test_plugin_manager.py +41 -0
- forge_core_di-0.2.0/tests/test_provider.py +58 -0
- forge_core_di-0.2.0/tests/test_registry.py +20 -0
- forge_core_di-0.2.0/tests/test_resolver.py +37 -0
- forge_core_di-0.2.0/tests/test_runtime.py +86 -0
- forge_core_di-0.2.0/tests/test_scope.py +9 -0
- forge_core_di-0.2.0/tests/test_scoped.py +97 -0
- forge_core_di-0.2.0/tests/test_service_descriptor_container.py +32 -0
- forge_core_di-0.2.0/tests/test_services.py +23 -0
- forge_core_di-0.2.0/tests/test_singleton.py +24 -0
- forge_core_di-0.2.0/tests/test_validation.py +28 -0
- forge_core_di-0.2.0/tests/test_version.py +13 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to ForgeCore will be documented here.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-06-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Scoped lifecycle (`ServiceScope.SCOPED`) — one instance per scope context
|
|
11
|
+
- Thread-safe scope via `threading.local()` — safe for concurrent applications
|
|
12
|
+
- Circular dependency detection with clear error messages (`CircularDependencyError`)
|
|
13
|
+
- Optional injection support (`Cache | None = None`)
|
|
14
|
+
- `ServiceProvider` system connected to `ForgeApp` boot pipeline
|
|
15
|
+
- `LifecycleManager` hooks — `on_start` and `on_stop` (LIFO order)
|
|
16
|
+
- `EventBus` connected to `ForgeApp` lifecycle — automatic lifecycle events
|
|
17
|
+
- `EventBus.unsubscribe()`, `has_listeners()`, and `clear()`
|
|
18
|
+
- `ForgeApp` boot guards — prevents double boot, double run, and stop without run
|
|
19
|
+
- Unified resolution pipeline — `get()` and `resolve()` now use the same path
|
|
20
|
+
- Public API — all core classes importable directly from `forgecore`
|
|
21
|
+
- Examples — `trading_bot.py`, `automation.py`, `event_driven.py`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- `ServiceContainer` now enforces explicit registration — no implicit auto-registration
|
|
25
|
+
- `ForgeApp.run()` now calls `boot()` automatically if not already booted
|
|
26
|
+
- `Development Status` classifier updated from Pre-Alpha to Alpha
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- `container.get()` now resolves bindings correctly (previously bypassed binding system)
|
|
30
|
+
- Duplicate `ServiceNotFoundError` definition in `exceptions.py`
|
|
31
|
+
- Forward reference string annotations now resolved correctly in autowiring
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## [0.1.0] - 2025-01-01
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- Initial release
|
|
39
|
+
- DI container with singleton and transient lifecycle
|
|
40
|
+
- Constructor autowiring via type hints
|
|
41
|
+
- Service binding — abstraction to implementation mapping
|
|
42
|
+
- Custom factory support
|
|
43
|
+
- `ForgeApp` application kernel
|
|
44
|
+
- `EventBus` runtime event system
|
|
45
|
+
- `LifecycleManager` state machine
|
|
46
|
+
- `Registry` key-value store
|
|
47
|
+
- `PluginManager` runtime extension system
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Contributing to ForgeCore
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to ForgeCore.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Python 3.12+
|
|
10
|
+
- Git
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/akmalmaulana/forgecore
|
|
18
|
+
cd forgecore
|
|
19
|
+
python -m venv .venv
|
|
20
|
+
source .venv/bin/activate
|
|
21
|
+
pip install -e ".[dev]"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akmal Maulana
|
|
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.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: forge-core-di
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Lightweight Dependency Injection and Application Runtime Core for Python.
|
|
5
|
+
Project-URL: Homepage, https://github.com/akmallmline/forgecore
|
|
6
|
+
Project-URL: Repository, https://github.com/akmallmline/forgecore
|
|
7
|
+
Author-email: Akmal Maulana <akmallmline@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: application-kernel,autowiring,dependency-injection,di-container,framework,lifecycle,plugin,python
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ForgeCore
|
|
22
|
+
|
|
23
|
+
Lightweight Dependency Injection and Application Runtime Core for Python.
|
|
24
|
+
|
|
25
|
+
ForgeCore is a framework engine — not just a DI container. It provides the
|
|
26
|
+
foundation for building web frameworks, CLI tools, bots, trading systems,
|
|
27
|
+
and AI agents in Python.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Explicit DI container — no magic auto-registration
|
|
34
|
+
- Constructor autowiring via type hints
|
|
35
|
+
- Singleton, Transient, and Scoped lifecycles
|
|
36
|
+
- Thread-safe scoped instances via `threading.local()`
|
|
37
|
+
- Circular dependency detection with clear error messages
|
|
38
|
+
- Optional injection (`Cache | None = None`)
|
|
39
|
+
- Service binding — abstraction to implementation mapping
|
|
40
|
+
- Custom factory support
|
|
41
|
+
- Application kernel with boot pipeline
|
|
42
|
+
- Service provider system for modular registration
|
|
43
|
+
- Lifecycle hooks (`on_start`, `on_stop`)
|
|
44
|
+
- Event system with subscribe, emit, and unsubscribe
|
|
45
|
+
- Plugin system for runtime extension
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Requirements
|
|
50
|
+
|
|
51
|
+
- Python 3.12+
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install forgecore
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ForgeCore
|
|
2
|
+
|
|
3
|
+
Lightweight Dependency Injection and Application Runtime Core for Python.
|
|
4
|
+
|
|
5
|
+
ForgeCore is a framework engine — not just a DI container. It provides the
|
|
6
|
+
foundation for building web frameworks, CLI tools, bots, trading systems,
|
|
7
|
+
and AI agents in Python.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Explicit DI container — no magic auto-registration
|
|
14
|
+
- Constructor autowiring via type hints
|
|
15
|
+
- Singleton, Transient, and Scoped lifecycles
|
|
16
|
+
- Thread-safe scoped instances via `threading.local()`
|
|
17
|
+
- Circular dependency detection with clear error messages
|
|
18
|
+
- Optional injection (`Cache | None = None`)
|
|
19
|
+
- Service binding — abstraction to implementation mapping
|
|
20
|
+
- Custom factory support
|
|
21
|
+
- Application kernel with boot pipeline
|
|
22
|
+
- Service provider system for modular registration
|
|
23
|
+
- Lifecycle hooks (`on_start`, `on_stop`)
|
|
24
|
+
- Event system with subscribe, emit, and unsubscribe
|
|
25
|
+
- Plugin system for runtime extension
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python 3.12+
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install forgecore
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
./tests/test_application.py
|
|
2
|
+
./tests/test_autowire.py
|
|
3
|
+
./tests/test_binding.py
|
|
4
|
+
./tests/test_binding_resolver.py
|
|
5
|
+
./tests/test_config.py
|
|
6
|
+
./tests/test_context.py
|
|
7
|
+
./tests/test_descriptor.py
|
|
8
|
+
./tests/test_factory.py
|
|
9
|
+
./tests/test_lifecycle.py
|
|
10
|
+
./tests/test_metadata.py
|
|
11
|
+
./tests/test_plugin.py
|
|
12
|
+
./tests/test_plugin_manager.py
|
|
13
|
+
./tests/test_provider.py
|
|
14
|
+
./tests/test_registry.py
|
|
15
|
+
./tests/test_resolver.py
|
|
16
|
+
./tests/test_runtime.py
|
|
17
|
+
./tests/test_scope.py
|
|
18
|
+
./tests/test_service_descriptor_container.py
|
|
19
|
+
./tests/test_services.py
|
|
20
|
+
./tests/test_singleton.py
|
|
21
|
+
./tests/test_validation.py
|
|
22
|
+
./tests/test_version.py
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from forgecore import (
|
|
2
|
+
ApplicationMetadata,
|
|
3
|
+
ForgeApp,
|
|
4
|
+
ServiceContainer,
|
|
5
|
+
ServiceProvider,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config:
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self.base_url = "https://api.example.com"
|
|
12
|
+
self.hub_id = "prajekan"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SessionManager:
|
|
16
|
+
def __init__(self, config: Config) -> None:
|
|
17
|
+
self.config = config
|
|
18
|
+
self.token = ""
|
|
19
|
+
|
|
20
|
+
def login(self) -> None:
|
|
21
|
+
print(f"Login ke {self.config.base_url}...")
|
|
22
|
+
self.token = "secret-token-123"
|
|
23
|
+
print("Login berhasil.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataFetcher:
|
|
27
|
+
def __init__(self, config: Config, session: SessionManager) -> None:
|
|
28
|
+
self.config = config
|
|
29
|
+
self.session = session
|
|
30
|
+
|
|
31
|
+
def fetch(self, item_id: str) -> dict:
|
|
32
|
+
return {
|
|
33
|
+
"id": item_id,
|
|
34
|
+
"hub": self.config.hub_id,
|
|
35
|
+
"status": "FOUND",
|
|
36
|
+
"token_used": self.session.token,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReportGenerator:
|
|
41
|
+
def __init__(self, fetcher: DataFetcher) -> None:
|
|
42
|
+
self.fetcher = fetcher
|
|
43
|
+
|
|
44
|
+
def run(self, ids: list[str]) -> None:
|
|
45
|
+
print("\n=== REPORT ===")
|
|
46
|
+
for item_id in ids:
|
|
47
|
+
result = self.fetcher.fetch(item_id)
|
|
48
|
+
print(f"{result['id']} → {result['status']}")
|
|
49
|
+
print("==============")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AppProvider(ServiceProvider):
|
|
53
|
+
def register(self, container: ServiceContainer) -> None:
|
|
54
|
+
container.singleton(Config)
|
|
55
|
+
container.singleton(SessionManager)
|
|
56
|
+
container.singleton(DataFetcher)
|
|
57
|
+
container.singleton(ReportGenerator)
|
|
58
|
+
|
|
59
|
+
def boot(self, container: ServiceContainer) -> None:
|
|
60
|
+
session = container.get(SessionManager)
|
|
61
|
+
session.login()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
app = ForgeApp(
|
|
65
|
+
metadata=ApplicationMetadata(
|
|
66
|
+
name="Automation Example",
|
|
67
|
+
version="1.0.0",
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
app.register_provider(AppProvider())
|
|
72
|
+
app.boot()
|
|
73
|
+
|
|
74
|
+
report = app.services.get(ReportGenerator)
|
|
75
|
+
report.run(["ID-001", "ID-002", "ID-003"])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from forgecore import (
|
|
2
|
+
ApplicationMetadata,
|
|
3
|
+
Event,
|
|
4
|
+
EventBus,
|
|
5
|
+
ForgeApp,
|
|
6
|
+
ServiceContainer,
|
|
7
|
+
ServiceProvider,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OrderService:
|
|
12
|
+
def __init__(self, bus: EventBus) -> None:
|
|
13
|
+
self.bus = bus
|
|
14
|
+
|
|
15
|
+
def place_order(self, item: str, qty: int) -> None:
|
|
16
|
+
print(f"Order ditempatkan: {qty}x {item}")
|
|
17
|
+
self.bus.emit(Event(
|
|
18
|
+
name="order.placed",
|
|
19
|
+
payload={"item": item, "qty": qty},
|
|
20
|
+
))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NotificationService:
|
|
24
|
+
def on_order_placed(self, event: Event) -> None:
|
|
25
|
+
item = event.payload["item"]
|
|
26
|
+
qty = event.payload["qty"]
|
|
27
|
+
print(f"[Notifikasi] Order baru: {qty}x {item}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AppProvider(ServiceProvider):
|
|
31
|
+
def register(self, container: ServiceContainer) -> None:
|
|
32
|
+
container.singleton(EventBus)
|
|
33
|
+
container.singleton(OrderService)
|
|
34
|
+
container.singleton(NotificationService)
|
|
35
|
+
|
|
36
|
+
def boot(self, container: ServiceContainer) -> None:
|
|
37
|
+
bus = container.get(EventBus)
|
|
38
|
+
notif = container.get(NotificationService)
|
|
39
|
+
bus.subscribe("order.placed", notif.on_order_placed)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
app = ForgeApp(
|
|
43
|
+
metadata=ApplicationMetadata(
|
|
44
|
+
name="Event Driven Example",
|
|
45
|
+
version="1.0.0",
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
app.register_provider(AppProvider())
|
|
50
|
+
app.boot()
|
|
51
|
+
|
|
52
|
+
order_service = app.services.get(OrderService)
|
|
53
|
+
order_service.place_order("XAUUSD Contract", 2)
|
|
54
|
+
order_service.place_order("Bitcoin Futures", 1)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from forgecore import (
|
|
2
|
+
ApplicationMetadata,
|
|
3
|
+
Event,
|
|
4
|
+
ForgeApp,
|
|
5
|
+
ServiceContainer,
|
|
6
|
+
ServiceProvider,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Config:
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self.symbol = "XAUUSD"
|
|
13
|
+
self.timeframe = "M5"
|
|
14
|
+
self.risk_percent = 1.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Logger:
|
|
18
|
+
def __init__(self, config: Config) -> None:
|
|
19
|
+
self.config = config
|
|
20
|
+
|
|
21
|
+
def log(self, msg: str) -> None:
|
|
22
|
+
print(f"[{self.config.symbol}] {msg}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MarketData:
|
|
26
|
+
def __init__(self, config: Config, logger: Logger) -> None:
|
|
27
|
+
self.config = config
|
|
28
|
+
self.logger = logger
|
|
29
|
+
|
|
30
|
+
def fetch(self) -> dict:
|
|
31
|
+
self.logger.log("Fetching market data...")
|
|
32
|
+
return {"price": 2345.50, "trend": "bullish"}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SignalEngine:
|
|
36
|
+
def __init__(self, data: MarketData, logger: Logger) -> None:
|
|
37
|
+
self.data = data
|
|
38
|
+
self.logger = logger
|
|
39
|
+
|
|
40
|
+
def analyze(self) -> str | None:
|
|
41
|
+
market = self.data.fetch()
|
|
42
|
+
if market["trend"] == "bullish":
|
|
43
|
+
self.logger.log(f"Signal: BUY at {market['price']}")
|
|
44
|
+
return "BUY"
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TelegramNotifier:
|
|
49
|
+
def __init__(self, config: Config) -> None:
|
|
50
|
+
self.config = config
|
|
51
|
+
|
|
52
|
+
def send(self, message: str) -> None:
|
|
53
|
+
print(f"[Telegram] {message}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BotProvider(ServiceProvider):
|
|
57
|
+
def register(self, container: ServiceContainer) -> None:
|
|
58
|
+
container.singleton(Config)
|
|
59
|
+
container.singleton(Logger)
|
|
60
|
+
container.singleton(MarketData)
|
|
61
|
+
container.singleton(SignalEngine)
|
|
62
|
+
container.singleton(TelegramNotifier)
|
|
63
|
+
|
|
64
|
+
def boot(self, container: ServiceContainer) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
app = ForgeApp(
|
|
69
|
+
metadata=ApplicationMetadata(
|
|
70
|
+
name="XAUUSD Signal Bot",
|
|
71
|
+
version="1.0.0",
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
app.register_provider(BotProvider())
|
|
76
|
+
|
|
77
|
+
app.events.subscribe("app.running", lambda e: print("Bot aktif..."))
|
|
78
|
+
app.events.subscribe("app.stopped", lambda e: print("Bot dimatikan."))
|
|
79
|
+
|
|
80
|
+
app.lifecycle.on_start(lambda: print("Koneksi broker dibuka"))
|
|
81
|
+
app.lifecycle.on_stop(lambda: print("Koneksi broker ditutup"))
|
|
82
|
+
|
|
83
|
+
app.run()
|
|
84
|
+
|
|
85
|
+
engine = app.services.get(SignalEngine)
|
|
86
|
+
notifier = app.services.get(TelegramNotifier)
|
|
87
|
+
|
|
88
|
+
signal = engine.analyze()
|
|
89
|
+
if signal:
|
|
90
|
+
notifier.send(f"Signal: {signal} XAUUSD @ 2345.50")
|
|
91
|
+
|
|
92
|
+
app.stop()
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
./src/forgecore/services/__init__.py:7:from .scope import ServiceScope
|
|
2
|
+
./src/forgecore/services/__init__.py:12: "ServiceScope",
|
|
3
|
+
./src/forgecore/services/container.py:9:from .scope import ServiceScope
|
|
4
|
+
./src/forgecore/services/container.py:22: scope: ServiceScope = ServiceScope.SINGLETON,
|
|
5
|
+
./src/forgecore/services/container.py:29: scope=scope,
|
|
6
|
+
./src/forgecore/services/container.py:48: scope=ServiceScope.SINGLETON,
|
|
7
|
+
./src/forgecore/services/container.py:64: scope=ServiceScope.TRANSIENT,
|
|
8
|
+
./src/forgecore/services/container.py:80: if descriptor.scope is ServiceScope.SINGLETON:
|
|
9
|
+
./src/forgecore/services/scope.py:4:class ServiceScope(str, Enum):
|
|
10
|
+
./src/forgecore/services/descriptor.py:7:from .scope import ServiceScope
|
|
11
|
+
./src/forgecore/services/descriptor.py:14: scope: ServiceScope = ServiceScope.SINGLETON
|
|
12
|
+
./src/forgecore/services/resolver.py:7:from .scope import ServiceScope
|
|
13
|
+
./src/forgecore/services/resolver.py:33: and descriptor.scope is ServiceScope.SINGLETON
|
|
14
|
+
./src/forgecore/services/resolver.py:72: scope=ServiceScope.SINGLETON,
|
|
15
|
+
./src/forgecore/services/resolver.py:75: elif descriptor.scope is ServiceScope.SINGLETON:
|
|
16
|
+
./src/forgecore/lifecycle/__init__.py:1:from .manager import LifecycleManager
|
|
17
|
+
./src/forgecore/lifecycle/__init__.py:2:from .state import LifecycleState
|
|
18
|
+
./src/forgecore/lifecycle/__init__.py:5: "LifecycleManager",
|
|
19
|
+
./src/forgecore/lifecycle/__init__.py:6: "LifecycleState",
|
|
20
|
+
./src/forgecore/lifecycle/manager.py:3:from .state import LifecycleState
|
|
21
|
+
./src/forgecore/lifecycle/manager.py:6:class LifecycleManager:
|
|
22
|
+
./src/forgecore/lifecycle/manager.py:8: self._state = LifecycleState.INITIALIZED
|
|
23
|
+
./src/forgecore/lifecycle/manager.py:11: def state(self) -> LifecycleState:
|
|
24
|
+
./src/forgecore/lifecycle/manager.py:15: self._state = LifecycleState.STARTING
|
|
25
|
+
./src/forgecore/lifecycle/manager.py:18: self._state = LifecycleState.RUNNING
|
|
26
|
+
./src/forgecore/lifecycle/manager.py:21: self._state = LifecycleState.STOPPING
|
|
27
|
+
./src/forgecore/lifecycle/manager.py:24: self._state = LifecycleState.STOPPED
|
|
28
|
+
./src/forgecore/lifecycle/state.py:6:class LifecycleState(str, Enum):
|
|
29
|
+
./src/forgecore/application/app.py:38: def lifecycle(self):
|
|
30
|
+
./src/forgecore/application/app.py:39: return self.context.lifecycle
|
|
31
|
+
./src/forgecore/application/app.py:42: self.lifecycle.start()
|
|
32
|
+
./src/forgecore/application/app.py:43: self.lifecycle.run()
|
|
33
|
+
./src/forgecore/application/app.py:46: self.lifecycle.stop()
|
|
34
|
+
./src/forgecore/application/app.py:47: self.lifecycle.shutdown()
|
|
35
|
+
./src/forgecore/context/context.py:4:from forgecore.lifecycle import LifecycleManager
|
|
36
|
+
./src/forgecore/context/context.py:23: self.lifecycle = LifecycleManager()
|
|
37
|
+
./tests/test_lifecycle.py:1:from forgecore.lifecycle import LifecycleManager, LifecycleState
|
|
38
|
+
./tests/test_lifecycle.py:4:def test_lifecycle():
|
|
39
|
+
./tests/test_lifecycle.py:5: manager = LifecycleManager()
|
|
40
|
+
./tests/test_lifecycle.py:7: assert manager.state is LifecycleState.INITIALIZED
|
|
41
|
+
./tests/test_lifecycle.py:10: assert manager.state is LifecycleState.STARTING
|
|
42
|
+
./tests/test_lifecycle.py:13: assert manager.state is LifecycleState.RUNNING
|
|
43
|
+
./tests/test_lifecycle.py:16: assert manager.state is LifecycleState.STOPPING
|
|
44
|
+
./tests/test_lifecycle.py:19: assert manager.state is LifecycleState.STOPPED
|
|
45
|
+
./tests/test_application.py:2:from forgecore.lifecycle import LifecycleState
|
|
46
|
+
./tests/test_application.py:8: assert app.lifecycle.state is LifecycleState.INITIALIZED
|
|
47
|
+
./tests/test_application.py:11: assert app.lifecycle.state is LifecycleState.RUNNING
|
|
48
|
+
./tests/test_application.py:14: assert app.lifecycle.state is LifecycleState.STOPPED
|
|
49
|
+
./tests/test_context.py:12: assert app.lifecycle is app.context.lifecycle
|
|
50
|
+
./tests/test_scope.py:1:from forgecore.services.scope import ServiceScope
|
|
51
|
+
./tests/test_scope.py:4:def test_singleton_scope():
|
|
52
|
+
./tests/test_scope.py:5: assert ServiceScope.SINGLETON.value == "singleton"
|
|
53
|
+
./tests/test_scope.py:8:def test_transient_scope():
|
|
54
|
+
./tests/test_scope.py:9: assert ServiceScope.TRANSIENT.value == "transient"
|
|
55
|
+
./tests/test_descriptor.py:1:from forgecore.services import ServiceDescriptor, ServiceScope
|
|
56
|
+
./tests/test_descriptor.py:16: assert descriptor.scope is ServiceScope.SINGLETON
|
|
57
|
+
./tests/test_descriptor.py:20:def test_descriptor_custom_scope():
|
|
58
|
+
./tests/test_descriptor.py:24: scope=ServiceScope.TRANSIENT,
|
|
59
|
+
./tests/test_descriptor.py:27: assert descriptor.scope is ServiceScope.TRANSIENT
|
|
60
|
+
./tests/test_service_descriptor_container.py:1:from forgecore.services import ServiceContainer, ServiceScope
|
|
61
|
+
./tests/test_service_descriptor_container.py:8:def test_register_scope():
|
|
62
|
+
./tests/test_service_descriptor_container.py:13: scope=ServiceScope.TRANSIENT,
|
|
63
|
+
./tests/test_service_descriptor_container.py:18: assert descriptor.scope is ServiceScope.TRANSIENT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "forge-core-di"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Lightweight Dependency Injection and Application Runtime Core for Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Akmal Maulana", email = "akmallmline@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"dependency-injection",
|
|
17
|
+
"di-container",
|
|
18
|
+
"framework",
|
|
19
|
+
"application-kernel",
|
|
20
|
+
"autowiring",
|
|
21
|
+
"lifecycle",
|
|
22
|
+
"plugin",
|
|
23
|
+
"python",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/akmallmline/forgecore"
|
|
37
|
+
Repository = "https://github.com/akmallmline/forgecore"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/forgecore"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 88
|
|
47
|
+
target-version = "py312"
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = [
|
|
51
|
+
"E",
|
|
52
|
+
"F",
|
|
53
|
+
"I",
|
|
54
|
+
]
|