Skip to content
Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- getdefaultlocale no longer work with newer versions of Python [#397](https://github.com/rstudio/rsconnect-python/issues/397) [#399](https://github.com/rstudio/rsconnect-python/issues/399).
- extra files not being included in write-manifest [#416](https://github.com/rstudio/rsconnect-python/issues/416).

## Unreleased

### Added
- Deploys for Posit Cloud and shinyapps.io now accept the `--visibility` flag.

## [1.16.0] - 2023-03-27

### Added
Expand Down
36 changes: 34 additions & 2 deletions rsconnect/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ def deploy_bundle(
bundle: IO = None,
env_vars=None,
app_mode=None,
visibility=None,
):
app_id = app_id or self.get("app_id")
deployment_name = deployment_name or self.get("deployment_name")
Expand All @@ -684,6 +685,7 @@ def deploy_bundle(
bundle = bundle or self.get("bundle")
env_vars = env_vars or self.get("env_vars")
app_mode = app_mode or self.get("app_mode")
visibility = visibility or self.get("visibility")

if isinstance(self.remote_server, RSConnectServer):
result = self.client.deploy(
Expand All @@ -709,6 +711,7 @@ def deploy_bundle(
deployment_name,
bundle_size,
bundle_hash,
visibility,
)
self.upload_rstudio_bundle(prepare_deploy_result, bundle_size, contents)
shinyapps_service.do_deploy(prepare_deploy_result.bundle_id, prepare_deploy_result.app_id)
Expand Down Expand Up @@ -1091,6 +1094,11 @@ def get_application(self, application_id):
self._server.handle_bad_response(response)
return response

def update_application_property(self, application_id: int, property: str, value: str):
response = self.put("/v1/applications/{}/properties/{}".format(application_id, property), body={"value": value})
self._server.handle_bad_response(response)
return response

def get_content(self, content_id):
response = self.get("/v1/content/{}".format(content_id))
self._server.handle_bad_response(response)
Expand All @@ -1117,6 +1125,9 @@ def create_revision(self, content_id):
self._server.handle_bad_response(response)
return response

def update_output(self, output_id: int, output_data: dict):
return self.patch("/v1/outputs/{}".format(output_id), body=output_data)

def get_accounts(self):
response = self.get("/v1/accounts/")
self._server.handle_bad_response(response)
Expand Down Expand Up @@ -1211,7 +1222,14 @@ def __init__(self, rstudio_client: PositClient, server: ShinyappsServer):
self._rstudio_client = rstudio_client
self._server = server

def prepare_deploy(self, app_id: typing.Optional[int], app_name: str, bundle_size: int, bundle_hash: str):
def prepare_deploy(
self,
app_id: typing.Optional[int],
app_name: str,
bundle_size: int,
bundle_hash: str,
visibility: typing.Optional[str],
):
accounts = self._rstudio_client.get_accounts()
self._server.handle_bad_response(accounts)
account = next(filter(lambda acct: acct["name"] == self._server.account_name, accounts["accounts"]), None)
Expand All @@ -1223,9 +1241,23 @@ def prepare_deploy(self, app_id: typing.Optional[int], app_name: str, bundle_siz

if app_id is None:
application = self._rstudio_client.create_application(account["id"], app_name)
self._server.handle_bad_response(application)
if visibility is not None:
property_update = self._rstudio_client.update_application_property(
application["id"], "application.visibility", visibility
)
self._server.handle_bad_response(property_update)

else:
application = self._rstudio_client.get_application(app_id)
self._server.handle_bad_response(application)
self._server.handle_bad_response(application)

if visibility is not None:
if visibility != application["deployment"]["properties"]["application.visibility"]:
self._rstudio_client.update_application_property(
application["id"], "application.visibility", visibility
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to call handle_bad_response?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :) thanks for catching that.


app_id_int = application["id"]
app_url = application["url"]

Expand Down
18 changes: 18 additions & 0 deletions rsconnect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ def wrapper(*args, **kwargs):
return wrapper


def shinyapps_deploy_args(func):
@click.option(
"--visibility",
"-V",
type=click.Choice(["public", "private"]),
help="The visibility of the resource being deployed. (shinyapps.io only; must be public (default) or private)",
)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)

return wrapper


def _passthrough(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
Expand Down Expand Up @@ -961,6 +975,7 @@ def deploy_voila(
@content_args
@cloud_shinyapps_args
@click.argument("file", type=click.Path(exists=True, dir_okay=True, file_okay=True))
@shinyapps_deploy_args
@cli_exception_handler
def deploy_manifest(
name: str,
Expand All @@ -977,6 +992,7 @@ def deploy_manifest(
verbose: bool,
file: str,
env_vars: typing.Dict[str, str],
visibility: typing.Optional[str],
):
kwargs = locals()
set_verbosity(verbose)
Expand Down Expand Up @@ -1272,6 +1288,7 @@ def generate_deploy_python(app_mode, alias, min_version):
nargs=-1,
type=click.Path(exists=True, dir_okay=False, file_okay=True),
)
@shinyapps_deploy_args
@cli_exception_handler
def deploy_app(
name: str,
Expand All @@ -1290,6 +1307,7 @@ def deploy_app(
verbose: bool,
directory,
extra_files,
visibility: typing.Optional[str],
env_vars: typing.Dict[str, str],
image: str,
account: str = None,
Expand Down
23 changes: 23 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ def post_application_callback(request, uri, response_headers):
status=200,
)

def post_application_property_callback(request, uri, response_headers):
parsed_request = _load_json(request.body)
try:
assert parsed_request == {"value": "private"}
except AssertionError as e:
return _error_to_response(e)
return [
201,
{},
b"",
]

httpretty.register_uri(
httpretty.PUT,
"https://api.shinyapps.io/v1/applications/8442/properties/application.visibility",
body=post_application_property_callback,
status=200,
)

def post_bundle_callback(request, uri, response_headers):
parsed_request = _load_json(request.body)
del parsed_request["checksum"]
Expand Down Expand Up @@ -246,6 +265,8 @@ def post_deploy_callback(request, uri, response_headers):
"c29tZVNlY3JldAo=",
"--title",
"myApp",
"--visibility",
"private"
]
try:
result = runner.invoke(cli, args)
Expand Down Expand Up @@ -428,6 +449,8 @@ def post_deploy_callback(request, uri, response_headers):
"c29tZVNlY3JldAo=",
"--title",
"myApp",
"--visibility",
"public"
]
try:
result = runner.invoke(cli, args)
Expand Down