diff --git a/docs/task_scheduling.md b/docs/task_scheduling.md
new file mode 100644
index 0000000..cbca67e
--- /dev/null
+++ b/docs/task_scheduling.md
@@ -0,0 +1,235 @@
+# Task Scheduling in PyNest ⏰
+
+## Introduction
+
+PyNest provides a robust task scheduling system using the APScheduler library. This feature enables you to execute functions at specific times or regular intervals, making it perfect for background tasks, periodic reports, data cleanup, and automated operations. The scheduling system integrates seamlessly with PyNest's dependency injection and module system.
+
+## Defining Scheduled Tasks
+
+PyNest offers two main decorators for task scheduling: `@Cron` for time-based scheduling and `@Interval` for interval-based execution.
+
+### @Cron Decorator
+
+The `@Cron` decorator allows you to schedule tasks using predefined cron expressions. It works with both standalone functions and class methods.
+
+```python
+from nest.core.decorators.scheduler import Cron
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+
+@Cron(expression=CronExpression.EVERY_DAY_AT_MIDNIGHT)
+def daily_cleanup():
+ """Execute daily cleanup at midnight."""
+ print("Running daily cleanup...")
+
+@Cron(expression=CronExpression.EVERY_WEEKDAY)
+def weekday_report():
+ """Generate reports on weekdays."""
+ print("Generating weekday report...")
+```
+
+### @Interval Decorator
+
+The `@Interval` decorator schedules tasks to run at regular intervals. You can specify intervals using seconds, minutes, hours, or days.
+
+```python
+from nest.core.decorators.scheduler import Interval
+
+@Interval(minutes=5)
+def health_check():
+ """Check system health every 5 minutes."""
+ print("Performing health check...")
+
+@Interval(hours=2)
+def cache_refresh():
+ """Refresh cache every 2 hours."""
+ print("Refreshing cache...")
+```
+
+## Predefined Cron Expressions
+
+PyNest provides a comprehensive set of predefined cron expressions for common scheduling needs:
+
+**Time Intervals:**
+- `EVERY_SECOND`, `EVERY_5_SECONDS`, `EVERY_30_SECONDS`
+- `EVERY_MINUTE`, `EVERY_5_MINUTES`, `EVERY_30_MINUTES`
+- `EVERY_HOUR`, `EVERY_2_HOURS`, `EVERY_6_HOURS`, `EVERY_12_HOURS`
+
+**Daily Schedules:**
+- `EVERY_DAY_AT_MIDNIGHT`, `EVERY_DAY_AT_NOON`
+- `EVERY_DAY_AT_1AM`, `EVERY_DAY_AT_6PM`
+
+**Weekly Schedules:**
+- `EVERY_WEEK`, `EVERY_WEEKDAY`, `EVERY_WEEKEND`
+- `MONDAY_TO_FRIDAY_AT_9AM`, `MONDAY_TO_FRIDAY_AT_5PM`
+
+**Monthly and Yearly:**
+- `EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT`
+- `EVERY_QUARTER`, `EVERY_6_MONTHS`, `EVERY_YEAR`
+
+## Using Scheduled Tasks in Services
+
+The scheduling decorators integrate seamlessly with PyNest's dependency injection system. When you decorate methods in an `@Injectable` service, PyNest automatically detects and schedules them during application startup.
+
+### Basic Service with Scheduling
+
+```python
+from nest.core import Injectable
+from nest.core.decorators.scheduler import Cron, Interval
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+import logging
+
+@Injectable
+class TaskService:
+ def __init__(self):
+ self.execution_count = 0
+ logging.info("TaskService initialized - scheduled methods activated automatically")
+
+ @Cron(expression=CronExpression.EVERY_DAY_AT_MIDNIGHT)
+ def daily_cleanup(self):
+ """Execute daily cleanup at midnight."""
+ self.execution_count += 1
+ logging.info(f"Daily cleanup executed #{self.execution_count}")
+
+ # Cleanup logic here
+ return f"Cleanup completed - execution #{self.execution_count}"
+
+ @Interval(minutes=5)
+ def health_check(self):
+ """Perform health check every 5 minutes."""
+ logging.info("Performing system health check")
+
+ # Health check logic here
+ return "System healthy"
+```
+
+### Dependency Injection with Scheduled Tasks
+
+```python
+from nest.core import Injectable
+from nest.core.decorators.scheduler import Cron
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+
+@Injectable
+class NotificationService:
+ def send_notification(self, message: str):
+ """Send notification."""
+ print(f"Notification sent: {message}")
+ return True
+
+@Injectable
+class ReportService:
+ def __init__(self, notification_service: NotificationService):
+ self.notification_service = notification_service
+
+ @Cron(expression=CronExpression.EVERY_WEEKDAY)
+ def generate_daily_report(self):
+ """Generate and notify daily report."""
+ # Generate report logic
+ report = "Daily report generated successfully"
+
+ # Send notification using injected service
+ self.notification_service.send_notification(report)
+ return report
+```
+
+## Creating a Complete Application
+
+Here's how to create a complete PyNest application with scheduled tasks:
+
+### Module Setup
+
+```python
+# task_module.py
+from nest.core import Module
+from .task_service import TaskService
+from .notification_service import NotificationService
+
+@Module(
+ providers=[TaskService, NotificationService],
+ exports=[TaskService],
+)
+class TaskModule:
+ pass
+```
+
+### Application Module
+
+```python
+# app_module.py
+from nest.core import Module, PyNestFactory
+from .task_module import TaskModule
+
+@Module(
+ imports=[TaskModule],
+)
+class AppModule:
+ pass
+
+app = PyNestFactory.create(AppModule, title="Scheduler App", version="1.0.0")
+```
+
+### Running the Application
+
+```python
+# main.py
+import uvicorn
+from app_module import app
+
+if __name__ == "__main__":
+ uvicorn.run("app_module:app", host="0.0.0.0", port=8000, reload=True)
+```
+
+## Configuration
+
+Task scheduling is automatically configured when you use the decorators. The scheduler starts automatically with your PyNest application and terminates gracefully on shutdown.
+
+### Required Dependencies
+
+Ensure the following dependencies are installed:
+
+```bash
+pip install APScheduler==3.10.4 pytz==2024.2 six==1.16.0 tzlocal==5.2
+```
+
+## Best Practices
+
+1. **Use Predefined Expressions**: Leverage PyNest's predefined cron expressions for reliability.
+
+2. **Handle Exceptions**: Always implement proper error handling in scheduled methods.
+
+3. **Keep Tasks Lightweight**: Scheduled tasks should be quick to execute and non-blocking.
+
+4. **Use Logging**: Implement comprehensive logging for monitoring and debugging.
+
+5. **Test Thoroughly**: Test scheduled tasks in development before deploying to production.
+
+## Troubleshooting
+
+### Common Issues
+
+**Tasks Not Running:**
+- Verify the PyNest application is running
+- Check that services are properly registered in modules
+- Ensure scheduled methods have the correct signature
+
+**Multiple Executions:**
+- Avoid running multiple application instances
+- Check for application restart loops
+- Verify job ID generation is working correctly
+
+## Conclusion 🎉
+
+PyNest's task scheduling system provides a powerful and intuitive way to automate tasks in your applications. By leveraging the `@Cron` and `@Interval` decorators along with PyNest's dependency injection system, you can create robust scheduled tasks that integrate seamlessly with your application architecture.
+
+The automatic detection and scheduling of decorated methods eliminates the need for manual configuration, making it easy to implement complex scheduling scenarios while maintaining clean, maintainable code.
+
+---
+
+
\ No newline at end of file
diff --git a/examples/SchedulerApp/main.py b/examples/SchedulerApp/main.py
new file mode 100644
index 0000000..6782914
--- /dev/null
+++ b/examples/SchedulerApp/main.py
@@ -0,0 +1,8 @@
+if __name__ == "__main__":
+ import uvicorn
+ uvicorn.run("main:http_server", host="0.0.0.0", port=8000, reload=True)
+
+# Import at module level for uvicorn
+from src.app_module import http_server
+
+
diff --git a/examples/SchedulerApp/src/app_controller.py b/examples/SchedulerApp/src/app_controller.py
new file mode 100644
index 0000000..24ae966
--- /dev/null
+++ b/examples/SchedulerApp/src/app_controller.py
@@ -0,0 +1,48 @@
+from nest.core import Controller, Get
+from .app_service import AppService
+from .scheduler_service import SchedulerService
+
+@Controller("/")
+class AppController:
+ """
+ Main application controller with endpoints to monitor scheduled tasks.
+ """
+
+ def __init__(self, app_service: AppService, scheduler_service: SchedulerService):
+ self.app_service = app_service
+ self.scheduler_service = scheduler_service
+
+ @Get("/")
+ def get_app_info(self):
+ """
+ Returns application information.
+ """
+ return self.app_service.get_app_info()
+
+ @Get("/scheduler/stats")
+ def get_scheduler_stats(self):
+ """
+ Returns statistics of scheduled tasks.
+ """
+ return self.scheduler_service.get_statistics()
+
+ @Get("/scheduler/logs")
+ def get_scheduler_logs(self):
+ """
+ Returns recent logs of task executions.
+ """
+ return self.scheduler_service.get_recent_logs()
+
+ @Get("/health")
+ def health_check(self):
+ """
+ Health check endpoint for application and scheduler.
+ """
+ from nest.core.apscheduler import scheduler
+
+ return {
+ "status": "healthy",
+ "scheduler_running": scheduler.running,
+ "active_jobs": len(scheduler.get_jobs()),
+ "app_info": self.app_service.get_app_info()
+ }
\ No newline at end of file
diff --git a/examples/SchedulerApp/src/app_module.py b/examples/SchedulerApp/src/app_module.py
new file mode 100644
index 0000000..0d4f582
--- /dev/null
+++ b/examples/SchedulerApp/src/app_module.py
@@ -0,0 +1,22 @@
+from .app_controller import AppController
+from .app_service import AppService
+from .scheduler_service import SchedulerService
+
+from nest.core import Module, PyNestFactory
+
+@Module(
+ controllers=[AppController],
+ providers=[AppService, SchedulerService],
+)
+class AppModule:
+ pass
+
+app = PyNestFactory.create(
+ AppModule,
+ description="PyNest Application with Task Scheduling",
+ title="Scheduler App",
+ version="1.0.0",
+ debug=True
+)
+
+http_server = app.get_server()
\ No newline at end of file
diff --git a/examples/SchedulerApp/src/app_service.py b/examples/SchedulerApp/src/app_service.py
new file mode 100644
index 0000000..9b8112b
--- /dev/null
+++ b/examples/SchedulerApp/src/app_service.py
@@ -0,0 +1,65 @@
+from nest.core import Injectable
+from datetime import datetime
+
+@Injectable
+class AppService:
+ """
+ Main application service that provides information about the scheduling system.
+ """
+
+ def __init__(self):
+ self.app_name = "SchedulerApp"
+ self.app_version = "1.0.0"
+ self.description = "PyNest Application with Task Scheduling System"
+ self.started_at = datetime.now()
+
+ def get_app_info(self):
+ """
+ Returns detailed application information.
+ """
+ uptime = datetime.now() - self.started_at
+
+ return {
+ "app_name": self.app_name,
+ "app_version": self.app_version,
+ "description": self.description,
+ "started_at": self.started_at.strftime("%Y-%m-%d %H:%M:%S"),
+ "uptime_seconds": int(uptime.total_seconds()),
+ "uptime_formatted": str(uptime).split('.')[0], # Remove microseconds
+ "features": [
+ "🕒 Task scheduling with @Cron",
+ "⏰ Task scheduling with @Interval",
+ "📅 Predefined cron expressions",
+ "🔄 Automatic integration with application lifecycle",
+ "📊 Real-time task monitoring",
+ "📝 Detailed execution logging"
+ ],
+ "endpoints": [
+ {
+ "path": "/",
+ "method": "GET",
+ "description": "Application information"
+ },
+ {
+ "path": "/scheduler/stats",
+ "method": "GET",
+ "description": "Scheduled tasks statistics"
+ },
+ {
+ "path": "/scheduler/logs",
+ "method": "GET",
+ "description": "Recent execution logs"
+ },
+ {
+ "path": "/health",
+ "method": "GET",
+ "description": "Application health check"
+ }
+ ],
+ "scheduler_info": {
+ "library": "APScheduler",
+ "background_scheduler": True,
+ "timezone": "UTC",
+ "auto_start": True
+ }
+ }
\ No newline at end of file
diff --git a/examples/SchedulerApp/src/scheduler_service.py b/examples/SchedulerApp/src/scheduler_service.py
new file mode 100644
index 0000000..5874335
--- /dev/null
+++ b/examples/SchedulerApp/src/scheduler_service.py
@@ -0,0 +1,157 @@
+from nest.core import Injectable
+from nest.core.decorators.scheduler import Cron, Interval
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+from nest.core.apscheduler import scheduler
+import logging
+from datetime import datetime
+
+logger = logging.getLogger(__name__)
+
+@Injectable
+class SchedulerService:
+ """
+ Service that demonstrates the use of task scheduling decorators.
+
+ This service contains examples of scheduled tasks using:
+ - @Cron: for tasks based on cron expressions
+ - @Interval: for tasks that run at regular intervals
+ """
+
+ def __init__(self):
+ self.execution_count = 0
+ self.task_logs = []
+ logger.info("SchedulerService initialized - scheduled methods will be activated automatically by PyNest")
+
+ @Interval(seconds=10)
+ def task_every_10_seconds(self):
+ """
+ Task that runs every 10 seconds for quick demonstration.
+ """
+ self.execution_count += 1
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ message = f"[{timestamp}] Task executed every 10 seconds - Execution #{self.execution_count}"
+
+ self.task_logs.append({
+ "task": "task_every_10_seconds",
+ "timestamp": timestamp,
+ "execution_number": self.execution_count
+ })
+
+ # Keep only the last 10 logs
+ if len(self.task_logs) > 10:
+ self.task_logs = self.task_logs[-10:]
+
+ logger.info(message)
+ print(message) # For console visualization
+ return message
+
+ @Cron(expression=CronExpression.EVERY_MINUTE)
+ def task_every_minute(self):
+ """
+ Task that runs every minute using cron expression.
+ """
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ message = f"[{timestamp}] Cron task executed - EVERY_MINUTE"
+
+ self.task_logs.append({
+ "task": "task_every_minute",
+ "timestamp": timestamp,
+ "type": "cron"
+ })
+
+ logger.info(message)
+ print(message)
+ return message
+
+ @Cron(expression=CronExpression.EVERY_30_SECONDS)
+ def task_every_30_seconds_cron(self):
+ """
+ Task that runs every 30 seconds using cron expression.
+ """
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ message = f"[{timestamp}] Cron task executed - EVERY_30_SECONDS"
+
+ self.task_logs.append({
+ "task": "task_every_30_seconds_cron",
+ "timestamp": timestamp,
+ "type": "cron"
+ })
+
+ logger.info(message)
+ print(message)
+ return message
+
+ @Interval(minutes=1)
+ def task_every_minute_interval(self):
+ """
+ Task that runs every minute using interval.
+ """
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ message = f"[{timestamp}] Interval task executed - every 1 minute"
+
+ self.task_logs.append({
+ "task": "task_every_minute_interval",
+ "timestamp": timestamp,
+ "type": "interval"
+ })
+
+ logger.info(message)
+ print(message)
+ return message
+
+ def get_statistics(self):
+ """
+ Returns statistics of scheduled tasks.
+ """
+ # Get information from active scheduler jobs
+ jobs_info = []
+ for job in scheduler.get_jobs():
+ jobs_info.append({
+ "id": job.id,
+ "name": job.name,
+ "next_run_time": str(job.next_run_time) if job.next_run_time else None,
+ "trigger": str(job.trigger)
+ })
+
+ return {
+ "execution_count": self.execution_count,
+ "active_jobs": len(scheduler.get_jobs()),
+ "scheduler_running": scheduler.running,
+ "jobs_details": jobs_info,
+ "recent_logs": self.task_logs[-5:], # Last 5 logs
+ "scheduled_tasks": [
+ {
+ "name": "task_every_10_seconds",
+ "type": "interval",
+ "description": "Runs every 10 seconds"
+ },
+ {
+ "name": "task_every_minute",
+ "type": "cron",
+ "description": "Runs every minute (cron)"
+ },
+ {
+ "name": "task_every_30_seconds_cron",
+ "type": "cron",
+ "description": "Runs every 30 seconds (cron)"
+ },
+ {
+ "name": "task_every_minute_interval",
+ "type": "interval",
+ "description": "Runs every minute (interval)"
+ }
+ ]
+ }
+
+ def get_recent_logs(self):
+ """
+ Returns recent logs of task executions.
+ """
+ return {
+ "total_executions": self.execution_count,
+ "recent_logs": self.task_logs,
+ "scheduler_status": {
+ "running": scheduler.running,
+ "jobs_count": len(scheduler.get_jobs())
+ }
+ }
\ No newline at end of file
diff --git a/nest/core/apscheduler/__init__.py b/nest/core/apscheduler/__init__.py
new file mode 100644
index 0000000..cac494f
--- /dev/null
+++ b/nest/core/apscheduler/__init__.py
@@ -0,0 +1,7 @@
+from .apscheduler import scheduler, start_scheduler, stop_scheduler
+
+__all__ = ['scheduler', 'start_scheduler', 'stop_scheduler']
+
+
+
+
diff --git a/nest/core/apscheduler/apscheduler.py b/nest/core/apscheduler/apscheduler.py
new file mode 100644
index 0000000..a18cfa9
--- /dev/null
+++ b/nest/core/apscheduler/apscheduler.py
@@ -0,0 +1,36 @@
+from pytz import utc
+from apscheduler.schedulers.background import BackgroundScheduler
+import atexit
+
+"""
+Instance of BackgroundScheduler from the APScheduler library.
+Configured to initialize automatically and terminate properly.
+"""
+
+# Create scheduler instance
+scheduler = BackgroundScheduler()
+scheduler.configure(timezone=utc)
+
+# Flag to control if scheduler has been started
+_scheduler_started = False
+
+def start_scheduler():
+ """
+ Starts the scheduler if it hasn't been started yet.
+ """
+ global _scheduler_started
+ if not _scheduler_started and not scheduler.running:
+ scheduler.start()
+ _scheduler_started = True
+
+def stop_scheduler():
+ """
+ Stops the scheduler safely.
+ """
+ global _scheduler_started
+ if scheduler.running:
+ scheduler.shutdown(wait=True)
+ _scheduler_started = False
+
+# Register shutdown function to be called when program ends
+atexit.register(stop_scheduler)
diff --git a/nest/core/apscheduler/enums/cron_expression.py b/nest/core/apscheduler/enums/cron_expression.py
new file mode 100644
index 0000000..a2ece54
--- /dev/null
+++ b/nest/core/apscheduler/enums/cron_expression.py
@@ -0,0 +1,100 @@
+from enum import Enum
+from apscheduler.triggers.cron import CronTrigger
+
+class CronExpression(Enum):
+ """
+ Enum that contains cron expressions.
+ A cron expression is a string representing a set of times, using 6 space-separated fields.
+ Fields:
+ - second (0-59)
+ - minute (0-59)
+ - hour (0-23)
+ - day of month (1-31)
+ - month (1-12)
+ - day of week (0-6) (Sunday to Saturday)
+ - year (optional)
+ - timezone (optional)
+ """
+ EVERY_SECOND = CronTrigger(second="*")
+ EVERY_5_SECONDS = CronTrigger(second="*/5")
+ EVERY_10_SECONDS = CronTrigger(second="*/10")
+ EVERY_30_SECONDS = CronTrigger(second="*/30")
+ EVERY_MINUTE = CronTrigger(minute="*/1")
+ EVERY_5_MINUTES = CronTrigger(minute="*/5")
+ EVERY_10_MINUTES = CronTrigger(minute="*/10")
+ EVERY_30_MINUTES = CronTrigger(minute="*/30")
+ EVERY_HOUR = CronTrigger(minute=0, hour="0-23/1")
+ EVERY_2_HOURS = CronTrigger(minute=0, hour="0-23/2")
+ EVERY_3_HOURS = CronTrigger(minute=0, hour="0-23/3")
+ EVERY_4_HOURS = CronTrigger(minute=0, hour="0-23/4")
+ EVERY_5_HOURS = CronTrigger(minute=0, hour="0-23/5")
+ EVERY_6_HOURS = CronTrigger(minute=0, hour="0-23/6")
+ EVERY_7_HOURS = CronTrigger(minute=0, hour="0-23/7")
+ EVERY_8_HOURS = CronTrigger(minute=0, hour="0-23/8")
+ EVERY_9_HOURS = CronTrigger(minute=0, hour="0-23/9")
+ EVERY_10_HOURS = CronTrigger(minute=0, hour="0-23/10")
+ EVERY_11_HOURS = CronTrigger(minute=0, hour="0-23/11")
+ EVERY_12_HOURS = CronTrigger(minute=0, hour="0-23/12")
+ EVERY_DAY_AT_1AM = CronTrigger(minute=0, hour=1)
+ EVERY_DAY_AT_2AM = CronTrigger(minute=0, hour=2)
+ EVERY_DAY_AT_3AM = CronTrigger(minute=0, hour=3)
+ EVERY_DAY_AT_4AM = CronTrigger(minute=0, hour=4)
+ EVERY_DAY_AT_5AM = CronTrigger(minute=0, hour=5)
+ EVERY_DAY_AT_6AM = CronTrigger(minute=0, hour=6)
+ EVERY_DAY_AT_7AM = CronTrigger(minute=0, hour=7)
+ EVERY_DAY_AT_8AM = CronTrigger(minute=0, hour=8)
+ EVERY_DAY_AT_9AM = CronTrigger(minute=0, hour=9)
+ EVERY_DAY_AT_10AM = CronTrigger(minute=0, hour=10)
+ EVERY_DAY_AT_11AM = CronTrigger(minute=0, hour=11)
+ EVERY_DAY_AT_NOON = CronTrigger(minute=0, hour=12)
+ EVERY_DAY_AT_1PM = CronTrigger(minute=0, hour=13)
+ EVERY_DAY_AT_2PM = CronTrigger(minute=0, hour=14)
+ EVERY_DAY_AT_3PM = CronTrigger(minute=0, hour=15)
+ EVERY_DAY_AT_4PM = CronTrigger(minute=0, hour=16)
+ EVERY_DAY_AT_5PM = CronTrigger(minute=0, hour=17)
+ EVERY_DAY_AT_6PM = CronTrigger(minute=0, hour=18)
+ EVERY_DAY_AT_7PM = CronTrigger(minute=0, hour=19)
+ EVERY_DAY_AT_8PM = CronTrigger(minute=0, hour=20)
+ EVERY_DAY_AT_9PM = CronTrigger(minute=0, hour=21)
+ EVERY_DAY_AT_10PM = CronTrigger(minute=0, hour=22)
+ EVERY_DAY_AT_11PM = CronTrigger(minute=0, hour=23)
+ EVERY_DAY_AT_MIDNIGHT = CronTrigger(minute=0, hour=0)
+ EVERY_WEEK = CronTrigger(minute=0, hour=0, day_of_week=0)
+ EVERY_WEEKDAY = CronTrigger(minute=0, hour=0, day_of_week="1-5")
+ EVERY_WEEKEND = CronTrigger(minute=0, hour=0, day_of_week="6,0")
+ EVERY_1ST_DAY_OF_MONTH_AT_MIDNIGHT = CronTrigger(minute=0, hour=0, day=1)
+ EVERY_1ST_DAY_OF_MONTH_AT_NOON = CronTrigger(minute=0, hour=12, day=1)
+ EVERY_2ND_HOUR = CronTrigger(minute=0, hour="*/2")
+ EVERY_2ND_HOUR_FROM_1AM_THROUGH_11PM = CronTrigger(minute=0, hour="1-23/2")
+ EVERY_2ND_MONTH = CronTrigger(minute=0, hour=0, day=1, month="*/2")
+ EVERY_QUARTER = CronTrigger(minute=0, hour=0, day=1, month="*/3")
+ EVERY_6_MONTHS = CronTrigger(minute=0, hour=0, day=1, month="*/6")
+ EVERY_YEAR = CronTrigger(minute=0, hour=0, day=1, month=1)
+ EVERY_30_MINUTES_BETWEEN_9AM_AND_5PM = CronTrigger(minute="*/30", hour="9-17")
+ EVERY_30_MINUTES_BETWEEN_9AM_AND_6PM = CronTrigger(minute="*/30", hour="9-18")
+ EVERY_30_MINUTES_BETWEEN_10AM_AND_7PM = CronTrigger(minute="*/30", hour="10-19")
+ MONDAY_TO_FRIDAY_AT_1AM = CronTrigger(minute=0, hour=1, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_2AM = CronTrigger(minute=0, hour=2, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_3AM = CronTrigger(minute=0, hour=3, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_4AM = CronTrigger(minute=0, hour=4, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_5AM = CronTrigger(minute=0, hour=5, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_6AM = CronTrigger(minute=0, hour=6, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_7AM = CronTrigger(minute=0, hour=7, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_8AM = CronTrigger(minute=0, hour=8, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_9AM = CronTrigger(minute=0, hour=9, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_09_30AM = CronTrigger(minute=30, hour=9, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_10AM = CronTrigger(minute=0, hour=10, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_11AM = CronTrigger(minute=0, hour=11, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_11_30AM = CronTrigger(minute=30, hour=11, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_12PM = CronTrigger(minute=0, hour=12, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_1PM = CronTrigger(minute=0, hour=13, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_2PM = CronTrigger(minute=0, hour=14, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_3PM = CronTrigger(minute=0, hour=15, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_4PM = CronTrigger(minute=0, hour=16, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_5PM = CronTrigger(minute=0, hour=17, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_6PM = CronTrigger(minute=0, hour=18, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_7PM = CronTrigger(minute=0, hour=19, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_8PM = CronTrigger(minute=0, hour=20, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_9PM = CronTrigger(minute=0, hour=21, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_10PM = CronTrigger(minute=0, hour=22, day_of_week="1-5")
+ MONDAY_TO_FRIDAY_AT_11PM = CronTrigger(minute=0, hour=23, day_of_week="1-5")
\ No newline at end of file
diff --git a/nest/core/apscheduler/enums/scheduler_type.py b/nest/core/apscheduler/enums/scheduler_type.py
new file mode 100644
index 0000000..919f6d2
--- /dev/null
+++ b/nest/core/apscheduler/enums/scheduler_type.py
@@ -0,0 +1,9 @@
+from enum import Enum
+
+class SchedulerTypes(Enum):
+ """
+ An enumeration of scheduler types.
+ """
+ CRON = 'cron'
+ DATE = 'date'
+ INTERVAL = 'interval'
\ No newline at end of file
diff --git a/nest/core/decorators/scheduler.py b/nest/core/decorators/scheduler.py
new file mode 100644
index 0000000..ffa7b19
--- /dev/null
+++ b/nest/core/decorators/scheduler.py
@@ -0,0 +1,277 @@
+from typing import Optional, Callable, Union
+from functools import wraps
+import inspect
+import weakref
+
+from nest.core.apscheduler import scheduler, start_scheduler
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+from nest.core.apscheduler.enums.scheduler_type import SchedulerTypes
+
+
+# Global registry for scheduled methods and their instances
+_scheduled_methods_registry = {}
+_pending_scheduled_methods = []
+
+def _execute_scheduled_method(instance_ref, method_name, *args, **kwargs):
+ """
+ Execute a scheduled method with the correct instance.
+ """
+ instance = instance_ref()
+ if instance is None:
+ return
+
+ try:
+ method = getattr(instance, method_name)
+ return method(*args, **kwargs)
+ except Exception as e:
+ raise
+
+def _register_pending_method(cls, method_name, job_config):
+ """
+ Register a method to be scheduled when an instance is created.
+ """
+ if cls not in _pending_scheduled_methods:
+ _pending_scheduled_methods.append((cls, method_name, job_config))
+
+def activate_scheduled_methods_for_instance(instance):
+ """
+ Activate all scheduled methods for a given instance.
+ This is called automatically by PyNest when services are instantiated.
+ """
+ cls = instance.__class__
+
+ # Check for scheduled methods in this class and its base classes
+ for method_name in dir(instance):
+ if method_name.startswith('_'):
+ continue
+
+ attr = getattr(cls, method_name, None)
+ if isinstance(attr, ScheduledMethod):
+ # Trigger the descriptor to schedule the method
+ getattr(instance, method_name)
+
+class ScheduledMethod:
+ """
+ Descriptor for scheduled methods that automatically schedules them when accessed.
+ """
+ def __init__(self, func, job_config):
+ self.func = func
+ self.job_config = job_config
+ self.scheduled_instances = weakref.WeakSet()
+ wraps(func)(self)
+
+ def __set_name__(self, owner, name):
+ self.name = name
+ self.owner = owner
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ return self
+
+ # Check if this method has already been scheduled for this instance
+ if instance not in self.scheduled_instances:
+ self._schedule_for_instance(instance)
+ self.scheduled_instances.add(instance)
+
+ return self.func.__get__(instance, owner)
+
+ def _schedule_for_instance(self, instance):
+ """
+ Schedule the method for a specific instance.
+ """
+ job_id = f"{self.job_config['type']}_{instance.__class__.__module__}_{instance.__class__.__name__}_{self.func.__name__}_{id(instance)}"
+
+ # Create weak reference to instance to avoid memory leaks
+ instance_ref = weakref.ref(instance)
+
+ # Create wrapper function that calls the method with correct instance
+ def method_wrapper():
+ return _execute_scheduled_method(instance_ref, self.func.__name__)
+
+ method_wrapper.__name__ = f"{instance.__class__.__name__}.{self.func.__name__}"
+
+ try:
+ if self.job_config['type'] == 'cron':
+ scheduler.add_job(
+ method_wrapper,
+ trigger=self.job_config['expression'].value,
+ id=job_id,
+ replace_existing=True,
+ name=f"Cron Task: {instance.__class__.__name__}.{self.func.__name__}"
+ )
+
+ elif self.job_config['type'] == 'interval':
+ scheduler.add_job(
+ method_wrapper,
+ trigger='interval',
+ id=job_id,
+ replace_existing=True,
+ name=f"Interval Task: {instance.__class__.__name__}.{self.func.__name__}",
+ **self.job_config['trigger_kwargs']
+ )
+
+ # Create interval description for logging
+ interval_desc = []
+ if self.job_config['trigger_kwargs'].get('days'):
+ interval_desc.append(f"{self.job_config['trigger_kwargs']['days']} days")
+ if self.job_config['trigger_kwargs'].get('hours'):
+ interval_desc.append(f"{self.job_config['trigger_kwargs']['hours']} hours")
+ if self.job_config['trigger_kwargs'].get('minutes'):
+ interval_desc.append(f"{self.job_config['trigger_kwargs']['minutes']} minutes")
+ if self.job_config['trigger_kwargs'].get('seconds'):
+ interval_desc.append(f"{self.job_config['trigger_kwargs']['seconds']} seconds")
+
+
+ # Start scheduler if not running
+ start_scheduler()
+
+ except Exception as e:
+ raise ValueError(f"Error scheduling method: {e}")
+
+def Cron(expression: CronExpression = CronExpression.EVERY_MINUTE) -> Callable:
+ """
+ Decorator to schedule a function or method to execute at specific times using cron expressions.
+
+ Args:
+ expression (CronExpression): A predefined cron expression.
+
+ Returns:
+ Callable: The decorated function or method.
+
+ Example:
+ @Cron(expression=CronExpression.EVERY_MINUTE)
+ def my_task():
+ print("Running every minute")
+
+ # Or in a class:
+ class MyService:
+ @Cron(expression=CronExpression.EVERY_MINUTE)
+ def my_method(self):
+ print("Method running every minute")
+ """
+ def decorator(func: Callable) -> Callable:
+ # Validate if the expression is valid
+ if not isinstance(expression, CronExpression):
+ raise ValueError("Invalid cron expression.")
+
+ # Check if it's a class method (has 'self' as first parameter)
+ sig = inspect.signature(func)
+ params = list(sig.parameters.keys())
+ is_method = len(params) > 0 and params[0] == 'self'
+
+ if is_method:
+ # For class methods, return a ScheduledMethod
+ return ScheduledMethod(func, {
+ 'type': 'cron',
+ 'expression': expression
+ })
+ else:
+ # For standalone functions, schedule directly
+ job_id = f"cron_{func.__module__}_{func.__name__}_{id(func)}"
+
+ try:
+ scheduler.add_job(
+ func,
+ trigger=expression.value,
+ id=job_id,
+ replace_existing=True,
+ name=f"Cron Task: {func.__name__}"
+ )
+
+ # Start scheduler if not running
+ start_scheduler()
+
+ except Exception as e:
+ raise ValueError(f"Error scheduling cron function: {e}")
+
+ return func
+
+ return decorator
+
+
+def Interval(
+ seconds: Optional[int] = None,
+ minutes: Optional[int] = None,
+ hours: Optional[int] = None,
+ days: Optional[int] = None
+) -> Callable:
+ """
+ Decorator to schedule a function or method to execute at regular intervals.
+
+ Args:
+ seconds (int, optional): Number of seconds between executions.
+ minutes (int, optional): Number of minutes between executions.
+ hours (int, optional): Number of hours between executions.
+ days (int, optional): Number of days between executions.
+
+ Returns:
+ Callable: The decorated function or method.
+
+ Example:
+ @Interval(seconds=30)
+ def my_task():
+ print("Running every 30 seconds")
+
+ # Or in a class:
+ class MyService:
+ @Interval(minutes=5)
+ def my_method(self):
+ print("Method running every 5 minutes")
+ """
+ def decorator(func: Callable) -> Callable:
+ # Validate that at least one time parameter was provided
+ if not any([seconds, minutes, hours, days]):
+ raise ValueError("At least one time parameter must be provided.")
+
+ # Prepare trigger parameters
+ trigger_kwargs = {}
+ if seconds is not None:
+ trigger_kwargs['seconds'] = seconds
+ if minutes is not None:
+ trigger_kwargs['minutes'] = minutes
+ if hours is not None:
+ trigger_kwargs['hours'] = hours
+ if days is not None:
+ trigger_kwargs['days'] = days
+
+ # Check if it's a class method (has 'self' as first parameter)
+ sig = inspect.signature(func)
+ params = list(sig.parameters.keys())
+ is_method = len(params) > 0 and params[0] == 'self'
+
+ if is_method:
+ # For class methods, return a ScheduledMethod
+ return ScheduledMethod(func, {
+ 'type': 'interval',
+ 'trigger_kwargs': trigger_kwargs
+ })
+ else:
+ # For standalone functions, schedule directly
+ job_id = f"interval_{func.__module__}_{func.__name__}_{id(func)}"
+
+ try:
+ scheduler.add_job(
+ func,
+ trigger='interval',
+ id=job_id,
+ replace_existing=True,
+ name=f"Interval Task: {func.__name__}",
+ **trigger_kwargs
+ )
+
+ # Start scheduler if not running
+ start_scheduler()
+
+ # Create interval description for logging
+ interval_desc = []
+ if days: interval_desc.append(f"{days} days")
+ if hours: interval_desc.append(f"{hours} hours")
+ if minutes: interval_desc.append(f"{minutes} minutes")
+ if seconds: interval_desc.append(f"{seconds} seconds")
+
+ except Exception as e:
+ raise ValueError(f"Error scheduling interval function: {e}")
+
+ return func
+
+ return decorator
\ No newline at end of file
diff --git a/nest/core/pynest_application.py b/nest/core/pynest_application.py
index 22e2669..6774d48 100644
--- a/nest/core/pynest_application.py
+++ b/nest/core/pynest_application.py
@@ -1,10 +1,13 @@
from typing import Any
+from contextlib import asynccontextmanager
from fastapi import FastAPI
from nest.common.route_resolver import RoutesResolver
from nest.core.pynest_app_context import PyNestApplicationContext
from nest.core.pynest_container import PyNestContainer
+from nest.core.apscheduler import start_scheduler, stop_scheduler
+from nest.core.decorators.scheduler import activate_scheduled_methods_for_instance
class PyNestApp(PyNestApplicationContext):
@@ -33,6 +36,51 @@ def __init__(self, container: PyNestContainer, http_server: FastAPI):
self.routes_resolver = RoutesResolver(self.container, self.http_server)
self.select_context_module()
self.register_routes()
+
+ # Activate scheduled methods for all instantiated services
+ self._activate_scheduled_methods()
+
+ # Setup lifecycle events for scheduler
+ self._setup_scheduler_lifecycle()
+
+ def _setup_scheduler_lifecycle(self):
+ """
+ Sets up lifecycle events to start and stop the scheduler.
+ """
+ @asynccontextmanager
+ async def lifespan(app: FastAPI):
+ # Startup
+ start_scheduler()
+ yield
+ # Shutdown
+ stop_scheduler()
+
+ self.http_server.router.lifespan_context = lifespan
+
+ def _activate_scheduled_methods(self):
+ """
+ Activate scheduled methods for all instantiated services in the container.
+ """
+ try:
+ # Get all modules from the container
+ for module in self.container.modules.values():
+ # Get all providers from each module
+ for provider_name, provider_class in module.providers.items():
+ try:
+ # Get the instance from the container
+ instance = self.container.get_instance(provider_class)
+ if instance is not None:
+ activate_scheduled_methods_for_instance(instance)
+ except Exception as e:
+ # Log the error for this specific provider but continue
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.debug(f"Could not activate scheduled methods for {provider_name}: {e}")
+ except Exception as e:
+ # Log the error but don't fail the application startup
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"Error activating scheduled methods: {e}")
def use(self, middleware: type, **options: Any) -> "PyNestApp":
"""
@@ -61,4 +109,4 @@ def register_routes(self):
"""
Register the routes using the RoutesResolver.
"""
- self.routes_resolver.register_routes()
+ self.routes_resolver.register_routes()
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 9acd99e..a20f11c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,48 +1,89 @@
[build-system]
-requires = ["setuptools>=61.0", "wheel>=0.37.0"]
-build-backend = "setuptools.build_meta"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
-[project]
+[tool.poetry]
name = "pynest-api"
+version = "0.4.0"
description = "PyNest is a FastAPI Abstraction for building microservices, influenced by NestJS."
+authors = ["itay.dar "]
readme = "README.md"
-requires-python = ">=3.8.1"
-license = { file = "LICENSE" }
-authors = [
- { name = "Itay Dar", email = "itay2803@gmail.com" },
+homepage = "https://github.com/PythonNest/PyNest"
+documentation = "https://pythonnest.github.io/PyNest/"
+packages = [
+ { include = "nest" }
]
-dynamic = ["version"]
classifiers = [
- "Development Status :: 5 - Production/Stable",
- "Intended Audience :: Developers",
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Operating System :: OS Independent",
+ "License :: OSI Approved :: MIT License",
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "Topic :: Software Development :: Libraries :: Python Modules",
]
-dependencies = [
- "click>=8.1.6",
- "fastapi>=0.88.0,<1.0.0",
- "python-dotenv>=1.0.0",
- "uvicorn>=0.23.1",
- "PyYAML>=6.0.1",
- "astor>=0.8.1",
- "black>=23.11.0",
- "injector>=0.20.1",
- "pydantic<2.0.0",
- "sqlalchemy == 2.0.19",
- "alembic == 1.7.5",
-]
-[tool.setuptools.dynamic]
-version = { attr = "nest.__init__.__version__" }
-[tool.pip]
-index-url = "https://pypi.org/simple"
-trusted-host = ["pypi.org", "files.pythonhosted.org"]
-[tools.black]
+[tool.poetry.dependencies]
+python = "^3.9"
+# Core dependencies
+click = "^8.1.7"
+injector = "^0.22.0"
+astor = "^0.8.1"
+pyyaml = "^6.0.2"
+fastapi = "^0.115.4"
+pydantic = "^2.9.2"
+uvicorn = "^0.32.0"
+APScheduler = "^3.10.4"
+pytz = "^2024.2"
+six = "^1.16.0"
+tzlocal = "^5.2"
+
+
+# Optional dependencies
+sqlalchemy = { version = "^2.0.36", optional = true }
+asyncpg = { version = "^0.30.0", optional = true }
+psycopg2 = { version = "^2.9.3", optional = true }
+alembic = { version = "^1.13.3", optional = true }
+beanie = { version = "^1.27.0", optional = true }
+python-dotenv = { version = "^1.0.1", optional = true }
+greenlet = { version = "^3.1.1", optional = true }
+black = "^24.10.0"
+
+
+
+[tool.poetry.extras]
+postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"]
+mongo = ["beanie", "python-dotenv"]
+test = ["pytest"]
+
+[tool.poetry.group.build.dependencies]
+setuptools = "^75.3.0"
+wheel = "^0.44.0"
+build = "^1.2.2.post1"
+twine = "^5.1.1"
+git-changelog = "^2.5.2"
+
+[tool.poetry.group.test.dependencies]
+pytest = "^7.0.1"
+fastapi = "^0.115.4"
+sqlalchemy = "^2.0.36"
+motor = "^3.2.0"
+beanie = "^1.27.0"
+pydantic = "^2.9.2"
+python-dotenv = "^1.0.1"
+uvicorn = "^0.32.0"
+
+[tool.poetry.group.docs.dependencies]
+mkdocs-material = "^9.5.43"
+mkdocstrings-python = "^1.12.2"
+
+
+[tool.black]
force-exclude = '''
/(
| /*venv*
@@ -58,34 +99,15 @@ force-exclude = '''
)/
'''
-[project.optional-dependencies]
-test = [
- "pytest == 6.2.5",
-]
-
-orm = [
- "sqlalchemy == 2.0.19",
- "alembic == 1.7.4",
-]
-mongo = [
- "pymongo == 3.12.0",
- "motor == 3.2.0",
- "beanie == 1.20.0",
-]
-
-[project.scripts]
-pynest = "nest.cli.cli:nest_cli"
-
-[tool.setuptools.packages.find]
-include = ["nest*"]
-namespaces = false
-
[tool.mypy]
exclude = [
"/*venv*"
]
ignore_missing_imports = true
-[project.urls]
-"Homepage" = "https://github.com/PythonNest/PyNest"
-"Documentation" = "https://pythonnest.github.io/PyNest/"
+[tool.poetry.urls]
+Homepage = "https://github.com/PythonNest/PyNest"
+Documentation = "https://pythonnest.github.io/PyNest/"
+
+[tool.poetry.scripts]
+pynest = "nest.cli.cli:nest_cli"
diff --git a/requirements-dev.txt b/requirements-dev.txt
index aa977bb..3e2c94a 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,4 +7,7 @@ click==8.1.6
PyYAML==6.0.1
injector==0.21.0
pydantic<2.0.0
-astor>=0.8.1
\ No newline at end of file
+APScheduler==3.10.4
+pytz==2024.2
+six==1.16.0
+tzlocal==5.2
\ No newline at end of file
diff --git a/requirements-release.txt b/requirements-release.txt
index 0d30d21..38111fe 100644
--- a/requirements-release.txt
+++ b/requirements-release.txt
@@ -6,6 +6,10 @@ beanie==1.20.0
PyYAML==6.0.1
injector==0.21.0
astor==0.8.1
+APScheduler==3.10.4
+pytz==2024.2
+six==1.16.0
+tzlocal==5.2
# package release
setuptools
diff --git a/requirements-tests.txt b/requirements-tests.txt
index a17e03c..f68f948 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -10,3 +10,7 @@ pydantic<2.0.0
python-dotenv>=1.0.0
uvicorn>=0.23.1
astor>=0.8.1
+APScheduler==3.10.4
+pytz==2024.2
+six==1.16.0
+tzlocal==5.2
\ No newline at end of file
diff --git a/tests/test_core/test_decorators/test_scheduler.py b/tests/test_core/test_decorators/test_scheduler.py
new file mode 100644
index 0000000..594e9c4
--- /dev/null
+++ b/tests/test_core/test_decorators/test_scheduler.py
@@ -0,0 +1,119 @@
+import pytest
+import time
+from unittest.mock import patch, MagicMock
+
+from nest.core.decorators.scheduler import Cron, Interval
+from nest.core.apscheduler.enums.cron_expression import CronExpression
+from nest.core.apscheduler import scheduler
+
+
+class TestSchedulerDecorators:
+ """Tests for task scheduling decorators."""
+
+ def setup_method(self):
+ """Setup executed before each test."""
+ # Clear all jobs from scheduler
+ scheduler.remove_all_jobs()
+
+ def teardown_method(self):
+ """Cleanup executed after each test."""
+ # Remove all jobs from scheduler
+ scheduler.remove_all_jobs()
+
+ @patch('nest.core.decorators.scheduler.scheduler')
+ def test_cron_decorator_valid_expression(self, mock_scheduler):
+ """Tests the @Cron decorator with valid expression."""
+ # Arrange
+ mock_scheduler.add_job = MagicMock()
+
+ # Act
+ @Cron(expression=CronExpression.EVERY_MINUTE)
+ def test_function():
+ return "test"
+
+ result = test_function()
+
+ # Assert
+ assert result == "test"
+ mock_scheduler.add_job.assert_called_once()
+ call_args = mock_scheduler.add_job.call_args
+ assert call_args[0][0] == test_function
+ assert call_args[1]['trigger'] == CronExpression.EVERY_MINUTE.value
+ assert 'id' in call_args[1]
+
+ def test_cron_decorator_invalid_expression(self):
+ """Tests the @Cron decorator with invalid expression."""
+ # Act & Assert
+ with pytest.raises(ValueError, match="Invalid cron expression"):
+ @Cron(expression="invalid")
+ def test_function():
+ return "test"
+
+ @patch('nest.core.decorators.scheduler.scheduler')
+ def test_interval_decorator_with_seconds(self, mock_scheduler):
+ """Tests the @Interval decorator with seconds."""
+ # Arrange
+ mock_scheduler.add_job = MagicMock()
+
+ # Act
+ @Interval(seconds=30)
+ def test_function():
+ return "test"
+
+ result = test_function()
+
+ # Assert
+ assert result == "test"
+ mock_scheduler.add_job.assert_called_once()
+ call_args = mock_scheduler.add_job.call_args
+ assert call_args[0][0] == test_function
+ assert call_args[1]['trigger'] == 'interval'
+ assert call_args[1]['seconds'] == 30
+
+ @patch('nest.core.decorators.scheduler.scheduler')
+ def test_interval_decorator_with_minutes(self, mock_scheduler):
+ """Tests the @Interval decorator with minutes."""
+ # Arrange
+ mock_scheduler.add_job = MagicMock()
+
+ # Act
+ @Interval(minutes=5)
+ def test_function():
+ return "test"
+
+ result = test_function()
+
+ # Assert
+ assert result == "test"
+ mock_scheduler.add_job.assert_called_once()
+ call_args = mock_scheduler.add_job.call_args
+ assert call_args[1]['minutes'] == 5
+
+ def test_interval_decorator_no_parameters(self):
+ """Tests the @Interval decorator without parameters."""
+ # Act & Assert
+ with pytest.raises(ValueError, match="At least one time parameter must be provided"):
+ @Interval()
+ def test_function():
+ return "test"
+
+ @patch('nest.core.decorators.scheduler.scheduler')
+ def test_interval_decorator_multiple_parameters(self, mock_scheduler):
+ """Tests the @Interval decorator with multiple parameters."""
+ # Arrange
+ mock_scheduler.add_job = MagicMock()
+
+ # Act
+ @Interval(seconds=30, minutes=1, hours=2)
+ def test_function():
+ return "test"
+
+ result = test_function()
+
+ # Assert
+ assert result == "test"
+ mock_scheduler.add_job.assert_called_once()
+ call_args = mock_scheduler.add_job.call_args
+ assert call_args[1]['seconds'] == 30
+ assert call_args[1]['minutes'] == 1
+ assert call_args[1]['hours'] == 2
\ No newline at end of file