22
33import json
44import os
5+ import shutil
56import subprocess
7+ import tempfile
8+ import time
69from typing import Any , Callable , TypeVar
710
811import pytest
@@ -128,6 +131,16 @@ def write_requirements_txt(app_dir: str) -> None:
128131 f .write (f"git+https://github.com/posit-dev/py-shiny.git@{ git_hash } \n " )
129132
130133
134+ def assert_rsconnect_file_updated (file_path : str , min_mtime : float ) -> None :
135+ """
136+ Asserts that the specified file has been updated since `min_mtime` (seconds since epoch).
137+ """
138+ mtime = os .path .getmtime (file_path )
139+ assert (
140+ mtime > min_mtime
141+ ), f"File '{ file_path } ' was not updated during app deployment which means the deployment failed"
142+
143+
131144def deploy_app (
132145 app_file_path : str ,
133146 location : str ,
@@ -140,26 +153,38 @@ def deploy_app(
140153
141154 run_on_ci = os .environ .get ("CI" , "False" ) == "true"
142155 repo = os .environ .get ("GITHUB_REPOSITORY" , "unknown" )
143- branch_name = os .environ .get ("GITHUB_HEAD_REF" , "unknown" )
144156
145- if (
146- not run_on_ci
147- or repo != "posit-dev/py-shiny"
148- or not (branch_name .startswith ("deploy" ) or branch_name == "main" )
149- ):
150- pytest .skip ("Not on CI or posit-dev/py-shiny repo or deploy* or main branch" )
151- raise RuntimeError ()
157+ if not (run_on_ci and repo == "posit-dev/py-shiny" ):
158+ pytest .skip ("Not on CI and within posit-dev/py-shiny repo" )
152159
153160 app_dir = os .path .dirname (app_file_path )
154- write_requirements_txt (app_dir )
155-
156- deployment_function = {
157- "connect" : quiet_deploy_to_connect ,
158- "shinyapps" : quiet_deploy_to_shinyapps ,
159- }[location ]
160-
161- url = deployment_function (app_name , app_dir )
162- return url
161+ app_dir_name = os .path .basename (app_dir )
162+
163+ # Use temporary directory to avoid modifying the original app directory
164+ # This allows us to run tests in parallel when deploying apps both modify the same rsconnect config file
165+ with tempfile .TemporaryDirectory ("deploy_app" ) as tmpdir :
166+
167+ # Creating a dir with same name instead of tmp to avoid issues
168+ # when deploying app to shinyapps.io using rsconnect package
169+ # since the rsconnect/*.json file needs the app_dir name to be same
170+ tmp_app_dir = os .path .join (tmpdir , app_dir_name )
171+ os .mkdir (tmp_app_dir )
172+ shutil .copytree (app_dir , tmp_app_dir , dirs_exist_ok = True )
173+ write_requirements_txt (tmp_app_dir )
174+
175+ deployment_function = {
176+ "connect" : quiet_deploy_to_connect ,
177+ "shinyapps" : quiet_deploy_to_shinyapps ,
178+ }[location ]
179+
180+ pre_deployment_time = time .time ()
181+ url = deployment_function (app_name , tmp_app_dir )
182+ tmp_rsconnect_config = os .path .join (
183+ tmp_app_dir , "rsconnect-python" , f"{ os .path .basename (tmp_app_dir )} .json"
184+ )
185+ assert_rsconnect_file_updated (tmp_rsconnect_config , pre_deployment_time )
186+
187+ return url
163188
164189
165190def create_deploys_app_url_fixture (
0 commit comments