dron 0.1.20241008__tar.gz

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.
Files changed (39) hide show
  1. dron-0.1.20241008/.ci/release +66 -0
  2. dron-0.1.20241008/.ci/run +41 -0
  3. dron-0.1.20241008/.github/workflows/main.yml +90 -0
  4. dron-0.1.20241008/.gitignore +160 -0
  5. dron-0.1.20241008/LICENSE.txt +21 -0
  6. dron-0.1.20241008/PKG-INFO +46 -0
  7. dron-0.1.20241008/README.org +135 -0
  8. dron-0.1.20241008/conftest.py +47 -0
  9. dron-0.1.20241008/mypy.ini +14 -0
  10. dron-0.1.20241008/pyproject.toml +56 -0
  11. dron-0.1.20241008/pytest.ini +13 -0
  12. dron-0.1.20241008/ruff.toml +141 -0
  13. dron-0.1.20241008/setup.cfg +4 -0
  14. dron-0.1.20241008/src/dron/__init__.py +6 -0
  15. dron-0.1.20241008/src/dron/__main__.py +5 -0
  16. dron-0.1.20241008/src/dron/api.py +106 -0
  17. dron-0.1.20241008/src/dron/cli.py +382 -0
  18. dron-0.1.20241008/src/dron/common.py +174 -0
  19. dron-0.1.20241008/src/dron/conftest.py +18 -0
  20. dron-0.1.20241008/src/dron/dron.py +510 -0
  21. dron-0.1.20241008/src/dron/launchd.py +415 -0
  22. dron-0.1.20241008/src/dron/launchd_wrapper.py +90 -0
  23. dron-0.1.20241008/src/dron/monitor.py +157 -0
  24. dron-0.1.20241008/src/dron/notify/common.py +53 -0
  25. dron-0.1.20241008/src/dron/notify/email.py +43 -0
  26. dron-0.1.20241008/src/dron/notify/ntfy_common.py +24 -0
  27. dron-0.1.20241008/src/dron/notify/ntfy_desktop.py +15 -0
  28. dron-0.1.20241008/src/dron/notify/ntfy_telegram.py +12 -0
  29. dron-0.1.20241008/src/dron/notify/telegram.py +42 -0
  30. dron-0.1.20241008/src/dron/py.typed +0 -0
  31. dron-0.1.20241008/src/dron/systemd.py +542 -0
  32. dron-0.1.20241008/src/dron/tests/test_dron.py +119 -0
  33. dron-0.1.20241008/src/dron.egg-info/PKG-INFO +46 -0
  34. dron-0.1.20241008/src/dron.egg-info/SOURCES.txt +37 -0
  35. dron-0.1.20241008/src/dron.egg-info/dependency_links.txt +1 -0
  36. dron-0.1.20241008/src/dron.egg-info/entry_points.txt +2 -0
  37. dron-0.1.20241008/src/dron.egg-info/requires.txt +20 -0
  38. dron-0.1.20241008/src/dron.egg-info/top_level.txt +1 -0
  39. dron-0.1.20241008/tox.ini +54 -0
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ '''
3
+ Run [[file:scripts/release][scripts/release]] to deploy Python package onto [[https://pypi.org][PyPi]] and [[https://test.pypi.org][test PyPi]].
4
+
5
+ The script expects =TWINE_PASSWORD= environment variable to contain the [[https://pypi.org/help/#apitoken][PyPi token]] (not the password!).
6
+
7
+ The script can be run manually.
8
+ It's also running as =pypi= job in [[file:.github/workflows/main.yml][Github Actions config]]. Packages are deployed on:
9
+ - every master commit, onto test pypi
10
+ - every new tag, onto production pypi
11
+
12
+ You'll need to set =TWINE_PASSWORD= and =TWINE_PASSWORD_TEST= in [[https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets][secrets]]
13
+ for Github Actions deployment to work.
14
+ '''
15
+
16
+ import os
17
+ import sys
18
+ from pathlib import Path
19
+ from subprocess import check_call
20
+ import shutil
21
+
22
+ is_ci = os.environ.get('CI') is not None
23
+
24
+ def main() -> None:
25
+ import argparse
26
+ p = argparse.ArgumentParser()
27
+ p.add_argument('--test', action='store_true', help='use test pypi')
28
+ args = p.parse_args()
29
+
30
+ extra = []
31
+ if args.test:
32
+ extra.extend(['--repository', 'testpypi'])
33
+
34
+ root = Path(__file__).absolute().parent.parent
35
+ os.chdir(root) # just in case
36
+
37
+ if is_ci:
38
+ # see https://github.com/actions/checkout/issues/217
39
+ check_call('git fetch --prune --unshallow'.split())
40
+
41
+ dist = root / 'dist'
42
+ if dist.exists():
43
+ shutil.rmtree(dist)
44
+
45
+ check_call(['python3', '-m', 'build'])
46
+
47
+ TP = 'TWINE_PASSWORD'
48
+ password = os.environ.get(TP)
49
+ if password is None:
50
+ print(f"WARNING: no {TP} passed", file=sys.stderr)
51
+ import pip_secrets
52
+ password = pip_secrets.token_test if args.test else pip_secrets.token # meh
53
+
54
+ check_call([
55
+ 'python3', '-m', 'twine',
56
+ 'upload', *dist.iterdir(),
57
+ *extra,
58
+ ], env={
59
+ 'TWINE_USERNAME': '__token__',
60
+ TP: password,
61
+ **os.environ,
62
+ })
63
+
64
+
65
+ if __name__ == '__main__':
66
+ main()
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ set -eu
3
+
4
+ cd "$(dirname "$0")"
5
+ cd .. # git root
6
+
7
+ if ! command -v sudo; then
8
+ # CI or Docker sometimes doesn't have it, so useful to have a dummy
9
+ function sudo {
10
+ "$@"
11
+ }
12
+ fi
13
+
14
+ if [ -n "${CI-}" ]; then
15
+ # install OS specific stuff here
16
+ case "$OSTYPE" in
17
+ darwin*)
18
+ # macos
19
+ :
20
+ ;;
21
+ cygwin* | msys* | win*)
22
+ # windows
23
+ :
24
+ ;;
25
+ *)
26
+ # must be linux?
27
+ # necessary for dbus-python
28
+ sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_SUSPEND=1 apt-get install --yes libdbus-1-dev libglib2.0-dev
29
+ ;;
30
+ esac
31
+ fi
32
+
33
+
34
+ PY_BIN="python3"
35
+ # some systems might have python pointing to python3
36
+ if ! command -v python3 &> /dev/null; then
37
+ PY_BIN="python"
38
+ fi
39
+
40
+ "$PY_BIN" -m pip install --user tox
41
+ "$PY_BIN" -m tox --parallel --parallel-live "$@"
@@ -0,0 +1,90 @@
1
+ # see https://github.com/karlicoss/pymplate for up-to-date reference
2
+
3
+ name: CI
4
+ on:
5
+ push:
6
+ branches: '*'
7
+ tags: 'v[0-9]+.*' # only trigger on 'release' tags for PyPi
8
+ # Ideally I would put this in the pypi job... but github syntax doesn't allow for regexes there :shrug:
9
+ # P.S. fuck made up yaml DSLs.
10
+ pull_request: # needed to trigger on others' PRs
11
+ # Note that people who fork it need to go to "Actions" tab on their fork and click "I understand my workflows, go ahead and enable them".
12
+ workflow_dispatch: # needed to trigger workflows manually
13
+ # todo cron?
14
+ inputs:
15
+ debug_enabled:
16
+ type: boolean
17
+ description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
18
+ required: false
19
+ default: false
20
+
21
+
22
+ jobs:
23
+ build:
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ platform: [ubuntu-latest, macos-latest]
28
+ python-version: ['3.9', '3.10', '3.11', '3.12']
29
+ # vvv just an example of excluding stuff from matrix
30
+ # exclude: [{platform: macos-latest, python-version: '3.6'}]
31
+
32
+ runs-on: ${{ matrix.platform }}
33
+
34
+ steps:
35
+ # ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
36
+ - run: echo "$HOME/.local/bin" >> $GITHUB_PATH
37
+
38
+ - uses: actions/setup-python@v5
39
+ with:
40
+ python-version: ${{ matrix.python-version }}
41
+
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ submodules: recursive
45
+ fetch-depth: 0 # nicer to have all git history when debugging/for tests
46
+
47
+ - uses: mxschmitt/action-tmate@v3
48
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
49
+
50
+ # explicit bash command is necessary for Windows CI runner, otherwise it thinks it's cmd...
51
+ - run: bash .ci/run
52
+
53
+ - if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ include-hidden-files: true
57
+ name: .coverage.mypy_${{ matrix.platform }}_${{ matrix.python-version }}
58
+ path: .coverage.mypy/
59
+
60
+
61
+ pypi:
62
+ runs-on: ubuntu-latest
63
+ needs: [build] # add all other jobs here
64
+
65
+ steps:
66
+ # ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
67
+ - run: echo "$HOME/.local/bin" >> $GITHUB_PATH
68
+
69
+ - uses: actions/setup-python@v5
70
+ with:
71
+ python-version: '3.8'
72
+
73
+ - uses: actions/checkout@v4
74
+ with:
75
+ submodules: recursive
76
+
77
+ - name: 'release to test pypi'
78
+ # always deploy merged master to test pypi
79
+ if: github.event_name != 'pull_request' && github.event.ref == 'refs/heads/master'
80
+ env:
81
+ TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD_TEST }}
82
+ run: pip3 install --user --upgrade build twine && .ci/release --test
83
+
84
+ - name: 'release to pypi'
85
+ # always deploy tags to release pypi
86
+ # NOTE: release tags are guarded by on: push: tags on the top
87
+ if: github.event_name != 'pull_request' && startsWith(github.event.ref, 'refs/tags')
88
+ env:
89
+ TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
90
+ run: pip3 install --user --upgrade build twine && .ci/release
@@ -0,0 +1,160 @@
1
+
2
+ # Created by https://www.gitignore.io/api/python,emacs
3
+ # Edit at https://www.gitignore.io/?templates=python,emacs
4
+
5
+ ### Emacs ###
6
+ # -*- mode: gitignore; -*-
7
+ *~
8
+ \#*\#
9
+ /.emacs.desktop
10
+ /.emacs.desktop.lock
11
+ *.elc
12
+ auto-save-list
13
+ tramp
14
+ .\#*
15
+
16
+ # Org-mode
17
+ .org-id-locations
18
+ *_archive
19
+
20
+ # flymake-mode
21
+ *_flymake.*
22
+
23
+ # eshell files
24
+ /eshell/history
25
+ /eshell/lastdir
26
+
27
+ # elpa packages
28
+ /elpa/
29
+
30
+ # reftex files
31
+ *.rel
32
+
33
+ # AUCTeX auto folder
34
+ /auto/
35
+
36
+ # cask packages
37
+ .cask/
38
+ dist/
39
+
40
+ # Flycheck
41
+ flycheck_*.el
42
+
43
+ # server auth directory
44
+ /server/
45
+
46
+ # projectiles files
47
+ .projectile
48
+
49
+ # directory configuration
50
+ .dir-locals.el
51
+
52
+ # network security
53
+ /network-security.data
54
+
55
+
56
+ ### Python ###
57
+ # Byte-compiled / optimized / DLL files
58
+ __pycache__/
59
+ *.py[cod]
60
+ *$py.class
61
+
62
+ # C extensions
63
+ *.so
64
+
65
+ # Distribution / packaging
66
+ .Python
67
+ build/
68
+ develop-eggs/
69
+ downloads/
70
+ eggs/
71
+ .eggs/
72
+ lib/
73
+ lib64/
74
+ parts/
75
+ sdist/
76
+ var/
77
+ wheels/
78
+ pip-wheel-metadata/
79
+ share/python-wheels/
80
+ *.egg-info/
81
+ .installed.cfg
82
+ *.egg
83
+ MANIFEST
84
+
85
+ # PyInstaller
86
+ # Usually these files are written by a python script from a template
87
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
88
+ *.manifest
89
+ *.spec
90
+
91
+ # Installer logs
92
+ pip-log.txt
93
+ pip-delete-this-directory.txt
94
+
95
+ # Unit test / coverage reports
96
+ htmlcov/
97
+ .tox/
98
+ .nox/
99
+ .coverage
100
+ .coverage.*
101
+ .cache
102
+ nosetests.xml
103
+ coverage.xml
104
+ *.cover
105
+ .hypothesis/
106
+ .pytest_cache/
107
+
108
+ # Translations
109
+ *.mo
110
+ *.pot
111
+
112
+ # Scrapy stuff:
113
+ .scrapy
114
+
115
+ # Sphinx documentation
116
+ docs/_build/
117
+
118
+ # PyBuilder
119
+ target/
120
+
121
+ # pyenv
122
+ .python-version
123
+
124
+ # pipenv
125
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
126
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
127
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
128
+ # install all needed dependencies.
129
+ #Pipfile.lock
130
+
131
+ # celery beat schedule file
132
+ celerybeat-schedule
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Spyder project settings
138
+ .spyderproject
139
+ .spyproject
140
+
141
+ # Rope project settings
142
+ .ropeproject
143
+
144
+ # Mr Developer
145
+ .mr.developer.cfg
146
+ .project
147
+ .pydevproject
148
+
149
+ # mkdocs documentation
150
+ /site
151
+
152
+ # mypy
153
+ .mypy_cache/
154
+ .dmypy.json
155
+ dmypy.json
156
+
157
+ # Pyre type checker
158
+ .pyre/
159
+
160
+ # End of https://www.gitignore.io/api/python,emacs
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Dima Gerasimov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: dron
3
+ Version: 0.1.20241008
4
+ Author-email: "Dima Gerasimov (@karlicoss)" <karlicoss@gmail.com>
5
+ Maintainer-email: "Dima Gerasimov (@karlicoss)" <karlicoss@gmail.com>
6
+ License: The MIT License (MIT)
7
+
8
+ Copyright (c) 2024 Dima Gerasimov
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/karlicoss/dron
29
+ Requires-Python: >=3.9
30
+ License-File: LICENSE.txt
31
+ Requires-Dist: click
32
+ Requires-Dist: prompt_toolkit
33
+ Requires-Dist: tzlocal
34
+ Requires-Dist: textual
35
+ Requires-Dist: tabulate
36
+ Requires-Dist: termcolor
37
+ Requires-Dist: mypy
38
+ Requires-Dist: loguru
39
+ Requires-Dist: dbus-python; platform_system != "Darwin"
40
+ Provides-Extra: testing
41
+ Requires-Dist: pytest; extra == "testing"
42
+ Requires-Dist: ruff; extra == "testing"
43
+ Requires-Dist: mypy; extra == "testing"
44
+ Requires-Dist: lxml; extra == "testing"
45
+ Provides-Extra: notify-telegram
46
+ Requires-Dist: telegram-send>=0.37; extra == "notify-telegram"
@@ -0,0 +1,135 @@
1
+ #+begin_src python :results drawer :exports results
2
+ import dron; return dron.make_parser().description
3
+ #+end_src
4
+
5
+ #+RESULTS:
6
+ :results:
7
+ dron -- simple frontend for Systemd, inspired by cron.
8
+
9
+ - *d* stands for 'Systemd'
10
+ - *ron* stands for 'cron'
11
+
12
+ dron is my attempt to overcome things that make working with Systemd tedious
13
+ :end:
14
+
15
+
16
+ #+begin_src python :results drawer :exports results
17
+ import dron; return dron.make_parser().epilog
18
+ #+end_src
19
+
20
+ #+RESULTS:
21
+ :results:
22
+
23
+ * What does it do?
24
+ In short, you type ~dron edit~ and edit your config file, similarly to ~crontab -e~:
25
+
26
+ : from dron.api import job
27
+ :
28
+ : # at the moment you're expected to define jobs() function that yields jobs
29
+ : # in the future I might add more mechanisms
30
+ : def jobs():
31
+ : # simple job that doesn't do much
32
+ : yield job(
33
+ : 'daily',
34
+ : '/home/user/scripts/run-borg /home/user',
35
+ : unit_name='borg-backup-home',
36
+ : )
37
+ :
38
+ : yield job(
39
+ : 'daily',
40
+ : 'linkchecker https://beepb00p.xyz',
41
+ : unit_name='linkchecker-beepb00p',
42
+ : )
43
+ :
44
+ : # drontab is simply python code!
45
+ : # so if you're annoyed by having to rememver Systemd syntax, you can use a helper function
46
+ : def every(*, mins: int) -> str:
47
+ : return f'*:0/{mins}'
48
+ :
49
+ : # make sure my website is alive, it will send local email on failure
50
+ : yield job(
51
+ : every(mins=10),
52
+ : 'ping https://beepb00p.xyz',
53
+ : unit_name='ping-beepb00p',
54
+ : )
55
+
56
+
57
+ After you save your changes and exit the editor, your drontab is checked for syntax and applied
58
+
59
+ - if checks have passed, your jobs are mapped onto Systemd units and started up
60
+ - if there are potential errors, you are prompted to fix them before retrying
61
+
62
+ * Why?
63
+ In short, because I want to benefit from the heavy lifting that Systemd does: timeouts, resource management, restart policies, powerful scheduling specs and logging,
64
+ while not having to manually manipulate numerous unit files and restart the daemon all over.
65
+
66
+ I elaborate on what led me to implement it and motivation [[https://beepb00p.xyz/scheduler.html#what_do_i_want][here]]. Also:
67
+
68
+ - why not just use [[https://beepb00p.xyz/scheduler.html#cron][cron]]?
69
+ - why not just use [[https://beepb00p.xyz/scheduler.html#systemd][systemd]]?
70
+
71
+ :end:
72
+
73
+
74
+ * Setting up
75
+
76
+ 1. install system dependencies (see =.ci/run= ) -- these are necessary for =dbus-python= library
77
+ 2. install dron: =pip3 install --user git+https://github.com/karlicoss/dron=
78
+ 3. install =sendmail= from your package manager if you want to recieve job failure emails
79
+
80
+ * Using
81
+
82
+ #+begin_src python :results value :exports results
83
+ import dron;
84
+ p = dron.make_parser()
85
+ p.prog = ''
86
+ p.epilog = ''
87
+ return p.format_help()
88
+ #+end_src
89
+
90
+ #+RESULTS:
91
+ #+begin_example
92
+ usage: [-h] [--marker MARKER] {monitor,past,edit,apply,lint,uninstall} ...
93
+
94
+ dron -- simple frontend for Systemd, inspired by cron.
95
+
96
+ - *d* stands for 'Systemd'
97
+ - *ron* stands for 'cron'
98
+
99
+ dron is my attempt to overcome things that make working with Systemd tedious
100
+
101
+ positional arguments:
102
+ {monitor,past,edit,apply,lint,uninstall}
103
+ monitor Monitor services/timers managed by dron
104
+ past List past job runs
105
+ edit Edit drontab (like 'crontab -e')
106
+ apply Apply drontab (like 'crontab' with no args)
107
+ lint Check drontab (no 'crontab' alternative, sadly!)
108
+ uninstall Uninstall all managed jobs
109
+
110
+ options:
111
+ -h, --help show this help message and exit
112
+ --marker MARKER Use custom marker instead of default `(MANAGED BY DRON)`. Possibly useful for developing/testing.
113
+ #+end_example
114
+
115
+
116
+ * Job syntax
117
+
118
+ The idea is that it's a simple python DSL that lets you define simple jobs with minimal friction.
119
+
120
+ However, if you wish you can pass arbitrary unit properties as keyword arguments as well.
121
+
122
+ * Caveats
123
+ - older systemd versions would only accept absolute path for =ExecStart=. That should be caught during =dron edit= though
124
+
125
+ * Potential improvements
126
+ - custom validation; at the moment it runs pylint, mypy and systemd verify
127
+ - make it more atomic?
128
+
129
+ E.g. roll back all the changes until daemon-reload
130
+ - more failure report mechanisms?
131
+
132
+ Ideally, benefit from [[https://github.com/dschep/ntfy][ntfy]]
133
+
134
+ ** TODO add issues with various questions that I had in code?
135
+
@@ -0,0 +1,47 @@
1
+ # this is a hack to monkey patch pytest so it handles tests inside namespace packages without __init__.py properly
2
+ # without it, pytest can't discover the package root for some reason
3
+ # also see https://github.com/karlicoss/pytest_namespace_pkgs for more
4
+
5
+ import os
6
+ import pathlib
7
+ from typing import Optional
8
+
9
+ import _pytest.main
10
+ import _pytest.pathlib
11
+
12
+ # we consider all dirs in repo/ to be namespace packages
13
+ root_dir = pathlib.Path(__file__).absolute().parent.resolve() / 'src'
14
+ assert root_dir.exists(), root_dir
15
+
16
+ # TODO assert it contains package name?? maybe get it via setuptools..
17
+
18
+ namespace_pkg_dirs = [str(d) for d in root_dir.iterdir() if d.is_dir()]
19
+
20
+ # resolve_package_path is called from _pytest.pathlib.import_path
21
+ # takes a full abs path to the test file and needs to return the path to the 'root' package on the filesystem
22
+ resolve_pkg_path_orig = _pytest.pathlib.resolve_package_path
23
+ def resolve_package_path(path: pathlib.Path) -> Optional[pathlib.Path]:
24
+ result = path # search from the test file upwards
25
+ for parent in result.parents:
26
+ if str(parent) in namespace_pkg_dirs:
27
+ return parent
28
+ if os.name == 'nt':
29
+ # ??? for some reason on windows it is trying to call this against conftest? but not on linux/osx
30
+ if path.name == 'conftest.py':
31
+ return resolve_pkg_path_orig(path)
32
+ raise RuntimeError("Couldn't determine path for ", path)
33
+ _pytest.pathlib.resolve_package_path = resolve_package_path
34
+
35
+
36
+ # without patching, the orig function returns just a package name for some reason
37
+ # (I think it's used as a sort of fallback)
38
+ # so we need to point it at the absolute path properly
39
+ # not sure what are the consequences.. maybe it wouldn't be able to run against installed packages? not sure..
40
+ search_pypath_orig = _pytest.main.search_pypath
41
+ def search_pypath(module_name: str) -> str:
42
+ mpath = root_dir / module_name.replace('.', os.sep)
43
+ if not mpath.is_dir():
44
+ mpath = mpath.with_suffix('.py')
45
+ assert mpath.exists(), mpath # just in case
46
+ return str(mpath)
47
+ _pytest.main.search_pypath = search_pypath
@@ -0,0 +1,14 @@
1
+ [mypy]
2
+ pretty = True
3
+ show_error_context = True
4
+ show_column_numbers = True
5
+ show_error_end = True
6
+ warn_redundant_casts = True
7
+ warn_unused_ignores = True
8
+ check_untyped_defs = True
9
+ strict_equality = True
10
+ enable_error_code = possibly-undefined
11
+
12
+ # an example of suppressing
13
+ # [mypy-my.config.repos.pdfannots.pdfannots]
14
+ # ignore_errors = True
@@ -0,0 +1,56 @@
1
+ # see https://github.com/karlicoss/pymplate for up-to-date reference
2
+ [project]
3
+ dynamic = ["version"] # version is managed by setuptools_scm
4
+ name = "dron"
5
+ dependencies = [
6
+ "click" , # CLI
7
+ "prompt_toolkit", # CLI
8
+ "tzlocal" , # for monitor, to determine host timezone
9
+ "textual" , # for 'new' monitor
10
+ "tabulate" , # for 'old' monitor
11
+ "termcolor" , # for 'old' monitor
12
+ "mypy" , # for checking units
13
+ "loguru" , # nicer logging
14
+ "dbus-python; platform_system != 'Darwin'", # dbus interface to systemd
15
+ ]
16
+ requires-python = ">=3.9"
17
+ # FIXME dbus
18
+
19
+ ## these need to be set if you're planning to upload to pypi
20
+ license = {file = "LICENSE.txt"}
21
+ authors = [
22
+ {name = "Dima Gerasimov (@karlicoss)", email = "karlicoss@gmail.com"},
23
+ ]
24
+ maintainers = [
25
+ {name = "Dima Gerasimov (@karlicoss)", email = "karlicoss@gmail.com"},
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/karlicoss/dron"
30
+ ##
31
+
32
+ [project.scripts]
33
+ dron = "dron.__main__:main"
34
+
35
+ [project.optional-dependencies]
36
+ testing = [
37
+ "pytest",
38
+ "ruff",
39
+ "mypy",
40
+ "lxml", # for mypy html coverage
41
+ ]
42
+ notify-telegram = [
43
+ # version before that had a bug that prevented it from working
44
+ # see https://github.com/rahiel/telegram-send/issues/115#issuecomment-1368728425
45
+ "telegram-send>=0.37",
46
+ ]
47
+
48
+
49
+ [build-system]
50
+ requires = ["setuptools", "setuptools-scm"]
51
+ build-backend = "setuptools.build_meta"
52
+
53
+ [tool.setuptools_scm]
54
+ version_scheme = "python-simplified-semver"
55
+ local_scheme = "dirty-tag"
56
+
@@ -0,0 +1,13 @@
1
+ [pytest]
2
+ # discover files that don't follow test_ naming. Useful to keep tests along with the source code
3
+ python_files = *.py
4
+ addopts =
5
+ # -rap to print tests summary even when they are successful
6
+ -rap
7
+ --verbose
8
+
9
+ # otherwise it won't discover doctests
10
+ --doctest-modules
11
+
12
+ # show all test durations (unless they are too short)
13
+ --durations=0