diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88b9159f..c2f5263d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -109,7 +109,6 @@ jobs: virtualenvs-ubuntu-latest-py3.8- - run: pip install -U pipenv pip - run: make deps - - run: pipenv run pip install -U 'SecretStorage>=3' - run: make dist id: create_dist - uses: actions/upload-artifact@v2 diff --git a/.gitignore b/.gitignore index 9f067926..336fb6d2 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ /notebooks*/ /rsconnect/version.py htmlcov -rsconnect/tests/testdata/**/rsconnect-python/ +/tests/testdata/**/rsconnect-python/ +/docs/docs/index.md diff --git a/README.md b/README.md index bdc6d94a..b913e354 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,49 @@ -# The rsconnect-python Library +# The rsconnect-python CLI and library -This package is a library used by the [`rsconnect-jupyter`](https://github.com/rstudio/rsconnect-jupyter) -package to deploy Jupyter notebooks to RStudio Connect. It contains a full deployment -API so can also be used by other Python-based deployment tools. Other types of content -supported by RStudio Connect may also be deployed by this package, including WSGi-style -APIs, as well as Dash, Streamlit, and Bokeh applications. +This package provides both a CLI (command-line interface) and a library for interacting +with and deploying to RStudio Connect. The library is also used by the +[`rsconnect-jupyter`](https://github.com/rstudio/rsconnect-jupyter) package to deploy +Jupyter notebooks via the Jupyter web console. Many types of content supported by RStudio +Connect may be deployed by this package, including WSGI-style APIs, Dash, Streamlit, and +Bokeh applications. -> **Important:** Streamlit and Bokeh support in RStudio Connect are currently in -> beta. You should not rely on them for deployments in production. +> **Important:** Bokeh support in RStudio Connect is currently in beta. -A command-line deployment tool is also provided that can be used directly to deploy -Jupyter notebooks, Python APIs and apps. Content types not directly supported by the -CLI can also be deployed if they include a prepared `manifest.json` file. See -["Deploying R or Other Content"](#deploying-r-or-other-content) for details. +Content types not directly supported by the CLI may also be deployed if they include a +prepared `manifest.json` file. See ["Deploying R or Other +Content"](#deploying-r-or-other-content) for details. ## Deploying Python Content to RStudio Connect -In addition to various kinds of R content, RStudio Connect also supports the deployment -of Jupyter notebooks, Python APIs (such as `flask`-based) and apps (such as Dash, Streamlit, -and Bokeh apps). Much like deploying R content to RStudio Connect, there are some caveats to -understand when replicating your environment on the RStudio Connect server: +RStudio Connect supports the deployment of Jupyter notebooks, Python APIs (such as +`flask`-based) and apps (such as Dash, Streamlit, and Bokeh apps). Much like deploying R +content to RStudio Connect, there are some caveats to understand when replicating your +environment on the RStudio Connect server: -RStudio Connect insists on matching versions of Python. For example, -a server with only Python 3.5 installed will fail to match content deployed with -Python 3.4. Your administrator may also enable exact Python version matching which +RStudio Connect insists on matching `` versions of Python. For example, +a server with only Python 3.8 installed will fail to match content deployed with +Python 3.7. Your administrator may also enable exact Python version matching which will be stricter and require matching major, minor, and patch versions. For more information see the [RStudio Connect Admin Guide chapter titled Python Version Matching](https://docs.rstudio.com/connect/admin/python.html#python-version-matching). ### Installation -To install `rsconnect-python` from this repository: +To install `rsconnect-python` from PYPI, you may use any python package manager such as +pip: ```bash -git clone https://github.com/rstudio/rsconnect-python -cd rsconnect-python -python setup.py install +pip install rsconnect-python ``` -To install the current version directly from pip: +You may also build and install a wheel directly from a repository clone: ```bash -pip install rsconnect-python +git clone https://github.com/rstudio/rsconnect-python.git +cd rsconnect-python +pip install pipenv +make deps dist +pip install ./dist/rsconnect_python-*.whl ``` ### Using the rsconnect CLI @@ -50,9 +52,9 @@ Here's an example command that deploys a Jupyter notebook to RStudio Connect. ```bash rsconnect deploy notebook \ - --server https://my.connect.server:3939 \ - --api-key my-api-key \ - my-notebook.ipynb + --server https://connect.example.org:3939 \ + --api-key my-api-key \ + my-notebook.ipynb ``` > **Note:** The examples here use long command line options, but there are short @@ -94,11 +96,11 @@ compinit ### Managing Server Information The information used by the `rsconnect` command to communicate with an RStudio Connect -server can be tedious to repeat on every command. To help, the CLI supports the idea +server can be tedious to repeat on every command. To help, the CLI supports the idea of saving this information, making it usable by a simple nickname. > **Important:** One item of information saved is the API key used to authenticate with -> RStudio Connect. Although the file where this information is saved is marked as +> RStudio Connect. Although the file where this information is saved is marked as > accessible by the owner only, it's important to remember that the key is present > in the file as plain text so care must be taken to prevent any unauthorized access > to the server information file. @@ -106,15 +108,15 @@ of saving this information, making it usable by a simple nickname. #### TLS Support and RStudio Connect Usually, an RStudio Connect server will be set up to be accessed in a secure manner, -using the `https` protocol rather than simple `http`. If RStudio Connect is set up +using the `https` protocol rather than simple `http`. If RStudio Connect is set up with a self-signed certificate, you will need to include the `--insecure` flag on -all commands. If RStudio Connect is set up to require a client-side certificate chain, +all commands. If RStudio Connect is set up to require a client-side certificate chain, you will need to include the `--cacert` option that points to your certificate -authority (CA) trusted certificates file. Both of these options can be saved along +authority (CA) trusted certificates file. Both of these options can be saved along with the URL and API Key for a server. > **Note:** When certificate information is saved for the server, the specified file -> is read and its _contents_ are saved under the server's nickname. If the CA file's +> is read and its _contents_ are saved under the server's nickname. If the CA file's > contents are ever changed, you will need to add the server information again. See the [Network Options](#network-options) section for more details about these options. @@ -125,13 +127,13 @@ Use the `add` command to store information about an RStudio Connect server: ```bash rsconnect add \ - --api-key my-api-key \ - --server https://my.connect.server:3939 \ - --name myserver + --api-key my-api-key \ + --server https://connect.example.org:3939 \ + --name myserver ``` > **Note:** The `rsconnect` CLI will verify that the serve URL and API key -> are valid. If either is found not to be, no information will be saved. +> are valid. If either is found not to be, no information will be saved. If any of the access information for the server changes, simply rerun the `add` command with the new information and it will replace the original @@ -173,14 +175,14 @@ You can verify that a URL refers to a running instance of RStudio Connect by usi the `details` command: ```bash -rsconnect details --server https://my.connect.server:3939 +rsconnect details --server https://connect.example.org:3939 ``` In this form, `rsconnect` will only tell you whether the URL given does, in fact, refer -to a running RStudio Connect instance. If you include a valid API key: +to a running RStudio Connect instance. If you include a valid API key: ```bash -rsconnect details --server https://my.connect.server:3939 --api-key my-api-key +rsconnect details --server https://connect.example.org:3939 --api-key my-api-key ``` the tool will provide the version of RStudio Connect (if the server is configured to @@ -244,7 +246,7 @@ rsconnect deploy notebook --static my-notebook.ipynb ### Creating a Manifest for Future Deployment You can create a `manifest.json` file for a Jupyter Notebook, then use that manifest -in a later deployment. Use the `write-manifest` command to do this. +in a later deployment. Use the `write-manifest` command to do this. The `write-manifest` command will also create a `requirements.txt` file, if it does not already exist or the `--force-generate` option is specified. It will contain the @@ -262,8 +264,8 @@ rsconnect write-manifest notebook my-notebook.ipynb ### API/Application Deployment Options -There are a variety of options available to you when deploying a Python WSGi-style API, -Dash, Streamlit, or Bokeh application. All options below apply equally to `api`, +There are a variety of options available to you when deploying a Python WSGI-style API, +Dash, Streamlit, or Bokeh application. All options below apply equally to `api`, `dash`, `streamlit`, and `bokeh` sub-commands. #### Including Extra Files @@ -278,7 +280,7 @@ rsconnect deploy api flask-api/ data.csv Since deploying an API or application starts at a directory level, there will be times when some files under that directory subtree should not be included in the deployment -or manifest. Use the `--exclude` option to specify files to exclude. An exclusion may +or manifest. Use the `--exclude` option to specify files to exclude. An exclusion may be a glob pattern and the `--exclude` option may be repeated. ```bash @@ -286,9 +288,9 @@ rsconnect deploy dash --exclude "workfiles/*" dash-app/ data.csv ``` You should always quote a glob pattern so that it will be passed to `rsconnect` as-is -instead of letting the shell expand it. If a file is specifically listed as an extra +instead of letting the shell expand it. If a file is specifically listed as an extra file that also matches an exclusion pattern, the file will still be included in the -deployment (i.e., extra files trumps exclusions). +deployment (i.e., extra files take precedence). #### Package Dependencies @@ -316,7 +318,7 @@ ensuring that you use the same Python that you use to run your API or applicatio ### Creating a Manifest for Future Deployment You can create a `manifest.json` file for an API or application, then use that -manifest in a later deployment. Use the `write-manifest` command to do this. +manifest in a later deployment. Use the `write-manifest` command to do this. The `write-manifest` command will also create a `requirements.txt` file, if it does not already exist or the `--force-generate` option is specified. It will contain @@ -392,7 +394,7 @@ is trusted by your Jupyter Notebook server, API client or user's browser, then y don't need to do anything special. You can test this out with the `details` command: ```bash -rsconnect details --api-key my-api-key --server https://my.connect.server:3939 +rsconnect details --api-key my-api-key --server https://connect.example.org:3939 ``` If this fails with a TLS Certificate Validation error, then you have two options. @@ -401,22 +403,22 @@ If this fails with a TLS Certificate Validation error, then you have two options RStudio Connect server. This will enable `rsconnect` to securely validate the server's TLS certificate. - ```bash - rsconnect details \ - --api-key my-api-key \ - --server https://my.connect.server:3939 \ - --cacert /path/to/certificate.pem - ``` + ```bash + rsconnect details \ + --api-key my-api-key \ + --server https://connect.example.org:3939 \ + --cacert /path/to/certificate.pem + ``` * RStudio Connect is in "insecure mode". This disables TLS certificate verification, which results in a less secure connection. - ```bash - rsconnect add \ - --api-key my-api-key \ - --server https://my.connect.server:3939 \ - --insecure - ``` + ```bash + rsconnect add \ + --api-key my-api-key \ + --server https://connect.example.org:3939 \ + --insecure + ``` Once you work out the combination of options that allow you to successfully work with an instance of RStudio Connect, you'll probably want to use the `add` command to have diff --git a/docs/Dockerfile b/docs/Dockerfile index d20c67d4..3c168489 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -6,12 +6,6 @@ MAINTAINER RStudio Connect ENV LC_ALL C.UTF-8 ENV LANG C.UTF-8 -RUN pip3 install \ - mkdocs==1.0.4 \ - mkdocs-material==4.5.1 \ - mkdocs-macros-plugin==0.2.4 \ - mkdocs-exclude==1.0.2 \ - markdown==3.1.1 \ - markdown-include==0.5.1 \ - pymdown-extensions==6.2 \ - Pygments==2.5.2 +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt && \ + rm -f requirements.txt diff --git a/docs/Makefile b/docs/Makefile index 6e963f64..9f276c68 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,9 +9,10 @@ endif BUILD_RUNNER = \ docker run --rm --name mkdocs \ $(DOCKER_RUN_AS) \ + -e PYTHONPATH=/rsconnect_python \ -e VERSION=$(VERSION) \ - -v $(CURDIR):/mkdocs \ - -w /mkdocs \ + -v $(CURDIR)/../:/rsconnect_python \ + -w /rsconnect_python/docs \ $(MKDOCS_IMAGE) .PHONY: all @@ -31,4 +32,4 @@ build: docs/index.md @rm docs/index.md docs/index.md: $(CURDIR)/../README.md - python patch_admonitions.py + python3 patch_admonitions.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 94cf6eca..f571f44c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -13,6 +13,12 @@ markdown_extensions: plugins: - macros + - search + - mkapi + +nav: + - index.md + - API: mkapi/api/rsconnect theme: name: material diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..76b1eef7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,10 @@ +mkdocs + +Pygments +markdown +markdown-include +mkapi +mkdocs-exclude +mkdocs-macros-plugin +mkdocs-material +pymdown-extensions diff --git a/pyproject.toml b/pyproject.toml index 38b3263f..df32c78f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ line-length = 120 target-version = ['py27', 'py35', 'py36', 'py37', 'py38'] [tool.coverage.run] -omit = ["rsconnect/tests/*"] +omit = ["tests/*"] [tool.setuptools_scm] write_to = "rsconnect/version.py" diff --git a/rsconnect/actions.py b/rsconnect/actions.py index 794add7e..4e21f579 100644 --- a/rsconnect/actions.py +++ b/rsconnect/actions.py @@ -1,3 +1,7 @@ +""" +Public API for managing settings and deploying content. +""" + import contextlib import json import logging @@ -909,7 +913,7 @@ def gather_basic_deployment_info_for_notebook(connect_server, app_store, file_na app_id, app_mode = app_store.resolve(connect_server.url, app_id, app_mode) if static and app_mode != AppModes.STATIC: raise api.RSConnectException( - 'Cannot change app mode to "static" once deployed. ' "Use --new to create a new deployment." + 'Cannot change app mode to "static" once deployed. Use --new to create a new deployment.' ) default_title = not bool(title) @@ -969,22 +973,11 @@ def gather_basic_deployment_info_from_manifest(connect_server, app_store, file_n def _generate_gather_basic_deployment_info_for_python(app_mode): + """ + Generates function to gather the necessary info for performing a deployment by app mode + """ + def gatherer(connect_server, app_store, directory, entry_point, new, app_id, title): - """ - Helps to gather the necessary info for performing a deployment. - - :param connect_server: the Connect server information. - :param app_store: the store for the specified directory. - :param directory: the primary file being deployed. - :param entry_point: the entry point for the API in ': format. if - the object name is omitted, it defaults to the module name. If nothing is specified, - it defaults to 'app'. - :param new: a flag noting whether we should force a new deployment. - :param app_id: the ID of the app to redeploy. - :param title: an optional title. If this isn't specified, a default title will - be generated. - :return: the entry point, app ID, name, title and mode for the deployment. - """ return _gather_basic_deployment_info_for_framework( connect_server, app_store, directory, entry_point, new, app_id, app_mode, title, ) diff --git a/rsconnect/api.py b/rsconnect/api.py index e50bfb28..00afb7e8 100644 --- a/rsconnect/api.py +++ b/rsconnect/api.py @@ -1,3 +1,7 @@ +""" +RStudio Connect API client and utility functions +""" + import time from _ssl import SSLError @@ -6,8 +10,10 @@ from rsconnect.models import AppModes _error_map = { - 4: "This content has been deployed before but could not be found on the server.\nUse the --new option to " - "deploy it as new content." + 4: ( + "This content has been deployed before but could not be found on the server.\nUse the --new option to " + "deploy it as new content." + ) } diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index 6196e753..deda2e01 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -1,3 +1,7 @@ +""" +Manifest generation and bundling utilities +""" + import hashlib import io import json diff --git a/rsconnect/environment.py b/rsconnect/environment.py index 247cabca..866678b6 100644 --- a/rsconnect/environment.py +++ b/rsconnect/environment.py @@ -1,4 +1,11 @@ #!/usr/bin/env python +""" +Environment data class abstraction that is usable as an executable module + +```bash +python -m rsconnect.environment +``` +""" import collections import datetime import json @@ -35,6 +42,9 @@ ) +Environment.__doc__ = "Data class encapsulating values needed by various deployment and metadata functions" + + class EnvironmentException(Exception): pass @@ -69,8 +79,10 @@ def detect_environment(dirname, force_generate=False, conda_mode=False, conda=No if result is not None: if conda_mode and result["package_manager"] != "conda": return Environment( - error='Conda was requested but no activated Conda environment was found. See "conda activate ' - '--help" for more information.' + error=( + 'Conda was requested but no activated Conda environment was found. See "conda activate ' + '--help" for more information.' + ) ) result["python"] = get_python_version(Environment(**result)) @@ -238,6 +250,9 @@ def conda_env_export(conda): def main(): + """ + Run `detect_environment` and dump the result as JSON. + """ try: if len(sys.argv) < 2: raise EnvironmentException("Usage: %s [-fc] DIRECTORY" % sys.argv[0]) diff --git a/rsconnect/http_support.py b/rsconnect/http_support.py index 7fd98147..61c98b68 100644 --- a/rsconnect/http_support.py +++ b/rsconnect/http_support.py @@ -1,6 +1,5 @@ """ -This file provides an HTTP API (wrapped around the standard library via six) tailored to -our needs. +HTTP support wrappers and utility functions """ import json import socket diff --git a/rsconnect/log.py b/rsconnect/log.py index 3d861eb6..62204b38 100644 --- a/rsconnect/log.py +++ b/rsconnect/log.py @@ -1,3 +1,7 @@ +""" +Logging wrapper and shared instance +""" + from logging import getLogger, LoggerAdapter, DEBUG import click diff --git a/rsconnect/main.py b/rsconnect/main.py index d43cff4f..363cceba 100644 --- a/rsconnect/main.py +++ b/rsconnect/main.py @@ -104,9 +104,11 @@ def _test_server_and_api(server, api_key, insecure, ca_cert): # noinspection SpellCheckingInspection @cli.command( short_help="Define a nickname for an RStudio Connect server.", - help="Associate a simple nickname with the information needed to interact with an RStudio Connect server. " - "Specifying an existing nickname will cause its stored information to be replaced by what is given " - "on the command line.", + help=( + "Associate a simple nickname with the information needed to interact with an RStudio Connect server. " + "Specifying an existing nickname will cause its stored information to be replaced by what is given " + "on the command line." + ), ) @click.option("--name", "-n", required=True, help="The nickname to associate with the server.") @click.option("--server", "-s", required=True, help="The URL for the RStudio Connect server.") @@ -165,10 +167,12 @@ def list_servers(verbose): # noinspection SpellCheckingInspection @cli.command( short_help="Show details about an RStudio Connect server.", - help="Show details about an RStudio Connect server and installed Python information. " - "Use this command to verify that a URL refers to an RStudio Connect server, optionally, that an " - "API key is valid for authentication for that server. It may also be used to verify that the " - "information stored as a nickname is still valid.", + help=( + "Show details about an RStudio Connect server and installed Python information. " + "Use this command to verify that a URL refers to an RStudio Connect server, optionally, that an " + "API key is valid for authentication for that server. It may also be used to verify that the " + "information stored as a nickname is still valid." + ), ) @click.option( "--name", "-n", help="The nickname of the RStudio Connect server to get details for.", @@ -226,8 +230,10 @@ def details(name, server, api_key, insecure, cacert, verbose): @cli.command( short_help="Remove the information about an RStudio Connect server.", - help="Remove the information about an RStudio Connect server by nickname or URL. " - "One of --name or --server is required.", + help=( + "Remove the information about an RStudio Connect server by nickname or URL. " + "One of --name or --server is required." + ), ) @click.option("--name", "-n", help="The nickname of the RStudio Connect server to remove.") @click.option("--server", "-s", help="The URL of the RStudio Connect server to remove.") @@ -278,9 +284,11 @@ def _get_names_to_check(file_or_directory): @cli.command( short_help="Show saved information about the specified deployment.", - help="Display information about the deployment of a Jupyter notebook or manifest. For any given file, " - "information about it" - "s deployments are saved on a per-server basis.", + help=( + "Display information about the deployment of a Jupyter notebook or manifest. For any given file, " + "information about it" + "s deployments are saved on a per-server basis." + ), ) @click.argument("file", type=click.Path(exists=True, dir_okay=True, file_okay=True)) def info(file): @@ -450,9 +458,11 @@ def _deploy_bundle( @deploy.command( name="notebook", short_help="Deploy Jupyter notebook to RStudio Connect [v1.7.0+].", - help="Deploy a Jupyter notebook to RStudio Connect. This may be done by source or as a static HTML " - "page. If the notebook is deployed as a static HTML page (--static), it cannot be scheduled or " - "rerun on the Connect server.", + help=( + "Deploy a Jupyter notebook to RStudio Connect. This may be done by source or as a static HTML " + "page. If the notebook is deployed as a static HTML page (--static), it cannot be scheduled or " + "rerun on the Connect server." + ), ) @click.option("--name", "-n", help="The nickname of the RStudio Connect server to deploy to.") @click.option( @@ -498,8 +508,10 @@ def _deploy_bundle( "--python", "-p", type=click.Path(exists=True), - help="Path to Python interpreter whose environment should be used. " - "The Python environment must have the rsconnect package installed.", + help=( + "Path to Python interpreter whose environment should be used. " + "The Python environment must have the rsconnect package installed." + ), ) @click.option( "--conda", "-C", is_flag=True, hidden=True, help="Use Conda to deploy (requires Connect version 1.8.2 or later)", @@ -683,23 +695,27 @@ def generate_deploy_python(app_mode, alias, min_version): "--entrypoint", "-e", help=( - "The module and executable object which serves as the entry point for the " "{desc} (defaults to app)" + "The module and executable object which serves as the entry point for the {desc} (defaults to app)" ).format(desc=app_mode.desc()), ) @click.option( "--exclude", "-x", multiple=True, - help="Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try " - "to expand this which will not do what you expect. Generally, it's safest to quote the pattern. " - "This option may be repeated.", + help=( + "Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try " + "to expand this which will not do what you expect. Generally, it's safest to quote the pattern. " + "This option may be repeated." + ), ) @click.option( "--new", "-N", is_flag=True, - help="Force a new deployment, even if there is saved metadata from a previous deployment. " - "Cannot be used with --app-id.", + help=( + "Force a new deployment, even if there is saved metadata from a previous deployment. " + "Cannot be used with --app-id." + ), ) @click.option( "--app-id", "-a", help="Existing app ID or GUID to replace. Cannot be used with --new.", @@ -711,8 +727,10 @@ def generate_deploy_python(app_mode, alias, min_version): "--python", "-p", type=click.Path(exists=True), - help="Path to Python interpreter whose environment should be used. " - "The Python environment must have the rsconnect package installed.", + help=( + "Path to Python interpreter whose environment should be used. " + "The Python environment must have the rsconnect package installed." + ), ) @click.option( "--conda", @@ -968,16 +986,18 @@ def generate_write_manifest_python(app_mode, alias): "--entrypoint", "-e", help=( - "The module and executable object which serves as the entry point for the " "{desc} (defaults to app)" + "The module and executable object which serves as the entry point for the {desc} (defaults to app)" ).format(desc=app_mode.desc()), ) @click.option( "--exclude", "-x", multiple=True, - help="Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try " - "to expand this which will not do what you expect. Generally, it's safest to quote the pattern. " - "This option may be repeated.", + help=( + "Specify a glob pattern for ignoring files when building the bundle. Note that your shell may try " + "to expand this which will not do what you expect. Generally, it's safest to quote the pattern. " + "This option may be repeated." + ), ) @click.option( "--python", diff --git a/rsconnect/metadata.py b/rsconnect/metadata.py index 6438e3a1..62dc668d 100644 --- a/rsconnect/metadata.py +++ b/rsconnect/metadata.py @@ -1,3 +1,7 @@ +""" +Metadata management objects and utility functions +""" + import base64 import hashlib import json diff --git a/rsconnect/models.py b/rsconnect/models.py index bb12eb7d..0f842632 100644 --- a/rsconnect/models.py +++ b/rsconnect/models.py @@ -1,5 +1,5 @@ """ -This file defines some support data models. +Data models """ import fnmatch import os @@ -9,6 +9,11 @@ class AppMode(object): + """ + Data class defining an "app mode" as understood by RStudio + Connect + """ + def __init__(self, ordinal, name, text, ext=None): self._ordinal = ordinal self._name = name @@ -35,6 +40,11 @@ def __repr__(self): class AppModes(object): + """ + Enumeration-like collection of known `AppMode`s with lookup + functions + """ + UNKNOWN = AppMode(0, "unknown", "") SHINY = AppMode(1, "shiny", "Shiny App", ".R") RMD = AppMode(3, "rmd-static", "R Markdown", ".Rmd") @@ -65,14 +75,17 @@ class AppModes(object): @classmethod def get_by_ordinal(cls, ordinal, return_unknown=False): + """Get an AppMode by its associated ordinal (integer)""" return cls._find_by(lambda mode: mode.ordinal() == ordinal, "with ordinal %s" % ordinal, return_unknown,) @classmethod def get_by_name(cls, name, return_unknown=False): + """Get an AppMode by name""" return cls._find_by(lambda mode: mode.name() == name, "named %s" % name, return_unknown) @classmethod def get_by_extension(cls, extension, return_unknown=False): + """Get an app mode by its associated extension""" # We can't allow a lookup by None since some modes have that for an extension. if extension is None: if return_unknown: @@ -170,6 +183,10 @@ def items_match(i1, i2): class GlobSet(object): + """ + Matches against a set of `GlobMatcher` patterns + """ + def __init__(self, patterns): self._matchers = [GlobMatcher(pattern) for pattern in patterns] diff --git a/scripts/install-deps b/scripts/install-deps index ea6cc145..a023ba2e 100755 --- a/scripts/install-deps +++ b/scripts/install-deps @@ -16,6 +16,9 @@ main() { ;; *) pipenv install --dev + if [[ "$(uname)" == Linux ]]; then + pipenv run pip install -U 'SecretStorage>=3' + fi ;; esac } diff --git a/scripts/runtests b/scripts/runtests index 960054b1..01adc4a9 100755 --- a/scripts/runtests +++ b/scripts/runtests @@ -10,9 +10,9 @@ case "${PYTHON_VERSION}" in set +o xtrace source "$(pipenv --venv)/bin/activate" set -o xtrace - pytest ${PYTEST_ARGS} ./rsconnect/ + pytest ${PYTEST_ARGS} ./tests/ ;; *) - pipenv run pytest ${PYTEST_ARGS} --mypy ./rsconnect/ + pipenv run pytest ${PYTEST_ARGS} --mypy ./tests/ ;; esac diff --git a/rsconnect/tests/__init__.py b/tests/__init__.py similarity index 100% rename from rsconnect/tests/__init__.py rename to tests/__init__.py diff --git a/rsconnect/tests/test_actions.py b/tests/test_actions.py similarity index 99% rename from rsconnect/tests/test_actions.py rename to tests/test_actions.py index 62427c28..4cda7119 100644 --- a/rsconnect/tests/test_actions.py +++ b/tests/test_actions.py @@ -43,7 +43,8 @@ ) from rsconnect.api import RSConnectException, RSConnectServer from rsconnect.environment import Environment -from rsconnect.tests.test_data_util import get_manifest_path, get_api_path, get_dir + +from .test_data_util import get_manifest_path, get_api_path, get_dir class TestActions(TestCase): diff --git a/rsconnect/tests/test_api.py b/tests/test_api.py similarity index 100% rename from rsconnect/tests/test_api.py rename to tests/test_api.py diff --git a/rsconnect/tests/test_bundle.py b/tests/test_bundle.py similarity index 99% rename from rsconnect/tests/test_bundle.py rename to tests/test_bundle.py index eec4d56e..f3d2f5a0 100644 --- a/rsconnect/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -13,7 +13,7 @@ make_notebook_source_bundle, keep_manifest_specified_file, ) -from rsconnect.tests.test_data_util import get_dir +from .test_data_util import get_dir class TestBundle(TestCase): diff --git a/rsconnect/tests/test_data_util.py b/tests/test_data_util.py similarity index 100% rename from rsconnect/tests/test_data_util.py rename to tests/test_data_util.py diff --git a/rsconnect/tests/test_environment.py b/tests/test_environment.py similarity index 98% rename from rsconnect/tests/test_environment.py rename to tests/test_environment.py index 93a5ca60..82093ccc 100644 --- a/rsconnect/tests/test_environment.py +++ b/tests/test_environment.py @@ -11,7 +11,7 @@ get_default_locale, get_python_version, ) -from rsconnect.tests.test_data_util import get_dir +from .test_data_util import get_dir version_re = re.compile(r"\d+\.\d+(\.\d+)?") diff --git a/rsconnect/tests/test_http_support.py b/tests/test_http_support.py similarity index 100% rename from rsconnect/tests/test_http_support.py rename to tests/test_http_support.py diff --git a/rsconnect/tests/test_main.py b/tests/test_main.py similarity index 97% rename from rsconnect/tests/test_main.py rename to tests/test_main.py index 85441450..ed4d6765 100644 --- a/rsconnect/tests/test_main.py +++ b/tests/test_main.py @@ -5,8 +5,8 @@ from click.testing import CliRunner from .test_data_util import get_dir, get_manifest_path, get_api_path -from ..api import RSConnectException -from ..main import cli, _validate_deploy_to_args, server_store +from rsconnect.api import RSConnectException +from rsconnect.main import cli, _validate_deploy_to_args, server_store from rsconnect import VERSION diff --git a/rsconnect/tests/test_metadata.py b/tests/test_metadata.py similarity index 100% rename from rsconnect/tests/test_metadata.py rename to tests/test_metadata.py diff --git a/rsconnect/tests/test_models.py b/tests/test_models.py similarity index 100% rename from rsconnect/tests/test_models.py rename to tests/test_models.py diff --git a/rsconnect/tests/testdata/R/shinyapp/app.R b/tests/testdata/R/shinyapp/app.R similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/app.R rename to tests/testdata/R/shinyapp/app.R diff --git a/rsconnect/tests/testdata/R/shinyapp/index.htm b/tests/testdata/R/shinyapp/index.htm similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/index.htm rename to tests/testdata/R/shinyapp/index.htm diff --git a/rsconnect/tests/testdata/R/shinyapp/manifest.json b/tests/testdata/R/shinyapp/manifest.json similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/manifest.json rename to tests/testdata/R/shinyapp/manifest.json diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/BH b/tests/testdata/R/shinyapp/packrat/desc/BH similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/BH rename to tests/testdata/R/shinyapp/packrat/desc/BH diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/R6 b/tests/testdata/R/shinyapp/packrat/desc/R6 similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/R6 rename to tests/testdata/R/shinyapp/packrat/desc/R6 diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/Rcpp b/tests/testdata/R/shinyapp/packrat/desc/Rcpp similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/Rcpp rename to tests/testdata/R/shinyapp/packrat/desc/Rcpp diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/crayon b/tests/testdata/R/shinyapp/packrat/desc/crayon similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/crayon rename to tests/testdata/R/shinyapp/packrat/desc/crayon diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/digest b/tests/testdata/R/shinyapp/packrat/desc/digest similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/digest rename to tests/testdata/R/shinyapp/packrat/desc/digest diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/htmltools b/tests/testdata/R/shinyapp/packrat/desc/htmltools similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/htmltools rename to tests/testdata/R/shinyapp/packrat/desc/htmltools diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/httpuv b/tests/testdata/R/shinyapp/packrat/desc/httpuv similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/httpuv rename to tests/testdata/R/shinyapp/packrat/desc/httpuv diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/jsonlite b/tests/testdata/R/shinyapp/packrat/desc/jsonlite similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/jsonlite rename to tests/testdata/R/shinyapp/packrat/desc/jsonlite diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/later b/tests/testdata/R/shinyapp/packrat/desc/later similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/later rename to tests/testdata/R/shinyapp/packrat/desc/later diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/magrittr b/tests/testdata/R/shinyapp/packrat/desc/magrittr similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/magrittr rename to tests/testdata/R/shinyapp/packrat/desc/magrittr diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/mime b/tests/testdata/R/shinyapp/packrat/desc/mime similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/mime rename to tests/testdata/R/shinyapp/packrat/desc/mime diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/packrat b/tests/testdata/R/shinyapp/packrat/desc/packrat similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/packrat rename to tests/testdata/R/shinyapp/packrat/desc/packrat diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/promises b/tests/testdata/R/shinyapp/packrat/desc/promises similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/promises rename to tests/testdata/R/shinyapp/packrat/desc/promises diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/rlang b/tests/testdata/R/shinyapp/packrat/desc/rlang similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/rlang rename to tests/testdata/R/shinyapp/packrat/desc/rlang diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/shiny b/tests/testdata/R/shinyapp/packrat/desc/shiny similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/shiny rename to tests/testdata/R/shinyapp/packrat/desc/shiny diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/sourcetools b/tests/testdata/R/shinyapp/packrat/desc/sourcetools similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/sourcetools rename to tests/testdata/R/shinyapp/packrat/desc/sourcetools diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/desc/xtable b/tests/testdata/R/shinyapp/packrat/desc/xtable similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/desc/xtable rename to tests/testdata/R/shinyapp/packrat/desc/xtable diff --git a/rsconnect/tests/testdata/R/shinyapp/packrat/packrat.lock b/tests/testdata/R/shinyapp/packrat/packrat.lock similarity index 100% rename from rsconnect/tests/testdata/R/shinyapp/packrat/packrat.lock rename to tests/testdata/R/shinyapp/packrat/packrat.lock diff --git a/rsconnect/tests/testdata/api/flask/app.py b/tests/testdata/api/flask/app.py similarity index 100% rename from rsconnect/tests/testdata/api/flask/app.py rename to tests/testdata/api/flask/app.py diff --git a/rsconnect/tests/testdata/api/flask/requirements.txt b/tests/testdata/api/flask/requirements.txt similarity index 100% rename from rsconnect/tests/testdata/api/flask/requirements.txt rename to tests/testdata/api/flask/requirements.txt diff --git a/rsconnect/tests/testdata/fake_broken_conda.sh b/tests/testdata/fake_broken_conda.sh similarity index 100% rename from rsconnect/tests/testdata/fake_broken_conda.sh rename to tests/testdata/fake_broken_conda.sh diff --git a/rsconnect/tests/testdata/fake_conda.sh b/tests/testdata/fake_conda.sh similarity index 100% rename from rsconnect/tests/testdata/fake_conda.sh rename to tests/testdata/fake_conda.sh diff --git a/rsconnect/tests/testdata/py2/conda1/.keep b/tests/testdata/py2/conda1/.keep similarity index 100% rename from rsconnect/tests/testdata/py2/conda1/.keep rename to tests/testdata/py2/conda1/.keep diff --git a/rsconnect/tests/testdata/py2/pip1/dummy.ipynb b/tests/testdata/py2/pip1/dummy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py2/pip1/dummy.ipynb rename to tests/testdata/py2/pip1/dummy.ipynb diff --git a/rsconnect/tests/testdata/py2/pip1/requirements.txt b/tests/testdata/py2/pip1/requirements.txt similarity index 100% rename from rsconnect/tests/testdata/py2/pip1/requirements.txt rename to tests/testdata/py2/pip1/requirements.txt diff --git a/rsconnect/tests/testdata/py2/pip2/data.csv b/tests/testdata/py2/pip2/data.csv similarity index 100% rename from rsconnect/tests/testdata/py2/pip2/data.csv rename to tests/testdata/py2/pip2/data.csv diff --git a/rsconnect/tests/testdata/py2/pip2/dummy.ipynb b/tests/testdata/py2/pip2/dummy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py2/pip2/dummy.ipynb rename to tests/testdata/py2/pip2/dummy.ipynb diff --git a/rsconnect/tests/testdata/py3/conda1/.keep b/tests/testdata/py3/conda1/.keep similarity index 100% rename from rsconnect/tests/testdata/py3/conda1/.keep rename to tests/testdata/py3/conda1/.keep diff --git a/rsconnect/tests/testdata/py3/data_file/data.csv b/tests/testdata/py3/data_file/data.csv similarity index 100% rename from rsconnect/tests/testdata/py3/data_file/data.csv rename to tests/testdata/py3/data_file/data.csv diff --git a/rsconnect/tests/testdata/py3/data_file/data_file.ipynb b/tests/testdata/py3/data_file/data_file.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/data_file/data_file.ipynb rename to tests/testdata/py3/data_file/data_file.ipynb diff --git a/rsconnect/tests/testdata/py3/minimal/minimal.ipynb b/tests/testdata/py3/minimal/minimal.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/minimal/minimal.ipynb rename to tests/testdata/py3/minimal/minimal.ipynb diff --git a/rsconnect/tests/testdata/py3/numpy/numpy.ipynb b/tests/testdata/py3/numpy/numpy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/numpy/numpy.ipynb rename to tests/testdata/py3/numpy/numpy.ipynb diff --git a/rsconnect/tests/testdata/py3/numpy_requirements/numpy.ipynb b/tests/testdata/py3/numpy_requirements/numpy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/numpy_requirements/numpy.ipynb rename to tests/testdata/py3/numpy_requirements/numpy.ipynb diff --git a/rsconnect/tests/testdata/py3/numpy_requirements/requirements.txt b/tests/testdata/py3/numpy_requirements/requirements.txt similarity index 100% rename from rsconnect/tests/testdata/py3/numpy_requirements/requirements.txt rename to tests/testdata/py3/numpy_requirements/requirements.txt diff --git a/rsconnect/tests/testdata/py3/pip1/dummy.ipynb b/tests/testdata/py3/pip1/dummy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/pip1/dummy.ipynb rename to tests/testdata/py3/pip1/dummy.ipynb diff --git a/rsconnect/tests/testdata/py3/pip1/requirements.txt b/tests/testdata/py3/pip1/requirements.txt similarity index 100% rename from rsconnect/tests/testdata/py3/pip1/requirements.txt rename to tests/testdata/py3/pip1/requirements.txt diff --git a/rsconnect/tests/testdata/py3/pip2/data.csv b/tests/testdata/py3/pip2/data.csv similarity index 100% rename from rsconnect/tests/testdata/py3/pip2/data.csv rename to tests/testdata/py3/pip2/data.csv diff --git a/rsconnect/tests/testdata/py3/pip2/dummy.ipynb b/tests/testdata/py3/pip2/dummy.ipynb similarity index 100% rename from rsconnect/tests/testdata/py3/pip2/dummy.ipynb rename to tests/testdata/py3/pip2/dummy.ipynb