We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error on scheduler: Only timezones from the pytz library are supported
### Steps to reproduce:
- Step 1: `flexget -L verbose daemon start`

#### Config:

```yaml
schedules:
  - tasks: ['some-task']
    interval:
      hours: 1
```
  
#### Backtrace:

```
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/__init__.py", line 44, in main
    manager.start()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 383, in start
    self.handle_cli()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 409, in handle_cli
    self.daemon_command(command_options)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 506, in daemon_command
    run_daemon()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 487, in run_daemon
    fire_event('manager.daemon.started', self)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/event.py", line 109, in fire_event
    result = event(*args, **kwargs)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/components/scheduler/scheduler.py", line 126, in setup_scheduler
    scheduler = BackgroundScheduler(
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 87, in __init__
    self.configure(gconfig, **options)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 126, in configure
    self._configure(config)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/background.py", line 29, in _configure
    super(BackgroundScheduler, self)._configure(config)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 697, in _configure
    self.timezone = astimezone(config.pop('timezone', None)) or get_localzone()
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/util.py", line 93, in astimezone
    raise TypeError('Only timezones from the pytz library are supported')
TypeError: Only timezones from the pytz library are supported
```

### Additional information:

- FlexGet version: 3.5.2
- Python version: 3.9.2
- Installation method:
- Using daemon (yes/no): yes

It seems to have started after https://github.com/Flexget/Flexget/pull/3453 that change the timezone argument to a non-pytz compatible object.
Error on scheduler: Only timezones from the pytz library are supported
### Steps to reproduce:
- Step 1: `flexget -L verbose daemon start`

#### Config:

```yaml
schedules:
  - tasks: ['some-task']
    interval:
      hours: 1
```
  
#### Backtrace:

```
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/__init__.py", line 44, in main
    manager.start()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 383, in start
    self.handle_cli()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 409, in handle_cli
    self.daemon_command(command_options)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 506, in daemon_command
    run_daemon()
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py", line 487, in run_daemon
    fire_event('manager.daemon.started', self)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/event.py", line 109, in fire_event
    result = event(*args, **kwargs)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/event.py", line 20, in __call__
    return self.func(*args, **kwargs)
  File "/home/pi/.local/lib/python3.9/site-packages/flexget/components/scheduler/scheduler.py", line 126, in setup_scheduler
    scheduler = BackgroundScheduler(
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 87, in __init__
    self.configure(gconfig, **options)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 126, in configure
    self._configure(config)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/background.py", line 29, in _configure
    super(BackgroundScheduler, self)._configure(config)
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py", line 697, in _configure
    self.timezone = astimezone(config.pop('timezone', None)) or get_localzone()
  File "/home/pi/.local/lib/python3.9/site-packages/apscheduler/util.py", line 93, in astimezone
    raise TypeError('Only timezones from the pytz library are supported')
TypeError: Only timezones from the pytz library are supported
```

### Additional information:

- FlexGet version: 3.5.2
- Python version: 3.9.2
- Installation method:
- Using daemon (yes/no): yes

It seems to have started after https://github.com/Flexget/Flexget/pull/3453 that change the timezone argument to a non-pytz compatible object.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `flexget/components/scheduler/scheduler.py`
Content:
```
1 import datetime
2 import hashlib
3 import logging
4 import os
5 import struct
6 
7 from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
8 from apscheduler.schedulers.background import BackgroundScheduler
9 from apscheduler.triggers.cron import CronTrigger
10 from loguru import logger
11 
12 from flexget.config_schema import format_checker, register_config_key, register_schema
13 from flexget.event import event
14 from flexget.manager import manager
15 from flexget.utils import json
16 
17 logger = logger.bind(name='scheduler')
18 
19 
20 # Add a format checker for more detailed errors on cron type schedules
21 @format_checker.checks('cron_schedule', raises=ValueError)
22 def is_cron_schedule(instance):
23     if not isinstance(instance, dict):
24         return True
25     try:
26         return CronTrigger(**instance)
27     except TypeError:
28         # A more specific error message about which key will also be shown by properties schema keyword
29         raise ValueError('Invalid key for schedule.')
30 
31 
32 DEFAULT_SCHEDULES = [{'tasks': ['*'], 'interval': {'hours': 1}}]
33 
34 UNITS = ['minutes', 'hours', 'days', 'weeks']
35 interval_schema = {
36     'type': 'object',
37     'title': 'Simple Interval',
38     'properties': {
39         'minutes': {'type': 'number'},
40         'hours': {'type': 'number'},
41         'days': {'type': 'number'},
42         'weeks': {'type': 'number'},
43         'jitter': {'type': 'integer'},
44     },
45     'anyOf': [{'required': [unit]} for unit in UNITS],
46     'error_anyOf': 'Interval must be specified as one or more of %s' % ', '.join(UNITS),
47     'additionalProperties': False,
48 }
49 
50 cron_schema = {
51     'type': 'object',
52     'title': 'Advanced Cron Interval',
53     'properties': {
54         'year': {'type': ['integer', 'string']},
55         'month': {'type': ['integer', 'string']},
56         'day': {'type': ['integer', 'string']},
57         'week': {'type': ['integer', 'string']},
58         'day_of_week': {'type': ['integer', 'string']},
59         'hour': {'type': ['integer', 'string']},
60         'minute': {'type': ['integer', 'string']},
61         'jitter': {'type': 'integer'},
62     },
63     'additionalProperties': False,
64 }
65 
66 schedule_schema = {
67     'type': 'object',
68     'title': 'Schedule',
69     'description': 'A schedule which runs specified tasks periodically.',
70     'properties': {
71         'tasks': {'type': ['array', 'string'], 'items': {'type': 'string'}},
72         'interval': interval_schema,
73         'schedule': cron_schema,
74     },
75     'required': ['tasks'],
76     'minProperties': 2,
77     'maxProperties': 2,
78     'error_minProperties': 'Either `cron` or `interval` must be defined.',
79     'error_maxProperties': 'Either `cron` or `interval` must be defined.',
80     'additionalProperties': False,
81 }
82 
83 main_schema = {
84     'oneOf': [
85         {'type': 'array', 'title': 'Enable', 'items': schedule_schema},
86         {'type': 'boolean', 'title': 'Disable', 'description': 'Disable task schedules'},
87     ]
88 }
89 
90 scheduler = None
91 scheduler_job_map = {}
92 
93 
94 def job_id(conf):
95     """Create a unique id for a schedule item in config."""
96     return hashlib.sha1(json.dumps(conf, sort_keys=True).encode('utf-8')).hexdigest()
97 
98 
99 def run_job(tasks):
100     """Add the execution to the queue and waits until it is finished"""
101     logger.debug('executing tasks: {}', tasks)
102     finished_events = manager.execute(
103         options={'tasks': tasks, 'cron': True, 'allow_manual': False}, priority=5
104     )
105     for _, task_name, event_ in finished_events:
106         logger.debug('task finished executing: {}', task_name)
107         event_.wait()
108     logger.debug('all tasks in schedule finished executing')
109 
110 
111 @event('manager.daemon.started')
112 def setup_scheduler(manager):
113     """Configure and start apscheduler"""
114     global scheduler
115     if logger.level(manager.options.loglevel).no > logger.level('DEBUG').no:
116         logging.getLogger('apscheduler').setLevel(logging.WARNING)
117     # Since APScheduler runs in a separate thread, slower devices can sometimes get a DB lock, so use a separate db
118     # for the jobs to avoid this
119     db_filename = os.path.join(manager.config_base, 'db-%s-jobs.sqlite' % manager.config_name)
120     # in case running on windows, needs double \\
121     db_filename = db_filename.replace('\\', '\\\\')
122     database_uri = 'sqlite:///%s' % db_filename
123     jobstores = {'default': SQLAlchemyJobStore(url=database_uri)}
124     # If job was meant to run within last day while daemon was shutdown, run it once when continuing
125     job_defaults = {'coalesce': True, 'misfire_grace_time': 60 * 60 * 24}
126     scheduler = BackgroundScheduler(
127         jobstores=jobstores,
128         job_defaults=job_defaults,
129         timezone=datetime.datetime.now().astimezone().tzinfo,
130     )
131     setup_jobs(manager)
132 
133 
134 @event('manager.config_updated')
135 def setup_jobs(manager):
136     """Set up the jobs for apscheduler to run."""
137     if not manager.is_daemon:
138         return
139 
140     global scheduler_job_map
141     scheduler_job_map = {}
142 
143     if 'schedules' not in manager.config:
144         logger.info(
145             'No schedules defined in config. Defaulting to run all tasks on a 1 hour interval.'
146         )
147     config = manager.config.get('schedules', True)
148     if config is True:
149         config = DEFAULT_SCHEDULES
150     elif not config:  # Schedules are disabled with `schedules: no`
151         if scheduler.running:
152             logger.info('Shutting down scheduler')
153             scheduler.shutdown()
154         return
155     if not scheduler.running:
156         logger.info('Starting scheduler')
157         scheduler.start(paused=True)
158     existing_job_ids = [job.id for job in scheduler.get_jobs()]
159     configured_job_ids = []
160     for job_config in config:
161         jid = job_id(job_config)
162         configured_job_ids.append(jid)
163         scheduler_job_map[id(job_config)] = jid
164         if jid in existing_job_ids:
165             continue
166         if 'interval' in job_config:
167             trigger, trigger_args = 'interval', job_config['interval']
168         else:
169             trigger, trigger_args = 'cron', job_config['schedule']
170         tasks = job_config['tasks']
171         if not isinstance(tasks, list):
172             tasks = [tasks]
173         name = ','.join(tasks)
174         scheduler.add_job(
175             run_job, args=(tasks,), id=jid, name=name, trigger=trigger, **trigger_args
176         )
177     # Remove jobs no longer in config
178     for jid in existing_job_ids:
179         if jid not in configured_job_ids:
180             scheduler.remove_job(jid)
181     scheduler.resume()
182 
183 
184 @event('manager.shutdown_requested')
185 def shutdown_requested(manager):
186     if scheduler and scheduler.running:
187         scheduler.shutdown(wait=True)
188 
189 
190 @event('manager.shutdown')
191 def stop_scheduler(manager):
192     if scheduler and scheduler.running:
193         scheduler.shutdown(wait=False)
194 
195 
196 @event('config.register')
197 def register_config():
198     register_config_key('schedules', main_schema)
199     register_schema('/schema/config/schedule', schedule_schema)
200
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/flexget/components/scheduler/scheduler.py b/flexget/components/scheduler/scheduler.py
--- a/flexget/components/scheduler/scheduler.py
+++ b/flexget/components/scheduler/scheduler.py
@@ -1,4 +1,3 @@
-import datetime
 import hashlib
 import logging
 import os
@@ -126,7 +125,6 @@
     scheduler = BackgroundScheduler(
         jobstores=jobstores,
         job_defaults=job_defaults,
-        timezone=datetime.datetime.now().astimezone().tzinfo,
     )
     setup_jobs(manager)

{"golden_diff": "diff --git a/flexget/components/scheduler/scheduler.py b/flexget/components/scheduler/scheduler.py\n--- a/flexget/components/scheduler/scheduler.py\n+++ b/flexget/components/scheduler/scheduler.py\n@@ -1,4 +1,3 @@\n-import datetime\n import hashlib\n import logging\n import os\n@@ -126,7 +125,6 @@\n     scheduler = BackgroundScheduler(\n         jobstores=jobstores,\n         job_defaults=job_defaults,\n-        timezone=datetime.datetime.now().astimezone().tzinfo,\n     )\n     setup_jobs(manager)\n", "issue": "Error on scheduler: Only timezones from the pytz library are supported\n### Steps to reproduce:\r\n- Step 1: `flexget -L verbose daemon start`\r\n\r\n#### Config:\r\n\r\n```yaml\r\nschedules:\r\n  - tasks: ['some-task']\r\n    interval:\r\n      hours: 1\r\n```\r\n  \r\n#### Backtrace:\r\n\r\n```\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/__init__.py\", line 44, in main\r\n    manager.start()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 383, in start\r\n    self.handle_cli()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 409, in handle_cli\r\n    self.daemon_command(command_options)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 506, in daemon_command\r\n    run_daemon()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 487, in run_daemon\r\n    fire_event('manager.daemon.started', self)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/event.py\", line 109, in fire_event\r\n    result = event(*args, **kwargs)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/event.py\", line 20, in __call__\r\n    return self.func(*args, **kwargs)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/components/scheduler/scheduler.py\", line 126, in setup_scheduler\r\n    scheduler = BackgroundScheduler(\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 87, in __init__\r\n    self.configure(gconfig, **options)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 126, in configure\r\n    self._configure(config)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/background.py\", line 29, in _configure\r\n    super(BackgroundScheduler, self)._configure(config)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 697, in _configure\r\n    self.timezone = astimezone(config.pop('timezone', None)) or get_localzone()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/util.py\", line 93, in astimezone\r\n    raise TypeError('Only timezones from the pytz library are supported')\r\nTypeError: Only timezones from the pytz library are supported\r\n```\r\n\r\n### Additional information:\r\n\r\n- FlexGet version: 3.5.2\r\n- Python version: 3.9.2\r\n- Installation method:\r\n- Using daemon (yes/no): yes\r\n\r\nIt seems to have started after https://github.com/Flexget/Flexget/pull/3453 that change the timezone argument to a non-pytz compatible object.\nError on scheduler: Only timezones from the pytz library are supported\n### Steps to reproduce:\r\n- Step 1: `flexget -L verbose daemon start`\r\n\r\n#### Config:\r\n\r\n```yaml\r\nschedules:\r\n  - tasks: ['some-task']\r\n    interval:\r\n      hours: 1\r\n```\r\n  \r\n#### Backtrace:\r\n\r\n```\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/__init__.py\", line 44, in main\r\n    manager.start()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 383, in start\r\n    self.handle_cli()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 409, in handle_cli\r\n    self.daemon_command(command_options)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 506, in daemon_command\r\n    run_daemon()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/manager.py\", line 487, in run_daemon\r\n    fire_event('manager.daemon.started', self)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/event.py\", line 109, in fire_event\r\n    result = event(*args, **kwargs)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/event.py\", line 20, in __call__\r\n    return self.func(*args, **kwargs)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/flexget/components/scheduler/scheduler.py\", line 126, in setup_scheduler\r\n    scheduler = BackgroundScheduler(\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 87, in __init__\r\n    self.configure(gconfig, **options)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 126, in configure\r\n    self._configure(config)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/background.py\", line 29, in _configure\r\n    super(BackgroundScheduler, self)._configure(config)\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/schedulers/base.py\", line 697, in _configure\r\n    self.timezone = astimezone(config.pop('timezone', None)) or get_localzone()\r\n  File \"/home/pi/.local/lib/python3.9/site-packages/apscheduler/util.py\", line 93, in astimezone\r\n    raise TypeError('Only timezones from the pytz library are supported')\r\nTypeError: Only timezones from the pytz library are supported\r\n```\r\n\r\n### Additional information:\r\n\r\n- FlexGet version: 3.5.2\r\n- Python version: 3.9.2\r\n- Installation method:\r\n- Using daemon (yes/no): yes\r\n\r\nIt seems to have started after https://github.com/Flexget/Flexget/pull/3453 that change the timezone argument to a non-pytz compatible object.\n", "before_files": [{"content": "import datetime\nimport hashlib\nimport logging\nimport os\nimport struct\n\nfrom apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore\nfrom apscheduler.schedulers.background import BackgroundScheduler\nfrom apscheduler.triggers.cron import CronTrigger\nfrom loguru import logger\n\nfrom flexget.config_schema import format_checker, register_config_key, register_schema\nfrom flexget.event import event\nfrom flexget.manager import manager\nfrom flexget.utils import json\n\nlogger = logger.bind(name='scheduler')\n\n\n# Add a format checker for more detailed errors on cron type schedules\n@format_checker.checks('cron_schedule', raises=ValueError)\ndef is_cron_schedule(instance):\n    if not isinstance(instance, dict):\n        return True\n    try:\n        return CronTrigger(**instance)\n    except TypeError:\n        # A more specific error message about which key will also be shown by properties schema keyword\n        raise ValueError('Invalid key for schedule.')\n\n\nDEFAULT_SCHEDULES = [{'tasks': ['*'], 'interval': {'hours': 1}}]\n\nUNITS = ['minutes', 'hours', 'days', 'weeks']\ninterval_schema = {\n    'type': 'object',\n    'title': 'Simple Interval',\n    'properties': {\n        'minutes': {'type': 'number'},\n        'hours': {'type': 'number'},\n        'days': {'type': 'number'},\n        'weeks': {'type': 'number'},\n        'jitter': {'type': 'integer'},\n    },\n    'anyOf': [{'required': [unit]} for unit in UNITS],\n    'error_anyOf': 'Interval must be specified as one or more of %s' % ', '.join(UNITS),\n    'additionalProperties': False,\n}\n\ncron_schema = {\n    'type': 'object',\n    'title': 'Advanced Cron Interval',\n    'properties': {\n        'year': {'type': ['integer', 'string']},\n        'month': {'type': ['integer', 'string']},\n        'day': {'type': ['integer', 'string']},\n        'week': {'type': ['integer', 'string']},\n        'day_of_week': {'type': ['integer', 'string']},\n        'hour': {'type': ['integer', 'string']},\n        'minute': {'type': ['integer', 'string']},\n        'jitter': {'type': 'integer'},\n    },\n    'additionalProperties': False,\n}\n\nschedule_schema = {\n    'type': 'object',\n    'title': 'Schedule',\n    'description': 'A schedule which runs specified tasks periodically.',\n    'properties': {\n        'tasks': {'type': ['array', 'string'], 'items': {'type': 'string'}},\n        'interval': interval_schema,\n        'schedule': cron_schema,\n    },\n    'required': ['tasks'],\n    'minProperties': 2,\n    'maxProperties': 2,\n    'error_minProperties': 'Either `cron` or `interval` must be defined.',\n    'error_maxProperties': 'Either `cron` or `interval` must be defined.',\n    'additionalProperties': False,\n}\n\nmain_schema = {\n    'oneOf': [\n        {'type': 'array', 'title': 'Enable', 'items': schedule_schema},\n        {'type': 'boolean', 'title': 'Disable', 'description': 'Disable task schedules'},\n    ]\n}\n\nscheduler = None\nscheduler_job_map = {}\n\n\ndef job_id(conf):\n    \"\"\"Create a unique id for a schedule item in config.\"\"\"\n    return hashlib.sha1(json.dumps(conf, sort_keys=True).encode('utf-8')).hexdigest()\n\n\ndef run_job(tasks):\n    \"\"\"Add the execution to the queue and waits until it is finished\"\"\"\n    logger.debug('executing tasks: {}', tasks)\n    finished_events = manager.execute(\n        options={'tasks': tasks, 'cron': True, 'allow_manual': False}, priority=5\n    )\n    for _, task_name, event_ in finished_events:\n        logger.debug('task finished executing: {}', task_name)\n        event_.wait()\n    logger.debug('all tasks in schedule finished executing')\n\n\n@event('manager.daemon.started')\ndef setup_scheduler(manager):\n    \"\"\"Configure and start apscheduler\"\"\"\n    global scheduler\n    if logger.level(manager.options.loglevel).no > logger.level('DEBUG').no:\n        logging.getLogger('apscheduler').setLevel(logging.WARNING)\n    # Since APScheduler runs in a separate thread, slower devices can sometimes get a DB lock, so use a separate db\n    # for the jobs to avoid this\n    db_filename = os.path.join(manager.config_base, 'db-%s-jobs.sqlite' % manager.config_name)\n    # in case running on windows, needs double \\\\\n    db_filename = db_filename.replace('\\\\', '\\\\\\\\')\n    database_uri = 'sqlite:///%s' % db_filename\n    jobstores = {'default': SQLAlchemyJobStore(url=database_uri)}\n    # If job was meant to run within last day while daemon was shutdown, run it once when continuing\n    job_defaults = {'coalesce': True, 'misfire_grace_time': 60 * 60 * 24}\n    scheduler = BackgroundScheduler(\n        jobstores=jobstores,\n        job_defaults=job_defaults,\n        timezone=datetime.datetime.now().astimezone().tzinfo,\n    )\n    setup_jobs(manager)\n\n\n@event('manager.config_updated')\ndef setup_jobs(manager):\n    \"\"\"Set up the jobs for apscheduler to run.\"\"\"\n    if not manager.is_daemon:\n        return\n\n    global scheduler_job_map\n    scheduler_job_map = {}\n\n    if 'schedules' not in manager.config:\n        logger.info(\n            'No schedules defined in config. Defaulting to run all tasks on a 1 hour interval.'\n        )\n    config = manager.config.get('schedules', True)\n    if config is True:\n        config = DEFAULT_SCHEDULES\n    elif not config:  # Schedules are disabled with `schedules: no`\n        if scheduler.running:\n            logger.info('Shutting down scheduler')\n            scheduler.shutdown()\n        return\n    if not scheduler.running:\n        logger.info('Starting scheduler')\n        scheduler.start(paused=True)\n    existing_job_ids = [job.id for job in scheduler.get_jobs()]\n    configured_job_ids = []\n    for job_config in config:\n        jid = job_id(job_config)\n        configured_job_ids.append(jid)\n        scheduler_job_map[id(job_config)] = jid\n        if jid in existing_job_ids:\n            continue\n        if 'interval' in job_config:\n            trigger, trigger_args = 'interval', job_config['interval']\n        else:\n            trigger, trigger_args = 'cron', job_config['schedule']\n        tasks = job_config['tasks']\n        if not isinstance(tasks, list):\n            tasks = [tasks]\n        name = ','.join(tasks)\n        scheduler.add_job(\n            run_job, args=(tasks,), id=jid, name=name, trigger=trigger, **trigger_args\n        )\n    # Remove jobs no longer in config\n    for jid in existing_job_ids:\n        if jid not in configured_job_ids:\n            scheduler.remove_job(jid)\n    scheduler.resume()\n\n\n@event('manager.shutdown_requested')\ndef shutdown_requested(manager):\n    if scheduler and scheduler.running:\n        scheduler.shutdown(wait=True)\n\n\n@event('manager.shutdown')\ndef stop_scheduler(manager):\n    if scheduler and scheduler.running:\n        scheduler.shutdown(wait=False)\n\n\n@event('config.register')\ndef register_config():\n    register_config_key('schedules', main_schema)\n    register_schema('/schema/config/schedule', schedule_schema)\n", "path": "flexget/components/scheduler/scheduler.py"}], "after_files": [{"content": "import hashlib\nimport logging\nimport os\nimport struct\n\nfrom apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore\nfrom apscheduler.schedulers.background import BackgroundScheduler\nfrom apscheduler.triggers.cron import CronTrigger\nfrom loguru import logger\n\nfrom flexget.config_schema import format_checker, register_config_key, register_schema\nfrom flexget.event import event\nfrom flexget.manager import manager\nfrom flexget.utils import json\n\nlogger = logger.bind(name='scheduler')\n\n\n# Add a format checker for more detailed errors on cron type schedules\n@format_checker.checks('cron_schedule', raises=ValueError)\ndef is_cron_schedule(instance):\n    if not isinstance(instance, dict):\n        return True\n    try:\n        return CronTrigger(**instance)\n    except TypeError:\n        # A more specific error message about which key will also be shown by properties schema keyword\n        raise ValueError('Invalid key for schedule.')\n\n\nDEFAULT_SCHEDULES = [{'tasks': ['*'], 'interval': {'hours': 1}}]\n\nUNITS = ['minutes', 'hours', 'days', 'weeks']\ninterval_schema = {\n    'type': 'object',\n    'title': 'Simple Interval',\n    'properties': {\n        'minutes': {'type': 'number'},\n        'hours': {'type': 'number'},\n        'days': {'type': 'number'},\n        'weeks': {'type': 'number'},\n        'jitter': {'type': 'integer'},\n    },\n    'anyOf': [{'required': [unit]} for unit in UNITS],\n    'error_anyOf': 'Interval must be specified as one or more of %s' % ', '.join(UNITS),\n    'additionalProperties': False,\n}\n\ncron_schema = {\n    'type': 'object',\n    'title': 'Advanced Cron Interval',\n    'properties': {\n        'year': {'type': ['integer', 'string']},\n        'month': {'type': ['integer', 'string']},\n        'day': {'type': ['integer', 'string']},\n        'week': {'type': ['integer', 'string']},\n        'day_of_week': {'type': ['integer', 'string']},\n        'hour': {'type': ['integer', 'string']},\n        'minute': {'type': ['integer', 'string']},\n        'jitter': {'type': 'integer'},\n    },\n    'additionalProperties': False,\n}\n\nschedule_schema = {\n    'type': 'object',\n    'title': 'Schedule',\n    'description': 'A schedule which runs specified tasks periodically.',\n    'properties': {\n        'tasks': {'type': ['array', 'string'], 'items': {'type': 'string'}},\n        'interval': interval_schema,\n        'schedule': cron_schema,\n    },\n    'required': ['tasks'],\n    'minProperties': 2,\n    'maxProperties': 2,\n    'error_minProperties': 'Either `cron` or `interval` must be defined.',\n    'error_maxProperties': 'Either `cron` or `interval` must be defined.',\n    'additionalProperties': False,\n}\n\nmain_schema = {\n    'oneOf': [\n        {'type': 'array', 'title': 'Enable', 'items': schedule_schema},\n        {'type': 'boolean', 'title': 'Disable', 'description': 'Disable task schedules'},\n    ]\n}\n\nscheduler = None\nscheduler_job_map = {}\n\n\ndef job_id(conf):\n    \"\"\"Create a unique id for a schedule item in config.\"\"\"\n    return hashlib.sha1(json.dumps(conf, sort_keys=True).encode('utf-8')).hexdigest()\n\n\ndef run_job(tasks):\n    \"\"\"Add the execution to the queue and waits until it is finished\"\"\"\n    logger.debug('executing tasks: {}', tasks)\n    finished_events = manager.execute(\n        options={'tasks': tasks, 'cron': True, 'allow_manual': False}, priority=5\n    )\n    for _, task_name, event_ in finished_events:\n        logger.debug('task finished executing: {}', task_name)\n        event_.wait()\n    logger.debug('all tasks in schedule finished executing')\n\n\n@event('manager.daemon.started')\ndef setup_scheduler(manager):\n    \"\"\"Configure and start apscheduler\"\"\"\n    global scheduler\n    if logger.level(manager.options.loglevel).no > logger.level('DEBUG').no:\n        logging.getLogger('apscheduler').setLevel(logging.WARNING)\n    # Since APScheduler runs in a separate thread, slower devices can sometimes get a DB lock, so use a separate db\n    # for the jobs to avoid this\n    db_filename = os.path.join(manager.config_base, 'db-%s-jobs.sqlite' % manager.config_name)\n    # in case running on windows, needs double \\\\\n    db_filename = db_filename.replace('\\\\', '\\\\\\\\')\n    database_uri = 'sqlite:///%s' % db_filename\n    jobstores = {'default': SQLAlchemyJobStore(url=database_uri)}\n    # If job was meant to run within last day while daemon was shutdown, run it once when continuing\n    job_defaults = {'coalesce': True, 'misfire_grace_time': 60 * 60 * 24}\n    scheduler = BackgroundScheduler(\n        jobstores=jobstores,\n        job_defaults=job_defaults,\n    )\n    setup_jobs(manager)\n\n\n@event('manager.config_updated')\ndef setup_jobs(manager):\n    \"\"\"Set up the jobs for apscheduler to run.\"\"\"\n    if not manager.is_daemon:\n        return\n\n    global scheduler_job_map\n    scheduler_job_map = {}\n\n    if 'schedules' not in manager.config:\n        logger.info(\n            'No schedules defined in config. Defaulting to run all tasks on a 1 hour interval.'\n        )\n    config = manager.config.get('schedules', True)\n    if config is True:\n        config = DEFAULT_SCHEDULES\n    elif not config:  # Schedules are disabled with `schedules: no`\n        if scheduler.running:\n            logger.info('Shutting down scheduler')\n            scheduler.shutdown()\n        return\n    if not scheduler.running:\n        logger.info('Starting scheduler')\n        scheduler.start(paused=True)\n    existing_job_ids = [job.id for job in scheduler.get_jobs()]\n    configured_job_ids = []\n    for job_config in config:\n        jid = job_id(job_config)\n        configured_job_ids.append(jid)\n        scheduler_job_map[id(job_config)] = jid\n        if jid in existing_job_ids:\n            continue\n        if 'interval' in job_config:\n            trigger, trigger_args = 'interval', job_config['interval']\n        else:\n            trigger, trigger_args = 'cron', job_config['schedule']\n        tasks = job_config['tasks']\n        if not isinstance(tasks, list):\n            tasks = [tasks]\n        name = ','.join(tasks)\n        scheduler.add_job(\n            run_job, args=(tasks,), id=jid, name=name, trigger=trigger, **trigger_args\n        )\n    # Remove jobs no longer in config\n    for jid in existing_job_ids:\n        if jid not in configured_job_ids:\n            scheduler.remove_job(jid)\n    scheduler.resume()\n\n\n@event('manager.shutdown_requested')\ndef shutdown_requested(manager):\n    if scheduler and scheduler.running:\n        scheduler.shutdown(wait=True)\n\n\n@event('manager.shutdown')\ndef stop_scheduler(manager):\n    if scheduler and scheduler.running:\n        scheduler.shutdown(wait=False)\n\n\n@event('config.register')\ndef register_config():\n    register_config_key('schedules', main_schema)\n    register_schema('/schema/config/schedule', schedule_schema)\n", "path": "flexget/components/scheduler/scheduler.py"}]}