diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index c843312..043716b 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -36,21 +36,21 @@ jobs: else EXPORT_VALUE="${TMP_GITHUB_REF}" fi - echo "##[set-output name=branch;]${EXPORT_VALUE}" + echo "branch=${EXPORT_VALUE}" >> $GITHUB_OUTPUT - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Install tox run: | python -m pip install --upgrade pip wheel - pip install tox tox-gh-actions + pip install tox - #- name: Run tox + #- name: Run tox bare-ass #run: | - #bash -c 'tox -e lint' + #tox -e lint - name: Run pylint id: analyze @@ -59,33 +59,33 @@ jobs: run: | rating=$(bash -c 'tox -e lint' | grep 'Your code has been rated at' | cut -f7 -d " ") echo "Pylint score: ${rating}" - echo "##[set-output name=rating;]${rating}" - echo "##[set-output name=path;]${BADGE_PATH}" + echo "rating=${rating}" >> $GITHUB_OUTPUT + echo "path=${BADGE_PATH}" >> $GITHUB_OUTPUT badge: # Only generate and publish if these conditions are met: # - The previous job/analyze step ended successfully # - At least one of these is true: - # - This is a push event and the push event is on branch 'master' or 'develop' + # - This is a push event and the push event is on branch 'main' or 'develop' # Note: if this repo is personal (ie, not an org repo) then you can # use the following to change the scope of the next 2 jobs # instead of running on branch push as shown below: # - This is a pull request event and the pull actor is the same as the repo owner - # if: ${{ ( github.event_name == 'pull_request' && github.actor == github.repository_owner ) || github.ref == 'refs/heads/master' }} + # if: ${{ ( github.event_name == 'pull_request' && github.actor == github.repository_owner ) || github.ref == 'refs/heads/main' }} name: Generate badge image with pylint score runs-on: ubuntu-20.04 needs: [pylint] if: ${{ github.event_name == 'push' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: badges path: badges # Use the output from the `analyze` step - name: Create pylint badge - uses: emibcn/badge-action@v1 + uses: emibcn/badge-action@v2.0.2 id: badge with: label: 'Pylint score' @@ -99,8 +99,8 @@ jobs: FILE: 'pylint-score.svg' working-directory: ./badges run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" mkdir -p "${BRANCH}" mv "${FILE}" "${BRANCH}" git add "${BRANCH}/${FILE}" diff --git a/.gitignore b/.gitignore index 7a2371d..d680932 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ __pycache__/ # generated/user and test files *.swu ext/ -pyserv/_version.py +src/pyserv/_version.py # Distribution / packaging .Python diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ef6fbfe..97f81ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: - --install-types - --non-interactive - --ignore-missing-imports - files: pyserv/ + files: src/ - repo: "https://github.com/asottile/blacken-docs" rev: "1.15.0" @@ -79,7 +79,7 @@ repos: rev: v2.2.0 hooks: - id: autoflake - files: pyserv/ + files: src/ args: - --in-place - --remove-all-unused-imports @@ -90,7 +90,7 @@ repos: rev: 6.1.0 hooks: - id: flake8 - files: pyserv/ + files: src/ additional_dependencies: ["flake8-bugbear"] - repo: https://github.com/PyCQA/bandit @@ -98,7 +98,7 @@ repos: hooks: - id: bandit args: ["-ll", "-q"] - files: pyserv/ + files: src/ # - repo: https://github.com/lovesegfault/beautysh # rev: v6.2.1 diff --git a/README.rst b/README.rst index 2bd375b..3c2359b 100644 --- a/README.rst +++ b/README.rst @@ -52,17 +52,30 @@ of your OTA update file. .. _OTA: https://en.wikipedia.org/wiki/Over-the-air_programming + +Daemons and Console Entry Points +================================ + +Pyserv contains modules with some backported features and a fix for broken +OTA clients. It provides multiple console commands for different protocols, +and two daemon wrappers for http and tftp. + +* console commands with simple arguments to run, well, from the console, or + for running via Procfile_ with something like Honcho_ +* daemon scripts to run in the background for workflows that need a simple + HTTP/WSGI server or TFTP server + +.. _Procfile: https://devcenter.heroku.com/articles/procfile +.. _Honcho: https://honcho.readthedocs.io/en/latest/index.html + Console command options ----------------------- -This package now installs several different command line interfaces to -support multiple protocols: - -* HTTP - the ``serv`` command and ``httpdaemon`` script +* HTTP - the ``serv`` console command * WSGI - the ``wsgi`` console command -* TFTP - the ``tftpd`` console command and ``tftpdaemon`` script +* TFTP - the ``tftpd`` console command -The standard Python console entry points all have these minimal/default +The above standard Python console entry points all have these minimal/default "features" with no arguments: * the document/server root is always the current directory @@ -73,7 +86,7 @@ The standard Python console entry points all have these minimal/default + TFTP: default port is ``8069`` and the server listens on localhost * the *only* allowed args are either port, or port *and* interface (or - app and port for WSGI) + app_name and port for WSGI) .. note:: *All* of the above are configurable via environment variables defined in the ``settings`` module (with the above defaults). @@ -150,7 +163,7 @@ Once installed in a virtual environment, check the ``help`` output:: Install with pip ----------------- +================ This refactored fork of pyserv is *not* published on PyPI, thus use one of the following commands to install the latest pyserv in a Python virtual @@ -305,9 +318,9 @@ Original gist: https://gist.github.com/mdonkers/63e115cc0c79b4f6b8b3a6b797e485c7 Pre-commit ----------- +========== -This repo is now pre-commit_ enabled for python/rst source and file-type +This repo is pre-commit_ enabled for python/rst source and file-type linting. The checks run automatically on commit and will fail the commit (if not clean) and perform simple file corrections. For example, if the mypy check fails on commit, you must first fix any fatal errors for the diff --git a/docs/source/conf.py b/docs/source/conf.py index 6c9e61d..a7b74fc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,12 +13,12 @@ import os import sys -import pkg_resources +if sys.version_info < (3, 8): + from importlib_metadata import version +else: + from importlib.metadata import version - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'pyserv'))) - -__version__ = pkg_resources.get_distribution('pyserv').version +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # -- Project information ----------------------------------------------------- @@ -27,9 +27,9 @@ author = 'Stephen Arnold' # The full version, including alpha/beta/rc tags -version = __version__ -release = version - +release = version('pyserv') +# The short X.Y version. +version = '.'.join(release.split('.')[:2]) # -- General configuration ------------------------------------------------ @@ -41,18 +41,20 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'sphinx_git', 'sphinxcontrib.apidoc', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'recommonmark', ] -apidoc_module_dir = '../../pyserv/' +apidoc_module_dir = '../../src/pyserv/' apidoc_output_dir = 'api' -apidoc_excluded_paths = ['test'] +apidoc_excluded_paths = ['tests'] apidoc_separate_modules = True # Add any paths that contain templates here, relative to this directory. @@ -69,7 +71,7 @@ # Brief project description # -description = 'Simple HTTP server to handle GET requests for local files.' +description = 'A collection of simple servers for HTTP, WSGI, and TFTP' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/index.rst b/docs/source/index.rst index a193425..85f5c58 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,13 @@ Welcome to the pyserv documentation! ==================================== +.. git_commit_detail:: + :branch: + :commit: + :sha_length: 10 + :uncommitted: + :untracked: + .. toctree:: :caption: Contents: :maxdepth: 3 diff --git a/pyproject.toml b/pyproject.toml index 8c6dd1e..a017d7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ markers = "subscript" [tool.coverage.run] branch = true source = [ - "pyserv/", + "src/", ".tox/py*/lib/python*/site-packages/", ] omit = [ @@ -26,7 +26,7 @@ omit = [ ] [tool.coverage.paths] -source = ["pyserv"] +source = ["src"] [tool.coverage.report] fail_under = 80 @@ -79,4 +79,4 @@ dirty = "{version}+d{build_date:%Y%m%d}" distance-dirty = "{next_version}.dev{distance}" [tool.versioningit.write] -file = "pyserv/_version.py" +file = "src/pyserv/_version.py" diff --git a/requirements.txt b/requirements.txt index b5464bc..7d8d44b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ # daemon requirements, useful for tox/pip -daemonizer @ git+https://github.com/sarnold/python-daemonizer.git@0.3.5#5f6bc3c80a90344b2c8e4cc24ed0b8c098a7af50; platform_system!="Windows" +daemonizer @ git+https://github.com/sarnold/python-daemonizer.git@0.4.0#5e0fd72b9176fdcda8c9386f8b0905f2c434b61f; platform_system!="Windows" tftpy @ git+https://github.com/VCTLabs/tftpy.git@0.8.2.1#58d1f5c60af2e3759fbb137b89816954d8ea6bc9 -appdirs +platformdirs diff --git a/scripts/httpdaemon b/scripts/httpdaemon index b25e8d0..35057d3 100755 --- a/scripts/httpdaemon +++ b/scripts/httpdaemon @@ -11,7 +11,8 @@ from pathlib import Path from daemon import Daemon from daemon.parent_logger import setup_logging -from pyserv import GetServer, __version__ +from pyserv import GetServer +from pyserv._version import __version__ from pyserv.settings import ( DEBUG, DOCROOT, diff --git a/scripts/tftpdaemon b/scripts/tftpdaemon index 69464b5..ac70473 100755 --- a/scripts/tftpdaemon +++ b/scripts/tftpdaemon @@ -4,7 +4,6 @@ TFTP daemon script using pyserv and py3tftp (see settings.py for env vars). """ import argparse -import logging import os import sys from pathlib import Path @@ -13,7 +12,7 @@ import tftpy from daemon import Daemon from daemon.parent_logger import setup_logging -from pyserv import __version__ +from pyserv._version import __version__ from pyserv.settings import ( DEBUG, DOCROOT, @@ -31,7 +30,6 @@ from pyserv.settings import ( LPNAME = os.getenv('LPNAME', default='tftpd') IFACE = os.getenv('IFACE', default='0.0.0.0') PORT = os.getenv('PORT', default='9069') -logger = logging.getLogger(__name__) class ServDaemon(Daemon): diff --git a/setup.cfg b/setup.cfg index 8f51b3a..7b399e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,13 @@ [metadata] name = pyserv version = attr: pyserv.__version__ -description = attr: pyserv.__doc__ +description = A collection of simple servers for HTTP, WSGI, and TFTP url = https://github.com/sarnold/pyserv author = Stephen L Arnold email = nerdboy@gentoo.org long_description = file: README.rst long_description_content_type = text/rst; charset=UTF-8 +license: MIT license_expression = MIT license_files = LICENSE classifiers = @@ -33,18 +34,20 @@ keywords = [options] python_requires = >= 3.6 install_requires = - appdirs - daemonizer @ git+https://github.com/sarnold/python-daemonizer.git@0.3.5#5f6bc3c80a90344b2c8e4cc24ed0b8c098a7af50 + daemonizer @ git+https://github.com/sarnold/python-daemonizer.git@0.4.0#5e0fd72b9176fdcda8c9386f8b0905f2c434b61f + platformdirs tftpy @ git+https://github.com/VCTLabs/tftpy.git@0.8.2.1#58d1f5c60af2e3759fbb137b89816954d8ea6bc9 -packages = find: -zip_safe = True +packages = find_namespace: +package_dir = + =src scripts = scripts/httpdaemon scripts/tftpdaemon [options.packages.find] +where = src exclude = examples* docs* @@ -61,6 +64,7 @@ console_scripts = [options.extras_require] doc = sphinx + sphinx_git recommonmark sphinx_rtd_theme sphinxcontrib-apidoc diff --git a/pyserv/__init__.py b/src/pyserv/__init__.py similarity index 94% rename from pyserv/__init__.py rename to src/pyserv/__init__.py index 7759c51..b6ccb36 100644 --- a/pyserv/__init__.py +++ b/src/pyserv/__init__.py @@ -1,5 +1,6 @@ """ Simple HTTP server classes with GET path rewriting and request/header logging. +Now includes a reference WSGI server and tftpdaemon script. """ import logging @@ -12,11 +13,8 @@ from wsgiref.simple_server import make_server from wsgiref.validate import validator -from ._version import __version__ - -VERSION = __version__ - -__all__ = ["__version__", "VERSION", "GetHandler", "GetServer", "GetServerWSGI"] +__all__ = ["__description__", "GetHandler", "GetServer", "GetServerWSGI", "munge_url"] +__description__ = "A collection of simple servers for HTTP, WSGI, and TFTP" def munge_url(ota_url): diff --git a/pyserv/server.py b/src/pyserv/server.py similarity index 100% rename from pyserv/server.py rename to src/pyserv/server.py diff --git a/pyserv/settings.py b/src/pyserv/settings.py similarity index 91% rename from pyserv/settings.py rename to src/pyserv/settings.py index f37683d..eaa7773 100644 --- a/pyserv/settings.py +++ b/src/pyserv/settings.py @@ -6,9 +6,9 @@ import sys from pathlib import Path -from appdirs import AppDirs +from platformdirs import PlatformDirs -from pyserv import __version__ as version +from ._version import __version__ as version def get_userdirs(): @@ -17,9 +17,9 @@ def get_userdirs(): :return tuple: logdir, piddir, docdir as Path objs """ - dirs = AppDirs(appname='pyserv', version=version) - logdir = Path(dirs.user_log_dir) - piddir = Path(dirs.user_cache_dir).joinpath('run') + dirs = PlatformDirs(appname='pyserv', appauthor='nerdboy') + logdir = dirs.user_log_path + piddir = dirs.user_runtime_path docdir = Path(os.getcwd()) return logdir, piddir, docdir diff --git a/pyserv/tftpd.py b/src/pyserv/tftpd.py similarity index 100% rename from pyserv/tftpd.py rename to src/pyserv/tftpd.py diff --git a/pyserv/wsgi.py b/src/pyserv/wsgi.py similarity index 100% rename from pyserv/wsgi.py rename to src/pyserv/wsgi.py diff --git a/tests/test_extras.py b/tests/test_extras.py index 0df1742..6f3bf16 100644 --- a/tests/test_extras.py +++ b/tests/test_extras.py @@ -27,11 +27,11 @@ def test_get_userdirs(): if WIN32: assert logdir.name == 'Logs' elif APPLE: - assert logdir.name == version + assert logdir.name == 'pyserv' else: assert logdir.name == 'log' - assert piddir.name == 'run' + assert piddir.name == 'pyserv' assert docdir.name == Path.cwd().name diff --git a/tests/test_serv.py b/tests/test_serv.py index 5ba4c71..3f68f86 100644 --- a/tests/test_serv.py +++ b/tests/test_serv.py @@ -16,7 +16,6 @@ iface = '127.0.0.1' port = 8000 wport = 5000 -tport = 8069 def get_request(port, iface): diff --git a/tox.ini b/tox.ini index 8764f33..52f17ef 100644 --- a/tox.ini +++ b/tox.ini @@ -49,7 +49,7 @@ passenv = setenv = COVERAGE_FILE = .coverage.{envname} - PYTHONPATH = {toxinidir} + PYTHONPATH = {toxinidir}/src allowlist_externals = bash @@ -177,10 +177,10 @@ setenv = PORT = {env:PORT:9069} URL = tftp://{env:IFACE}:{env:PORT} LPNAME = {env:LPNAME:tftpd} - DEBUG = {env:DEBUG:1} + DEBUG = {env:DEBUG:} LOG = {env:LOG:{envlogdir}/{env:LPNAME}.log} PID = {env:PID:{envtmpdir}/{env:LPNAME}.pid} - TAIL = {env:TAIL:1} + TAIL = {env:TAIL:5} TST_FILE = {env:TST_FILE:testbin.swu} BLK_SIZE = {env:BLK_SIZE:8192} @@ -219,7 +219,7 @@ commands = tail -n {env:TAIL} {env:LOG} cmp {env:TST_FILE} tests/{env:TST_FILE} ls -l {env:TST_FILE} tests/{env:TST_FILE} - bash -c 'rm -f $TST_FILE tests/$TST_FILE' + bash -c 'rm -f {env:TST_FILE} tests/{env:TST_FILE}' commands_post = tftpdaemon stop @@ -282,9 +282,13 @@ deps = pip>=21.1 requests +changedir = + dist/ + commands = - pip install pyserv --pre -f dist/ - python -c "from pyserv import server; print(server.__doc__)" + python -m pip install pyserv --force-reinstall --pre --prefer-binary -f ./ + python -m pip show -f pyserv + python -c "import pyserv; print(pyserv.__description__)" python -c 'from pyserv.settings import show_uservars; show_uservars()' [testenv:lint] @@ -295,7 +299,7 @@ passenv = CI PYTHONIOENCODING -setenv = PYTHONPATH = {toxinidir} +setenv = PYTHONPATH = {toxinidir}/src deps = {[base]deps} @@ -304,10 +308,10 @@ deps = commands_pre = # need to generate version info in a fresh checkout - bash -c '[[ -f pyserv/_version.py ]] || python setup.py egg_info' + bash -c '[[ -f src/pyserv/_version.py ]] || python setup.py egg_info' commands = - pylint --ignore=_version.py --fail-under=9.80 pyserv/ scripts/ + pylint --ignore=_version.py --fail-under=9.80 src/ scripts/ [testenv:style] passenv = @@ -321,12 +325,12 @@ deps = flake8-bugbear commands = - flake8 pyserv/ + flake8 src/ [testenv:mypy] skip_install = true -setenv = PYTHONPATH = {toxinidir} +setenv = PYTHONPATH = {toxinidir}/src deps = {[base]deps} @@ -335,22 +339,22 @@ deps = commands_pre = # need to generate version info in a fresh checkout - bash -c '[[ -f pyserv/_version.py ]] || python setup.py egg_info' + bash -c '[[ -f src/pyserv/_version.py ]] || python setup.py egg_info' commands = - python -m mypy --follow-imports=normal --install-types --non-interactive pyserv/ scripts/ + python -m mypy --follow-imports=normal --install-types --non-interactive src/ scripts/ [testenv:isort] skip_install = true -setenv = PYTHONPATH = {toxinidir} +setenv = PYTHONPATH = {toxinidir}/src deps = {[base]deps} isort commands = - python -m isort pyserv/ scripts/ + python -m isort src/ scripts/ [testenv:clean] skip_install = true @@ -361,4 +365,4 @@ deps = pip>=21.1 commands = - bash -c 'rm -rf build/ dist/ __pycache__/ *.egg-info/ docs/source/api/' + bash -c 'rm -rf build/ dist/ */*/_version.py __pycache__/ src/*.egg-info/ docs/source/api/'