
========== ./src/forgecore/__init__.py ==========

from .application import ApplicationMetadata, ForgeApp
from .config import Config
from .core import (
    DEFAULT_ENCODING,
    DEFAULT_TIMEOUT,
    JSON,
    MAX_RETRIES,
    PACKAGE_NAME,
    VERSION,
    ConfigurationError,
    ForgeCoreError,
    InitializationError,
    JSONList,
    UnsupportedPlatformError,
    ValidationError,
    __version__,
)
from .logging import get_logger
from .plugins import Plugin, PluginManager
from .utils import (
    machine,
    operating_system,
    package_version,
    python_version,
)
from .validation import (
    is_boolean,
    is_float,
    is_integer,
    is_non_empty_string,
    is_string,
)

__all__ = [
    "__version__",
    "VERSION",
    "PACKAGE_NAME",
    "DEFAULT_ENCODING",
    "DEFAULT_TIMEOUT",
    "MAX_RETRIES",
    "ForgeCoreError",
    "ConfigurationError",
    "ValidationError",
    "InitializationError",
    "UnsupportedPlatformError",
    "JSON",
    "JSONList",
    "Config",
    "get_logger",
    "package_version",
    "python_version",
    "operating_system",
    "machine",
    "is_string",
    "is_integer",
    "is_float",
    "is_boolean",
    "is_non_empty_string",
    "ForgeApp",
    "Plugin",
    "PluginManager",
    "ApplicationMetadata",
]

========== ./src/forgecore/application/__init__.py ==========

from .app import ForgeApp
from .metadata import ApplicationMetadata

__all__ = [
    "ForgeApp",
    "ApplicationMetadata",
]

========== ./src/forgecore/application/app.py ==========

from __future__ import annotations

from forgecore.context import ApplicationContext

from .metadata import ApplicationMetadata


class ForgeApp:
    def __init__(
        self,
        metadata: ApplicationMetadata | None = None,
    ) -> None:
        if metadata is None:
            metadata = ApplicationMetadata(
                name="Forge Application",
                version="0.0.0",
            )

        self.context = ApplicationContext(metadata)

    @property
    def metadata(self) -> ApplicationMetadata:
        return self.context.metadata

    @property
    def services(self):
        return self.context.services

    @property
    def registry(self):
        return self.context.registry

    @property
    def events(self):
        return self.context.events

    @property
    def lifecycle(self):
        return self.context.lifecycle

    def run(self) -> None:
        self.lifecycle.start()
        self.lifecycle.run()

    def stop(self) -> None:
        self.lifecycle.stop()
        self.lifecycle.shutdown()

========== ./src/forgecore/application/metadata.py ==========

from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class ApplicationMetadata:
    name: str
    version: str
    description: str = ""
    author: str = ""

========== ./src/forgecore/config/__init__.py ==========

from .config import Config

__all__ = [
    "Config",
]

========== ./src/forgecore/config/config.py ==========

from __future__ import annotations

import json
from pathlib import Path
from typing import Any


class Config:
    def __init__(self) -> None:
        self._data: dict[str, Any] = {}

    def load_json(self, path: str | Path) -> None:
        with open(path, encoding="utf-8") as file:
            self._data = json.load(file)

    def get(self, key: str, default: Any = None) -> Any:
        return self._data.get(key, default)

    def set(self, key: str, value: Any) -> None:
        self._data[key] = value

    def to_dict(self) -> dict[str, Any]:
        return dict(self._data)

========== ./src/forgecore/context/__init__.py ==========

from .context import ApplicationContext

__all__ = [
    "ApplicationContext",
]

========== ./src/forgecore/context/context.py ==========

from __future__ import annotations

from forgecore.application.metadata import ApplicationMetadata
from forgecore.lifecycle import LifecycleManager
from forgecore.registry import Registry
from forgecore.runtime import EventBus
from forgecore.services import ServiceContainer


class ApplicationContext:
    def __init__(
        self,
        metadata: ApplicationMetadata | None = None,
    ) -> None:
        self.metadata = metadata or ApplicationMetadata(
            name="Forge Application",
            version="0.0.0",
        )

        self.services = ServiceContainer()
        self.registry = Registry()
        self.events = EventBus()
        self.lifecycle = LifecycleManager()

========== ./src/forgecore/core/__init__.py ==========

from .constants import (
    DEFAULT_ENCODING,
    DEFAULT_TIMEOUT,
    MAX_RETRIES,
    PACKAGE_NAME,
)
from .exceptions import (
    ConfigurationError,
    ForgeCoreError,
    InitializationError,
    UnsupportedPlatformError,
    ValidationError,
)
from .types import JSON, JSONList
from .version import VERSION, __version__

__all__ = [
    "PACKAGE_NAME",
    "DEFAULT_ENCODING",
    "DEFAULT_TIMEOUT",
    "MAX_RETRIES",
    "ForgeCoreError",
    "ConfigurationError",
    "ValidationError",
    "InitializationError",
    "UnsupportedPlatformError",
    "VERSION",
    "__version__",
    "JSON",
    "JSONList",
]

========== ./src/forgecore/core/constants.py ==========

PACKAGE_NAME = "forgecore"

DEFAULT_ENCODING = "utf-8"

DEFAULT_TIMEOUT = 30

MAX_RETRIES = 3

========== ./src/forgecore/core/exceptions.py ==========

class ForgeCoreError(Exception):
    pass


class ConfigurationError(ForgeCoreError):
    pass


class ValidationError(ForgeCoreError):
    pass


class InitializationError(ForgeCoreError):
    pass


class UnsupportedPlatformError(ForgeCoreError):
    pass

========== ./src/forgecore/core/types.py ==========

from typing import Any, TypeAlias

JSON: TypeAlias = dict[str, Any]
JSONList: TypeAlias = list[JSON]

========== ./src/forgecore/core/version.py ==========

VERSION = (0, 1, 0)

__version__ = ".".join(map(str, VERSION))

========== ./src/forgecore/lifecycle/__init__.py ==========

from .manager import LifecycleManager
from .state import LifecycleState

__all__ = [
    "LifecycleManager",
    "LifecycleState",
]

========== ./src/forgecore/lifecycle/manager.py ==========

from __future__ import annotations

from .state import LifecycleState


class LifecycleManager:
    def __init__(self) -> None:
        self._state = LifecycleState.INITIALIZED

    @property
    def state(self) -> LifecycleState:
        return self._state

    def start(self) -> None:
        self._state = LifecycleState.STARTING

    def run(self) -> None:
        self._state = LifecycleState.RUNNING

    def stop(self) -> None:
        self._state = LifecycleState.STOPPING

    def shutdown(self) -> None:
        self._state = LifecycleState.STOPPED

========== ./src/forgecore/lifecycle/state.py ==========

from __future__ import annotations

from enum import Enum


class LifecycleState(str, Enum):
    INITIALIZED = "initialized"
    STARTING = "starting"
    RUNNING = "running"
    STOPPING = "stopping"
    STOPPED = "stopped"

========== ./src/forgecore/logging/__init__.py ==========

from .logger import get_logger

__all__ = [
    "get_logger",
]

========== ./src/forgecore/logging/logger.py ==========

from __future__ import annotations

import logging
import sys

_FORMAT = "[%(asctime)s] %(levelname)s %(name)s: %(message)s"


def get_logger(name: str) -> logging.Logger:
    logger = logging.getLogger(name)

    if logger.handlers:
        return logger

    logger.setLevel(logging.INFO)

    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(logging.Formatter(_FORMAT))

    logger.addHandler(handler)
    logger.propagate = False

    return logger

========== ./src/forgecore/plugins/__init__.py ==========

from .manager import PluginManager
from .plugin import Plugin

__all__ = [
    "Plugin",
    "PluginManager",
]

========== ./src/forgecore/plugins/manager.py ==========

from __future__ import annotations

from forgecore.context import ApplicationContext

from .plugin import Plugin


class PluginManager:
    def __init__(self, context: ApplicationContext) -> None:
        self._context = context
        self._plugins: dict[str, Plugin] = {}

    def register(self, plugin: Plugin) -> None:
        self._plugins[plugin.name] = plugin

    def unregister(self, name: str) -> None:
        self._plugins.pop(name, None)

    def get(self, name: str) -> Plugin | None:
        return self._plugins.get(name)

    def all(self) -> tuple[Plugin, ...]:
        return tuple(self._plugins.values())

    def start_all(self) -> None:
        for plugin in self._plugins.values():
            plugin.start(self._context)

    def stop_all(self) -> None:
        for plugin in reversed(tuple(self._plugins.values())):
            plugin.stop(self._context)

========== ./src/forgecore/plugins/plugin.py ==========

from __future__ import annotations

from abc import ABC, abstractmethod

from forgecore.context import ApplicationContext


class Plugin(ABC):
    @property
    @abstractmethod
    def name(self) -> str: ...

    @property
    @abstractmethod
    def version(self) -> str: ...

    @abstractmethod
    def start(self, context: ApplicationContext) -> None: ...

    @abstractmethod
    def stop(self, context: ApplicationContext) -> None: ...

========== ./src/forgecore/providers/__init__.py ==========

from .provider import ServiceProvider

__all__ = [
    "ServiceProvider",
]

========== ./src/forgecore/providers/provider.py ==========

from __future__ import annotations

from abc import ABC, abstractmethod

from forgecore.services import ServiceContainer


class ServiceProvider(ABC):
    @abstractmethod
    def register(self, container: ServiceContainer) -> None:
        """Register services into the container."""

    def boot(self, container: ServiceContainer) -> None:
        """Boot the provider."""

========== ./src/forgecore/registry/__init__.py ==========

from .exceptions import RegistryKeyError
from .registry import Registry

__all__ = [
    "Registry",
    "RegistryKeyError",
]

========== ./src/forgecore/registry/exceptions.py ==========

class RegistryKeyError(KeyError):
    """Raised when a registry key cannot be found."""

========== ./src/forgecore/registry/registry.py ==========

from __future__ import annotations

from typing import Any

from .exceptions import RegistryKeyError


class Registry:
    def __init__(self) -> None:
        self._items: dict[str, Any] = {}

    def register(self, key: str, value: Any) -> None:
        self._items[key] = value

    def get(self, key: str) -> Any:
        try:
            return self._items[key]
        except KeyError as exc:
            raise RegistryKeyError(f"Registry key '{key}' is not registered.") from exc

    def contains(self, key: str) -> bool:
        return key in self._items

    def remove(self, key: str) -> None:
        self._items.pop(key, None)

    def clear(self) -> None:
        self._items.clear()

    def __len__(self) -> int:
        return len(self._items)

========== ./src/forgecore/runtime/__init__.py ==========

from .bus import EventBus
from .event import Event
from .exceptions import EventError

__all__ = [
    "Event",
    "EventBus",
    "EventError",
]

========== ./src/forgecore/runtime/bus.py ==========

from __future__ import annotations

from collections import defaultdict
from typing import Callable

from .event import Event

Listener = Callable[[Event], None]


class EventBus:
    def __init__(self) -> None:
        self._listeners: dict[str, list[Listener]] = defaultdict(list)

    def subscribe(
        self,
        event_name: str,
        listener: Listener,
    ) -> None:
        self._listeners[event_name].append(listener)

    def emit(self, event: Event) -> None:
        for listener in self._listeners[event.name]:
            listener(event)

========== ./src/forgecore/runtime/event.py ==========

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass(slots=True, frozen=True)
class Event:
    name: str
    payload: dict[str, Any] = field(default_factory=dict)

========== ./src/forgecore/runtime/exceptions.py ==========

class EventError(Exception):
    pass

========== ./src/forgecore/services/__init__.py ==========

from .autowire import constructor_dependencies
from .container import ServiceContainer
from .descriptor import ServiceDescriptor
from .exceptions import ServiceNotFoundError
from .factory import ServiceFactory
from .resolver import ServiceResolver
from .scope import ServiceScope

__all__ = [
    "ServiceContainer",
    "ServiceNotFoundError",
    "ServiceScope",
    "ServiceDescriptor",
    "constructor_dependencies",
    "ServiceResolver",
    "ServiceFactory",
]

========== ./src/forgecore/services/autowire.py ==========

from __future__ import annotations

from inspect import signature
from typing import Any


def constructor_dependencies(cls: type[Any]) -> list[type[Any]]:
    """
    Return constructor dependencies from type annotations.
    """

    parameters = signature(cls.__init__).parameters.values()

    dependencies: list[type[Any]] = []

    for parameter in parameters:
        if parameter.name == "self":
            continue

        if parameter.annotation is parameter.empty:
            raise TypeError(
                f"{cls.__name__}.{parameter.name} is missing a type annotation."
            )

        dependencies.append(parameter.annotation)

    return dependencies

========== ./src/forgecore/services/container.py ==========

from __future__ import annotations

from typing import Any

from .descriptor import ServiceDescriptor
from .exceptions import ServiceNotFoundError
from .factory import ServiceFactory
from .resolver import ServiceResolver
from .scope import ServiceScope


class ServiceContainer:
    def __init__(self) -> None:
        self._services: dict[type[Any], ServiceDescriptor] = {}
        self._bindings: dict[type[Any], type[Any]] = {}
        self._resolver = ServiceResolver(self)

    def register(
        self,
        service: Any,
        *,
        scope: ServiceScope = ServiceScope.SINGLETON,
    ) -> None:
        service_type = type(service)

        self._services[service_type] = ServiceDescriptor(
            service_type=service_type,
            implementation=service_type,
            scope=scope,
            instance=service,
        )

    def bind(
        self,
        abstraction: type[Any],
        implementation: type[Any],
    ) -> None:
        self._bindings[abstraction] = implementation

    def singleton(
        self,
        service_type: type[Any],
        factory: callable | None = None,
    ) -> None:
        descriptor = ServiceDescriptor(
            service_type=service_type,
            implementation=service_type,
            scope=ServiceScope.SINGLETON,
        )

        if factory is not None:
            descriptor.factory = ServiceFactory(factory)

        self._services[service_type] = descriptor

    def transient(
        self,
        service_type: type[Any],
        factory: callable | None = None,
    ) -> None:
        descriptor = ServiceDescriptor(
            service_type=service_type,
            implementation=service_type,
            scope=ServiceScope.TRANSIENT,
        )

        if factory is not None:
            descriptor.factory = ServiceFactory(factory)

        self._services[service_type] = descriptor

    def get(self, service_type: type[Any]) -> Any:
        try:
            descriptor = self._services[service_type]
        except KeyError as exc:
            raise ServiceNotFoundError(
                f"Service '{service_type.__name__}' is not registered."
            ) from exc

        if descriptor.scope is ServiceScope.SINGLETON:
            if descriptor.instance is None:
                if descriptor.factory is not None:
                    descriptor.instance = descriptor.factory.create(self)
                else:
                    descriptor.instance = self._resolver.resolve(
                        descriptor.implementation
                    )

            return descriptor.instance

        if descriptor.factory is not None:
            return descriptor.factory.create(self)

        return self._resolver.resolve(descriptor.implementation)

    def resolve(self, service_type: type[Any]) -> Any:
        return self._resolver.resolve(service_type)

    def contains(self, service_type: type[Any]) -> bool:
        return service_type in self._services

    def clear(self) -> None:
        self._services.clear()
        self._bindings.clear()

    def __len__(self) -> int:
        return len(self._services)

========== ./src/forgecore/services/descriptor.py ==========

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from .factory import ServiceFactory
from .scope import ServiceScope


@dataclass(slots=True)
class ServiceDescriptor:
    service_type: type[Any]
    implementation: type[Any]
    scope: ServiceScope = ServiceScope.SINGLETON
    instance: Any | None = None
    factory: ServiceFactory | None = None

========== ./src/forgecore/services/exceptions.py ==========

class ServiceNotFoundError(LookupError):
    """Raised when a requested service is not registered."""

========== ./src/forgecore/services/factory.py ==========

from __future__ import annotations

from collections.abc import Callable
from typing import Any


class ServiceFactory:
    """
    Wraps a callable used to create a service instance.
    """

    def __init__(
        self,
        factory: Callable[[Any], Any],
    ) -> None:
        self._factory = factory

    def create(
        self,
        container: Any,
    ) -> Any:
        return self._factory(container)

========== ./src/forgecore/services/resolver.py ==========

from __future__ import annotations

import inspect
from typing import Any

from .descriptor import ServiceDescriptor
from .scope import ServiceScope


class ServiceResolver:
    """
    Automatically resolves services using constructor type hints
    and registered service bindings.
    """

    def __init__(self, container: Any) -> None:
        self._container = container

    def resolve(self, service_type: type[Any]) -> Any:
        """
        Resolve a service recursively.
        """

        # Resolve abstraction -> implementation
        if service_type in self._container._bindings:
            service_type = self._container._bindings[service_type]

        descriptor = self._container._services.get(service_type)

        # Return cached singleton if available
        if (
            descriptor is not None
            and descriptor.scope is ServiceScope.SINGLETON
            and descriptor.instance is not None
        ):
            return descriptor.instance

        # Resolve concrete implementation
        if descriptor is not None:
            service_type = descriptor.implementation

        signature = inspect.signature(service_type.__init__)

        kwargs: dict[str, Any] = {}

        for name, parameter in signature.parameters.items():
            if name == "self":
                continue

            if parameter.kind in (
                inspect.Parameter.VAR_POSITIONAL,
                inspect.Parameter.VAR_KEYWORD,
            ):
                continue

            if parameter.annotation is inspect._empty:
                raise TypeError(
                    f"Constructor parameter '{name}' "
                    f"of '{service_type.__name__}' "
                    "must have a type annotation."
                )

            kwargs[name] = self.resolve(parameter.annotation)

        instance = service_type(**kwargs)

        # Auto-register unresolved classes as singleton
        if descriptor is None:
            self._container._services[service_type] = ServiceDescriptor(
                service_type=service_type,
                implementation=service_type,
                scope=ServiceScope.SINGLETON,
                instance=instance,
            )
        elif descriptor.scope is ServiceScope.SINGLETON:
            descriptor.instance = instance

        return instance

========== ./src/forgecore/services/scope.py ==========

from enum import Enum


class ServiceScope(str, Enum):
    SINGLETON = "singleton"
    TRANSIENT = "transient"

========== ./src/forgecore/utils/__init__.py ==========

from .inspect import package_version
from .system import machine, operating_system, python_version

__all__ = [
    "package_version",
    "python_version",
    "operating_system",
    "machine",
]

========== ./src/forgecore/utils/inspect.py ==========

from __future__ import annotations

from importlib.metadata import version


def package_version(package: str) -> str:
    return version(package)

========== ./src/forgecore/utils/system.py ==========

from __future__ import annotations

import platform


def python_version() -> str:
    return platform.python_version()


def operating_system() -> str:
    return platform.system()


def machine() -> str:
    return platform.machine()

========== ./src/forgecore/validation/__init__.py ==========

from .validators import (
    is_boolean,
    is_float,
    is_integer,
    is_non_empty_string,
    is_string,
)

__all__ = [
    "is_string",
    "is_integer",
    "is_float",
    "is_boolean",
    "is_non_empty_string",
]

========== ./src/forgecore/validation/validators.py ==========

from __future__ import annotations


def is_string(value: object) -> bool:
    return isinstance(value, str)


def is_integer(value: object) -> bool:
    return isinstance(value, int)


def is_float(value: object) -> bool:
    return isinstance(value, float)


def is_boolean(value: object) -> bool:
    return isinstance(value, bool)


def is_non_empty_string(value: object) -> bool:
    return isinstance(value, str) and bool(value.strip())

========== ./src/forgecore/version.py ==========

__version__ = "0.1.0"

========== ./tests/test_application.py ==========

from forgecore.application import ForgeApp
from forgecore.lifecycle import LifecycleState


def test_application_run_and_stop():
    app = ForgeApp()

    assert app.lifecycle.state is LifecycleState.INITIALIZED

    app.run()
    assert app.lifecycle.state is LifecycleState.RUNNING

    app.stop()
    assert app.lifecycle.state is LifecycleState.STOPPED

========== ./tests/test_autowire.py ==========

from forgecore.services.container import ServiceContainer


class Logger:
    pass


class Database:
    def __init__(self, logger: Logger) -> None:
        self.logger = logger


class UserService:
    def __init__(self, database: Database) -> None:
        self.database = database


def test_autowire_single_dependency():
    container = ServiceContainer()

    database = container.resolve(Database)

    assert isinstance(database, Database)
    assert isinstance(database.logger, Logger)


def test_autowire_recursive_dependencies():
    container = ServiceContainer()

    service = container.resolve(UserService)

    assert isinstance(service, UserService)
    assert isinstance(service.database, Database)
    assert isinstance(service.database.logger, Logger)


def test_resolve_returns_same_singleton():
    container = ServiceContainer()

    first = container.resolve(UserService)
    second = container.resolve(UserService)

    assert first is second
    assert first.database is second.database
    assert first.database.logger is second.database.logger

========== ./tests/test_binding.py ==========

from forgecore.services import ServiceContainer


class Database:
    pass


class PostgreSQLDatabase(Database):
    pass


def test_bind_registers_mapping():
    container = ServiceContainer()

    container.bind(Database, PostgreSQLDatabase)

    assert container._bindings[Database] is PostgreSQLDatabase

========== ./tests/test_binding_resolver.py ==========

from forgecore.services import ServiceContainer


class Database:
    pass


class PostgreSQLDatabase(Database):
    pass


def test_resolve_bound_service():
    container = ServiceContainer()

    container.bind(Database, PostgreSQLDatabase)

    database = container.resolve(Database)

    assert isinstance(database, PostgreSQLDatabase)

========== ./tests/test_config.py ==========

import forgecore


def test_config():
    cfg = forgecore.Config()

    cfg.set("name", "ForgeCore")
    cfg.set("version", "0.1.0")

    assert cfg.get("name") == "ForgeCore"
    assert cfg.get("version") == "0.1.0"
    assert cfg.get("missing", 123) == 123

========== ./tests/test_context.py ==========

from forgecore.application import ForgeApp
from forgecore.context import ApplicationContext


def test_application_context():
    app = ForgeApp()

    assert isinstance(app.context, ApplicationContext)
    assert app.services is app.context.services
    assert app.registry is app.context.registry
    assert app.events is app.context.events
    assert app.lifecycle is app.context.lifecycle

========== ./tests/test_descriptor.py ==========

from forgecore.services import ServiceDescriptor, ServiceScope


class Database:
    pass


def test_descriptor_defaults():
    descriptor = ServiceDescriptor(
        service_type=Database,
        implementation=Database,
    )

    assert descriptor.service_type is Database
    assert descriptor.implementation is Database
    assert descriptor.scope is ServiceScope.SINGLETON
    assert descriptor.instance is None


def test_descriptor_custom_scope():
    descriptor = ServiceDescriptor(
        service_type=Database,
        implementation=Database,
        scope=ServiceScope.TRANSIENT,
    )

    assert descriptor.scope is ServiceScope.TRANSIENT

========== ./tests/test_factory.py ==========

from forgecore.services import ServiceContainer


class Config:
    pass


class Database:
    def __init__(self, config: Config):
        self.config = config


class UserRepository:
    def __init__(self, database: Database):
        self.database = database


def test_singleton_factory():
    container = ServiceContainer()

    container.singleton(
        Config,
        lambda c: Config(),
    )

    first = container.resolve(Config)
    second = container.resolve(Config)

    assert first is second


def test_transient_factory():
    container = ServiceContainer()

    container.transient(
        Config,
        lambda c: Config(),
    )

    first = container.resolve(Config)
    second = container.resolve(Config)

    assert first is not second


def test_factory_dependency():
    container = ServiceContainer()

    container.singleton(
        Config,
        lambda c: Config(),
    )

    container.singleton(
        Database,
        lambda c: Database(c.resolve(Config)),
    )

    database = container.resolve(Database)

    assert isinstance(database.config, Config)


def test_factory_recursive_dependency():
    container = ServiceContainer()

    container.singleton(
        Config,
        lambda c: Config(),
    )

    container.singleton(
        Database,
        lambda c: Database(c.resolve(Config)),
    )

    repository = container.resolve(UserRepository)

    assert isinstance(repository.database, Database)

========== ./tests/test_lifecycle.py ==========

from forgecore.lifecycle import LifecycleManager, LifecycleState


def test_lifecycle():
    manager = LifecycleManager()

    assert manager.state is LifecycleState.INITIALIZED

    manager.start()
    assert manager.state is LifecycleState.STARTING

    manager.run()
    assert manager.state is LifecycleState.RUNNING

    manager.stop()
    assert manager.state is LifecycleState.STOPPING

    manager.shutdown()
    assert manager.state is LifecycleState.STOPPED

========== ./tests/test_metadata.py ==========

from dataclasses import FrozenInstanceError

import pytest

from forgecore.application import ApplicationMetadata, ForgeApp


def test_default_metadata():
    app = ForgeApp()

    assert app.metadata.name == "Forge Application"
    assert app.metadata.version == "0.0.0"


def test_custom_metadata():
    metadata = ApplicationMetadata(
        name="ForgeCore Demo",
        version="1.0.0",
        description="Demo Application",
        author="Akmal",
    )

    app = ForgeApp(metadata=metadata)

    assert app.metadata is metadata
    assert app.context.metadata is metadata


def test_metadata_is_immutable():
    metadata = ApplicationMetadata(
        name="Demo",
        version="1.0.0",
    )

    with pytest.raises(FrozenInstanceError):
        metadata.name = "Changed"

========== ./tests/test_plugin.py ==========

from forgecore.context import ApplicationContext
from forgecore.plugins import Plugin


class DemoPlugin(Plugin):
    @property
    def name(self) -> str:
        return "demo"

    @property
    def version(self) -> str:
        return "1.0.0"

    def start(self, context: ApplicationContext) -> None:
        context.registry.register("plugin", self)

    def stop(self, context: ApplicationContext) -> None:
        pass


def test_plugin_contract():
    ctx = ApplicationContext()
    plugin = DemoPlugin()

    plugin.start(ctx)

    assert ctx.registry.get("plugin") is plugin

========== ./tests/test_plugin_manager.py ==========

from forgecore.context import ApplicationContext
from forgecore.plugins import Plugin, PluginManager


class DemoPlugin(Plugin):
    def __init__(self) -> None:
        self.started = False
        self.stopped = False

    @property
    def name(self) -> str:
        return "demo"

    @property
    def version(self) -> str:
        return "1.0.0"

    def start(self, context: ApplicationContext) -> None:
        self.started = True

    def stop(self, context: ApplicationContext) -> None:
        self.stopped = True


def test_plugin_manager():
    ctx = ApplicationContext()
    manager = PluginManager(ctx)

    plugin = DemoPlugin()

    manager.register(plugin)

    assert manager.get("demo") is plugin

    manager.start_all()

    assert plugin.started

    manager.stop_all()

    assert plugin.stopped

========== ./tests/test_provider.py ==========


========== ./tests/test_registry.py ==========

from forgecore.registry import Registry


def test_registry_register_and_get():
    registry = Registry()

    registry.register("answer", 42)

    assert registry.contains("answer")
    assert registry.get("answer") == 42


def test_registry_clear():
    registry = Registry()

    registry.register("value", object())

    registry.clear()

    assert len(registry) == 0

========== ./tests/test_resolver.py ==========

from forgecore.services import ServiceContainer, ServiceResolver


class Database:
    pass


class Logger:
    pass


class UserService:
    def __init__(
        self,
        database: Database,
        logger: Logger,
    ) -> None:
        self.database = database
        self.logger = logger


def test_service_resolver():
    container = ServiceContainer()

    database = Database()
    logger = Logger()

    container.register(database)
    container.register(logger)

    resolver = ServiceResolver(container)

    service = resolver.resolve(UserService)

    assert service.database is database
    assert service.logger is logger

========== ./tests/test_runtime.py ==========

from forgecore.runtime import Event, EventBus


def test_event_bus():
    bus = EventBus()

    received = []

    def listener(event: Event) -> None:
        received.append(event.payload["value"])

    bus.subscribe("demo", listener)

    bus.emit(
        Event(
            name="demo",
            payload={
                "value": 100,
            },
        )
    )

    assert received == [100]

========== ./tests/test_scope.py ==========

from forgecore.services.scope import ServiceScope


def test_singleton_scope():
    assert ServiceScope.SINGLETON.value == "singleton"


def test_transient_scope():
    assert ServiceScope.TRANSIENT.value == "transient"

========== ./tests/test_service_descriptor_container.py ==========

from forgecore.services import ServiceContainer, ServiceScope


class Database:
    pass


def test_register_scope():
    container = ServiceContainer()

    container.register(
        Database(),
        scope=ServiceScope.TRANSIENT,
    )

    descriptor = container._services[Database]

    assert descriptor.scope is ServiceScope.TRANSIENT


def test_register_descriptor():
    container = ServiceContainer()

    db = Database()

    container.register(db)

    descriptor = container._services[Database]

    assert descriptor.instance is db
    assert descriptor.implementation is Database
    assert descriptor.service_type is Database

========== ./tests/test_services.py ==========

from forgecore.config import Config
from forgecore.services import ServiceContainer


def test_register_service():
    container = ServiceContainer()

    config = Config()

    container.register(config)

    assert container.contains(Config)
    assert container.get(Config) is config


def test_clear_services():
    container = ServiceContainer()

    container.register(Config())

    container.clear()

    assert len(container) == 0

========== ./tests/test_singleton.py ==========

from forgecore.services import ServiceContainer


class Database:
    pass


def test_singleton_returns_same_instance():
    container = ServiceContainer()

    container.singleton(Database)

    first = container.get(Database)
    second = container.get(Database)

    assert first is second


def test_singleton_is_registered():
    container = ServiceContainer()

    container.singleton(Database)

    assert container.contains(Database)

========== ./tests/test_validation.py ==========

import forgecore


def test_is_string():
    assert forgecore.is_string("ForgeCore")
    assert not forgecore.is_string(10)


def test_is_integer():
    assert forgecore.is_integer(42)
    assert not forgecore.is_integer("42")


def test_is_float():
    assert forgecore.is_float(3.14)
    assert not forgecore.is_float(3)


def test_is_boolean():
    assert forgecore.is_boolean(True)
    assert forgecore.is_boolean(False)
    assert not forgecore.is_boolean(1)


def test_is_non_empty_string():
    assert forgecore.is_non_empty_string("ForgeCore")
    assert not forgecore.is_non_empty_string("")
    assert not forgecore.is_non_empty_string("   ")

========== ./tests/test_version.py ==========

import forgecore


def test_version():
    assert forgecore.__version__ == "0.1.0"


def test_package_name():
    assert forgecore.PACKAGE_NAME == "forgecore"


def test_timeout():
    assert forgecore.DEFAULT_TIMEOUT == 30
