schd 0.0.16__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
schd/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.0.16'
1
+ __version__ = '0.1.1'
schd/cmds/base.py CHANGED
@@ -4,6 +4,6 @@ class CommandBase:
4
4
  def add_arguments(self, parser):
5
5
  pass
6
6
 
7
- def run(self, args):
7
+ def run(self, args, config):
8
8
  pass
9
9
 
schd/cmds/daemon.py CHANGED
@@ -8,12 +8,10 @@ from schd import __version__ as schd_version
8
8
 
9
9
  class DaemonCommand(CommandBase):
10
10
  def add_arguments(self, parser):
11
- parser.add_argument('--config', '-c')
12
11
  parser.add_argument('--logfile')
13
12
 
14
- def run(self, args):
15
- config_file = args.config
16
- print(f'starting schd, {schd_version}, config_file={config_file}')
13
+ def run(self, args, config):
14
+ print(f'starting schd, {schd_version}')
17
15
 
18
16
  if args.logfile:
19
17
  log_stream = open(args.logfile, 'a', encoding='utf8')
@@ -23,4 +21,4 @@ class DaemonCommand(CommandBase):
23
21
  log_stream = sys.stdout
24
22
 
25
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', stream=log_stream)
26
- asyncio.run(run_daemon(config_file))
24
+ asyncio.run(run_daemon(config))
schd/cmds/jobs.py CHANGED
@@ -1,16 +1,19 @@
1
1
  """
2
2
  list jobs
3
3
  """
4
-
5
- from schd.scheduler import read_config
4
+ import sys
6
5
  from .base import CommandBase
7
6
 
8
7
 
9
8
  class JobsCommand(CommandBase):
10
9
  def add_arguments(self, parser):
11
- parser.add_argument('--config', '-c', default=None, help='config file')
10
+ # parser.add_argument('--config', '-c', default=None, help='config file')
11
+ pass
12
+
13
+ def run(self, args, config=None):
14
+ if config is None:
15
+ print("No configuration provided.")
16
+ sys.exit(1)
12
17
 
13
- def run(self, args):
14
- config = read_config(config_file=args.config)
15
18
  for job_name, _ in config.jobs.items():
16
19
  print(job_name)
schd/cmds/run.py CHANGED
@@ -1,26 +1,27 @@
1
+ import asyncio
1
2
  import logging
3
+ import sys
2
4
  from schd.cmds.base import CommandBase
3
- from schd.scheduler import build_job, read_config, JobContext
5
+ from schd.scheduler import LocalScheduler, build_job
4
6
 
5
7
 
6
- def run_job(config_file, job_name):
7
- config = read_config(config_file)
8
-
8
+ async def run_job(config, job_name):
9
+ scheduler = LocalScheduler(config)
9
10
  job_config = config.jobs[job_name]
10
-
11
11
  job = build_job(job_name, job_config.cls, job_config)
12
- job_context = JobContext(job_name)
13
- job_context.output_to_console = True
14
- job(context=job_context)
12
+ await scheduler.add_job(job, job_name, job_config)
13
+ scheduler.execute_job(job_name)
15
14
 
16
15
 
17
16
  class RunCommand(CommandBase):
18
17
  def add_arguments(self, parser):
19
18
  parser.add_argument('job')
20
- parser.add_argument('--config', '-c')
21
19
 
22
- def run(self, args):
23
- logging.basicConfig(level=logging.INFO)
20
+ def run(self, args, config):
21
+ if config is None:
22
+ print("No configuration provided.")
23
+ sys.exit(1)
24
+
25
+ logging.basicConfig(format='%(asctime)s %(name)s - %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)
24
26
  job_name = args.job
25
- config_file = args.config
26
- run_job(config_file, job_name)
27
+ asyncio.run(run_job(config, job_name))
schd/cmds/schd.py CHANGED
@@ -1,10 +1,11 @@
1
1
  import argparse
2
2
  import sys
3
3
  from schd.cmds.jobs import JobsCommand
4
- from schd.scheduler import main as scheduler_main
4
+ from schd.config import ConfigFileNotFound, read_config
5
+ from schd import __version__ as schd_version
5
6
  from .daemon import DaemonCommand
6
7
  from .run import RunCommand
7
- from schd import __version__ as schd_version
8
+
8
9
 
9
10
  commands = {
10
11
  'daemon': DaemonCommand(),
@@ -16,6 +17,7 @@ def main():
16
17
  sys.path.append('.')
17
18
  parser = argparse.ArgumentParser('schd')
18
19
  parser.add_argument('--version', action='store_true', default=False)
20
+ parser.add_argument('--config')
19
21
  sub_command_parsers = parser.add_subparsers(dest='cmd')
20
22
 
21
23
  for cmd, cmd_obj in commands.items():
@@ -23,6 +25,10 @@ def main():
23
25
  cmd_obj.add_arguments(sub_command_parser)
24
26
 
25
27
  args = parser.parse_args()
28
+ try:
29
+ config = read_config(args.config)
30
+ except ConfigFileNotFound:
31
+ config = None
26
32
 
27
33
  if args.version:
28
34
  print('schd version ', schd_version)
@@ -32,7 +38,7 @@ def main():
32
38
  parser.print_help()
33
39
  return
34
40
 
35
- commands[args.cmd].run(args)
41
+ commands[args.cmd].run(args, config=config)
36
42
 
37
43
 
38
44
  if __name__ == '__main__':
@@ -0,0 +1,87 @@
1
+ """
2
+ scsendmail - Send email via command line using EmailService
3
+
4
+ Usage:
5
+ scsendmail --to someone@example.com --title "Report" --content "Text body"
6
+ scsendmail --to a@example.com --content-html-file ./body.html -a report.pdf
7
+ """
8
+
9
+ import argparse
10
+ import logging
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ import sys
14
+
15
+ from schd.config import read_config, ConfigFileNotFound, EmailConfig
16
+ from schd.email import EmailService
17
+
18
+
19
+ def parse_recipients(values: Optional[List[str]]) -> List[str]:
20
+ if not values:
21
+ return []
22
+ emails = []
23
+ for val in values:
24
+ emails.extend(email.strip() for email in val.split(',') if email.strip())
25
+ return emails
26
+
27
+
28
+ def main():
29
+ parser = argparse.ArgumentParser(description='scsendmail command')
30
+ parser.add_argument('--title', default='report', help='Email subject')
31
+ parser.add_argument('--content', default='no content', help='Plain text content')
32
+ parser.add_argument('--content-html-file', help='Path to HTML file for HTML content')
33
+ parser.add_argument('--to', dest='recipients', action='append', help='To recipients (comma-separated or multiple flags)')
34
+ parser.add_argument('--cc', action='append', help='CC recipients (comma-separated or multiple flags)')
35
+ parser.add_argument('--bcc', action='append', help='BCC recipients (comma-separated or multiple flags)')
36
+ parser.add_argument('--add-attach', '-a', action='append', dest='attachments', help='Attachment file paths')
37
+ parser.add_argument('--debug', action='store_true', default=False, help='Print instead of sending')
38
+ parser.add_argument('--config')
39
+ parser.add_argument('--loglevel', default='INFO', help='Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)')
40
+
41
+ args = parser.parse_args()
42
+
43
+ logging.basicConfig(level=args.loglevel.upper(),
44
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
45
+
46
+ # Load HTML content if provided
47
+ html_content = None
48
+ if args.content_html_file:
49
+ try:
50
+ html_content = Path(args.content_html_file).read_text(encoding='utf-8')
51
+ except Exception as e:
52
+ print(f"Failed to read HTML content file: {e}", file=sys.stderr)
53
+ sys.exit(1)
54
+
55
+ # Load config from environment or config file
56
+ schd_config = read_config(args.config)
57
+ logging.debug(schd_config.email)
58
+ service = EmailService.from_config(schd_config.email)
59
+
60
+ to_emails = parse_recipients(args.recipients)
61
+ cc_emails = parse_recipients(args.cc)
62
+ bcc_emails = parse_recipients(args.bcc)
63
+ attachments = args.attachments or []
64
+
65
+ if args.debug:
66
+ print("DEBUG MODE: Email will not be sent")
67
+ print(f"Subject: {args.title}")
68
+ print(f"To: {to_emails}")
69
+ print(f"CC: {cc_emails}")
70
+ print(f"BCC: {bcc_emails}")
71
+ print(f"Attachments: {attachments}")
72
+ print(f"Content: {args.content}")
73
+ print(f"HTML Content File: {args.content_html_file}")
74
+ else:
75
+ service.send_mail(
76
+ title=args.title,
77
+ content=args.content,
78
+ content_html=html_content,
79
+ to_emails=to_emails,
80
+ cc_emails=cc_emails,
81
+ bcc_emails=bcc_emails,
82
+ attachments=attachments
83
+ )
84
+
85
+
86
+ if __name__ == '__main__':
87
+ main()
schd/config.py CHANGED
@@ -28,15 +28,20 @@ class ConfigValue:
28
28
  type_hints = get_type_hints(cls)
29
29
  init_data:Dict[str,Any] = {}
30
30
  if not is_dataclass(cls):
31
- raise TypeError('class %s is not dataclass' % cls)
31
+ raise TypeError(f'class {cls} is not dataclass')
32
32
 
33
33
  for f in fields(cls):
34
34
  field_name = f.name
35
35
  json_key = f.metadata.get("json", f.name)
36
+ envvar_key = f.metadata.get('env_var')
36
37
  field_type = type_hints[field_name]
37
38
  origin = get_origin(field_type)
38
39
  args = get_args(field_type)
39
40
 
41
+ if envvar_key and envvar_key in os.environ:
42
+ init_data[field_name] = _cast_type(os.environ[envvar_key], field_type)
43
+ continue
44
+
40
45
  if json_key in data:
41
46
  value = data[json_key]
42
47
  # Handle nested ConfigValue objects
@@ -63,6 +68,44 @@ class ConfigValue:
63
68
  init_data[field_name] = value
64
69
  return cls(**init_data)
65
70
 
71
+ def _cast_type(value, target_type):
72
+ origin = get_origin(target_type)
73
+ args = get_args(target_type)
74
+
75
+ # Handle Optional[T] or Union[T1, T2, ...]
76
+ if origin is Union:
77
+ # Optional[str] is Union[str, NoneType]
78
+ for typ in args:
79
+ if typ is type(None):
80
+ continue # skip NoneType
81
+ try:
82
+ return _cast_type(value, typ)
83
+ except (ValueError, TypeError):
84
+ continue
85
+ raise ValueError(f"Cannot cast {value!r} to any of {args}")
86
+
87
+ # Handle base types
88
+ if target_type == bool:
89
+ return value.lower() in ('true', '1', 'yes', 'on', True)
90
+ elif target_type == int:
91
+ return int(value)
92
+ elif target_type == float:
93
+ return float(value)
94
+ elif target_type == str:
95
+ return value
96
+ else:
97
+ raise TypeError(f"Unsupported type: {target_type}")
98
+
99
+ @dataclass
100
+ class EmailConfig(ConfigValue):
101
+ smtp_server: Optional[str] = field(metadata={'env_var': 'SCHD_SMTP_SERVER'}, default=None)
102
+ smtp_user: Optional[str] = field(metadata={'env_var': 'SCHD_SMTP_USER'}, default=None)
103
+ smtp_password: Optional[str] = field(metadata={'env_var': 'SCHD_SMTP_PASS'}, default=None)
104
+ from_addr: Optional[str] = field(metadata={'env_var': 'SCHD_SMTP_FROM'}, default=None)
105
+ to_addr: Optional[str] = field(metadata={'env_var': 'SCHD_SMTP_TO'}, default=None)
106
+ smtp_port: int = field(metadata={'env_var': 'SCHD_SMTP_PORT'}, default=25)
107
+ smtp_starttls: bool = field(metadata={'env_var': 'SCHD_SMTP_TLS'}, default=False)
108
+
66
109
 
67
110
  @dataclass
68
111
  class JobConfig(ConfigValue):
@@ -80,6 +123,7 @@ class SchdConfig(ConfigValue):
80
123
  scheduler_cls: str = 'LocalScheduler'
81
124
  scheduler_remote_host: Optional[str] = None
82
125
  worker_name: str = 'local'
126
+ email: EmailConfig = field(default_factory=lambda: EmailConfig.from_dict({}))
83
127
 
84
128
  def __getitem__(self,key):
85
129
  # compatible to old fashion config['key']
@@ -89,13 +133,19 @@ class SchdConfig(ConfigValue):
89
133
  raise KeyError(key)
90
134
 
91
135
 
92
- def read_config(config_file=None) -> SchdConfig:
93
- if config_file is None and 'SCHD_CONFIG' in os.environ:
94
- config_file = os.environ['SCHD_CONFIG']
136
+ class ConfigFileNotFound(Exception):...
95
137
 
96
- if config_file is None:
97
- config_file = 'conf/schd.yaml'
98
138
 
99
- with open(config_file, 'r', encoding='utf8') as f:
139
+ def read_config(config_file=None) -> SchdConfig:
140
+ if config_file:
141
+ config_filepath = config_file
142
+ elif 'SCHD_CONFIG' in os.environ:
143
+ config_filepath = os.environ['SCHD_CONFIG']
144
+ elif os.path.exists('conf/schd.yaml'):
145
+ config_filepath = 'conf/schd.yaml'
146
+ else:
147
+ raise ConfigFileNotFound()
148
+
149
+ with open(config_filepath, 'r', encoding='utf8') as f:
100
150
  config = SchdConfig.from_dict(yaml.load(f, Loader=yaml.FullLoader))
101
151
  return config
schd/email.py ADDED
@@ -0,0 +1,71 @@
1
+ import smtplib
2
+ from email.message import EmailMessage
3
+ import logging
4
+ from typing import List, Optional, Union
5
+ import os
6
+ from pathlib import Path
7
+ from schd.config import EmailConfig
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class EmailService:
13
+ def __init__(self, smtp_server: str, smtp_user: str, smtp_password: str,
14
+ from_addr: str, smtp_port: int = 25, smtp_starttls: bool = False):
15
+ self.smtp_server = smtp_server
16
+ self.smtp_user = smtp_user
17
+ self.smtp_password = smtp_password
18
+ self.from_addr = from_addr
19
+ self.smtp_port = smtp_port
20
+ self.smtp_starttls = smtp_starttls
21
+
22
+ def send_mail(self, title: str, content: str, to_emails: Union[str, List[str]],
23
+ attachments: Optional[List[str]] = None,
24
+ content_html: Optional[str] = None,
25
+ cc_emails: Optional[List[str]] = None,
26
+ bcc_emails: Optional[List[str]] = None):
27
+ msg = EmailMessage()
28
+ msg['Subject'] = title
29
+ msg['From'] = self.from_addr
30
+ if isinstance(to_emails, str):
31
+ to_emails = [to_emails]
32
+ msg['To'] = ', '.join(to_emails)
33
+ if cc_emails:
34
+ msg['Cc'] = ', '.join(cc_emails)
35
+
36
+ recipients = to_emails + (cc_emails or []) + (bcc_emails or [])
37
+
38
+ # Add text and HTML
39
+ if content_html:
40
+ msg.set_content(content)
41
+ msg.add_alternative(content_html, subtype='html')
42
+ else:
43
+ msg.set_content(content)
44
+
45
+ # Attach files
46
+ for filepath in attachments or []:
47
+ file_path = Path(filepath)
48
+ with open(file_path, 'rb') as f:
49
+ file_data = f.read()
50
+ msg.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_path.name)
51
+
52
+ # Send email
53
+ with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
54
+ if self.smtp_starttls:
55
+ server.starttls()
56
+ if self.smtp_user and self.smtp_password:
57
+ server.login(self.smtp_user, self.smtp_password)
58
+ else:
59
+ logger.info('no username/pass, skip logging in.')
60
+ server.send_message(msg, from_addr=self.from_addr, to_addrs=recipients)
61
+
62
+ @classmethod
63
+ def from_config(cls, config: 'EmailConfig') -> 'EmailService':
64
+ return cls(
65
+ smtp_server=config.smtp_server,
66
+ smtp_user=config.smtp_user,
67
+ smtp_password=config.smtp_password,
68
+ from_addr=config.from_addr,
69
+ smtp_port=config.smtp_port,
70
+ smtp_starttls=config.smtp_starttls
71
+ )
schd/scheduler.py CHANGED
@@ -5,6 +5,7 @@ import logging
5
5
  import importlib
6
6
  import io
7
7
  import os
8
+ import socket
8
9
  import sys
9
10
  from typing import Any, Optional, Dict
10
11
  import smtplib
@@ -16,6 +17,7 @@ from apscheduler.schedulers.blocking import BlockingScheduler
16
17
  from apscheduler.triggers.cron import CronTrigger
17
18
  from apscheduler.executors.pool import ThreadPoolExecutor
18
19
  from schd import __version__ as schd_version
20
+ from schd.email import EmailService
19
21
  from schd.schedulers.remote import RemoteScheduler
20
22
  from schd.util import ensure_bool
21
23
  from schd.job import Job, JobContext, JobExecutionResult
@@ -179,7 +181,7 @@ class ConsoleErrorNotifier:
179
181
 
180
182
 
181
183
  class LocalScheduler:
182
- def __init__(self, max_concurrent_jobs: int = 10):
184
+ def __init__(self, config:SchdConfig, max_concurrent_jobs: int = 10):
183
185
  """
184
186
  Initialize the LocalScheduler with support for concurrent job execution.
185
187
 
@@ -190,6 +192,9 @@ class LocalScheduler:
190
192
  }
191
193
  self.scheduler = BlockingScheduler(executors=executors)
192
194
  self._jobs:Dict[str, Job] = {}
195
+ self.email_service = EmailService.from_config(config.email)
196
+ self.to_mail = config.email.to_addr
197
+ self.worker_name = config.worker_name or socket.gethostname()
193
198
  logger.info("LocalScheduler initialized in 'local' mode with concurrency support")
194
199
 
195
200
  async def init(self):
@@ -234,8 +239,13 @@ class LocalScheduler:
234
239
  logger.exception('error when executing job, %s', ex)
235
240
  ret_code = -1
236
241
 
242
+ output = output_stream.getvalue()
237
243
  logger.info('job %s execute complete: %d', job_name, ret_code)
238
- logger.info('job %s process output: \n%s', job_name, output_stream.getvalue())
244
+ logger.info('job %s process output: \n%s', job_name, output)
245
+ if ret_code != 0 and self.to_mail:
246
+ self.email_service.send_mail('job failed %s %s' % (self.worker_name, job_name),
247
+ content=output,
248
+ to_emails=self.to_mail)
239
249
 
240
250
  def run(self):
241
251
  """
@@ -255,7 +265,7 @@ def build_scheduler(config:SchdConfig):
255
265
  scheduler_cls = os.environ.get('SCHD_SCHEDULER_CLS') or config.scheduler_cls
256
266
 
257
267
  if scheduler_cls == 'LocalScheduler':
258
- scheduler = LocalScheduler()
268
+ scheduler = LocalScheduler(config)
259
269
  elif scheduler_cls == 'RemoteScheduler':
260
270
  logger.info('scheduler_cls: %s', scheduler_cls)
261
271
  scheduler_remote_host = os.environ.get('SCHD_SCHEDULER_REMOTE_HOST') or config.scheduler_remote_host
@@ -270,8 +280,7 @@ def build_scheduler(config:SchdConfig):
270
280
  return scheduler
271
281
 
272
282
 
273
- async def run_daemon(config_file=None):
274
- config = read_config(config_file=config_file)
283
+ async def run_daemon(config):
275
284
  scheduler = build_scheduler(config)
276
285
  await scheduler.init()
277
286
 
@@ -319,11 +328,12 @@ async def main():
319
328
  parser.add_argument('--logfile')
320
329
  parser.add_argument('--config', '-c')
321
330
  args = parser.parse_args()
322
- config_file = args.config
323
331
 
324
332
  logging.basicConfig(level=logging.DEBUG)
325
333
 
326
- print(f'starting schd, {schd_version}, config_file={config_file}')
334
+ config = read_config(args.config)
335
+ print(f'starting schd, {schd_version}')
336
+
327
337
 
328
338
  if args.logfile:
329
339
  log_stream = open(args.logfile, 'a', encoding='utf8')
@@ -333,7 +343,7 @@ async def main():
333
343
  log_stream = sys.stdout
334
344
 
335
345
  logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s - %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', stream=log_stream)
336
- await run_daemon(config_file)
346
+ await run_daemon(config)
337
347
 
338
348
 
339
349
  if __name__ == '__main__':
@@ -1,9 +1,11 @@
1
- Metadata-Version: 2.1
2
- Name: schd
3
- Version: 0.0.16
4
- Home-page: https://github.com/kevenli/schd
5
- License: ApacheV2
6
- Requires-Dist: apscheduler<4.0
7
- Requires-Dist: pyaml
8
- Requires-Dist: aiohttp
9
-
1
+ Metadata-Version: 2.4
2
+ Name: schd
3
+ Version: 0.1.1
4
+ Home-page: https://github.com/kevenli/schd
5
+ License: ApacheV2
6
+ Requires-Dist: apscheduler<4.0
7
+ Requires-Dist: pyaml
8
+ Requires-Dist: aiohttp
9
+ Dynamic: home-page
10
+ Dynamic: license
11
+ Dynamic: requires-dist
@@ -0,0 +1,20 @@
1
+ schd/__init__.py,sha256=zGU39ZwaRkNKxWLnAB7Zte9ZtfyfMEsBJp-bHUfQTyA,23
2
+ schd/config.py,sha256=YjwqUFkULjNONDw-Q0QQeNnWQK3XMGHuGSjAHZMGwh8,5981
3
+ schd/email.py,sha256=eBKzNnuQxcJwlWOeXcAMNkGZlUa3O5zM7amfUW7h3gE,2633
4
+ schd/job.py,sha256=AoW-2W1hRY_O4nz_pKukgmEXYkjlP2XmYimyoGCs-Bw,614
5
+ schd/scheduler.py,sha256=IJu_FW5n9U_AXXrjcQAVvJN7bLTI8LoweV1PO_k9WuM,13400
6
+ schd/util.py,sha256=NH4EqIns1Y01yz1Rf3cw-tlKSLc-XNuS9hHbAdq5D_I,592
7
+ schd/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ schd/cmds/base.py,sha256=lsVIqE1m-uBRRNt7UDYJIIthYR7pyINccfS5hVK3BWA,130
9
+ schd/cmds/daemon.py,sha256=8Ia99abtMatCfYUliDNvlUjRY1-RZPSWUYIuzftLnpI,783
10
+ schd/cmds/jobs.py,sha256=F3ArEeBTOiFpe3gmtkFg6kRtoYCjQGp4kAc3r6UJbtA,459
11
+ schd/cmds/run.py,sha256=woTK4C2h6D5JEYcwXOzS9IHuTVtaTKWMf5jfFhikW2k,888
12
+ schd/cmds/schd.py,sha256=anVfbXXElKRI5Zd8sJYYroBfyqS35RGAtXD5nTEv6ag,1164
13
+ schd/cmds/scsendmail.py,sha256=WIQzzXOFfh8Rwxtzdbn7HH14oG0jUrbxLyA3KL10yM4,3285
14
+ schd/schedulers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ schd/schedulers/remote.py,sha256=iaQmddf-g095jAIJAGAn0mQKrLRfu9gNZFvE0NkJVy0,8087
16
+ schd-0.1.1.dist-info/METADATA,sha256=XHxYNbTdnNTyBraS-odUpJLGZb2612_vm_rRUigi01E,243
17
+ schd-0.1.1.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
18
+ schd-0.1.1.dist-info/entry_points.txt,sha256=lY18p1hQAKgv47FBqPj37z8o4Kr1MHnbGlsrNLS4ZKs,84
19
+ schd-0.1.1.dist-info/top_level.txt,sha256=Vojim8xSOsYyQHrZBNhx7n0F7WqbEJ2SeBFAvEnrJ_U,5
20
+ schd-0.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  schd = schd.cmds.schd:main
3
+ scsendmail = schd.cmds.scsendmail:main
@@ -1,18 +0,0 @@
1
- schd/__init__.py,sha256=3kGSHQKV1gPB0rPzBMZNIYjHiZim2MThuzEWpP2YZcg,24
2
- schd/config.py,sha256=YBSRiXZApC_ylPYUM4cwTU7FjVqMHwG3vPy-F6SA95k,3930
3
- schd/job.py,sha256=AoW-2W1hRY_O4nz_pKukgmEXYkjlP2XmYimyoGCs-Bw,614
4
- schd/scheduler.py,sha256=EYIU7aX8qs-RYS_eXJc-JZU3e1T-mTMzkEv51b-AXgM,12931
5
- schd/util.py,sha256=NH4EqIns1Y01yz1Rf3cw-tlKSLc-XNuS9hHbAdq5D_I,592
6
- schd/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- schd/cmds/base.py,sha256=ZnNcJozQFLbpYyNp8dhHzm3BzFQa0hm6KyCC6URfueY,122
8
- schd/cmds/daemon.py,sha256=MReUf8TDyE-zEUjJ1T1v4RBD8f2edpE7TeGdooaHD6Y,889
9
- schd/cmds/jobs.py,sha256=DwAuTxZHjqMsG952IpaReTzCjvBII44bZquriFncl1Q,411
10
- schd/cmds/run.py,sha256=Z-fO-Qk9RS9lBAszmPuuJbVPczIU7AA40Qnd0ngmgsI,752
11
- schd/cmds/schd.py,sha256=vOlfQCQT81KhLMvlP3tlymEWpxmqlBOa5vnJoeFXVlw,996
12
- schd/schedulers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- schd/schedulers/remote.py,sha256=iaQmddf-g095jAIJAGAn0mQKrLRfu9gNZFvE0NkJVy0,8087
14
- schd-0.0.16.dist-info/METADATA,sha256=OUyzeY7EMMeUkGCW74DmnhYwJjrl_bBJbJCM6xOa6GI,195
15
- schd-0.0.16.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
16
- schd-0.0.16.dist-info/entry_points.txt,sha256=VvUhIaucvHlggoz-4lWtQgXbDwoWH9x-iT0QOUHEMyI,45
17
- schd-0.0.16.dist-info/top_level.txt,sha256=Vojim8xSOsYyQHrZBNhx7n0F7WqbEJ2SeBFAvEnrJ_U,5
18
- schd-0.0.16.dist-info/RECORD,,