Skip to content

Commit 881e90a

Browse files
authored
Merge branch 'master' into bcwu-redeploy2unknown
2 parents 796fe1d + 650d9f3 commit 881e90a

File tree

5 files changed

+76
-111
lines changed

5 files changed

+76
-111
lines changed

rsconnect/bundle.py

+68-97
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pprint import pformat
1515
from collections import defaultdict
1616
from mimetypes import guess_type
17+
from pathlib import Path
1718
import click
1819

1920

@@ -33,7 +34,7 @@
3334

3435
# From https://github.com/rstudio/rsconnect/blob/485e05a26041ab8183a220da7a506c9d3a41f1ff/R/bundle.R#L85-L88
3536
# noinspection SpellCheckingInspection
36-
directories_to_ignore = [
37+
directories_ignore_list = [
3738
".Rproj.user/",
3839
".env/",
3940
".git/",
@@ -47,6 +48,7 @@
4748
"rsconnect/",
4849
"venv/",
4950
]
51+
directories_to_ignore = {Path(d) for d in directories_ignore_list}
5052

5153

5254
# noinspection SpellCheckingInspection
@@ -423,17 +425,20 @@ def make_notebook_html_bundle(
423425
return bundle_file
424426

425427

426-
def keep_manifest_specified_file(relative_path):
428+
def keep_manifest_specified_file(relative_path, ignore_path_set=directories_to_ignore):
427429
"""
428430
A helper to see if the relative path given, which is assumed to have come
429431
from a manifest.json file, should be kept or ignored.
430432
431433
:param relative_path: the relative path name to check.
432434
:return: True, if the path should kept or False, if it should be ignored.
433435
"""
434-
for ignore_me in directories_to_ignore:
435-
if relative_path.startswith(ignore_me):
436+
p = Path(relative_path)
437+
for parent in p.parents:
438+
if parent in ignore_path_set:
436439
return False
440+
if p in ignore_path_set:
441+
return False
437442
return True
438443

439444

@@ -550,56 +555,6 @@ def list_environment_dirs(directory):
550555
return envs
551556

552557

553-
def _create_api_file_list(
554-
directory, # type: str
555-
requirements_file_name, # type: str
556-
extra_files=None, # type: typing.Optional[typing.List[str]]
557-
excludes=None, # type: typing.Optional[typing.List[str]]
558-
):
559-
# type: (...) -> typing.List[str]
560-
"""
561-
Builds a full list of files under the given directory that should be included
562-
in a manifest or bundle. Extra files and excludes are relative to the given
563-
directory and work as you'd expect.
564-
565-
:param directory: the directory to walk for files.
566-
:param requirements_file_name: the name of the requirements file for the current
567-
Python environment.
568-
:param extra_files: a sequence of any extra files to include in the bundle.
569-
:param excludes: a sequence of glob patterns that will exclude matched files.
570-
:return: the list of relevant files, relative to the given directory.
571-
"""
572-
# Don't let these top-level files be added via the extra files list.
573-
extra_files = extra_files or []
574-
skip = [requirements_file_name, "manifest.json"]
575-
extra_files = sorted(list(set(extra_files) - set(skip)))
576-
577-
# Don't include these top-level files.
578-
excludes = list(excludes) if excludes else []
579-
excludes.append("manifest.json")
580-
excludes.append(requirements_file_name)
581-
excludes.extend(list_environment_dirs(directory))
582-
glob_set = create_glob_set(directory, excludes)
583-
584-
file_list = []
585-
586-
for subdir, dirs, files in os.walk(directory):
587-
for file in files:
588-
abs_path = os.path.join(subdir, file)
589-
rel_path = os.path.relpath(abs_path, directory)
590-
591-
if keep_manifest_specified_file(rel_path) and (rel_path in extra_files or not glob_set.matches(abs_path)):
592-
file_list.append(rel_path)
593-
# Don't add extra files more than once.
594-
if rel_path in extra_files:
595-
extra_files.remove(rel_path)
596-
597-
for rel_path in extra_files:
598-
file_list.append(rel_path)
599-
600-
return sorted(file_list)
601-
602-
603558
def make_api_manifest(
604559
directory: str,
605560
entry_point: str,
@@ -624,7 +579,17 @@ def make_api_manifest(
624579
if is_environment_dir(directory):
625580
excludes = list(excludes or []) + ["bin/", "lib/"]
626581

627-
relevant_files = _create_api_file_list(directory, environment.filename, extra_files, excludes)
582+
extra_files = extra_files or []
583+
skip = [environment.filename, "manifest.json"]
584+
extra_files = sorted(list(set(extra_files) - set(skip)))
585+
586+
# Don't include these top-level files.
587+
excludes = list(excludes) if excludes else []
588+
excludes.append("manifest.json")
589+
excludes.append(environment.filename)
590+
excludes.extend(list_environment_dirs(directory))
591+
592+
relevant_files = create_file_list(directory, extra_files, excludes)
628593
manifest = make_source_manifest(app_mode, environment, entry_point, None, image)
629594

630595
manifest_add_buffer(manifest, environment.filename, environment.contents)
@@ -655,6 +620,8 @@ def make_html_bundle_content(
655620
"""
656621
extra_files = list(extra_files) if extra_files else []
657622
entrypoint = entrypoint or infer_entrypoint(path=path, mimetype="text/html")
623+
if not entrypoint:
624+
raise RSConnectException("Unable to find a valid html entry point.")
658625

659626
if path.startswith(os.curdir):
660627
path = relpath(path)
@@ -667,37 +634,15 @@ def make_html_bundle_content(
667634

668635
extra_files = extra_files or []
669636
skip = ["manifest.json"]
670-
extra_files = sorted(list(set(extra_files) - set(skip)))
637+
extra_files = sorted(set(extra_files) - set(skip))
671638

672639
# Don't include these top-level files.
673640
excludes = list(excludes) if excludes else []
674641
excludes.append("manifest.json")
675642
if not isfile(path):
676643
excludes.extend(list_environment_dirs(path))
677-
glob_set = create_glob_set(path, excludes)
678-
679-
file_list = []
680644

681-
for rel_path in extra_files:
682-
file_list.append(rel_path)
683-
684-
if isfile(path):
685-
file_list.append(path)
686-
else:
687-
for subdir, dirs, files in os.walk(path):
688-
for file in files:
689-
abs_path = os.path.join(subdir, file)
690-
rel_path = os.path.relpath(abs_path, path)
691-
692-
if keep_manifest_specified_file(rel_path) and (
693-
rel_path in extra_files or not glob_set.matches(abs_path)
694-
):
695-
file_list.append(rel_path)
696-
# Don't add extra files more than once.
697-
if rel_path in extra_files:
698-
extra_files.remove(rel_path)
699-
700-
relevant_files = sorted(file_list)
645+
relevant_files = create_file_list(path, extra_files, excludes)
701646
manifest = make_html_manifest(entrypoint, image)
702647

703648
for rel_path in relevant_files:
@@ -706,6 +651,48 @@ def make_html_bundle_content(
706651
return manifest, relevant_files
707652

708653

654+
def create_file_list(
655+
path: str,
656+
extra_files: typing.List[str] = None,
657+
excludes: typing.List[str] = None,
658+
) -> typing.List[str]:
659+
"""
660+
Builds a full list of files under the given path that should be included
661+
in a manifest or bundle. Extra files and excludes are relative to the given
662+
directory and work as you'd expect.
663+
664+
:param path: a file, or a directory to walk for files.
665+
:param extra_files: a sequence of any extra files to include in the bundle.
666+
:param excludes: a sequence of glob patterns that will exclude matched files.
667+
:return: the list of relevant files, relative to the given directory.
668+
"""
669+
extra_files = extra_files or []
670+
excludes = excludes if excludes else []
671+
glob_set = create_glob_set(path, excludes)
672+
exclude_paths = {Path(p) for p in excludes}
673+
file_set = set() # type: typing.Set[str]
674+
file_set.union(extra_files)
675+
676+
if isfile(path):
677+
file_set.add(path)
678+
return sorted(file_set)
679+
680+
for subdir, dirs, files in os.walk(path):
681+
if Path(subdir) in exclude_paths:
682+
continue
683+
for file in files:
684+
abs_path = os.path.join(subdir, file)
685+
rel_path = os.path.relpath(abs_path, path)
686+
687+
if Path(abs_path) in exclude_paths:
688+
continue
689+
if keep_manifest_specified_file(rel_path, exclude_paths | directories_to_ignore) and (
690+
rel_path in extra_files or not glob_set.matches(abs_path)
691+
):
692+
file_set.add(rel_path)
693+
return sorted(file_set)
694+
695+
709696
def infer_entrypoint(path, mimetype):
710697
if os.path.isfile(path):
711698
return path
@@ -823,25 +810,9 @@ def _create_quarto_file_list(
823810
excludes = list(excludes) if excludes else []
824811
excludes.append("manifest.json")
825812
excludes.extend(list_environment_dirs(directory))
826-
glob_set = create_glob_set(directory, excludes)
827-
828-
file_list = []
829-
830-
for subdir, dirs, files in os.walk(directory):
831-
for file in files:
832-
abs_path = os.path.join(subdir, file)
833-
rel_path = os.path.relpath(abs_path, directory)
834-
835-
if keep_manifest_specified_file(rel_path) and (rel_path in extra_files or not glob_set.matches(abs_path)):
836-
file_list.append(rel_path)
837-
# Don't add extra files more than once.
838-
if rel_path in extra_files:
839-
extra_files.remove(rel_path)
840-
841-
for rel_path in extra_files:
842-
file_list.append(rel_path)
843813

844-
return sorted(file_list)
814+
file_list = create_file_list(directory, extra_files, excludes)
815+
return file_list
845816

846817

847818
def make_quarto_manifest(

rsconnect/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def bootstrap(
407407
def add(ctx, name, server, api_key, insecure, cacert, account, token, secret, verbose):
408408

409409
set_verbosity(verbose)
410-
if sys.version_info >= (3, 8):
410+
if click.__version__ >= "8.0.0" and sys.version_info >= (3, 7):
411411
click.echo("Detected the following inputs:")
412412
for k, v in locals().items():
413413
if k in {"ctx", "verbose"}:

rsconnect/validation.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ def validate_connection_options(url, api_key, insecure, cacert, account_name, to
2525
)
2626
)
2727
if not name and not url and not shinyapps_options:
28-
raise RSConnectException(
29-
"You must specify one of -n/--name OR -s/--server OR T/--token, -S/--secret."
30-
)
28+
raise RSConnectException("You must specify one of -n/--name OR -s/--server OR T/--token, -S/--secret.")
3129

3230
present_connect_options = _get_present_options(connect_options)
3331
present_shinyapps_options = _get_present_options(shinyapps_options)
@@ -40,13 +38,9 @@ def validate_connection_options(url, api_key, insecure, cacert, account_name, to
4038
)
4139
)
4240

43-
if url and ('posit.cloud' in url or 'rstudio.cloud' in url):
41+
if url and ("posit.cloud" in url or "rstudio.cloud" in url):
4442
if len(present_cloud_options) != len(cloud_options):
45-
raise RSConnectException(
46-
"-T/--token and -S/--secret must be provided for Posit Cloud."
47-
)
43+
raise RSConnectException("-T/--token and -S/--secret must be provided for Posit Cloud.")
4844
elif present_shinyapps_options:
4945
if len(present_shinyapps_options) != len(shinyapps_options):
50-
raise RSConnectException(
51-
"-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io."
52-
)
46+
raise RSConnectException("-A/--account, -T/--token, and -S/--secret must all be provided for shinyapps.io.")

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ project_urls =
1717
[options]
1818
install_requires =
1919
six>=1.14.0
20-
click>=7.0.0
20+
click>=8.0.0
2121
pip>=10.0.0
2222
semver>=2.0.0,<3.0.0
2323
pyjwt>=2.4.0

tests/test_bundle.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,9 @@ def do_test_html_bundle(self, directory):
276276
def test_keep_manifest_specified_file(self):
277277
self.assertTrue(keep_manifest_specified_file("app.R"))
278278
self.assertFalse(keep_manifest_specified_file("packrat/packrat.lock"))
279-
self.assertTrue(keep_manifest_specified_file("rsconnect"))
279+
self.assertFalse(keep_manifest_specified_file("rsconnect"))
280280
self.assertFalse(keep_manifest_specified_file("rsconnect/bogus.file"))
281-
self.assertTrue(keep_manifest_specified_file("rsconnect-python"))
281+
self.assertFalse(keep_manifest_specified_file("rsconnect-python"))
282282
self.assertFalse(keep_manifest_specified_file("rsconnect-python/bogus.file"))
283283
self.assertFalse(keep_manifest_specified_file(".svn/bogus.file"))
284284
self.assertFalse(keep_manifest_specified_file(".env/share/jupyter/kernels/python3/kernel.json"))

0 commit comments

Comments
 (0)