Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-yacron for openSUSE:Factory 
checked in at 2024-01-03 12:24:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-yacron (Old)
 and      /work/SRC/openSUSE:Factory/.python-yacron.new.28375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-yacron"

Wed Jan  3 12:24:28 2024 rev:7 rq:1135636 version:0.19.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-yacron/python-yacron.changes      
2022-09-01 22:13:15.044577049 +0200
+++ /work/SRC/openSUSE:Factory/.python-yacron.new.28375/python-yacron.changes   
2024-01-03 12:24:34.638576919 +0100
@@ -1,0 +2,13 @@
+Fri Dec 29 10:21:16 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 0.19.0:
+  * Add ability to configure yacron's own logging (#81 #82 #83,
+  * Add config value for SMTP(validate_certs=False) (David
+    Batley)
+  * fixes "Job is always executed immediately on yacron start"
+    (#67)
+  * add an `enabled` option in jobs (#73)
+  * give a better error message when no configuration file is
+    provided or exists (#72)
+
+-------------------------------------------------------------------

Old:
----
  yacron-0.17.0.tar.gz

New:
----
  yacron-0.19.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-yacron.spec ++++++
--- /var/tmp/diff_new_pack.RQdHOa/_old  2024-01-03 12:24:35.326602057 +0100
+++ /var/tmp/diff_new_pack.RQdHOa/_new  2024-01-03 12:24:35.330602203 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-yacron
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
 %define skip_python2 1
 %define skip_python36 1
 Name:           python-yacron
-Version:        0.17.0
+Version:        0.19.0
 Release:        0
 Summary:        Docker-friendly Cron replacement
 License:        MIT

++++++ yacron-0.17.0.tar.gz -> yacron-0.19.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/HISTORY.rst 
new/yacron-0.19.0/HISTORY.rst
--- old/yacron-0.17.0/HISTORY.rst       2022-06-26 15:01:52.000000000 +0200
+++ new/yacron-0.19.0/HISTORY.rst       2023-03-11 14:38:45.000000000 +0100
@@ -2,6 +2,19 @@
 History
 =======
 
+0.19.0 (2023-03-11)
+-------------------
+
+* Add ability to configure yacron's own logging (#81 #82 #83, gjcarneiro, 
bdamian)
+* Add config value for SMTP(validate_certs=False) (David Batley)
+
+0.18.0 (2023-01-01)
+-------------------
+
+* fixes "Job is always executed immediately on yacron start" (#67)
+* add an `enabled` option in jobs (#73)
+* give a better error message when no configuration file is provided or exists 
(#72)
+
 0.17.0 (2022-06-26)
 -------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/PKG-INFO new/yacron-0.19.0/PKG-INFO
--- old/yacron-0.17.0/PKG-INFO  2022-06-26 15:02:52.030362400 +0200
+++ new/yacron-0.19.0/PKG-INFO  2023-03-11 14:54:57.224146000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: yacron
-Version: 0.17.0
+Version: 0.19.0
 Summary: A modern Cron replacement that is Docker-friendly
 Home-page: https://github.com/gjcarneiro/yacron
 Author: Gustavo Carneiro
@@ -134,9 +134,9 @@
         schedule: "*/5 * * * *"
 
 
-The `schedule` option can be a string in the traditional crontab format
-(including @reboot, which will only run the job when yacron is initially
-executed), or can be an object with properties.  The following configuration
+The `schedule` option can be a string in a crontab format specified by 
https://github.com/josiahcarlson/parse-crontab (this module is used by yacron).
+Additionally @reboot can be included , which will only run the job when yacron 
is initially
+executed. Further `schedule` can be an object with properties.  The following 
configuration
 runs a command every 5 minutes, but only on the specific date 2017-07-19, and
 doesn't run it in any other date:
 
@@ -767,11 +767,75 @@
         sentry:
           ...
 
+Custom logging
+++++++++++++++
+
+It's possible to provide a custom logging configuration, via the ``logging``
+configuration section.  For example, the following configuration displays log 
lines with
+an embedded timestamp for each message.
+
+.. code-block:: yaml
+
+    logging:
+      # In the format of:
+      # 
https://docs.python.org/3/library/logging.config.html#dictionary-schema-details
+      version: 1
+      disable_existing_loggers: false
+      formatters:
+        simple:
+          format: '%(asctime)s [%(processName)s/%(threadName)s] %(levelname)s 
(%(name)s): %(message)s'
+      handlers:
+        console:
+          class: logging.StreamHandler
+          level: DEBUG
+          formatter: simple
+          stream: ext://sys.stdout
+      root:
+        level: INFO
+        handlers:
+          - console
+
+Obscure configuration options
++++++++++++++++++++++++++++++
+
+enabled: true|false (default true)
+##################################
+
+(new in yacron 0.18)
+
+It is possible to disable a specific cron job by adding a `enabled: false` 
option.  Jobs
+with `enabled: false` will simply be skipped, as if they aren't there, apart 
from
+validating the configuration.
+
+.. code-block:: yaml
+
+    jobs:
+      - name: test-01
+        enabled: false  # this cron job will not run until you change this to 
`true`
+        command: echo "foobar"
+        shell: /bin/bash
+        schedule: "* * * * *"
+
+
+
 
 =======
 History
 =======
 
+0.19.0 (2023-03-11)
+-------------------
+
+* Add ability to configure yacron's own logging (#81 #82 #83, gjcarneiro, 
bdamian)
+* Add config value for SMTP(validate_certs=False) (David Batley)
+
+0.18.0 (2023-01-01)
+-------------------
+
+* fixes "Job is always executed immediately on yacron start" (#67)
+* add an `enabled` option in jobs (#73)
+* give a better error message when no configuration file is provided or exists 
(#72)
+
 0.17.0 (2022-06-26)
 -------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/README.rst new/yacron-0.19.0/README.rst
--- old/yacron-0.17.0/README.rst        2022-03-30 12:06:01.000000000 +0200
+++ new/yacron-0.19.0/README.rst        2023-03-11 14:54:45.000000000 +0100
@@ -110,9 +110,9 @@
         schedule: "*/5 * * * *"
 
 
-The `schedule` option can be a string in the traditional crontab format
-(including @reboot, which will only run the job when yacron is initially
-executed), or can be an object with properties.  The following configuration
+The `schedule` option can be a string in a crontab format specified by 
https://github.com/josiahcarlson/parse-crontab (this module is used by yacron).
+Additionally @reboot can be included , which will only run the job when yacron 
is initially
+executed. Further `schedule` can be an object with properties.  The following 
configuration
 runs a command every 5 minutes, but only on the specific date 2017-07-19, and
 doesn't run it in any other date:
 
@@ -742,3 +742,54 @@
       report:
         sentry:
           ...
+
+Custom logging
+++++++++++++++
+
+It's possible to provide a custom logging configuration, via the ``logging``
+configuration section.  For example, the following configuration displays log 
lines with
+an embedded timestamp for each message.
+
+.. code-block:: yaml
+
+    logging:
+      # In the format of:
+      # 
https://docs.python.org/3/library/logging.config.html#dictionary-schema-details
+      version: 1
+      disable_existing_loggers: false
+      formatters:
+        simple:
+          format: '%(asctime)s [%(processName)s/%(threadName)s] %(levelname)s 
(%(name)s): %(message)s'
+      handlers:
+        console:
+          class: logging.StreamHandler
+          level: DEBUG
+          formatter: simple
+          stream: ext://sys.stdout
+      root:
+        level: INFO
+        handlers:
+          - console
+
+Obscure configuration options
++++++++++++++++++++++++++++++
+
+enabled: true|false (default true)
+##################################
+
+(new in yacron 0.18)
+
+It is possible to disable a specific cron job by adding a `enabled: false` 
option.  Jobs
+with `enabled: false` will simply be skipped, as if they aren't there, apart 
from
+validating the configuration.
+
+.. code-block:: yaml
+
+    jobs:
+      - name: test-01
+        enabled: false  # this cron job will not run until you change this to 
`true`
+        command: echo "foobar"
+        shell: /bin/bash
+        schedule: "* * * * *"
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/example/adhoc.yacron.d/_inc.yaml 
new/yacron-0.19.0/example/adhoc.yacron.d/_inc.yaml
--- old/yacron-0.17.0/example/adhoc.yacron.d/_inc.yaml  2021-10-01 
20:24:06.000000000 +0200
+++ new/yacron-0.19.0/example/adhoc.yacron.d/_inc.yaml  2023-03-11 
14:36:36.000000000 +0100
@@ -46,3 +46,21 @@
     host: localhost
     port: 8125
     prefix: my.cron.jobs.prefix.test01
+
+
+logging:
+  version: 1
+  disable_existing_loggers: false
+  formatters:
+    simple:
+      format: '%(asctime)s [%(processName)s/%(threadName)s] %(levelname)s 
(%(name)s): %(message)s'
+  handlers:
+    console:
+      class: logging.StreamHandler
+      level: DEBUG
+      formatter: simple
+      stream: ext://sys.stdout
+  root:
+    level: INFO
+    handlers:
+      - console
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/pyinstaller/requirements.txt 
new/yacron-0.19.0/pyinstaller/requirements.txt
--- old/yacron-0.17.0/pyinstaller/requirements.txt      2022-06-26 
12:27:14.000000000 +0200
+++ new/yacron-0.19.0/pyinstaller/requirements.txt      2023-03-11 
11:29:20.000000000 +0100
@@ -4,7 +4,7 @@
 appdirs==1.4.4
 async-timeout==4.0.2
 attrs==21.2.0
-certifi==2021.5.30
+certifi==2022.12.7
 chardet==3.0.4
 coverage==5.5
 crontab==0.22.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/requirements_dev.txt 
new/yacron-0.19.0/requirements_dev.txt
--- old/yacron-0.17.0/requirements_dev.txt      2022-06-26 12:24:45.000000000 
+0200
+++ new/yacron-0.19.0/requirements_dev.txt      2023-03-11 11:29:20.000000000 
+0100
@@ -1,33 +1,10 @@
-aiohttp==3.8.1
-appdirs==1.4.4
-async-timeout==4.0.2
-attrs==21.2.0
-chardet==3.0.4
-coverage==5.5
-distlib==0.3.2
-filelock==3.0.12
-flake8==3.9.2
-idna==3.2
-iniconfig==1.1.1
-mccabe==0.6.1
-multidict==5.1.0
+aiohttp~=3.8
+flake8
 mypy==0.910
-mypy-extensions==0.4.3
-packaging==20.9
-pluggy==0.13.1
-py==1.10.0
-pycodestyle==2.7.0
-pyflakes==2.3.1
-pyparsing==2.4.7
-pytest==7.1.1
-pytest-cov==2.12.1
-pytz==2021.1
-six==1.16.0
-toml==0.10.2
-tomli==2.0.1
-tox==3.23.1
-typing-extensions==3.10.0.0
-virtualenv==20.4.7
-yarl==1.6.3
-types-pytz==2021.1.0
-pytest-asyncio==0.18.3
+mypy-extensions
+pytest
+pytest-asyncio
+pytest-cov
+tox
+types-pytz
+pytest-asyncio~=0.18
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/tests/test_config.py 
new/yacron-0.19.0/tests/test_config.py
--- old/yacron-0.17.0/tests/test_config.py      2021-11-10 11:42:42.000000000 
+0100
+++ new/yacron-0.19.0/tests/test_config.py      2023-03-11 14:36:36.000000000 
+0100
@@ -31,7 +31,7 @@
 
 
 def test_simple_config1():
-    jobs, web_config, _ = config.parse_config_string(
+    conf = config.parse_config_string(
         """
 defaults:
   shell: /bin/bash
@@ -51,9 +51,9 @@
                        """,
         "",
     )
-    assert web_config is None
-    assert len(jobs) == 1
-    job = jobs[0]
+    assert conf.web_config is None
+    assert len(conf.jobs) == 1
+    job = conf.jobs[0]
     assert job.name == "test-03"
     assert job.command == (
         "trap \"echo '(ignoring SIGTERM)'\" TERM\n"
@@ -69,7 +69,7 @@
 
 
 def test_config_default_report():
-    jobs, _, _ = config.parse_config_string(
+    conf = config.parse_config_string(
         """
 defaults:
   onFailure:
@@ -89,8 +89,8 @@
                        """,
         "",
     )
-    assert len(jobs) == 1
-    job = jobs[0]
+    assert len(conf.jobs) == 1
+    job = conf.jobs[0]
     assert job.onFailure == (
         {
             "report": {
@@ -117,6 +117,7 @@
                     },
                     "tls": False,
                     "starttls": False,
+                    "validate_certs": False,
                     "html": False,
                 },
                 "sentry": (
@@ -137,7 +138,7 @@
 def test_config_default_report_override():
     # even if the default says send email on error, it should be possible for
     # specific jobs to override the default and disable sending email.
-    jobs, _, _ = config.parse_config_string(
+    conf = config.parse_config_string(
         """
 defaults:
   onFailure:
@@ -162,8 +163,8 @@
                        """,
         "",
     )
-    assert len(jobs) == 1
-    job = jobs[0]
+    assert len(conf.jobs) == 1
+    job = conf.jobs[0]
     assert job.onFailure == (
         {
             "report": {
@@ -190,6 +191,7 @@
                     },
                     "tls": False,
                     "starttls": False,
+                    "validate_certs": False,
                     "html": False,
                 },
                 "sentry": (
@@ -208,13 +210,13 @@
 
 
 def test_empty_config1():
-    jobs, web_config, _ = config.parse_config_string("", "")
-    assert len(jobs) == 0
-    assert web_config is None
+    conf = config.parse_config_string("", "")
+    assert len(conf.jobs) == 0
+    assert conf.web_config is None
 
 
 def test_environ_file():
-    jobs, _, _ = config.parse_config_string(
+    conf = config.parse_config_string(
         """
 defaults:
   shell: /bin/bash
@@ -237,7 +239,7 @@
 """,
         "",
     )
-    job = jobs[0]
+    job = conf.jobs[0]
 
     # NOTE: the file format implicitly verifies that the parsing is being
     # done correctly on these fronts:
@@ -261,7 +263,7 @@
 def test_invalid_environ_file():
     # invalid file (no key-value)
     with pytest.raises(ConfigError) as exc:
-        jobs, _, _ = config.parse_config_string(
+        config.parse_config_string(
             """
     defaults:
       shell: /bin/bash
@@ -289,7 +291,7 @@
 
     # non-existent file should raise ConfigError, not OSError
     with pytest.raises(ConfigError) as exc:
-        jobs, _, _ = config.parse_config_string(
+        config.parse_config_string(
             """
     defaults:
       shell: /bin/bash
@@ -317,12 +319,39 @@
 
 
 def test_config_include():
-    jobs, _ = config.parse_config(
+    conf = config.parse_config(
         os.path.join(os.path.dirname(__file__), "test_include_parent.yaml")
     )
-    assert len(jobs) == 2
-    job1, job2 = jobs
+    assert len(conf.jobs) == 2
+    job1, job2 = conf.jobs
     assert job1.name == "common-task"
     assert job2.name == "test-03"
     assert job1.shell == "/bin/ksh"
     assert job2.shell == "/bin/ksh"
+
+
+def test_logging_config():
+    conf = config.parse_config_string(
+        """
+logging:
+    version: 1
+    incremental: false
+    disable_existing_loggers: false
+    formatters: one
+    filters: two
+    handlers: three
+    loggers: four
+    root: five
+        """,
+        "",
+    )
+    assert conf.logging_config == {
+        "version": 1,
+        "incremental": False,
+        "disable_existing_loggers": False,
+        "formatters": "one",
+        "filters": "two",
+        "handlers": "three",
+        "loggers": "four",
+        "root": "five",
+    }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/tests/test_cron.py 
new/yacron-0.19.0/tests/test_cron.py
--- old/yacron-0.17.0/tests/test_cron.py        2022-04-03 17:21:22.000000000 
+0200
+++ new/yacron-0.19.0/tests/test_cron.py        2023-03-11 11:29:20.000000000 
+0100
@@ -79,7 +79,7 @@
   - name: test
     command: |
       echo "foobar"
-    schedule: "* * * * *"
+    schedule: "@reboot"
 """
 
 JOB_THAT_FAILS = """
@@ -88,7 +88,7 @@
     command: |
       echo "foobar"
       exit 2
-    schedule: "* * * * *"
+    schedule: "@reboot"
 """
 
 
@@ -143,7 +143,7 @@
     command: |
       echo "foobar"
       exit 2
-    schedule: "* * * * *"
+    schedule: "@reboot"
     onFailure:
       retry:
         maximumRetries: 2
@@ -153,6 +153,7 @@
 """
 
 
+@pytest.mark.asyncio
 async def test_fail_retry(tracing_running_job):
     cron = yacron.cron.Cron(None, config_yaml=RETRYING_JOB_THAT_FAILS)
 
@@ -211,13 +212,14 @@
       echo "starting..."
       sleep 10
       echo "all done."
-    schedule: "* * * * *"
+    schedule: "@reboot"
     captureStdout: true
     executionTimeout: 0.25
     killTimeout: 0.25
 """
 
 
+@pytest.mark.asyncio
 async def test_execution_timeout(tracing_running_job):
     cron = yacron.cron.Cron(None, config_yaml=JOB_THAT_HANGS)
 
@@ -266,7 +268,7 @@
       echo "starting..."
       sleep 0.5
       echo "all done."
-    schedule: "* * * * *"
+    schedule: "@reboot"
     captureStdout: true
     concurrencyPolicy: {policy}
 """
@@ -277,6 +279,7 @@
     "policy,expected_numjobs,expected_max_running",
     [("Allow", 2, 2), ("Forbid", 1, 1), ("Replace", 2, 1)],
 )
+@pytest.mark.asyncio
 async def test_concurrency_policy(
     monkeypatch,
     tracing_running_job,
@@ -366,7 +369,7 @@
     command: |
       echo "foobar"
       exit 2
-    schedule: "* * * * *"
+    schedule: "@reboot"
     onFailure:
       retry:
         maximumRetries: 1
@@ -376,7 +379,7 @@
 """
 
 
-# @pytest.mark.xfail
+@pytest.mark.asyncio
 async def test_concurrency_and_backoff(monkeypatch, tracing_running_job):
     START_TIME = datetime.datetime(
         year=1999,
@@ -483,7 +486,7 @@
 
 
 @pytest.mark.parametrize(
-    "schedule, timezone, utc, now, reboot, result",
+    "schedule, timezone, utc, now, startup, enabled, result",
     [
         (
             "* * * * *",
@@ -491,6 +494,7 @@
             "",
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
             False,
+            "",
             True,
         ),
         (
@@ -499,14 +503,25 @@
             "",
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
             False,
+            "",
             True,
         ),
         (
+            "59 14 * * *",
+            "",
+            "",
+            DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
+            True,  # startup
+            "",
+            False,
+        ),
+        (
             "49 14 * * *",
             "",
             "",
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
             False,
+            "",
             False,
         ),
         (
@@ -515,6 +530,7 @@
             "utc: true",
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
             False,
+            "",
             True,
         ),
         (
@@ -523,6 +539,7 @@
             "utc: true",  # London is UTC+1 during DST
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC).astimezone(LONDON),
             False,
+            "",
             True,
         ),
         (
@@ -531,6 +548,7 @@
             "utc: false",  # London is UTC+1 during DST
             DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC).astimezone(LONDON),
             False,
+            "",
             False,
         ),
         (
@@ -539,6 +557,7 @@
             "",
             DT(2020, 7, 20, 15, 1, 1, tzinfo=UTC),
             False,
+            "",
             True,
         ),
         (
@@ -547,6 +566,7 @@
             "",
             DT(2020, 7, 20, 15, 1, 1, tzinfo=UTC),
             False,
+            "",
             False,
         ),
         (
@@ -555,6 +575,7 @@
             "",
             DT(2020, 7, 20, 15, 1, 1, tzinfo=UTC),
             False,
+            "",
             False,
         ),
         (
@@ -563,12 +584,24 @@
             "",
             DT(2020, 7, 20, 15, 1, 1, tzinfo=UTC),
             True,
+            "",
             True,
         ),
+
+        # enabled: false
+        (
+            "* * * * *",
+            "",
+            "",
+            DT(2020, 7, 20, 14, 59, 1, tzinfo=UTC),
+            False,
+            "enabled: false",
+            False,
+        ),
     ],
 )
 def test_job_should_run(
-    monkeypatch, schedule, timezone, utc, now, reboot, result
+    monkeypatch, schedule, timezone, utc, now, startup, enabled, result
 ):
     def get_now(timezone):
         print("timezone: ", timezone)
@@ -580,7 +613,7 @@
 
     monkeypatch.setattr("yacron.cron.get_now", get_now)
 
-    config_yaml = """
+    config_yaml = f"""
 jobs:
   - name: test
     command: |
@@ -588,10 +621,9 @@
     schedule: "{schedule}"
     {timezone}
     {utc}
-                            """.format(
-        schedule=schedule, timezone=timezone, utc=utc
-    )
+    {enabled}
+                            """
     print(config_yaml)
     cron = yacron.cron.Cron(None, config_yaml=config_yaml)
     job = list(cron.cron_jobs.values())[0]
-    assert cron.job_should_run(reboot, job) == result
+    assert cron.job_should_run(startup, job) == result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/tests/test_job.py 
new/yacron-0.19.0/tests/test_job.py
--- old/yacron-0.17.0/tests/test_job.py 2022-06-26 12:35:27.000000000 +0200
+++ new/yacron-0.19.0/tests/test_job.py 2023-03-11 14:36:36.000000000 +0100
@@ -42,7 +42,7 @@
         "cronjob-1", "stderr", fake_stream, "", save_limit
     )
 
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -52,7 +52,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
 
     async def producer(fake_stream):
@@ -76,7 +76,7 @@
         "cronjob-1", "stderr", fake_stream, "", 500
     )
 
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -86,7 +86,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
 
     async def producer(fake_stream):
@@ -173,8 +173,8 @@
 )
 @pytest.mark.asyncio
 async def test_report_mail(success, stdout, stderr, subject, body):
-    config, _, _ = yacron.config.parse_config_string(A_JOB, "")
-    job_config = config[0]
+    conf = yacron.config.parse_config_string(A_JOB, "")
+    job_config = conf.jobs[0]
     print(job_config.onSuccess["report"])
     job = Mock(
         config=job_config,
@@ -226,7 +226,12 @@
 
     assert smtp_init_args == (
         (),
-        {"hostname": "smtp1", "port": 1025, "use_tls": False},
+        {
+            "hostname": "smtp1",
+            "port": 1025,
+            "use_tls": False,
+            "validate_certs": False,
+        },
     )
     assert len(connect_calls) == 1
     assert len(start_tls_calls) == 1
@@ -309,8 +314,8 @@
     tmpdir,
     monkeypatch,
 ):
-    config, _, _ = yacron.config.parse_config_string(A_JOB, "")
-    job_config = config[0]
+    conf = yacron.config.parse_config_string(A_JOB, "")
+    job_config = conf.jobs[0]
 
     p = tmpdir.join("sentry-secret-dsn")
     p.write("http://xxx:yyy@sentry/2";)
@@ -430,7 +435,7 @@
     with tempfile.TemporaryDirectory() as tmp:
         out_file_path = os.path.join(tmp, "unit_test_file")
 
-        config, _, _ = yacron.config.parse_config_string(
+        conf = yacron.config.parse_config_string(
             f"""
 jobs:
   - name: test
@@ -446,7 +451,7 @@
     """,
             "",
         )
-        job_config = config[0]
+        job_config = conf.jobs[0]
 
         job = Mock(
             config=job_config,
@@ -526,7 +531,7 @@
     else:
         command_snippet = "    command: " + command
 
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -543,7 +548,7 @@
         ),
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
 
     job = yacron.job.RunningJob(job_config, None)
 
@@ -568,7 +573,7 @@
 
 @pytest.mark.asyncio
 async def test_execution_timeout():
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -583,7 +588,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
     await job.start()
     await job.wait()
@@ -592,7 +597,7 @@
 
 @pytest.mark.asyncio
 async def test_error1():
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -601,7 +606,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
 
     await job.start()
@@ -612,7 +617,7 @@
 
 @pytest.mark.asyncio
 async def test_error2():
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -621,7 +626,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
 
     with pytest.raises(RuntimeError):
@@ -630,7 +635,7 @@
 
 @pytest.mark.asyncio
 async def test_error3():
-    config, _, _ = yacron.config.parse_config_string(
+    conf = yacron.config.parse_config_string(
         """
 jobs:
   - name: test
@@ -639,7 +644,7 @@
 """,
         "",
     )
-    job_config = config[0]
+    job_config = conf.jobs[0]
     job = yacron.job.RunningJob(job_config, None)
 
     with pytest.raises(RuntimeError):
@@ -673,7 +678,7 @@
         host, port = transport.get_extra_info("sockname")
         print("Listening UDP on %s:%s" % (host, port))
 
-        config, _, _ = yacron.config.parse_config_string(
+        conf = yacron.config.parse_config_string(
             """
 jobs:
   - name: test
@@ -688,7 +693,7 @@
             ),
             "",
         )
-        job_config = config[0]
+        job_config = conf.jobs[0]
 
         job = yacron.job.RunningJob(job_config, None)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron/__main__.py 
new/yacron-0.19.0/yacron/__main__.py
--- old/yacron-0.17.0/yacron/__main__.py        2022-06-25 16:25:07.000000000 
+0200
+++ new/yacron-0.19.0/yacron/__main__.py        2023-03-11 11:29:20.000000000 
+0100
@@ -4,18 +4,27 @@
 import logging
 import signal
 import sys
+import os
 
 from yacron.cron import Cron, ConfigError
 import yacron.version
 
+CONFIG_DEFAULT = "/etc/yacron.d"
+
 
 def main_loop(loop):
-    parser = argparse.ArgumentParser()
+    parser = argparse.ArgumentParser(prog="yacron")
     parser.add_argument(
-        "-c", "--config", default="/etc/yacron.d", metavar="FILE-OR-DIR"
+        "-c",
+        "--config",
+        default=CONFIG_DEFAULT,
+        metavar="FILE-OR-DIR",
+        help="configuration file, or directory containing configuration files",
     )
     parser.add_argument("-l", "--log-level", default="INFO")
-    parser.add_argument("-v", "--validate-config", default=False, 
action="store_true")
+    parser.add_argument(
+        "-v", "--validate-config", default=False, action="store_true"
+    )
     parser.add_argument("--version", default=False, action="store_true")
     args = parser.parse_args()
 
@@ -27,6 +36,15 @@
         print(yacron.version.version)
         sys.exit(0)
 
+    if args.config == CONFIG_DEFAULT and not os.path.exists(args.config):
+        print(
+            "yacron error: configuration file not found, please provide one "
+            "with the --config option",
+            file=sys.stderr,
+        )
+        parser.print_help(sys.stderr)
+        sys.exit(1)
+
     try:
         cron = Cron(args.config)
     except ConfigError as err:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron/config.py 
new/yacron-0.19.0/yacron/config.py
--- old/yacron-0.17.0/yacron/config.py  2021-12-05 14:34:57.000000000 +0100
+++ new/yacron-0.19.0/yacron/config.py  2023-03-11 14:36:36.000000000 +0100
@@ -1,14 +1,16 @@
+from dataclasses import dataclass
 from pwd import getpwnam
 from grp import getgrnam
 import logging
 import os.path
 from typing import Union  # noqa
-from typing import List, Optional, Any, Dict, NewType, Tuple
+from typing import List, Optional, Any, Dict, NewType
 import datetime
 import pytz
 
 import strictyaml
 from strictyaml import Optional as Opt, EmptyDict
+from strictyaml import Any as YamlAny
 from strictyaml import (
     Bool,
     EmptyNone,
@@ -27,6 +29,7 @@
 logger = logging.getLogger("yacron.config")
 WebConfig = NewType("WebConfig", Dict[str, Any])
 JobDefaults = NewType("JobDefaults", Dict[str, Any])
+LoggingConfig = NewType("LoggingConfig", Dict[str, Any])
 
 
 class ConfigError(Exception):
@@ -73,6 +76,7 @@
         "smtpPort": 25,
         "tls": False,
         "starttls": False,
+        "validate_certs": False,
         "html": False,
         "subject": DEFAULT_SUBJECT_TEMPLATE,
         "body": DEFAULT_BODY_TEMPLATE,
@@ -92,7 +96,7 @@
     "captureStderr": True,
     "captureStdout": False,
     "saveLimit": 4096,
-    "maxLineLength": 16*1024*1024,
+    "maxLineLength": 16 * 1024 * 1024,
     "utc": True,
     "timezone": None,
     "failsWhen": {
@@ -118,6 +122,7 @@
     "killTimeout": 30,
     "statsd": None,
     "streamPrefix": "[{job_name} {stream_name}] ",
+    "enabled": True,
 }
 
 
@@ -158,6 +163,7 @@
                 ),
                 Opt("tls"): Bool(),
                 Opt("starttls"): Bool(),
+                Opt("validate_certs"): Bool(),
                 Opt("html"): Bool(),
             }
         ),
@@ -210,6 +216,7 @@
     Opt("user"): Str() | Int(),
     Opt("group"): Str() | Int(),
     Opt("streamPrefix"): Str(),
+    Opt("enabled"): Bool(),
 }
 
 _job_schema_dict = dict(_job_defaults_common)
@@ -237,6 +244,18 @@
         Opt("jobs"): Seq(Map(_job_schema_dict)),
         Opt("web"): Map({"listen": Seq(Str())}),
         Opt("include"): Seq(Str()),
+        Opt("logging"): Map(
+            {
+                "version": Int(),
+                Opt("incremental"): Bool(),
+                Opt("disable_existing_loggers"): Bool(),
+                Opt("formatters"): YamlAny(),
+                Opt("filters"): YamlAny(),
+                Opt("handlers"): YamlAny(),
+                Opt("loggers"): YamlAny(),
+                Opt("root"): YamlAny(),
+            }
+        ),
     }
 )
 
@@ -292,6 +311,7 @@
         self.saveLimit = config.pop("saveLimit")
         self.maxLineLength = config.pop("maxLineLength")
         self.utc = config.pop("utc")
+        self.enabled: bool = config.pop("enabled")
         self.timezone = None  # type: Optional[datetime.tzinfo]
         if config["timezone"] is not None:
             try:
@@ -397,9 +417,15 @@
     return environ
 
 
-def parse_config_string(
-    data: str, path: str
-) -> Tuple[List[JobConfig], Optional[WebConfig], JobDefaults]:
+@dataclass
+class YacronConfig:
+    jobs: List[JobConfig]
+    web_config: Optional[WebConfig]
+    job_defaults: JobDefaults
+    logging_config: Optional[LoggingConfig]
+
+
+def parse_config_string(data: str, path: str) -> YacronConfig:
     try:
         doc = strictyaml.load(data, CONFIG_SCHEMA, label=path).data
     except YAMLError as ex:
@@ -408,36 +434,44 @@
     inc_defaults_merged: dict = {}
     jobs = []
     webconf = WebConfig(doc["web"]) if "web" in doc else None
+    logging_conf = LoggingConfig(doc["logging"]) if "logging" in doc else None
     for include in doc.get("include", ()):
         inc_path = os.path.join(os.path.dirname(path), include)
-        inc_jobs, inc_webconf, inc_defaults = parse_config_file(inc_path)
+        inc_config = parse_config_file(inc_path)
         inc_defaults_merged = dict(
-            mergedicts(inc_defaults_merged, inc_defaults)
+            mergedicts(inc_defaults_merged, inc_config.job_defaults)
         )
-        jobs.extend(inc_jobs)
-        if inc_webconf:
+        jobs.extend(inc_config.jobs)
+        if inc_config.web_config:
             if webconf:
-                raise ConfigError("multiple web config")
-            webconf = inc_webconf
+                raise ConfigError("multiple web configs")
+            webconf = inc_config.web_config
+        if inc_config.logging_config:
+            if logging_conf:
+                raise ConfigError("multiple logging configs")
+            logging_conf = inc_config.logging_config
     defaults = dict(mergedicts(DEFAULT_CONFIG, inc_defaults_merged))
     defaults = dict(mergedicts(defaults, doc.get("defaults", {})))
     for config_job in doc.get("jobs", []):
         job_dict = dict(mergedicts(defaults, config_job))
         jobs.append(JobConfig(job_dict))
-    return jobs, webconf, JobDefaults(defaults)
+    return YacronConfig(
+        jobs=jobs,
+        web_config=webconf,
+        job_defaults=JobDefaults(defaults),
+        logging_config=logging_conf,
+    )
 
 
 def parse_config_file(
     path: str,
-) -> Tuple[List[JobConfig], Optional[WebConfig], JobDefaults]:
+) -> YacronConfig:
     with open(path, "rt", encoding="utf-8") as stream:
         data = stream.read()
     return parse_config_string(data, path)
 
 
-def parse_config(
-    config_arg: str,
-) -> Tuple[List[JobConfig], Optional[WebConfig]]:
+def parse_config(config_arg: str) -> YacronConfig:
     jobs = []
     config_errors = {}
     web_config = None
@@ -449,16 +483,16 @@
                 continue
             if ext in {".yml", ".yaml"}:
                 try:
-                    config, webconf, _ = parse_config_file(direntry.path)
+                    config = parse_config_file(direntry.path)
                 except ConfigError as err:
                     config_errors[direntry.path] = str(err)
                 except OSError as ex:
                     config_errors[config_arg] = str(ex)
                 else:
-                    jobs.extend(config)
-                    if webconf is not None:
+                    jobs.extend(config.jobs)
+                    if config.web_config is not None:
                         if web_config is None:
-                            web_config = webconf
+                            web_config = config.web_config
                             web_config_source_fname = direntry.path
                         else:
                             raise ConfigError(
@@ -469,11 +503,16 @@
                             )
     else:
         try:
-            config, web_config, _ = parse_config_file(config_arg)
+            config = parse_config_file(config_arg)
         except OSError as ex:
             config_errors[config_arg] = str(ex)
         else:
-            jobs.extend(config)
+            jobs.extend(config.jobs)
     if config_errors:
         raise ConfigError("\n---".join(config_errors.values()))
-    return jobs, web_config
+    return YacronConfig(
+        jobs=jobs,
+        web_config=config.web_config,
+        job_defaults=config.job_defaults,
+        logging_config=config.logging_config,
+    )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron/cron.py 
new/yacron-0.19.0/yacron/cron.py
--- old/yacron-0.17.0/yacron/cron.py    2022-06-26 11:35:49.000000000 +0200
+++ new/yacron-0.19.0/yacron/cron.py    2023-03-11 14:36:36.000000000 +0100
@@ -1,3 +1,4 @@
+import logging.config
 import asyncio
 import asyncio.subprocess
 import datetime
@@ -13,6 +14,8 @@
     ConfigError,
     parse_config_string,
     WebConfig,
+    YacronConfig,
+    JobDefaults,
 )
 from yacron.job import RunningJob, JobRetryState
 from crontab import CronTab  # noqa
@@ -82,8 +85,10 @@
             self.update_config()
         if config_yaml is not None:
             # config_yaml is for unit testing
-            config, _, _ = parse_config_string(config_yaml, "")
-            self.cron_jobs = OrderedDict((job.name, job) for job in config)
+            config = parse_config_string(config_yaml, "")
+            self.cron_jobs = OrderedDict(
+                (job.name, job) for job in config.jobs
+            )
 
         self._wait_for_running_jobs_task = None  # type: Optional[asyncio.Task]
         self._stop_event = asyncio.Event()
@@ -98,10 +103,11 @@
         )
 
         startup = True
+        logging_configured = False
         while not self._stop_event.is_set():
             try:
-                web_config = self.update_config()
-                await self.start_stop_web_app(web_config)
+                config = self.update_config()
+                await self.start_stop_web_app(config.web_config)
             except ConfigError as err:
                 logger.error(
                     "Error in configuration file(s), so not updating "
@@ -110,6 +116,19 @@
                 )
             except Exception:  # pragma: nocover
                 logger.exception("please report this as a bug (1)")
+            if config.logging_config is not None and not logging_configured:
+                logging_configured = True
+                try:
+                    logging.config.dictConfig(config.logging_config)
+                except Exception as ex:
+                    logger.error(
+                        "Error while configuring logging: %s\n"
+                        "Check for correct format at "
+                        "https://docs.python.org/3/library/logging.config.html";
+                        "#dictionary-schema-details\n%s",
+                        ex,
+                        config.logging_config,
+                    )
             await self.spawn_jobs(startup)
             startup = False
             sleep_interval = next_sleep_interval()
@@ -135,12 +154,17 @@
         logger.debug("Signalling shutdown")
         self._stop_event.set()
 
-    def update_config(self) -> Optional[WebConfig]:
+    def update_config(self) -> YacronConfig:
         if self.config_arg is None:
-            return None
-        config, web_config = parse_config(self.config_arg)
-        self.cron_jobs = OrderedDict((job.name, job) for job in config)
-        return web_config
+            return YacronConfig(
+                jobs=[],
+                web_config=None,
+                job_defaults=JobDefaults({}),
+                logging_config=None,
+            )
+        config = parse_config(self.config_arg)
+        self.cron_jobs = OrderedDict((job.name, job) for job in config.jobs)
+        return config
 
     async def _web_get_version(self, request: web.Request) -> web.Response:
         return web.Response(text=yacron.version.version)
@@ -250,18 +274,24 @@
 
     @staticmethod
     def job_should_run(startup: bool, job: JobConfig) -> bool:
-        if (
-            startup
-            and isinstance(job.schedule, str)
-            and job.schedule == "@reboot"
-        ):
+        if not job.enabled:
             logger.debug(
-                "Job %s (%s) is scheduled for startup (@reboot)",
+                "Job %s (%s) is disabled in the config",
                 job.name,
                 job.schedule_unparsed,
             )
-            return True
-        elif isinstance(job.schedule, CronTab):
+            return False
+        if startup:
+            if isinstance(job.schedule, str) and job.schedule == "@reboot":
+                logger.debug(
+                    "Job %s (%s) is scheduled for startup (@reboot)",
+                    job.name,
+                    job.schedule_unparsed,
+                )
+                return True
+            else:
+                return False
+        if isinstance(job.schedule, CronTab):
             crontab = job.schedule  # type: CronTab
             if crontab.test(get_now(job.timezone).replace(second=0)):
                 logger.debug(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron/job.py 
new/yacron-0.19.0/yacron/job.py
--- old/yacron-0.17.0/yacron/job.py     2022-06-26 12:35:04.000000000 +0200
+++ new/yacron-0.19.0/yacron/job.py     2023-03-11 11:29:20.000000000 +0100
@@ -43,8 +43,8 @@
         stream_prefix: str,
         save_limit: int,
     ) -> None:
-        self.save_top = []  # type: List[str]
-        self.save_bottom = []  # type: List[str]
+        self.save_top: List[str] = []
+        self.save_bottom: List[str] = []
         self.job_name = job_name
         self.save_limit = save_limit
         self.stream_name = stream_name
@@ -207,7 +207,10 @@
         else:
             message.set_content(body)
         smtp = aiosmtplib.SMTP(
-            hostname=smtp_host, port=smtp_port, use_tls=mail["tls"]
+            hostname=smtp_host,
+            port=smtp_port,
+            use_tls=mail["tls"],
+            validate_certs=mail["validate_certs"],
         )
         await smtp.connect()
         if mail["starttls"]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron/version.py 
new/yacron-0.19.0/yacron/version.py
--- old/yacron-0.17.0/yacron/version.py 2022-06-26 15:02:51.000000000 +0200
+++ new/yacron-0.19.0/yacron/version.py 2023-03-11 14:54:57.000000000 +0100
@@ -1,5 +1,5 @@
 # coding: utf-8
 # file generated by setuptools_scm
 # don't change, don't track in version control
-__version__ = version = '0.17.0'
-__version_tuple__ = version_tuple = (0, 17, 0)
+__version__ = version = '0.19.0'
+__version_tuple__ = version_tuple = (0, 19, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yacron-0.17.0/yacron.egg-info/PKG-INFO 
new/yacron-0.19.0/yacron.egg-info/PKG-INFO
--- old/yacron-0.17.0/yacron.egg-info/PKG-INFO  2022-06-26 15:02:52.000000000 
+0200
+++ new/yacron-0.19.0/yacron.egg-info/PKG-INFO  2023-03-11 14:54:57.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: yacron
-Version: 0.17.0
+Version: 0.19.0
 Summary: A modern Cron replacement that is Docker-friendly
 Home-page: https://github.com/gjcarneiro/yacron
 Author: Gustavo Carneiro
@@ -134,9 +134,9 @@
         schedule: "*/5 * * * *"
 
 
-The `schedule` option can be a string in the traditional crontab format
-(including @reboot, which will only run the job when yacron is initially
-executed), or can be an object with properties.  The following configuration
+The `schedule` option can be a string in a crontab format specified by 
https://github.com/josiahcarlson/parse-crontab (this module is used by yacron).
+Additionally @reboot can be included , which will only run the job when yacron 
is initially
+executed. Further `schedule` can be an object with properties.  The following 
configuration
 runs a command every 5 minutes, but only on the specific date 2017-07-19, and
 doesn't run it in any other date:
 
@@ -767,11 +767,75 @@
         sentry:
           ...
 
+Custom logging
+++++++++++++++
+
+It's possible to provide a custom logging configuration, via the ``logging``
+configuration section.  For example, the following configuration displays log 
lines with
+an embedded timestamp for each message.
+
+.. code-block:: yaml
+
+    logging:
+      # In the format of:
+      # 
https://docs.python.org/3/library/logging.config.html#dictionary-schema-details
+      version: 1
+      disable_existing_loggers: false
+      formatters:
+        simple:
+          format: '%(asctime)s [%(processName)s/%(threadName)s] %(levelname)s 
(%(name)s): %(message)s'
+      handlers:
+        console:
+          class: logging.StreamHandler
+          level: DEBUG
+          formatter: simple
+          stream: ext://sys.stdout
+      root:
+        level: INFO
+        handlers:
+          - console
+
+Obscure configuration options
++++++++++++++++++++++++++++++
+
+enabled: true|false (default true)
+##################################
+
+(new in yacron 0.18)
+
+It is possible to disable a specific cron job by adding a `enabled: false` 
option.  Jobs
+with `enabled: false` will simply be skipped, as if they aren't there, apart 
from
+validating the configuration.
+
+.. code-block:: yaml
+
+    jobs:
+      - name: test-01
+        enabled: false  # this cron job will not run until you change this to 
`true`
+        command: echo "foobar"
+        shell: /bin/bash
+        schedule: "* * * * *"
+
+
+
 
 =======
 History
 =======
 
+0.19.0 (2023-03-11)
+-------------------
+
+* Add ability to configure yacron's own logging (#81 #82 #83, gjcarneiro, 
bdamian)
+* Add config value for SMTP(validate_certs=False) (David Batley)
+
+0.18.0 (2023-01-01)
+-------------------
+
+* fixes "Job is always executed immediately on yacron start" (#67)
+* add an `enabled` option in jobs (#73)
+* give a better error message when no configuration file is provided or exists 
(#72)
+
 0.17.0 (2022-06-26)
 -------------------
 

Reply via email to