spurplus


Namespurplus JSON
Version 2.3.5 PyPI version JSON
download
home_pagehttp://github.com/Parquery/spurplus
SummaryManage remote machines and file operations over SSH.
upload_time2024-03-29 09:50:35
maintainerNone
docs_urlNone
authorMarko Ristin
requires_pythonNone
licenseLicense :: OSI Approved :: MIT License
keywords ssh sftp spur paramiko execute remote commands modify files
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage No coveralls.
            Spur+
=====

.. image:: https://api.travis-ci.com/Parquery/spurplus.svg?branch=master
    :target: https://api.travis-ci.com/Parquery/spurplus.svg?branch=master
    :alt: Build Status

.. image:: https://coveralls.io/repos/github/Parquery/spurplus/badge.svg?branch=master
    :target: https://coveralls.io/github/Parquery/spurplus?branch=master
    :alt: Coverage

.. image:: https://readthedocs.org/projects/spurplus/badge/?version=latest
    :target: https://spurplus.readthedocs.io/en/latest/?badge=latest
    :alt: Documentation Status
    
.. image:: https://badge.fury.io/py/spurplus.svg
    :target: https://pypi.org/project/spurplus/
    :alt: PyPi

.. image:: https://img.shields.io/pypi/pyversions/spurplus.svg
    :alt: PyPI - Python Version

Spur+ is a library to manage remote machines and perform file operations over SSH.

It builds on top of Spur_ and Paramiko_ libraries. While we already find that Spur_ and Paramiko_ provide most of the
functionality out-of-the-box, we missed certain features:

- typing. Since spur supports both Python 2 and 3, it does not provide any type annotations which makes it harder to use
  with type checkers such as mypy.

- pathlib.Path support. We find it easier to manipulate paths using pathlib.Path instead of plain strings. spur+
  provides support for both.

- a function for creating directories. spur relies on sftp client. While it is fairly straightforward to get an sftp
  client from ``spur.SshShell`` and create a directory, we think that it merits a wrapper function akin to
  ``pathlib.Path.mkdir()`` provided how often this functionality is needed.

- reading/writing text and binary data in one go. Similarly to creating directories, ``spur.SshShell.open()`` already
  provides all the functionality you need to read/write files. However, we found the usage code to be more readable when
  written in one line and no extra variables for file descriptors are introduced.

- a function for putting and getting files to/from the remote host, respectively.

- a function to sync a local directory to a remote directory (similar to ``rsync``).

- a function for computing MD5 checksums.

- a function to check if a file exists.

- a more elaborate context manager for a temporary directory which allows for specifying prefix, suffix and
  base directory and gives you a pathlib.Path. In contrast, ``spur.temporary_directory()`` gives you only a string with
  no knobs.

- an initializer function to repeatedly re-connect on connection failure. We found this function particularly important
  when you spin a virtual instance in the cloud and need to wait for it to initialize.

- a wrapper around paramiko's SFTP client (``spurplus.sftp.ReconnectingSFTP``) to automatically reconnect if the SFTP
  client experienced a connection failure. While original ``spur.SshShell.open()`` creates a new SFTP client on every
  call in order to prevent issues with time-outs, `spurplus.SshShell` is able to re-use the SFTP client over multiple
  calls via ``spurplus.sftp.ReconnectingSFTP``.

  This can lead up to 10x speed-up (see the benchmark in ``tests/live_test.py``).

.. _Spur: https://github.com/mwilliamson/spur.py
.. _Paramiko: https://github.com/paramiko/paramiko

Usage
=====
.. code-block:: python

    import pathlib

    import spurplus

    # Re-try on connection failure; sftp client and the underlying spur SshShell
    # are automatically closed when the shell is closed.
    with spurplus.connect_with_retries(
            hostname='some-machine.example.com', username='devop') as shell:
        p = pathlib.Path('/some/directory')

        # Create a directory
        shell.mkdir(remote_path=p, parents=True, exist_ok=True)

        # Write a file
        shell.write_text(remote_path=p/'some-file', text='hello world!')

        # Read from a file
        text = shell.read_text(remote_path=p/'some-file')

        # Change the permissions
        shell.chmod(remote_path=p/'some-file', mode=0o444)

        # Sync a local directory to a remote.
        # Only differing files are uploaded,
        # files missing locally are deleted before the transfer and
        # the permissions are mirrored from the local.
        sync_to_remote(
            local_path="/some/local/directory",
            remote_path="/some/remote/directory",
            delete=spurplus.Delete.BEFORE,
            preserve_permissions = True)

        # Stat the file
        print("The stat of {}: {}".format(p/'some-file', shell.stat(p/'some-file')))

        # Use a wrapped SFTP client
        sftp = shell.as_sftp()
        # Do something with the SFTP
        for attr in sftp.listdir_attr(path=p.as_posix()):
            do_something(attr.filename, attr.st_size)

Documentation
=============
The documentation is available on `readthedocs <https://spurplus.readthedocs.io/en/latest/>`_.

Installation
============

* Create a virtual environment:

.. code-block:: bash

    python3 -m venv venv3

* Activate it:

.. code-block:: bash

    source venv3/bin/activate

* Install spur+ with pip:

.. code-block:: bash

    pip3 install spurplus

Development
===========

* Check out the repository.

* In the repository root, create the virtual environment:

.. code-block:: bash

    python3 -m venv venv3

* Activate the virtual environment:

.. code-block:: bash

    source venv3/bin/activate

* Install the development dependencies:

.. code-block:: bash

    pip3 install -e .[dev]

* There are live tests for which you need to have a running SSH server. The parameters of the tests
  are passed via environment variables:

    - ``TEST_SSH_HOSTNAME`` (host name of the SSH server, defaults to "127.0.0.1"),
    - ``TEST_SSH_PORT`` (optional, defaults to 22),
    - ``TEST_SSH_USERNAME`` (optional, uses paramiko's default),
    - ``TEST_SSH_PASSWORD`` (optional, uses private key file if not specified) and
    - ``TEST_SSH_PRIVATE_KEY_FILE`` (optional, looks for private key in expected places if not specified).

We use tox for testing and packaging the distribution. Assuming that the above-mentioned environment variables has
been set, the virutal environment has been activated and the development dependencies have been installed, run:

.. code-block:: bash

    tox

Pre-commit Checks
-----------------
We provide a set of pre-commit checks that lint and check code for formatting.

Namely, we use:

* `yapf <https://github.com/google/yapf>`_ to check the formatting.
* The style of the docstrings is checked with `pydocstyle <https://github.com/PyCQA/pydocstyle>`_.
* Static type analysis is performed with `mypy <http://mypy-lang.org/>`_.
* Various linter checks are done with `pylint <https://www.pylint.org/>`_.
* Doctests are executed using the Python `doctest module <https://docs.python.org/3.5/library/doctest.html>`_.

Run the pre-commit checks locally from an activated virtual environment with development dependencies:

.. code-block:: bash

    ./precommit.py

* The pre-commit script can also automatically format the code:

.. code-block:: bash

    ./precommit.py  --overwrite


Versioning
==========
We follow `Semantic Versioning <http://semver.org/spec/v1.0.0.html>`_. The version X.Y.Z indicates:

* X is the major version (backward-incompatible),
* Y is the minor version (backward-compatible), and
* Z is the patch version (backward-compatible bug fix).

            

Raw data

            {
    "_id": null,
    "home_page": "http://github.com/Parquery/spurplus",
    "name": "spurplus",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "ssh sftp spur paramiko execute remote commands modify files",
    "author": "Marko Ristin",
    "author_email": "marko.ristin@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/c2/5f/a11bbda84921ffbbc12196cdc21bee2f6754372b8a8ec9b11f9b4f3b4087/spurplus-2.3.5.tar.gz",
    "platform": null,
    "description": "Spur+\n=====\n\n.. image:: https://api.travis-ci.com/Parquery/spurplus.svg?branch=master\n    :target: https://api.travis-ci.com/Parquery/spurplus.svg?branch=master\n    :alt: Build Status\n\n.. image:: https://coveralls.io/repos/github/Parquery/spurplus/badge.svg?branch=master\n    :target: https://coveralls.io/github/Parquery/spurplus?branch=master\n    :alt: Coverage\n\n.. image:: https://readthedocs.org/projects/spurplus/badge/?version=latest\n    :target: https://spurplus.readthedocs.io/en/latest/?badge=latest\n    :alt: Documentation Status\n    \n.. image:: https://badge.fury.io/py/spurplus.svg\n    :target: https://pypi.org/project/spurplus/\n    :alt: PyPi\n\n.. image:: https://img.shields.io/pypi/pyversions/spurplus.svg\n    :alt: PyPI - Python Version\n\nSpur+ is a library to manage remote machines and perform file operations over SSH.\n\nIt builds on top of Spur_ and Paramiko_ libraries. While we already find that Spur_ and Paramiko_ provide most of the\nfunctionality out-of-the-box, we missed certain features:\n\n- typing. Since spur supports both Python 2 and 3, it does not provide any type annotations which makes it harder to use\n  with type checkers such as mypy.\n\n- pathlib.Path support. We find it easier to manipulate paths using pathlib.Path instead of plain strings. spur+\n  provides support for both.\n\n- a function for creating directories. spur relies on sftp client. While it is fairly straightforward to get an sftp\n  client from ``spur.SshShell`` and create a directory, we think that it merits a wrapper function akin to\n  ``pathlib.Path.mkdir()`` provided how often this functionality is needed.\n\n- reading/writing text and binary data in one go. Similarly to creating directories, ``spur.SshShell.open()`` already\n  provides all the functionality you need to read/write files. However, we found the usage code to be more readable when\n  written in one line and no extra variables for file descriptors are introduced.\n\n- a function for putting and getting files to/from the remote host, respectively.\n\n- a function to sync a local directory to a remote directory (similar to ``rsync``).\n\n- a function for computing MD5 checksums.\n\n- a function to check if a file exists.\n\n- a more elaborate context manager for a temporary directory which allows for specifying prefix, suffix and\n  base directory and gives you a pathlib.Path. In contrast, ``spur.temporary_directory()`` gives you only a string with\n  no knobs.\n\n- an initializer function to repeatedly re-connect on connection failure. We found this function particularly important\n  when you spin a virtual instance in the cloud and need to wait for it to initialize.\n\n- a wrapper around paramiko's SFTP client (``spurplus.sftp.ReconnectingSFTP``) to automatically reconnect if the SFTP\n  client experienced a connection failure. While original ``spur.SshShell.open()`` creates a new SFTP client on every\n  call in order to prevent issues with time-outs, `spurplus.SshShell` is able to re-use the SFTP client over multiple\n  calls via ``spurplus.sftp.ReconnectingSFTP``.\n\n  This can lead up to 10x speed-up (see the benchmark in ``tests/live_test.py``).\n\n.. _Spur: https://github.com/mwilliamson/spur.py\n.. _Paramiko: https://github.com/paramiko/paramiko\n\nUsage\n=====\n.. code-block:: python\n\n    import pathlib\n\n    import spurplus\n\n    # Re-try on connection failure; sftp client and the underlying spur SshShell\n    # are automatically closed when the shell is closed.\n    with spurplus.connect_with_retries(\n            hostname='some-machine.example.com', username='devop') as shell:\n        p = pathlib.Path('/some/directory')\n\n        # Create a directory\n        shell.mkdir(remote_path=p, parents=True, exist_ok=True)\n\n        # Write a file\n        shell.write_text(remote_path=p/'some-file', text='hello world!')\n\n        # Read from a file\n        text = shell.read_text(remote_path=p/'some-file')\n\n        # Change the permissions\n        shell.chmod(remote_path=p/'some-file', mode=0o444)\n\n        # Sync a local directory to a remote.\n        # Only differing files are uploaded,\n        # files missing locally are deleted before the transfer and\n        # the permissions are mirrored from the local.\n        sync_to_remote(\n            local_path=\"/some/local/directory\",\n            remote_path=\"/some/remote/directory\",\n            delete=spurplus.Delete.BEFORE,\n            preserve_permissions = True)\n\n        # Stat the file\n        print(\"The stat of {}: {}\".format(p/'some-file', shell.stat(p/'some-file')))\n\n        # Use a wrapped SFTP client\n        sftp = shell.as_sftp()\n        # Do something with the SFTP\n        for attr in sftp.listdir_attr(path=p.as_posix()):\n            do_something(attr.filename, attr.st_size)\n\nDocumentation\n=============\nThe documentation is available on `readthedocs <https://spurplus.readthedocs.io/en/latest/>`_.\n\nInstallation\n============\n\n* Create a virtual environment:\n\n.. code-block:: bash\n\n    python3 -m venv venv3\n\n* Activate it:\n\n.. code-block:: bash\n\n    source venv3/bin/activate\n\n* Install spur+ with pip:\n\n.. code-block:: bash\n\n    pip3 install spurplus\n\nDevelopment\n===========\n\n* Check out the repository.\n\n* In the repository root, create the virtual environment:\n\n.. code-block:: bash\n\n    python3 -m venv venv3\n\n* Activate the virtual environment:\n\n.. code-block:: bash\n\n    source venv3/bin/activate\n\n* Install the development dependencies:\n\n.. code-block:: bash\n\n    pip3 install -e .[dev]\n\n* There are live tests for which you need to have a running SSH server. The parameters of the tests\n  are passed via environment variables:\n\n    - ``TEST_SSH_HOSTNAME`` (host name of the SSH server, defaults to \"127.0.0.1\"),\n    - ``TEST_SSH_PORT`` (optional, defaults to 22),\n    - ``TEST_SSH_USERNAME`` (optional, uses paramiko's default),\n    - ``TEST_SSH_PASSWORD`` (optional, uses private key file if not specified) and\n    - ``TEST_SSH_PRIVATE_KEY_FILE`` (optional, looks for private key in expected places if not specified).\n\nWe use tox for testing and packaging the distribution. Assuming that the above-mentioned environment variables has\nbeen set, the virutal environment has been activated and the development dependencies have been installed, run:\n\n.. code-block:: bash\n\n    tox\n\nPre-commit Checks\n-----------------\nWe provide a set of pre-commit checks that lint and check code for formatting.\n\nNamely, we use:\n\n* `yapf <https://github.com/google/yapf>`_ to check the formatting.\n* The style of the docstrings is checked with `pydocstyle <https://github.com/PyCQA/pydocstyle>`_.\n* Static type analysis is performed with `mypy <http://mypy-lang.org/>`_.\n* Various linter checks are done with `pylint <https://www.pylint.org/>`_.\n* Doctests are executed using the Python `doctest module <https://docs.python.org/3.5/library/doctest.html>`_.\n\nRun the pre-commit checks locally from an activated virtual environment with development dependencies:\n\n.. code-block:: bash\n\n    ./precommit.py\n\n* The pre-commit script can also automatically format the code:\n\n.. code-block:: bash\n\n    ./precommit.py  --overwrite\n\n\nVersioning\n==========\nWe follow `Semantic Versioning <http://semver.org/spec/v1.0.0.html>`_. The version X.Y.Z indicates:\n\n* X is the major version (backward-incompatible),\n* Y is the minor version (backward-compatible), and\n* Z is the patch version (backward-compatible bug fix).\n",
    "bugtrack_url": null,
    "license": "License :: OSI Approved :: MIT License",
    "summary": "Manage remote machines and file operations over SSH.",
    "version": "2.3.5",
    "project_urls": {
        "Homepage": "http://github.com/Parquery/spurplus"
    },
    "split_keywords": [
        "ssh",
        "sftp",
        "spur",
        "paramiko",
        "execute",
        "remote",
        "commands",
        "modify",
        "files"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6cffce6fbdb6ef2d11faa37907babf01a4d1fce88259dac6b34b338ff7def7a3",
                "md5": "d6fa7dbabdbc93dbe4ff5181ae0673a7",
                "sha256": "bde03d81d439753d89ce84f6330c50c426b334b3bb1ab62e39c2e81d7555a0dd"
            },
            "downloads": -1,
            "filename": "spurplus-2.3.5-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d6fa7dbabdbc93dbe4ff5181ae0673a7",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 19937,
            "upload_time": "2024-03-29T09:50:33",
            "upload_time_iso_8601": "2024-03-29T09:50:33.583739Z",
            "url": "https://files.pythonhosted.org/packages/6c/ff/ce6fbdb6ef2d11faa37907babf01a4d1fce88259dac6b34b338ff7def7a3/spurplus-2.3.5-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c25fa11bbda84921ffbbc12196cdc21bee2f6754372b8a8ec9b11f9b4f3b4087",
                "md5": "b954816076e17063d44cddfa8e5178a2",
                "sha256": "07752555e36c63655fdb2c8bd6e64dfb48c535045a8d4a9d0fbdbf1991ca99ca"
            },
            "downloads": -1,
            "filename": "spurplus-2.3.5.tar.gz",
            "has_sig": false,
            "md5_digest": "b954816076e17063d44cddfa8e5178a2",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 28918,
            "upload_time": "2024-03-29T09:50:35",
            "upload_time_iso_8601": "2024-03-29T09:50:35.031351Z",
            "url": "https://files.pythonhosted.org/packages/c2/5f/a11bbda84921ffbbc12196cdc21bee2f6754372b8a8ec9b11f9b4f3b4087/spurplus-2.3.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-29 09:50:35",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Parquery",
    "github_project": "spurplus",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "spurplus"
}
        
Elapsed time: 0.33109s