parsl 2024.3.18__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 +26 -6
  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 +53 -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 +259 -223
  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 +307 -285
  57. parsl/executors/high_throughput/interchange.py +137 -168
  58. parsl/executors/high_throughput/manager_record.py +4 -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 +77 -75
  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 +38 -55
  75. parsl/executors/taskvine/factory.py +1 -1
  76. parsl/executors/taskvine/factory_config.py +1 -1
  77. parsl/executors/taskvine/manager.py +17 -13
  78. parsl/executors/taskvine/manager_config.py +7 -2
  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 +28 -112
  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 +0 -6
  91. parsl/log_utils.py +1 -2
  92. parsl/monitoring/db_manager.py +55 -93
  93. parsl/monitoring/errors.py +6 -0
  94. parsl/monitoring/monitoring.py +85 -311
  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 +8 -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 -9
  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 +3 -9
  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 +0 -7
  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 +11 -10
  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 +5 -5
  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 +5 -8
  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 +137 -4
  313. parsl/version.py +1 -1
  314. {parsl-2024.3.18.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.18.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +77 -75
  317. parsl-2025.1.13.dist-info/METADATA +96 -0
  318. parsl-2025.1.13.dist-info/RECORD +462 -0
  319. {parsl-2024.3.18.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.18.dist-info/METADATA +0 -98
  360. parsl-2024.3.18.dist-info/RECORD +0 -449
  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.18.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
  367. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
  368. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
  369. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -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
@@ -0,0 +1,66 @@
1
+ """Test usage_tracking values."""
2
+
3
+ import pytest
4
+
5
+ import parsl
6
+ from parsl.config import Config
7
+ from parsl.errors import ConfigurationError
8
+
9
+
10
+ @pytest.mark.local
11
+ def test_config_load():
12
+ """Test loading a config with usage tracking."""
13
+ with parsl.load(Config(usage_tracking=3)):
14
+ pass
15
+ parsl.clear()
16
+
17
+
18
+ @pytest.mark.local
19
+ @pytest.mark.parametrize("level", (0, 1, 2, 3, False, True))
20
+ def test_valid(level):
21
+ """Test valid usage_tracking values."""
22
+ Config(usage_tracking=level)
23
+ assert Config(usage_tracking=level).usage_tracking == level
24
+
25
+
26
+ @pytest.mark.local
27
+ @pytest.mark.parametrize("level", (12, 1000, -1))
28
+ def test_invalid_values(level):
29
+ """Test invalid usage_tracking values."""
30
+ with pytest.raises(ConfigurationError):
31
+ Config(usage_tracking=level)
32
+
33
+
34
+ @pytest.mark.local
35
+ @pytest.mark.parametrize("level", ("abcd", None, bytes(1), 1.0, 1j, object()))
36
+ def test_invalid_types(level):
37
+ """Test invalid usage_tracking types."""
38
+ with pytest.raises(Exception) as ex:
39
+ Config(usage_tracking=level)
40
+
41
+ # with typeguard 4.x this is TypeCheckError,
42
+ # with typeguard 2.x this is TypeError
43
+ # we can't instantiate TypeCheckError if we're in typeguard 2.x environment
44
+ # because it does not exist... so check name using strings.
45
+ assert ex.type.__name__ in ["TypeCheckError", "TypeError"]
46
+
47
+
48
+ @pytest.mark.local
49
+ def test_valid_project_name():
50
+ """Test valid project_name."""
51
+ assert (
52
+ Config(
53
+ usage_tracking=3,
54
+ project_name="unit-test",
55
+ ).project_name == "unit-test"
56
+ )
57
+
58
+
59
+ @pytest.mark.local
60
+ @pytest.mark.parametrize("name", (1, 1.0, True, object()))
61
+ def test_invalid_project_name(name):
62
+ """Test invalid project_name."""
63
+ with pytest.raises(Exception) as ex:
64
+ Config(usage_tracking=3, project_name=name)
65
+
66
+ assert ex.type.__name__ in ["TypeCheckError", "TypeError"]
@@ -0,0 +1,65 @@
1
+ import inspect
2
+ from abc import abstractmethod
3
+ from functools import singledispatch
4
+ from typing import Any, List, Sequence
5
+
6
+ from parsl.utils import RepresentationMixin
7
+
8
+ # Traverse the configuration hierarchy, returning a JSON component
9
+ # for each one. Configuration components which implement
10
+ # RepresentationMixin will be in the right form for inspecting
11
+ # object attributes. Configuration components which are lists or tuples
12
+ # are traversed in sequence. Other types default to reporting no
13
+ # usage information.
14
+
15
+
16
+ @singledispatch
17
+ def get_parsl_usage(obj) -> List[Any]:
18
+ return []
19
+
20
+
21
+ @get_parsl_usage.register
22
+ def get_parsl_usage_representation_mixin(obj: RepresentationMixin) -> List[Any]:
23
+ t = type(obj)
24
+ qualified_name = t.__module__ + "." + t.__name__
25
+
26
+ # me can contain anything that can be rendered as JSON
27
+ me: List[Any] = []
28
+
29
+ if isinstance(obj, UsageInformation):
30
+ # report rich usage information for this component
31
+ attrs = {'c': qualified_name}
32
+ attrs.update(obj.get_usage_information())
33
+ me = [attrs]
34
+ else:
35
+ # report the class name of this component
36
+ me = [qualified_name]
37
+
38
+ # unwrap typeguard-style unwrapping
39
+ init: Any = type(obj).__init__
40
+ if hasattr(init, '__wrapped__'):
41
+ init = init.__wrapped__
42
+
43
+ argspec = inspect.getfullargspec(init)
44
+
45
+ for arg in argspec.args[1:]: # skip first arg, self
46
+ arg_value = getattr(obj, arg)
47
+ d = get_parsl_usage(arg_value)
48
+ me += d
49
+
50
+ return me
51
+
52
+
53
+ @get_parsl_usage.register(list)
54
+ @get_parsl_usage.register(tuple)
55
+ def get_parsl_usage_sequence(obj: Sequence) -> List[Any]:
56
+ result = []
57
+ for v in obj:
58
+ result += get_parsl_usage(v)
59
+ return result
60
+
61
+
62
+ class UsageInformation:
63
+ @abstractmethod
64
+ def get_usage_information(self) -> dict:
65
+ pass
@@ -0,0 +1,6 @@
1
+ """Module for defining the usage tracking levels."""
2
+
3
+ DISABLED = 0 # Tracking is disabled
4
+ LEVEL_1 = 1 # Share info about Parsl version, Python version, platform
5
+ LEVEL_2 = 2 # Share info about config + level 1
6
+ LEVEL_3 = 3 # Share info about app count, app fails, execution time + level 2