parsl 2024.3.11__py3-none-any.whl → 2025.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. parsl/__init__.py +9 -10
  2. parsl/addresses.py +29 -7
  3. parsl/app/app.py +7 -8
  4. parsl/app/bash.py +15 -8
  5. parsl/app/errors.py +10 -13
  6. parsl/app/futures.py +8 -10
  7. parsl/app/python.py +2 -1
  8. parsl/benchmark/perf.py +2 -1
  9. parsl/concurrent/__init__.py +2 -2
  10. parsl/config.py +57 -10
  11. parsl/configs/ASPIRE1.py +6 -5
  12. parsl/configs/Azure.py +9 -8
  13. parsl/configs/bridges.py +6 -4
  14. parsl/configs/cc_in2p3.py +3 -3
  15. parsl/configs/ec2.py +3 -1
  16. parsl/configs/expanse.py +4 -3
  17. parsl/configs/frontera.py +3 -4
  18. parsl/configs/htex_local.py +3 -4
  19. parsl/configs/illinoiscluster.py +3 -1
  20. parsl/configs/improv.py +34 -0
  21. parsl/configs/kubernetes.py +4 -3
  22. parsl/configs/local_threads.py +5 -1
  23. parsl/configs/midway.py +5 -3
  24. parsl/configs/osg.py +4 -2
  25. parsl/configs/polaris.py +4 -2
  26. parsl/configs/stampede2.py +6 -5
  27. parsl/configs/summit.py +3 -3
  28. parsl/configs/toss3_llnl.py +4 -3
  29. parsl/configs/vineex_local.py +6 -4
  30. parsl/configs/wqex_local.py +5 -3
  31. parsl/curvezmq.py +4 -0
  32. parsl/data_provider/data_manager.py +4 -3
  33. parsl/data_provider/file_noop.py +1 -2
  34. parsl/data_provider/files.py +3 -3
  35. parsl/data_provider/ftp.py +1 -3
  36. parsl/data_provider/globus.py +7 -6
  37. parsl/data_provider/http.py +2 -2
  38. parsl/data_provider/rsync.py +1 -1
  39. parsl/data_provider/staging.py +2 -2
  40. parsl/data_provider/zip.py +135 -0
  41. parsl/dataflow/dependency_resolvers.py +115 -0
  42. parsl/dataflow/dflow.py +262 -224
  43. parsl/dataflow/errors.py +3 -5
  44. parsl/dataflow/futures.py +27 -14
  45. parsl/dataflow/memoization.py +5 -5
  46. parsl/dataflow/rundirs.py +5 -6
  47. parsl/dataflow/taskrecord.py +4 -5
  48. parsl/executors/__init__.py +4 -2
  49. parsl/executors/base.py +45 -15
  50. parsl/executors/errors.py +13 -0
  51. parsl/executors/execute_task.py +37 -0
  52. parsl/executors/flux/execute_parsl_task.py +3 -3
  53. parsl/executors/flux/executor.py +18 -19
  54. parsl/executors/flux/flux_instance_manager.py +26 -27
  55. parsl/executors/high_throughput/errors.py +43 -3
  56. parsl/executors/high_throughput/executor.py +316 -282
  57. parsl/executors/high_throughput/interchange.py +158 -167
  58. parsl/executors/high_throughput/manager_record.py +5 -0
  59. parsl/executors/high_throughput/manager_selector.py +55 -0
  60. parsl/executors/high_throughput/monitoring_info.py +2 -1
  61. parsl/executors/high_throughput/mpi_executor.py +113 -0
  62. parsl/executors/high_throughput/mpi_prefix_composer.py +10 -11
  63. parsl/executors/high_throughput/mpi_resource_management.py +6 -17
  64. parsl/executors/high_throughput/probe.py +9 -7
  65. parsl/executors/high_throughput/process_worker_pool.py +115 -77
  66. parsl/executors/high_throughput/zmq_pipes.py +81 -23
  67. parsl/executors/radical/executor.py +130 -79
  68. parsl/executors/radical/rpex_resources.py +17 -15
  69. parsl/executors/radical/rpex_worker.py +4 -3
  70. parsl/executors/status_handling.py +157 -51
  71. parsl/executors/taskvine/__init__.py +1 -1
  72. parsl/executors/taskvine/errors.py +1 -1
  73. parsl/executors/taskvine/exec_parsl_function.py +2 -2
  74. parsl/executors/taskvine/executor.py +41 -57
  75. parsl/executors/taskvine/factory.py +1 -1
  76. parsl/executors/taskvine/factory_config.py +1 -1
  77. parsl/executors/taskvine/manager.py +18 -13
  78. parsl/executors/taskvine/manager_config.py +9 -5
  79. parsl/executors/threads.py +6 -6
  80. parsl/executors/workqueue/errors.py +1 -1
  81. parsl/executors/workqueue/exec_parsl_function.py +6 -5
  82. parsl/executors/workqueue/executor.py +64 -63
  83. parsl/executors/workqueue/parsl_coprocess.py +1 -1
  84. parsl/jobs/error_handlers.py +2 -2
  85. parsl/jobs/job_status_poller.py +30 -113
  86. parsl/jobs/states.py +7 -2
  87. parsl/jobs/strategy.py +43 -31
  88. parsl/launchers/__init__.py +12 -3
  89. parsl/launchers/errors.py +1 -1
  90. parsl/launchers/launchers.py +6 -12
  91. parsl/log_utils.py +9 -6
  92. parsl/monitoring/db_manager.py +59 -95
  93. parsl/monitoring/errors.py +6 -0
  94. parsl/monitoring/monitoring.py +87 -356
  95. parsl/monitoring/queries/pandas.py +1 -2
  96. parsl/monitoring/radios/base.py +13 -0
  97. parsl/monitoring/radios/filesystem.py +52 -0
  98. parsl/monitoring/radios/htex.py +57 -0
  99. parsl/monitoring/radios/multiprocessing.py +17 -0
  100. parsl/monitoring/radios/udp.py +56 -0
  101. parsl/monitoring/radios/zmq.py +17 -0
  102. parsl/monitoring/remote.py +33 -37
  103. parsl/monitoring/router.py +212 -0
  104. parsl/monitoring/types.py +5 -6
  105. parsl/monitoring/visualization/app.py +4 -2
  106. parsl/monitoring/visualization/models.py +0 -1
  107. parsl/monitoring/visualization/plots/default/workflow_plots.py +11 -4
  108. parsl/monitoring/visualization/plots/default/workflow_resource_plots.py +1 -0
  109. parsl/monitoring/visualization/utils.py +0 -1
  110. parsl/monitoring/visualization/views.py +16 -8
  111. parsl/multiprocessing.py +0 -1
  112. parsl/process_loggers.py +1 -2
  113. parsl/providers/__init__.py +8 -17
  114. parsl/providers/aws/aws.py +2 -3
  115. parsl/providers/azure/azure.py +4 -5
  116. parsl/providers/base.py +2 -18
  117. parsl/providers/cluster_provider.py +4 -12
  118. parsl/providers/condor/condor.py +7 -17
  119. parsl/providers/errors.py +2 -2
  120. parsl/providers/googlecloud/googlecloud.py +2 -1
  121. parsl/providers/grid_engine/grid_engine.py +5 -14
  122. parsl/providers/kubernetes/kube.py +80 -40
  123. parsl/providers/local/local.py +13 -26
  124. parsl/providers/lsf/lsf.py +5 -23
  125. parsl/providers/pbspro/pbspro.py +5 -17
  126. parsl/providers/slurm/slurm.py +81 -39
  127. parsl/providers/torque/torque.py +3 -14
  128. parsl/serialize/__init__.py +8 -3
  129. parsl/serialize/base.py +1 -2
  130. parsl/serialize/concretes.py +5 -4
  131. parsl/serialize/facade.py +3 -3
  132. parsl/serialize/proxystore.py +3 -2
  133. parsl/tests/__init__.py +1 -1
  134. parsl/tests/configs/azure_single_node.py +4 -5
  135. parsl/tests/configs/bridges.py +3 -2
  136. parsl/tests/configs/cc_in2p3.py +1 -3
  137. parsl/tests/configs/comet.py +2 -1
  138. parsl/tests/configs/ec2_single_node.py +1 -2
  139. parsl/tests/configs/ec2_spot.py +1 -2
  140. parsl/tests/configs/flux_local.py +11 -0
  141. parsl/tests/configs/frontera.py +2 -3
  142. parsl/tests/configs/htex_local.py +3 -5
  143. parsl/tests/configs/htex_local_alternate.py +11 -15
  144. parsl/tests/configs/htex_local_intask_staging.py +5 -9
  145. parsl/tests/configs/htex_local_rsync_staging.py +4 -8
  146. parsl/tests/configs/local_radical.py +1 -3
  147. parsl/tests/configs/local_radical_mpi.py +2 -2
  148. parsl/tests/configs/local_threads_checkpoint_periodic.py +8 -10
  149. parsl/tests/configs/local_threads_monitoring.py +0 -1
  150. parsl/tests/configs/midway.py +2 -2
  151. parsl/tests/configs/nscc_singapore.py +3 -3
  152. parsl/tests/configs/osg_htex.py +1 -1
  153. parsl/tests/configs/petrelkube.py +3 -2
  154. parsl/tests/configs/slurm_local.py +24 -0
  155. parsl/tests/configs/summit.py +1 -0
  156. parsl/tests/configs/taskvine_ex.py +4 -7
  157. parsl/tests/configs/user_opts.py +2 -8
  158. parsl/tests/configs/workqueue_ex.py +4 -6
  159. parsl/tests/conftest.py +27 -13
  160. parsl/tests/integration/test_stress/test_python_simple.py +3 -4
  161. parsl/tests/integration/test_stress/test_python_threads.py +3 -5
  162. parsl/tests/manual_tests/htex_local.py +4 -6
  163. parsl/tests/manual_tests/test_basic.py +1 -0
  164. parsl/tests/manual_tests/test_log_filter.py +3 -1
  165. parsl/tests/manual_tests/test_memory_limits.py +6 -8
  166. parsl/tests/manual_tests/test_regression_220.py +2 -1
  167. parsl/tests/manual_tests/test_udp_simple.py +4 -4
  168. parsl/tests/manual_tests/test_worker_count.py +3 -2
  169. parsl/tests/scaling_tests/htex_local.py +2 -4
  170. parsl/tests/scaling_tests/test_scale.py +0 -9
  171. parsl/tests/scaling_tests/vineex_condor.py +1 -2
  172. parsl/tests/scaling_tests/vineex_local.py +1 -2
  173. parsl/tests/site_tests/site_config_selector.py +1 -6
  174. parsl/tests/site_tests/test_provider.py +4 -2
  175. parsl/tests/site_tests/test_site.py +2 -0
  176. parsl/tests/sites/test_affinity.py +7 -7
  177. parsl/tests/sites/test_dynamic_executor.py +3 -4
  178. parsl/tests/sites/test_ec2.py +3 -2
  179. parsl/tests/sites/test_worker_info.py +4 -5
  180. parsl/tests/test_aalst_patterns.py +0 -1
  181. parsl/tests/test_bash_apps/test_apptimeout.py +2 -2
  182. parsl/tests/test_bash_apps/test_basic.py +10 -4
  183. parsl/tests/test_bash_apps/test_error_codes.py +5 -7
  184. parsl/tests/test_bash_apps/test_inputs_default.py +25 -0
  185. parsl/tests/test_bash_apps/test_kwarg_storage.py +1 -1
  186. parsl/tests/test_bash_apps/test_memoize.py +2 -8
  187. parsl/tests/test_bash_apps/test_memoize_ignore_args.py +9 -14
  188. parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py +9 -14
  189. parsl/tests/test_bash_apps/test_multiline.py +1 -1
  190. parsl/tests/test_bash_apps/test_pipeline.py +1 -1
  191. parsl/tests/test_bash_apps/test_std_uri.py +123 -0
  192. parsl/tests/test_bash_apps/test_stdout.py +33 -8
  193. parsl/tests/test_callables.py +2 -2
  194. parsl/tests/test_checkpointing/test_periodic.py +21 -39
  195. parsl/tests/test_checkpointing/test_python_checkpoint_1.py +1 -0
  196. parsl/tests/test_checkpointing/test_python_checkpoint_2.py +2 -2
  197. parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -1
  198. parsl/tests/test_checkpointing/test_regression_239.py +1 -1
  199. parsl/tests/test_checkpointing/test_task_exit.py +2 -3
  200. parsl/tests/test_docs/test_from_slides.py +5 -2
  201. parsl/tests/test_docs/test_kwargs.py +4 -1
  202. parsl/tests/test_docs/test_tutorial_1.py +1 -2
  203. parsl/tests/test_docs/test_workflow1.py +2 -2
  204. parsl/tests/test_docs/test_workflow2.py +0 -1
  205. parsl/tests/test_error_handling/test_rand_fail.py +2 -2
  206. parsl/tests/test_error_handling/test_resource_spec.py +10 -12
  207. parsl/tests/test_error_handling/test_retries.py +6 -16
  208. parsl/tests/test_error_handling/test_retry_handler.py +1 -0
  209. parsl/tests/test_error_handling/test_retry_handler_failure.py +2 -1
  210. parsl/tests/test_error_handling/test_serialization_fail.py +1 -1
  211. parsl/tests/test_error_handling/test_wrap_with_logs.py +1 -0
  212. parsl/tests/test_execute_task.py +29 -0
  213. parsl/tests/test_flux.py +1 -1
  214. parsl/tests/test_htex/test_basic.py +2 -3
  215. parsl/tests/test_htex/test_block_manager_selector_unit.py +20 -0
  216. parsl/tests/test_htex/test_command_client_timeout.py +66 -0
  217. parsl/tests/test_htex/test_connected_blocks.py +3 -2
  218. parsl/tests/test_htex/test_cpu_affinity_explicit.py +6 -10
  219. parsl/tests/test_htex/test_disconnected_blocks.py +6 -5
  220. parsl/tests/test_htex/test_disconnected_blocks_failing_provider.py +71 -0
  221. parsl/tests/test_htex/test_drain.py +79 -0
  222. parsl/tests/test_htex/test_htex.py +51 -25
  223. parsl/tests/test_htex/test_manager_failure.py +0 -1
  224. parsl/tests/test_htex/test_manager_selector_by_block.py +51 -0
  225. parsl/tests/test_htex/test_managers_command.py +36 -0
  226. parsl/tests/test_htex/test_missing_worker.py +2 -12
  227. parsl/tests/test_htex/test_multiple_disconnected_blocks.py +9 -9
  228. parsl/tests/test_htex/test_resource_spec_validation.py +45 -0
  229. parsl/tests/test_htex/test_zmq_binding.py +29 -8
  230. parsl/tests/test_monitoring/test_app_names.py +86 -0
  231. parsl/tests/test_monitoring/test_basic.py +73 -25
  232. parsl/tests/test_monitoring/test_db_locks.py +6 -4
  233. parsl/tests/test_monitoring/test_fuzz_zmq.py +19 -8
  234. parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +80 -0
  235. parsl/tests/test_monitoring/test_incomplete_futures.py +5 -4
  236. parsl/tests/test_monitoring/test_memoization_representation.py +4 -2
  237. parsl/tests/test_monitoring/test_stdouterr.py +134 -0
  238. parsl/tests/test_monitoring/test_viz_colouring.py +1 -0
  239. parsl/tests/test_mpi_apps/test_bad_mpi_config.py +33 -26
  240. parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py +28 -11
  241. parsl/tests/test_mpi_apps/test_mpi_prefix.py +4 -4
  242. parsl/tests/test_mpi_apps/test_mpi_scheduler.py +7 -2
  243. parsl/tests/test_mpi_apps/test_mpiex.py +64 -0
  244. parsl/tests/test_mpi_apps/test_resource_spec.py +42 -49
  245. parsl/tests/test_providers/test_kubernetes_provider.py +102 -0
  246. parsl/tests/test_providers/test_local_provider.py +3 -132
  247. parsl/tests/test_providers/test_pbspro_template.py +2 -3
  248. parsl/tests/test_providers/test_slurm_template.py +2 -3
  249. parsl/tests/test_providers/test_submiterror_deprecation.py +2 -1
  250. parsl/tests/test_python_apps/test_context_manager.py +128 -0
  251. parsl/tests/test_python_apps/test_dep_standard_futures.py +2 -1
  252. parsl/tests/test_python_apps/test_dependencies_deep.py +59 -0
  253. parsl/tests/test_python_apps/test_fail.py +0 -25
  254. parsl/tests/test_python_apps/test_futures.py +2 -1
  255. parsl/tests/test_python_apps/test_inputs_default.py +22 -0
  256. parsl/tests/test_python_apps/test_join.py +0 -1
  257. parsl/tests/test_python_apps/test_lifted.py +11 -7
  258. parsl/tests/test_python_apps/test_memoize_bad_id_for_memo.py +1 -0
  259. parsl/tests/test_python_apps/test_outputs.py +1 -1
  260. parsl/tests/test_python_apps/test_pluggable_future_resolution.py +161 -0
  261. parsl/tests/test_radical/test_mpi_funcs.py +1 -2
  262. parsl/tests/test_regression/test_1480.py +2 -1
  263. parsl/tests/test_regression/test_1653.py +2 -1
  264. parsl/tests/test_regression/test_226.py +1 -0
  265. parsl/tests/test_regression/test_2652.py +1 -0
  266. parsl/tests/test_regression/test_69a.py +0 -1
  267. parsl/tests/test_regression/test_854.py +4 -2
  268. parsl/tests/test_regression/test_97_parallelism_0.py +1 -2
  269. parsl/tests/test_regression/test_98.py +0 -1
  270. parsl/tests/test_scaling/test_block_error_handler.py +9 -4
  271. parsl/tests/test_scaling/test_regression_1621.py +11 -15
  272. parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py +84 -0
  273. parsl/tests/test_scaling/test_regression_3696_oscillation.py +103 -0
  274. parsl/tests/test_scaling/test_scale_down.py +2 -5
  275. parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py +6 -18
  276. parsl/tests/test_scaling/test_scale_down_htex_unregistered.py +71 -0
  277. parsl/tests/test_scaling/test_shutdown_scalein.py +73 -0
  278. parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +90 -0
  279. parsl/tests/test_serialization/test_2555_caching_deserializer.py +1 -1
  280. parsl/tests/test_serialization/test_3495_deserialize_managerlost.py +47 -0
  281. parsl/tests/test_serialization/test_basic.py +2 -1
  282. parsl/tests/test_serialization/test_htex_code_cache.py +3 -4
  283. parsl/tests/test_serialization/test_pack_resource_spec.py +2 -1
  284. parsl/tests/test_serialization/test_proxystore_configured.py +10 -6
  285. parsl/tests/test_serialization/test_proxystore_impl.py +5 -3
  286. parsl/tests/test_shutdown/test_kill_monitoring.py +64 -0
  287. parsl/tests/test_staging/staging_provider.py +2 -2
  288. parsl/tests/test_staging/test_1316.py +3 -4
  289. parsl/tests/test_staging/test_docs_1.py +2 -1
  290. parsl/tests/test_staging/test_docs_2.py +2 -1
  291. parsl/tests/test_staging/test_elaborate_noop_file.py +2 -3
  292. parsl/tests/{test_data → test_staging}/test_file.py +6 -6
  293. parsl/tests/{test_data → test_staging}/test_output_chain_filenames.py +3 -0
  294. parsl/tests/test_staging/test_staging_ftp.py +1 -0
  295. parsl/tests/test_staging/test_staging_https.py +5 -2
  296. parsl/tests/test_staging/test_staging_stdout.py +64 -0
  297. parsl/tests/test_staging/test_zip_in.py +39 -0
  298. parsl/tests/test_staging/test_zip_out.py +110 -0
  299. parsl/tests/test_staging/test_zip_to_zip.py +41 -0
  300. parsl/tests/test_summary.py +2 -2
  301. parsl/tests/test_thread_parallelism.py +0 -1
  302. parsl/tests/test_threads/test_configs.py +1 -2
  303. parsl/tests/test_threads/test_lazy_errors.py +2 -2
  304. parsl/tests/test_utils/test_execute_wait.py +35 -0
  305. parsl/tests/test_utils/test_sanitize_dns.py +76 -0
  306. parsl/tests/unit/test_address.py +20 -0
  307. parsl/tests/unit/test_file.py +99 -0
  308. parsl/tests/unit/test_usage_tracking.py +66 -0
  309. parsl/usage_tracking/api.py +65 -0
  310. parsl/usage_tracking/levels.py +6 -0
  311. parsl/usage_tracking/usage.py +104 -62
  312. parsl/utils.py +139 -6
  313. parsl/version.py +1 -1
  314. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/exec_parsl_function.py +6 -5
  315. parsl-2025.1.13.data/scripts/interchange.py +649 -0
  316. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +115 -77
  317. parsl-2025.1.13.dist-info/METADATA +96 -0
  318. parsl-2025.1.13.dist-info/RECORD +462 -0
  319. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/WHEEL +1 -1
  320. parsl/channels/__init__.py +0 -7
  321. parsl/channels/base.py +0 -141
  322. parsl/channels/errors.py +0 -113
  323. parsl/channels/local/local.py +0 -164
  324. parsl/channels/oauth_ssh/oauth_ssh.py +0 -110
  325. parsl/channels/ssh/ssh.py +0 -276
  326. parsl/channels/ssh_il/__init__.py +0 -0
  327. parsl/channels/ssh_il/ssh_il.py +0 -74
  328. parsl/configs/ad_hoc.py +0 -35
  329. parsl/executors/radical/rpex_master.py +0 -42
  330. parsl/monitoring/radios.py +0 -175
  331. parsl/providers/ad_hoc/__init__.py +0 -0
  332. parsl/providers/ad_hoc/ad_hoc.py +0 -248
  333. parsl/providers/cobalt/__init__.py +0 -0
  334. parsl/providers/cobalt/cobalt.py +0 -236
  335. parsl/providers/cobalt/template.py +0 -17
  336. parsl/tests/configs/ad_hoc_cluster_htex.py +0 -35
  337. parsl/tests/configs/cooley_htex.py +0 -37
  338. parsl/tests/configs/htex_ad_hoc_cluster.py +0 -28
  339. parsl/tests/configs/local_adhoc.py +0 -18
  340. parsl/tests/configs/swan_htex.py +0 -43
  341. parsl/tests/configs/theta.py +0 -37
  342. parsl/tests/integration/test_channels/__init__.py +0 -0
  343. parsl/tests/integration/test_channels/test_channels.py +0 -17
  344. parsl/tests/integration/test_channels/test_local_channel.py +0 -42
  345. parsl/tests/integration/test_channels/test_scp_1.py +0 -45
  346. parsl/tests/integration/test_channels/test_ssh_1.py +0 -40
  347. parsl/tests/integration/test_channels/test_ssh_errors.py +0 -46
  348. parsl/tests/integration/test_channels/test_ssh_file_transport.py +0 -41
  349. parsl/tests/integration/test_channels/test_ssh_interactive.py +0 -24
  350. parsl/tests/manual_tests/test_ad_hoc_htex.py +0 -48
  351. parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +0 -88
  352. parsl/tests/manual_tests/test_oauth_ssh.py +0 -13
  353. parsl/tests/sites/test_local_adhoc.py +0 -61
  354. parsl/tests/test_channels/__init__.py +0 -0
  355. parsl/tests/test_channels/test_large_output.py +0 -22
  356. parsl/tests/test_data/__init__.py +0 -0
  357. parsl/tests/test_mpi_apps/test_mpi_mode_disabled.py +0 -51
  358. parsl/tests/test_providers/test_cobalt_deprecation_warning.py +0 -16
  359. parsl-2024.3.11.dist-info/METADATA +0 -98
  360. parsl-2024.3.11.dist-info/RECORD +0 -447
  361. parsl/{channels/local → monitoring/radios}/__init__.py +0 -0
  362. parsl/{channels/oauth_ssh → tests/test_shutdown}/__init__.py +0 -0
  363. parsl/tests/{test_data → test_staging}/test_file_apps.py +0 -0
  364. parsl/tests/{test_data → test_staging}/test_file_staging.py +0 -0
  365. parsl/{channels/ssh → tests/unit}/__init__.py +0 -0
  366. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
  367. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
  368. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
  369. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import pytest
2
2
 
3
- from parsl import python_app, File
3
+ from parsl import File, python_app
4
4
 
5
5
 
6
6
  @python_app
@@ -12,6 +12,7 @@ def convert(inputs=[], outputs=[]):
12
12
 
13
13
 
14
14
  @pytest.mark.cleannet
15
+ @pytest.mark.staging_required
15
16
  def test():
16
17
  # create an remote Parsl file
17
18
  inp = File('ftp://ftp.iana.org/pub/mirror/rirstats/arin/ARIN-STATS-FORMAT-CHANGE.txt')
@@ -1,5 +1,6 @@
1
1
  import pytest
2
- from parsl import bash_app, File
2
+
3
+ from parsl import File, bash_app
3
4
  from parsl.tests.configs.local_threads import fresh_config as local_config
4
5
 
5
6
 
@@ -6,15 +6,15 @@
6
6
  # of the globus staging provider
7
7
 
8
8
  import logging
9
+
9
10
  import pytest
10
11
 
11
12
  import parsl
12
-
13
13
  from parsl import bash_app, python_app
14
14
  from parsl.config import Config
15
15
  from parsl.data_provider.files import File
16
16
  from parsl.executors.threads import ThreadPoolExecutor
17
- from parsl.tests.test_staging.staging_provider import NoOpTestingFileStaging, NoOpError
17
+ from parsl.tests.test_staging.staging_provider import NoOpError, NoOpTestingFileStaging
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
@@ -44,7 +44,6 @@ def storage_access_parsl():
44
44
  yield _setup_config
45
45
 
46
46
  parsl.dfk().cleanup()
47
- parsl.clear()
48
47
 
49
48
 
50
49
  @pytest.mark.local
@@ -22,11 +22,11 @@ def test_files():
22
22
 
23
23
 
24
24
  @pytest.mark.local
25
- def test_open():
26
- with open('test-open.txt', 'w') as tfile:
27
- tfile.write('Hello')
25
+ def test_open(tmpd_cwd):
26
+ fpath = tmpd_cwd / 'test-open.txt'
27
+ fpath.write_text('Hello')
28
28
 
29
- pfile = File('test-open.txt')
29
+ pfile = File(fpath)
30
30
 
31
- with open(str(pfile), 'r') as opfile:
32
- assert (opfile.readlines()[0] == 'Hello')
31
+ with open(pfile) as opfile:
32
+ assert (opfile.read() == 'Hello')
@@ -1,5 +1,7 @@
1
1
  from concurrent.futures import Future
2
2
 
3
+ import pytest
4
+
3
5
  from parsl import File
4
6
  from parsl.app.app import bash_app
5
7
 
@@ -14,6 +16,7 @@ def app2(inputs=(), outputs=(), stdout=None, stderr=None, mock=False):
14
16
  return f"echo '{inputs[0]}' > {outputs[0]}"
15
17
 
16
18
 
19
+ @pytest.mark.shared_fs
17
20
  def test_behavior(tmpd_cwd):
18
21
  expected_path = str(tmpd_cwd / "simple-out.txt")
19
22
  app1_future = app1(
@@ -15,6 +15,7 @@ def sort_strings(inputs=[], outputs=[]):
15
15
 
16
16
 
17
17
  @pytest.mark.cleannet
18
+ @pytest.mark.staging_required
18
19
  def test_staging_ftp():
19
20
  """Test staging for an ftp file
20
21
 
@@ -1,9 +1,9 @@
1
+ import pytest
2
+
1
3
  import parsl
2
4
  from parsl.app.app import python_app
3
5
  from parsl.data_provider.files import File
4
6
 
5
- import pytest
6
-
7
7
  # This config is for the local test which will adding an executor.
8
8
  # Most tests in this file should be non-local and use the configuration
9
9
  # specificed with --config, not this one.
@@ -48,6 +48,7 @@ def sort_strings_additional_executor(inputs=(), outputs=()):
48
48
 
49
49
 
50
50
  @pytest.mark.cleannet
51
+ @pytest.mark.staging_required
51
52
  def test_staging_https_cleannet(tmpd_cwd):
52
53
  unsorted_file = File(_unsorted_url)
53
54
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -68,6 +69,7 @@ def test_staging_https_local(tmpd_cwd):
68
69
 
69
70
 
70
71
  @pytest.mark.cleannet
72
+ @pytest.mark.staging_required
71
73
  def test_staging_https_kwargs(tmpd_cwd):
72
74
  unsorted_file = File(_unsorted_url)
73
75
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -78,6 +80,7 @@ def test_staging_https_kwargs(tmpd_cwd):
78
80
 
79
81
 
80
82
  @pytest.mark.cleannet
83
+ @pytest.mark.staging_required
81
84
  def test_staging_https_args(tmpd_cwd):
82
85
  unsorted_file = File(_unsorted_url)
83
86
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -0,0 +1,64 @@
1
+ import logging
2
+ import os
3
+ import zipfile
4
+
5
+ import pytest
6
+
7
+ import parsl
8
+ from parsl.app.futures import DataFuture
9
+ from parsl.data_provider.files import File
10
+ from parsl.tests.configs.htex_local import fresh_config as local_config
11
+
12
+
13
+ @parsl.bash_app
14
+ def output_to_stds(*, stdout=parsl.AUTO_LOGNAME, stderr=parsl.AUTO_LOGNAME):
15
+ return "echo hello ; echo goodbye >&2"
16
+
17
+
18
+ @pytest.mark.staging_required
19
+ def test_stdout_staging_file(tmpd_cwd, caplog):
20
+ basename = str(tmpd_cwd) + "/stdout.txt"
21
+ stdout_file = File("file://" + basename)
22
+
23
+ app_future = output_to_stds(stdout=stdout_file)
24
+
25
+ assert isinstance(app_future.stdout, DataFuture)
26
+ app_future.stdout.result()
27
+
28
+ assert os.path.exists(basename)
29
+
30
+ for record in caplog.records:
31
+ assert record.levelno < logging.ERROR
32
+
33
+
34
+ @pytest.mark.staging_required
35
+ def test_stdout_stderr_staging_zip(tmpd_cwd, caplog):
36
+ zipfile_name = str(tmpd_cwd) + "/staging.zip"
37
+ stdout_relative_path = "somewhere/test-out.txt"
38
+ stdout_file = File("zip:" + zipfile_name + "/" + stdout_relative_path)
39
+
40
+ stderr_relative_path = "somewhere/test-error.txt"
41
+ stderr_file = File("zip:" + zipfile_name + "/" + stderr_relative_path)
42
+
43
+ app_future = output_to_stds(stdout=stdout_file, stderr=stderr_file)
44
+
45
+ assert isinstance(app_future.stdout, DataFuture)
46
+ app_future.stdout.result()
47
+
48
+ # check the file exists as soon as possible
49
+ assert os.path.exists(zipfile_name)
50
+ with zipfile.ZipFile(zipfile_name) as z:
51
+ with z.open(stdout_relative_path) as f:
52
+ assert f.readlines() == [b'hello\n']
53
+
54
+ assert isinstance(app_future.stderr, DataFuture)
55
+ app_future.stderr.result()
56
+ with zipfile.ZipFile(zipfile_name) as z:
57
+ with z.open(stderr_relative_path) as f:
58
+ # The last line of stderr should be goodbye, but Parsl will write
59
+ # other Parsl-specific into to stderr before that, so only assert
60
+ # the behaviour of the final line.
61
+ assert f.readlines()[-1] == b'goodbye\n'
62
+
63
+ for record in caplog.records:
64
+ assert record.levelno < logging.ERROR
@@ -0,0 +1,39 @@
1
+ import random
2
+ import zipfile
3
+
4
+ import pytest
5
+
6
+ import parsl
7
+ from parsl.config import Config
8
+ from parsl.data_provider.files import File
9
+ from parsl.data_provider.zip import ZipAuthorityError, ZipFileStaging
10
+ from parsl.executors import HighThroughputExecutor
11
+ from parsl.launchers import SimpleLauncher
12
+ from parsl.providers import LocalProvider
13
+ from parsl.tests.configs.htex_local import fresh_config as local_config
14
+
15
+
16
+ @parsl.python_app
17
+ def count_lines(file):
18
+ with open(file, "r") as f:
19
+ return len(f.readlines())
20
+
21
+
22
+ @pytest.mark.local
23
+ def test_zip_in(tmpd_cwd):
24
+ # basic test of zip file stage-in
25
+ zip_path = tmpd_cwd / "container.zip"
26
+ file_base = "data.txt"
27
+ zip_file = File(f"zip:{zip_path / file_base}")
28
+
29
+ # create a zip file containing one file with some abitrary number of lines
30
+ n_lines = random.randint(0, 1000)
31
+
32
+ with zipfile.ZipFile(zip_path, mode='w') as z:
33
+ with z.open(file_base, mode='w') as f:
34
+ for _ in range(n_lines):
35
+ f.write(b'someline\n')
36
+
37
+ app_future = count_lines(zip_file)
38
+
39
+ assert app_future.result() == n_lines
@@ -0,0 +1,110 @@
1
+ import zipfile
2
+
3
+ import pytest
4
+
5
+ import parsl
6
+ from parsl.config import Config
7
+ from parsl.data_provider.data_manager import default_staging
8
+ from parsl.data_provider.files import File
9
+ from parsl.data_provider.zip import ZipAuthorityError, ZipFileStaging
10
+ from parsl.executors import HighThroughputExecutor
11
+ from parsl.launchers import SimpleLauncher
12
+ from parsl.providers import LocalProvider
13
+ from parsl.tests.configs.htex_local import fresh_config as local_config
14
+
15
+
16
+ @pytest.mark.local
17
+ def test_zip_path_split():
18
+ from parsl.data_provider.zip import zip_path_split
19
+ assert zip_path_split("/tmp/foo/this.zip/inside/here.txt") == ("/tmp/foo/this.zip", "inside/here.txt")
20
+
21
+
22
+ @parsl.bash_app
23
+ def output_something(outputs=()):
24
+ """This should output something into every specified output file:
25
+ the position in the output sequence will be written into the
26
+ corresponding output file.
27
+ """
28
+ cmds = []
29
+ for n in range(len(outputs)):
30
+ cmds.append(f"echo {n} > {outputs[n]}")
31
+
32
+ return "; ".join(cmds)
33
+
34
+
35
+ @pytest.mark.local
36
+ def test_zip_out(tmpd_cwd):
37
+ # basic test of zip file stage-out
38
+ zip_path = tmpd_cwd / "container.zip"
39
+ file_base = "data.txt"
40
+ of = File(f"zip:{zip_path / file_base}")
41
+
42
+ app_future = output_something(outputs=[of])
43
+ output_file_future = app_future.outputs[0]
44
+
45
+ app_future.result()
46
+ output_file_future.result()
47
+
48
+ assert zipfile.is_zipfile(zip_path)
49
+
50
+ with zipfile.ZipFile(zip_path) as z:
51
+ assert file_base in z.namelist()
52
+ assert len(z.namelist()) == 1
53
+ with z.open(file_base) as f:
54
+ assert f.readlines() == [b'0\n']
55
+
56
+
57
+ @pytest.mark.local
58
+ def test_zip_out_multi(tmpd_cwd):
59
+ # tests multiple files, multiple zip files and multiple
60
+ # sub-paths
61
+
62
+ zip_path_1 = tmpd_cwd / "container1.zip"
63
+ zip_path_2 = tmpd_cwd / "container2.zip"
64
+
65
+ relative_file_path_1 = "a/b/c/data.txt"
66
+ relative_file_path_2 = "something.txt"
67
+ relative_file_path_3 = "a/d/other.txt"
68
+ of1 = File(f"zip:{zip_path_1 / relative_file_path_1}")
69
+ of2 = File(f"zip:{zip_path_1 / relative_file_path_2}")
70
+ of3 = File(f"zip:{zip_path_2 / relative_file_path_3}")
71
+
72
+ app_future = output_something(outputs=[of1, of2, of3])
73
+
74
+ for f in app_future.outputs:
75
+ f.result()
76
+
77
+ app_future.result()
78
+
79
+ assert zipfile.is_zipfile(zip_path_1)
80
+
81
+ with zipfile.ZipFile(zip_path_1) as z:
82
+ assert relative_file_path_1 in z.namelist()
83
+ assert relative_file_path_2 in z.namelist()
84
+ assert len(z.namelist()) == 2
85
+ with z.open(relative_file_path_1) as f:
86
+ assert f.readlines() == [b'0\n']
87
+ with z.open(relative_file_path_2) as f:
88
+ assert f.readlines() == [b'1\n']
89
+
90
+ assert zipfile.is_zipfile(zip_path_2)
91
+
92
+ with zipfile.ZipFile(zip_path_2) as z:
93
+ assert relative_file_path_3 in z.namelist()
94
+ assert len(z.namelist()) == 1
95
+ with z.open(relative_file_path_3) as f:
96
+ assert f.readlines() == [b'2\n']
97
+
98
+
99
+ @pytest.mark.local
100
+ def test_zip_bad_authority(tmpd_cwd):
101
+ # tests that there's an exception when staging a ZIP url with an authority
102
+ # section specified, rather than silently ignoring it. This simulates a
103
+ # user who misunderstands what that piece of what a zip: URL means.
104
+
105
+ zip_path = tmpd_cwd / "container.zip"
106
+ file_base = "data.txt"
107
+ of = File(f"zip://someauthority/{zip_path / file_base}")
108
+
109
+ with pytest.raises(ZipAuthorityError):
110
+ output_something(outputs=[of])
@@ -0,0 +1,41 @@
1
+ import random
2
+ import zipfile
3
+
4
+ import pytest
5
+
6
+ import parsl
7
+ from parsl.config import Config
8
+ from parsl.data_provider.files import File
9
+ from parsl.data_provider.zip import ZipAuthorityError, ZipFileStaging
10
+ from parsl.executors import HighThroughputExecutor
11
+ from parsl.launchers import SimpleLauncher
12
+ from parsl.providers import LocalProvider
13
+ from parsl.tests.configs.htex_local import fresh_config as local_config
14
+
15
+
16
+ @parsl.python_app
17
+ def generate_lines(n: int, *, outputs):
18
+ with open(outputs[0], "w") as f:
19
+ for x in range(n):
20
+ # write numbered lines
21
+ f.write(str(x) + "\n")
22
+
23
+
24
+ @parsl.python_app
25
+ def count_lines(file):
26
+ with open(file, "r") as f:
27
+ return len(f.readlines())
28
+
29
+
30
+ @pytest.mark.local
31
+ def test_zip_pipeline(tmpd_cwd):
32
+ # basic test of zip file stage-in
33
+ zip_path = tmpd_cwd / "container.zip"
34
+ file_base = "data.txt"
35
+ zip_file = File(f"zip:{zip_path / file_base}")
36
+
37
+ n_lines = random.randint(0, 1000)
38
+ generate_fut = generate_lines(n_lines, outputs=[zip_file])
39
+ n_lines_out = count_lines(generate_fut.outputs[0]).result()
40
+
41
+ assert n_lines == n_lines_out
@@ -1,5 +1,6 @@
1
- import parsl
2
1
  import pytest
2
+
3
+ import parsl
3
4
  from parsl.tests.configs.local_threads import fresh_config
4
5
 
5
6
 
@@ -22,7 +23,6 @@ def test_summary(caplog):
22
23
  fail().exception()
23
24
 
24
25
  parsl.dfk().cleanup()
25
- parsl.clear()
26
26
 
27
27
  assert "Summary of tasks in DFK:" in caplog.text
28
28
  assert "Tasks in state States.exec_done: 1" in caplog.text
@@ -5,7 +5,6 @@ import pytest
5
5
  from parsl.app.app import bash_app, python_app
6
6
  from parsl.tests.configs.local_threads import config
7
7
 
8
-
9
8
  local_config = config
10
9
 
11
10
 
@@ -7,9 +7,9 @@ from parsl.tests.configs.local_threads import fresh_config
7
7
 
8
8
  @python_app
9
9
  def worker_identify(x, sleep_dur=0.2):
10
- import time
11
10
  import os
12
11
  import threading
12
+ import time
13
13
  time.sleep(sleep_dur)
14
14
  return {"pid": os.getpid(),
15
15
  "tid": threading.current_thread()}
@@ -30,5 +30,4 @@ def test_parallel_for():
30
30
  assert thread_count <= config.executors[0].max_threads, "More threads than allowed"
31
31
  assert process_count == 1, "More processes than allowed"
32
32
  dfk.cleanup()
33
- parsl.clear()
34
33
  return d
@@ -1,5 +1,6 @@
1
- import parsl
2
1
  import pytest
2
+
3
+ import parsl
3
4
  from parsl import python_app
4
5
  from parsl.tests.configs.local_threads import fresh_config
5
6
 
@@ -24,5 +25,4 @@ def test_lazy_behavior():
24
25
  assert f.done()
25
26
 
26
27
  parsl.dfk().cleanup()
27
- parsl.clear()
28
28
  return
@@ -0,0 +1,35 @@
1
+ import pytest
2
+
3
+ from parsl.utils import execute_wait
4
+
5
+
6
+ @pytest.mark.local
7
+ def test_env():
8
+ ''' Regression testing for issue #27
9
+ '''
10
+
11
+ rc, stdout, stderr = execute_wait("env", 1)
12
+
13
+ stdout = stdout.split('\n')
14
+ x = [s for s in stdout if s.startswith("PATH=")]
15
+ assert x, "PATH not found"
16
+
17
+ x = [s for s in stdout if s.startswith("HOME=")]
18
+ assert x, "HOME not found"
19
+
20
+
21
+ @pytest.mark.local
22
+ def test_large_output_2210():
23
+ """Regression test for #2210.
24
+ execute_wait was hanging if the specified command gave too
25
+ much output, due to a race condition between process exiting and
26
+ pipes filling up.
27
+ """
28
+
29
+ # this will output 128kb of stdout
30
+ execute_wait("yes | dd count=128 bs=1024", walltime=60)
31
+
32
+ # if this test fails, execute_wait should raise a timeout
33
+ # exception.
34
+
35
+ # The contents out the output is not verified by this test
@@ -0,0 +1,76 @@
1
+ import random
2
+ import re
3
+
4
+ import pytest
5
+
6
+ from parsl.utils import sanitize_dns_label_rfc1123, sanitize_dns_subdomain_rfc1123
7
+
8
+ # Ref: https://datatracker.ietf.org/doc/html/rfc1123
9
+ DNS_LABEL_REGEX = r'^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$'
10
+ DNS_SUBDOMAIN_REGEX = r'^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(\.[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)*$'
11
+
12
+ test_labels = [
13
+ "example-label-123", # Valid label
14
+ "EXAMPLE", # Case sensitivity
15
+ "!@#example*", # Remove invalid characters
16
+ "--leading-and-trailing--", # Leading and trailing hyphens
17
+ "..leading.and.trailing..", # Leading and tailing dots
18
+ "multiple..dots", # Consecutive dots
19
+ "valid--label", # Consecutive hyphens
20
+ "a" * random.randint(64, 70), # Longer than 63 characters
21
+ f"{'a' * 62}-a", # Trailing hyphen at max length
22
+ ]
23
+
24
+
25
+ def _generate_test_subdomains(num_subdomains: int):
26
+ subdomains = []
27
+ for _ in range(num_subdomains):
28
+ num_labels = random.randint(1, 5)
29
+ labels = [test_labels[random.randint(0, num_labels - 1)] for _ in range(num_labels)]
30
+ subdomain = ".".join(labels)
31
+ subdomains.append(subdomain)
32
+ return subdomains
33
+
34
+
35
+ @pytest.mark.local
36
+ @pytest.mark.parametrize("raw_string", test_labels)
37
+ def test_sanitize_dns_label_rfc1123(raw_string: str):
38
+ print(sanitize_dns_label_rfc1123(raw_string))
39
+ assert re.match(DNS_LABEL_REGEX, sanitize_dns_label_rfc1123(raw_string))
40
+
41
+
42
+ @pytest.mark.local
43
+ @pytest.mark.parametrize("raw_string", ("", "-", "@", "$$$"))
44
+ def test_sanitize_dns_label_rfc1123_empty(raw_string: str):
45
+ with pytest.raises(ValueError) as e_info:
46
+ sanitize_dns_label_rfc1123(raw_string)
47
+ assert str(e_info.value) == f"Sanitized DNS label is empty for input '{raw_string}'"
48
+
49
+
50
+ @pytest.mark.local
51
+ @pytest.mark.parametrize("raw_string", _generate_test_subdomains(10))
52
+ def test_sanitize_dns_subdomain_rfc1123(raw_string: str):
53
+ assert re.match(DNS_SUBDOMAIN_REGEX, sanitize_dns_subdomain_rfc1123(raw_string))
54
+
55
+
56
+ @pytest.mark.local
57
+ @pytest.mark.parametrize("char", ("-", "."))
58
+ def test_sanitize_dns_subdomain_rfc1123_trailing_non_alphanumeric_at_max_length(char: str):
59
+ raw_string = (f"{'a' * 61}." * 4) + f".aaaa{char}a"
60
+ assert re.match(DNS_SUBDOMAIN_REGEX, sanitize_dns_subdomain_rfc1123(raw_string))
61
+
62
+
63
+ @pytest.mark.local
64
+ @pytest.mark.parametrize("raw_string", ("", ".", "..."))
65
+ def test_sanitize_dns_subdomain_rfc1123_empty(raw_string: str):
66
+ with pytest.raises(ValueError) as e_info:
67
+ sanitize_dns_subdomain_rfc1123(raw_string)
68
+ assert str(e_info.value) == f"Sanitized DNS subdomain is empty for input '{raw_string}'"
69
+
70
+
71
+ @pytest.mark.local
72
+ @pytest.mark.parametrize(
73
+ "raw_string", ("a" * 253, "a" * random.randint(254, 300)), ids=("254 chars", ">253 chars")
74
+ )
75
+ def test_sanitize_dns_subdomain_rfc1123_max_length(raw_string: str):
76
+ assert len(sanitize_dns_subdomain_rfc1123(raw_string)) <= 253
@@ -0,0 +1,20 @@
1
+ import pytest
2
+
3
+ from parsl.addresses import tcp_url
4
+
5
+
6
+ @pytest.mark.local
7
+ @pytest.mark.parametrize("address, port,expected", [
8
+ ("127.0.0.1", 55001, "tcp://127.0.0.1:55001"),
9
+ ("127.0.0.1", "55001", "tcp://127.0.0.1:55001"),
10
+ ("127.0.0.1", None, "tcp://127.0.0.1"),
11
+ ("::1", "55001", "tcp://[::1]:55001"),
12
+ ("::ffff:127.0.0.1", 55001, "tcp://[::ffff:127.0.0.1]:55001"),
13
+ ("::ffff:127.0.0.1", None, "tcp://::ffff:127.0.0.1"),
14
+ ("::ffff:127.0.0.1", None, "tcp://::ffff:127.0.0.1"),
15
+ ("*", None, "tcp://*"),
16
+ ])
17
+ def test_tcp_url(address, port, expected):
18
+ """Confirm valid address generation"""
19
+ result = tcp_url(address, port)
20
+ assert result == expected
@@ -0,0 +1,99 @@
1
+ import os
2
+ from unittest import mock
3
+
4
+ import pytest
5
+
6
+ from parsl import File
7
+
8
+ _MOCK_BASE = "parsl.data_provider.files."
9
+
10
+
11
+ @pytest.mark.local
12
+ @pytest.mark.parametrize("scheme", ("http", "https", "ftp", "ftps", "asdfasdf"))
13
+ def test_file_init_scheme(scheme):
14
+ basename = "some_base_name"
15
+ path = f"/some/path/1/2/3/{basename}"
16
+ fqdn = "some.fqdn.example.com"
17
+ exp_url = f"{scheme}://{fqdn}{path}"
18
+ f = File(exp_url)
19
+ assert f.url == exp_url, "Expected given url to be stored"
20
+ assert f.scheme == scheme
21
+ assert f.netloc == fqdn
22
+ assert f.path == path
23
+ assert f.filename == basename
24
+ assert f.local_path is None, "Expect only set by API consumer, not constructor"
25
+
26
+
27
+ @pytest.mark.local
28
+ @pytest.mark.parametrize("url", ("some weird :// url", "", "a"))
29
+ def test_file_init_file_url_fallback(url):
30
+ exp_url = "some weird :// url"
31
+ f = File(exp_url)
32
+ assert f.url == exp_url
33
+ assert not f.netloc, "invalid host, should be no netloc"
34
+ assert f.path == exp_url, "Should fail to fully parse, so path is whole url"
35
+ assert f.filename == exp_url.rsplit("/", 1)[-1]
36
+
37
+ assert f.scheme == "file"
38
+
39
+
40
+ @pytest.mark.local
41
+ def test_file_proxies_for_filepath(randomstring):
42
+ # verify (current) expected internal hookup
43
+ exp_filepath = randomstring()
44
+ with mock.patch(
45
+ f"{_MOCK_BASE}File.filepath", new_callable=mock.PropertyMock
46
+ ) as mock_fpath:
47
+ mock_fpath.return_value = exp_filepath
48
+ f = File("")
49
+ assert str(f) == exp_filepath
50
+ assert os.fspath(f) == exp_filepath
51
+
52
+
53
+ @pytest.mark.local
54
+ @pytest.mark.parametrize("scheme", ("file://", ""))
55
+ def test_file_filepath_local_path_is_priority(scheme, randomstring):
56
+ exp_path = "/some/local/path"
57
+ url = f"{scheme}{exp_path}"
58
+ f = File(url)
59
+
60
+ f.local_path = randomstring()
61
+ assert f.filepath == f.local_path
62
+
63
+ f.local_path = None
64
+ assert f.filepath == exp_path
65
+
66
+
67
+ @pytest.mark.local
68
+ def test_file_filepath_requires_local_accessible_path():
69
+ with pytest.raises(ValueError) as pyt_exc:
70
+ _ = File("http://").filepath
71
+
72
+ assert "No local_path" in str(pyt_exc.value), "Expected reason in exception"
73
+
74
+
75
+ @pytest.mark.local
76
+ @pytest.mark.parametrize("scheme", ("https", "ftps", "", "file", "asdfasdf"))
77
+ def test_file_repr(scheme):
78
+ netloc = "some.netloc"
79
+ filename = "some_file_name"
80
+ path = f"/some/path/{filename}"
81
+ if scheme:
82
+ url = f"{scheme}://{netloc}{path}"
83
+ else:
84
+ scheme = "file"
85
+ url = path
86
+
87
+ f = File(url)
88
+ r = repr(f)
89
+ assert r.startswith("<")
90
+ assert r.endswith(">")
91
+ assert f"<{type(f).__name__} " in r
92
+ assert f" at 0x{id(f):x}" in r
93
+ assert f" url={url}" in r
94
+ assert f" scheme={scheme}" in r
95
+ assert f" path={path}" in r
96
+ assert f" filename={filename}" in r
97
+
98
+ if scheme != "file":
99
+ assert f" netloc={netloc}" in r