maidr 1.4.10__tar.gz → 1.6.0__tar.gz

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 (156) hide show
  1. {maidr-1.4.10 → maidr-1.6.0}/CHANGELOG.md +23 -0
  2. {maidr-1.4.10 → maidr-1.6.0}/PKG-INFO +3 -1
  3. maidr-1.6.0/example/flask/test_flask_app.py +71 -0
  4. {maidr-1.4.10 → maidr-1.6.0}/maidr/__init__.py +1 -1
  5. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/maidr.py +45 -2
  6. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/environment.py +127 -0
  7. {maidr-1.4.10 → maidr-1.6.0}/pyproject.toml +4 -2
  8. {maidr-1.4.10 → maidr-1.6.0}/uv.lock +54 -1
  9. {maidr-1.4.10 → maidr-1.6.0}/.commitlintrc.cjs +0 -0
  10. {maidr-1.4.10 → maidr-1.6.0}/.editorconfig +0 -0
  11. {maidr-1.4.10 → maidr-1.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {maidr-1.4.10 → maidr-1.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {maidr-1.4.10 → maidr-1.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {maidr-1.4.10 → maidr-1.6.0}/.github/copilot-instructions.md +0 -0
  15. {maidr-1.4.10 → maidr-1.6.0}/.github/workflows/ci.yml +0 -0
  16. {maidr-1.4.10 → maidr-1.6.0}/.github/workflows/docs.yml +0 -0
  17. {maidr-1.4.10 → maidr-1.6.0}/.github/workflows/release.yml +0 -0
  18. {maidr-1.4.10 → maidr-1.6.0}/.gitignore +0 -0
  19. {maidr-1.4.10 → maidr-1.6.0}/.pre-commit-config.yaml +0 -0
  20. {maidr-1.4.10 → maidr-1.6.0}/.vscode/extensions.json +0 -0
  21. {maidr-1.4.10 → maidr-1.6.0}/.vscode/settings.json +0 -0
  22. {maidr-1.4.10 → maidr-1.6.0}/CONDUCT.md +0 -0
  23. {maidr-1.4.10 → maidr-1.6.0}/CONTRIBUTING.md +0 -0
  24. {maidr-1.4.10 → maidr-1.6.0}/LICENSE +0 -0
  25. {maidr-1.4.10 → maidr-1.6.0}/README.md +0 -0
  26. {maidr-1.4.10 → maidr-1.6.0}/docs/.gitignore +0 -0
  27. {maidr-1.4.10 → maidr-1.6.0}/docs/CNAME +0 -0
  28. {maidr-1.4.10 → maidr-1.6.0}/docs/_environment +0 -0
  29. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/machow/interlinks/.gitignore +0 -0
  30. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/machow/interlinks/_extension.yml +0 -0
  31. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/machow/interlinks/interlinks.lua +0 -0
  32. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/shafayetShafee/line-highlight/_extension.yml +0 -0
  33. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/shafayetShafee/line-highlight/line-highlight.lua +0 -0
  34. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/shafayetShafee/line-highlight/resources/css/line-highlight.css +0 -0
  35. {maidr-1.4.10 → maidr-1.6.0}/docs/_extensions/shafayetShafee/line-highlight/resources/js/line-highlight.js +0 -0
  36. {maidr-1.4.10 → maidr-1.6.0}/docs/_quarto.yml +0 -0
  37. {maidr-1.4.10 → maidr-1.6.0}/docs/examples.qmd +0 -0
  38. {maidr-1.4.10 → maidr-1.6.0}/docs/index.qmd +0 -0
  39. {maidr-1.4.10 → maidr-1.6.0}/docs/styles.css +0 -0
  40. {maidr-1.4.10 → maidr-1.6.0}/example/bar/example_bar_plot.ipynb +0 -0
  41. {maidr-1.4.10 → maidr-1.6.0}/example/bar/matplotlib/example_mpl_bar_plot.py +0 -0
  42. {maidr-1.4.10 → maidr-1.6.0}/example/bar/seaborn/example_sns_bar_plot.py +0 -0
  43. {maidr-1.4.10 → maidr-1.6.0}/example/box/example_box_plot.ipynb +0 -0
  44. {maidr-1.4.10 → maidr-1.6.0}/example/box/matplotlib/example_mpl_box.py +0 -0
  45. {maidr-1.4.10 → maidr-1.6.0}/example/box/seaborn/example_sns_box.py +0 -0
  46. {maidr-1.4.10 → maidr-1.6.0}/example/candle_stick/legacy_candlestick_example.py +0 -0
  47. {maidr-1.4.10 → maidr-1.6.0}/example/candle_stick/mplfinance_candlestick_example.py +0 -0
  48. {maidr-1.4.10 → maidr-1.6.0}/example/candle_stick/volcandat.csv +0 -0
  49. {maidr-1.4.10 → maidr-1.6.0}/example/count/example_count_plot.ipynb +0 -0
  50. {maidr-1.4.10 → maidr-1.6.0}/example/count/seaborn/example_sns_count_plot.py +0 -0
  51. {maidr-1.4.10 → maidr-1.6.0}/example/dodged/matplotlib/example_mpl_dodged.py +0 -0
  52. {maidr-1.4.10 → maidr-1.6.0}/example/dodged/seaborn/example_sns_dodged.py +0 -0
  53. {maidr-1.4.10 → maidr-1.6.0}/example/facet-subplots/matplotlib/example_mpl_facet_bar_plot.py +0 -0
  54. {maidr-1.4.10 → maidr-1.6.0}/example/facet-subplots/matplotlib/example_mpl_facet_combined_plot.py +0 -0
  55. {maidr-1.4.10 → maidr-1.6.0}/example/facet-subplots/seaborn/example_sns_facet_bar_plot.py +0 -0
  56. {maidr-1.4.10 → maidr-1.6.0}/example/facet-subplots/seaborn/example_sns_facet_combined_plot.py +0 -0
  57. {maidr-1.4.10 → maidr-1.6.0}/example/heatmap/example_heatmap_plot.ipynb +0 -0
  58. {maidr-1.4.10 → maidr-1.6.0}/example/heatmap/matplotlib/example_mpl_heatmap.py +0 -0
  59. {maidr-1.4.10 → maidr-1.6.0}/example/heatmap/seaborn/example_sns_heatmap.py +0 -0
  60. {maidr-1.4.10 → maidr-1.6.0}/example/histogram/example_histogram_plot.ipynb +0 -0
  61. {maidr-1.4.10 → maidr-1.6.0}/example/histogram/matplotlib/example_mpl_hist.py +0 -0
  62. {maidr-1.4.10 → maidr-1.6.0}/example/histogram/matplotlib/histogram_with_kde_matplotlib.py +0 -0
  63. {maidr-1.4.10 → maidr-1.6.0}/example/histogram/seaborn/example_sns_hist.py +0 -0
  64. {maidr-1.4.10 → maidr-1.6.0}/example/histogram/seaborn/histogram_with_kde_seaborn.py +0 -0
  65. {maidr-1.4.10 → maidr-1.6.0}/example/kde/example_kde_plots.ipynb +0 -0
  66. {maidr-1.4.10 → maidr-1.6.0}/example/kde/matplotlib/multiple_kde_matplotlib.py +0 -0
  67. {maidr-1.4.10 → maidr-1.6.0}/example/kde/matplotlib/single_kde_matplotlib.py +0 -0
  68. {maidr-1.4.10 → maidr-1.6.0}/example/kde/seaborn/multiple_kde_seaborn.py +0 -0
  69. {maidr-1.4.10 → maidr-1.6.0}/example/kde/seaborn/single_kde_seaborn.py +0 -0
  70. {maidr-1.4.10 → maidr-1.6.0}/example/line/example_line_plot.ipynb +0 -0
  71. {maidr-1.4.10 → maidr-1.6.0}/example/line/matplotlib/example_mpl_line.py +0 -0
  72. {maidr-1.4.10 → maidr-1.6.0}/example/line/seaborn/example_sns_line.py +0 -0
  73. {maidr-1.4.10 → maidr-1.6.0}/example/multilayer/example_mpl_multilayer.py +0 -0
  74. {maidr-1.4.10 → maidr-1.6.0}/example/multilayer/example_multilayer_plot.ipynb +0 -0
  75. {maidr-1.4.10 → maidr-1.6.0}/example/multiline/example_multiline_plot.ipynb +0 -0
  76. {maidr-1.4.10 → maidr-1.6.0}/example/multiline/matplotlib/example_mpl_multiline.py +0 -0
  77. {maidr-1.4.10 → maidr-1.6.0}/example/multiline/seaborn/example_sns_multiline.py +0 -0
  78. {maidr-1.4.10 → maidr-1.6.0}/example/multipanel/example_multipanel_plot.ipynb +0 -0
  79. {maidr-1.4.10 → maidr-1.6.0}/example/multipanel/matplotlib/example_mpl_multipanel.py +0 -0
  80. {maidr-1.4.10 → maidr-1.6.0}/example/multipanel/seaborn/example_sns_multipanel.py +0 -0
  81. {maidr-1.4.10 → maidr-1.6.0}/example/quarto/demo.qmd +0 -0
  82. {maidr-1.4.10 → maidr-1.6.0}/example/reg/example_reg_plots.ipynb +0 -0
  83. {maidr-1.4.10 → maidr-1.6.0}/example/reg/matplotlib/example_matplotlib_smooth_plot.py +0 -0
  84. {maidr-1.4.10 → maidr-1.6.0}/example/reg/seaborn/example_sns_reg.py +0 -0
  85. {maidr-1.4.10 → maidr-1.6.0}/example/scatter/example_scatter_plot.ipynb +0 -0
  86. {maidr-1.4.10 → maidr-1.6.0}/example/scatter/matplotlib/example_mpl_scatter.py +0 -0
  87. {maidr-1.4.10 → maidr-1.6.0}/example/scatter/seaborn/example_sns_scatter.py +0 -0
  88. {maidr-1.4.10 → maidr-1.6.0}/example/shiny/example_shiny_scatter.py +0 -0
  89. {maidr-1.4.10 → maidr-1.6.0}/example/stacked/matplotlib/example_mpl_stacked.html +0 -0
  90. {maidr-1.4.10 → maidr-1.6.0}/example/stacked/matplotlib/example_mpl_stacked.py +0 -0
  91. {maidr-1.4.10 → maidr-1.6.0}/example/stacked/seaborn/example_sns_stacked.html +0 -0
  92. {maidr-1.4.10 → maidr-1.6.0}/example/stacked/seaborn/example_sns_stacked.py +0 -0
  93. {maidr-1.4.10 → maidr-1.6.0}/example/streamlit/example_streamlit_app.py +0 -0
  94. {maidr-1.4.10 → maidr-1.6.0}/maidr/api.py +0 -0
  95. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/__init__.py +0 -0
  96. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/context_manager.py +0 -0
  97. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/enum/__init__.py +0 -0
  98. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/enum/library.py +0 -0
  99. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/enum/maidr_key.py +0 -0
  100. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/enum/plot_type.py +0 -0
  101. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/enum/smooth_keywords.py +0 -0
  102. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/figure_manager.py +0 -0
  103. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/__init__.py +0 -0
  104. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/barplot.py +0 -0
  105. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/boxplot.py +0 -0
  106. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/candlestick.py +0 -0
  107. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/grouped_barplot.py +0 -0
  108. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/heatmap.py +0 -0
  109. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/histogram.py +0 -0
  110. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/lineplot.py +0 -0
  111. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/maidr_plot.py +0 -0
  112. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/maidr_plot_factory.py +0 -0
  113. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/mplfinance_barplot.py +0 -0
  114. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/mplfinance_lineplot.py +0 -0
  115. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/regplot.py +0 -0
  116. {maidr-1.4.10 → maidr-1.6.0}/maidr/core/plot/scatterplot.py +0 -0
  117. {maidr-1.4.10 → maidr-1.6.0}/maidr/exception/__init__.py +0 -0
  118. {maidr-1.4.10 → maidr-1.6.0}/maidr/exception/extraction_error.py +0 -0
  119. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/__init__.py +0 -0
  120. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/barplot.py +0 -0
  121. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/boxplot.py +0 -0
  122. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/candlestick.py +0 -0
  123. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/clear.py +0 -0
  124. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/common.py +0 -0
  125. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/heatmap.py +0 -0
  126. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/highlight.py +0 -0
  127. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/histogram.py +0 -0
  128. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/kdeplot.py +0 -0
  129. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/lineplot.py +0 -0
  130. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/mplfinance.py +0 -0
  131. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/regplot.py +0 -0
  132. {maidr-1.4.10 → maidr-1.6.0}/maidr/patch/scatterplot.py +0 -0
  133. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/__init__.py +0 -0
  134. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/dedup_utils.py +0 -0
  135. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/mixin/__init__.py +0 -0
  136. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/mixin/extractor_mixin.py +0 -0
  137. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/mixin/merger_mixin.py +0 -0
  138. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/mplfinance_utils.py +0 -0
  139. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/plot_detection.py +0 -0
  140. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/regression_line_utils.py +0 -0
  141. {maidr-1.4.10 → maidr-1.6.0}/maidr/util/svg_utils.py +0 -0
  142. {maidr-1.4.10 → maidr-1.6.0}/maidr/widget/__init__.py +0 -0
  143. {maidr-1.4.10 → maidr-1.6.0}/maidr/widget/shiny.py +0 -0
  144. {maidr-1.4.10 → maidr-1.6.0}/tests/__init__.py +0 -0
  145. {maidr-1.4.10 → maidr-1.6.0}/tests/conftest.py +0 -0
  146. {maidr-1.4.10 → maidr-1.6.0}/tests/core/__init__.py +0 -0
  147. {maidr-1.4.10 → maidr-1.6.0}/tests/core/enum/__init__.py +0 -0
  148. {maidr-1.4.10 → maidr-1.6.0}/tests/core/plot/__init__.py +0 -0
  149. {maidr-1.4.10 → maidr-1.6.0}/tests/core/test_figure_manager.py +0 -0
  150. {maidr-1.4.10 → maidr-1.6.0}/tests/core/test_maidr_plot.py +0 -0
  151. {maidr-1.4.10 → maidr-1.6.0}/tests/core/test_maidr_plot_factory.py +0 -0
  152. {maidr-1.4.10 → maidr-1.6.0}/tests/fixture/__init__.py +0 -0
  153. {maidr-1.4.10 → maidr-1.6.0}/tests/fixture/library_factory.py +0 -0
  154. {maidr-1.4.10 → maidr-1.6.0}/tests/fixture/matplotlib_factory.py +0 -0
  155. {maidr-1.4.10 → maidr-1.6.0}/tests/fixture/seaborn_factory.py +0 -0
  156. {maidr-1.4.10 → maidr-1.6.0}/tox.ini +0 -0
@@ -1,6 +1,29 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.6.0 (2025-08-04)
5
+
6
+ ### Continuous Integration
7
+
8
+ - Update uv.lock ([#227](https://github.com/xability/py-maidr/pull/227),
9
+ [`f162971`](https://github.com/xability/py-maidr/commit/f162971f4e2585e055de3c837ce9342b292cb378))
10
+
11
+ ### Features
12
+
13
+ - Add WSL compatibility for opening HTML files
14
+ ([#224](https://github.com/xability/py-maidr/pull/224),
15
+ [`2f2a899`](https://github.com/xability/py-maidr/commit/2f2a899b1df4137db94aeb843099ee6f0df952f0))
16
+
17
+
18
+ ## v1.5.0 (2025-08-04)
19
+
20
+ ### Features
21
+
22
+ - Detect if running in a flask app and render in an iframe
23
+ ([#225](https://github.com/xability/py-maidr/pull/225),
24
+ [`dcd0e0f`](https://github.com/xability/py-maidr/commit/dcd0e0fb1f7a995e4eb2578e9f67cc31e45171ea))
25
+
26
+
4
27
  ## v1.4.10 (2025-07-30)
5
28
 
6
29
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maidr
3
- Version: 1.4.10
3
+ Version: 1.6.0
4
4
  Summary: Multimodal Access and Interactive Data Representations
5
5
  Project-URL: Homepage, https://xability.github.io/py-maidr
6
6
  Project-URL: Repository, https://github.com/xability/py-maidr
@@ -34,12 +34,14 @@ Provides-Extra: jupyter
34
34
  Requires-Dist: ipykernel>=6.0.0; extra == 'jupyter'
35
35
  Requires-Dist: jupyter<2,>=1.0.0; extra == 'jupyter'
36
36
  Provides-Extra: test
37
+ Requires-Dist: flask>=3.0.0; extra == 'test'
37
38
  Requires-Dist: jupyter<2,>=1.0.0; extra == 'test'
38
39
  Requires-Dist: matplotlib>=3.8; extra == 'test'
39
40
  Requires-Dist: pytest-mock<4,>=3.12.0; extra == 'test'
40
41
  Requires-Dist: pytest<8,>=7.3.2; extra == 'test'
41
42
  Requires-Dist: seaborn>=0.12; extra == 'test'
42
43
  Provides-Extra: visualization
44
+ Requires-Dist: flask>=3.0.0; extra == 'visualization'
43
45
  Requires-Dist: matplotlib>=3.8; extra == 'visualization'
44
46
  Requires-Dist: seaborn>=0.12; extra == 'visualization'
45
47
  Requires-Dist: statsmodels>=0.14.4; extra == 'visualization'
@@ -0,0 +1,71 @@
1
+ import matplotlib
2
+ matplotlib.use('Agg') # Use non-interactive backend for Flask
3
+
4
+ from flask import Flask
5
+ import matplotlib.pyplot as plt
6
+ import seaborn as sns
7
+ import maidr
8
+ from maidr.util.environment import Environment
9
+ import logging
10
+
11
+ # Set up logging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ app = Flask(__name__)
16
+
17
+ @app.route('/')
18
+ def index():
19
+ # Log environment detection
20
+ logger.info("=== Environment Detection ===")
21
+ logger.info(f"is_flask(): {Environment.is_flask()}")
22
+ logger.info(f"is_notebook(): {Environment.is_notebook()}")
23
+ logger.info(f"is_shiny(): {Environment.is_shiny()}")
24
+ logger.info(f"get_renderer(): {Environment.get_renderer()}")
25
+
26
+ # Check iframe detection logic
27
+ use_iframe = True
28
+ flask_detected = Environment.is_flask()
29
+ notebook_detected = Environment.is_notebook()
30
+ shiny_detected = Environment.is_shiny()
31
+
32
+ logger.info("=== Iframe Detection Logic ===")
33
+ logger.info(f"use_iframe: {use_iframe}")
34
+ logger.info(f"flask_detected: {flask_detected}")
35
+ logger.info(f"notebook_detected: {notebook_detected}")
36
+ logger.info(f"shiny_detected: {shiny_detected}")
37
+ logger.info("Condition: use_iframe and (flask_detected or notebook_detected or shiny_detected)")
38
+ logger.info(f"Result: {use_iframe and (flask_detected or notebook_detected or shiny_detected)}")
39
+
40
+ # Load the penguins dataset
41
+ penguins = sns.load_dataset("penguins")
42
+
43
+ # Create a bar plot showing the average body mass of penguins by species
44
+ plt.figure(figsize=(10, 6))
45
+
46
+ # Create the bar plot and assign to variable
47
+ bar_plot = sns.barplot(
48
+ x="species", y="body_mass_g", data=penguins, errorbar="sd", palette="Blues_d"
49
+ )
50
+ plt.title("Average Body Mass of Penguins by Species")
51
+ plt.xlabel("Species")
52
+ plt.ylabel("Body Mass (g)")
53
+
54
+ # Use the user-facing API: maidr.render() with plot object
55
+ logger.info("=== Using maidr.render() API ===")
56
+ maidr_html = maidr.render(bar_plot)
57
+ logger.info(f"Type of maidr.render(): {type(maidr_html)}")
58
+ logger.info(f"String representation: {str(maidr_html)[:300]}...")
59
+
60
+ # Check if the output contains iframe
61
+ html_str = str(maidr_html)
62
+ contains_iframe = "<iframe" in html_str.lower()
63
+ logger.info(f"Contains iframe: {contains_iframe}")
64
+
65
+ # Return the maidr HTML directly
66
+ return str(maidr_html)
67
+
68
+ if __name__ == '__main__':
69
+ logger.info("Starting Flask app with logging...")
70
+ logger.info("Visit http://localhost:5002 to see the maidr plot in Flask")
71
+ app.run(debug=False, host='0.0.0.0', port=5002)
@@ -1,4 +1,4 @@
1
- __version__ = "1.4.10"
1
+ __version__ = "1.6.0"
2
2
 
3
3
  from .api import close, render, save_html, show, stacked
4
4
  from .core import Maidr
@@ -7,6 +7,8 @@ import os
7
7
  import tempfile
8
8
  import uuid
9
9
  import webbrowser
10
+ import subprocess
11
+ from pathlib import Path
10
12
  from typing import Any, Literal, cast
11
13
 
12
14
  import matplotlib.pyplot as plt
@@ -137,7 +139,48 @@ class Maidr:
137
139
  html_file_path = self.save_html(
138
140
  temp_file_path
139
141
  ) # This will use use_iframe=False
140
- webbrowser.open(f"file://{html_file_path}")
142
+ if Environment.is_wsl():
143
+ wsl_distro_name = Environment.get_wsl_distro_name()
144
+
145
+ # Validate that WSL distro name is available
146
+ if not wsl_distro_name:
147
+ raise ValueError(
148
+ "WSL_DISTRO_NAME environment variable is not set or is empty. "
149
+ "Cannot construct WSL file URL. Please ensure you are running in a WSL environment."
150
+ )
151
+
152
+ # Ensure html_file_path is an absolute POSIX path for proper WSL URL construction
153
+ html_file_path = Path(html_file_path).resolve().as_posix()
154
+
155
+ url = f"file://wsl$/{wsl_distro_name}{html_file_path}"
156
+
157
+ # Try to open the file in Windows using explorer.exe with robust path detection
158
+ explorer_path = Environment.find_explorer_path()
159
+ if explorer_path:
160
+ try:
161
+ result = subprocess.run(
162
+ [explorer_path, url],
163
+ capture_output=True,
164
+ text=True,
165
+ timeout=10
166
+ )
167
+
168
+ if result.returncode == 0:
169
+ return
170
+ else:
171
+ # Fall back to webbrowser.open() if explorer.exe fails
172
+ webbrowser.open(url)
173
+
174
+ except subprocess.TimeoutExpired:
175
+ webbrowser.open(url)
176
+ except Exception:
177
+ # Fall back to webbrowser.open() if explorer.exe fails
178
+ webbrowser.open(url)
179
+ else:
180
+ webbrowser.open(url)
181
+
182
+ else:
183
+ webbrowser.open(f"file://{html_file_path}")
141
184
 
142
185
  def _create_html_tag(self, use_iframe: bool = True) -> Tag:
143
186
  """Create the MAIDR HTML using HTML tags."""
@@ -311,7 +354,7 @@ class Maidr:
311
354
  # Render the plot inside an iframe if in a Jupyter notebook, Google Colab
312
355
  # or VSCode notebook. No need for iframe if this is a Quarto document.
313
356
  # For TypeScript we will use iframe by default for now
314
- if use_iframe and (Environment.is_notebook() or Environment.is_shiny()):
357
+ if use_iframe and (Environment.is_flask() or Environment.is_notebook() or Environment.is_shiny()):
315
358
  unique_id = "iframe_" + Maidr._unique_id()
316
359
 
317
360
  def generate_iframe_script(unique_id: str) -> str:
@@ -1,11 +1,51 @@
1
1
  import json
2
2
  import os
3
+ import subprocess
3
4
  import sys
5
+ from typing import Union
4
6
 
5
7
 
6
8
  class Environment:
7
9
  _engine = "ts"
8
10
 
11
+ @staticmethod
12
+ def is_flask() -> bool:
13
+ """
14
+ Check if the current environment is a Flask application.
15
+
16
+ This method detects Flask applications by checking if Flask's app context
17
+ is available using `flask.has_app_context()`. The app context is Flask's
18
+ way of tracking the current application state and is only available when
19
+ code is running within a Flask application.
20
+
21
+ Returns
22
+ -------
23
+ bool
24
+ True if the environment is a Flask application, False otherwise.
25
+
26
+ Examples
27
+ --------
28
+ >>> from maidr.util.environment import Environment
29
+ >>> Environment.is_flask()
30
+ False # When not in a Flask app
31
+ """
32
+ try:
33
+ # Import Flask's has_app_context function
34
+ from flask import has_app_context
35
+
36
+ # has_app_context() returns True only when code is running within
37
+ # a Flask application context. This is Flask's built-in mechanism
38
+ # for detecting if the current execution environment is a Flask app.
39
+ #
40
+ # The app context is automatically created by Flask when:
41
+ # - A Flask app is running (app.run())
42
+ # - Code is executed within a Flask request context
43
+ # - The app context is manually pushed
44
+ return has_app_context()
45
+ except ImportError:
46
+ # Flask is not installed, so we're definitely not in a Flask app
47
+ return False
48
+
9
49
  @staticmethod
10
50
  def is_interactive_shell() -> bool:
11
51
  """Return True if the environment is an interactive shell."""
@@ -61,6 +101,93 @@ class Environment:
61
101
  return False
62
102
  except ImportError:
63
103
  return False
104
+ @staticmethod
105
+ def is_wsl() -> bool:
106
+ """
107
+ Check if the current environment is WSL (Windows Subsystem for Linux).
108
+
109
+ This method detects WSL environments by reading the `/proc/version` file
110
+ and checking for 'microsoft' or 'wsl' keywords in the version string.
111
+ WSL environments typically contain these identifiers in their kernel version.
112
+
113
+ Returns
114
+ -------
115
+ bool
116
+ True if the environment is WSL, False otherwise.
117
+
118
+ Examples
119
+ --------
120
+ >>> from maidr.util.environment import Environment
121
+ >>> Environment.is_wsl()
122
+ False # When not in WSL
123
+ """
124
+ try:
125
+ with open('/proc/version', 'r') as f:
126
+ version_info = f.read().lower()
127
+ if 'microsoft' in version_info or 'wsl' in version_info:
128
+ return True
129
+ except FileNotFoundError:
130
+ pass
131
+ return False
132
+
133
+ @staticmethod
134
+ def get_wsl_distro_name() -> str:
135
+ """
136
+ Get the WSL distribution name from environment variables.
137
+
138
+ This method retrieves the WSL distribution name from the `WSL_DISTRO_NAME`
139
+ environment variable, which is automatically set by WSL when running
140
+ in a WSL environment.
141
+
142
+ Returns
143
+ -------
144
+ str
145
+ The WSL distribution name (e.g., 'Ubuntu-20.04', 'Debian') if set,
146
+ otherwise an empty string.
147
+
148
+ Examples
149
+ --------
150
+ >>> from maidr.util.environment import Environment
151
+ >>> Environment.get_wsl_distro_name()
152
+ '' # When not in WSL or WSL_DISTRO_NAME not set
153
+ """
154
+ return os.environ.get('WSL_DISTRO_NAME', '')
155
+
156
+ @staticmethod
157
+ def find_explorer_path() -> Union[str, None]:
158
+ """
159
+ Find the correct path to explorer.exe in WSL environment.
160
+
161
+ This method checks if explorer.exe is available in the PATH
162
+ and returns the path if found.
163
+
164
+ Returns
165
+ -------
166
+ str | None
167
+ The path to explorer.exe if found, None otherwise.
168
+
169
+ Examples
170
+ --------
171
+ >>> from maidr.util.environment import Environment
172
+ >>> Environment.find_explorer_path()
173
+ '/mnt/c/Windows/explorer.exe' # When found
174
+ """
175
+ # Check if explorer.exe is in PATH
176
+ try:
177
+ result = subprocess.run(
178
+ ['which', 'explorer.exe'],
179
+ capture_output=True,
180
+ text=True,
181
+ timeout=5
182
+ )
183
+ if result.returncode == 0:
184
+ return result.stdout.strip()
185
+ except Exception:
186
+ pass
187
+
188
+ return None
189
+
190
+
64
191
 
65
192
  @staticmethod
66
193
  def get_renderer() -> str:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "maidr"
7
- version = "1.4.10"
7
+ version = "1.6.0"
8
8
  description = "Multimodal Access and Interactive Data Representations"
9
9
  authors = [
10
10
  { name = "JooYoung Seo", email = "jseo1005@illinois.edu" },
@@ -55,7 +55,8 @@ dependencies = [
55
55
  visualization = [
56
56
  "matplotlib>=3.8",
57
57
  "seaborn>=0.12",
58
- "statsmodels>=0.14.4"
58
+ "statsmodels>=0.14.4",
59
+ "flask>=3.0.0"
59
60
  ]
60
61
  jupyter = [
61
62
  "jupyter>=1.0.0,<2",
@@ -67,6 +68,7 @@ test = [
67
68
  "jupyter>=1.0.0,<2",
68
69
  "pytest>=7.3.2,<8",
69
70
  "pytest-mock>=3.12.0,<4",
71
+ "flask>=3.0.0"
70
72
  ]
71
73
 
72
74
  [project.urls]
@@ -199,6 +199,15 @@ css = [
199
199
  { name = "tinycss2" },
200
200
  ]
201
201
 
202
+ [[package]]
203
+ name = "blinker"
204
+ version = "1.9.0"
205
+ source = { registry = "https://pypi.org/simple" }
206
+ sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
207
+ wheels = [
208
+ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
209
+ ]
210
+
202
211
  [[package]]
203
212
  name = "cachetools"
204
213
  version = "6.1.0"
@@ -720,6 +729,25 @@ wheels = [
720
729
  { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
721
730
  ]
722
731
 
732
+ [[package]]
733
+ name = "flask"
734
+ version = "3.1.1"
735
+ source = { registry = "https://pypi.org/simple" }
736
+ dependencies = [
737
+ { name = "blinker" },
738
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
739
+ { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
740
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
741
+ { name = "itsdangerous" },
742
+ { name = "jinja2" },
743
+ { name = "markupsafe" },
744
+ { name = "werkzeug" },
745
+ ]
746
+ sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440, upload-time = "2025-05-13T15:01:17.447Z" }
747
+ wheels = [
748
+ { url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305, upload-time = "2025-05-13T15:01:15.591Z" },
749
+ ]
750
+
723
751
  [[package]]
724
752
  name = "fonttools"
725
753
  version = "4.58.5"
@@ -1059,6 +1087,15 @@ wheels = [
1059
1087
  { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
1060
1088
  ]
1061
1089
 
1090
+ [[package]]
1091
+ name = "itsdangerous"
1092
+ version = "2.2.0"
1093
+ source = { registry = "https://pypi.org/simple" }
1094
+ sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
1095
+ wheels = [
1096
+ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
1097
+ ]
1098
+
1062
1099
  [[package]]
1063
1100
  name = "jedi"
1064
1101
  version = "0.19.2"
@@ -1638,7 +1675,7 @@ wheels = [
1638
1675
 
1639
1676
  [[package]]
1640
1677
  name = "maidr"
1641
- version = "1.4.9"
1678
+ version = "1.5.0"
1642
1679
  source = { editable = "." }
1643
1680
  dependencies = [
1644
1681
  { name = "htmltools" },
@@ -1657,6 +1694,7 @@ jupyter = [
1657
1694
  { name = "jupyter" },
1658
1695
  ]
1659
1696
  test = [
1697
+ { name = "flask" },
1660
1698
  { name = "jupyter" },
1661
1699
  { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
1662
1700
  { name = "matplotlib", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
@@ -1665,6 +1703,7 @@ test = [
1665
1703
  { name = "seaborn" },
1666
1704
  ]
1667
1705
  visualization = [
1706
+ { name = "flask" },
1668
1707
  { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
1669
1708
  { name = "matplotlib", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
1670
1709
  { name = "seaborn" },
@@ -1684,6 +1723,8 @@ dev = [
1684
1723
 
1685
1724
  [package.metadata]
1686
1725
  requires-dist = [
1726
+ { name = "flask", marker = "extra == 'test'", specifier = ">=3.0.0" },
1727
+ { name = "flask", marker = "extra == 'visualization'", specifier = ">=3.0.0" },
1687
1728
  { name = "htmltools", specifier = ">=0.5" },
1688
1729
  { name = "ipykernel", marker = "extra == 'jupyter'", specifier = ">=6.0.0" },
1689
1730
  { name = "jupyter", marker = "extra == 'jupyter'", specifier = ">=1.0.0,<2" },
@@ -3900,6 +3941,18 @@ wheels = [
3900
3941
  { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
3901
3942
  ]
3902
3943
 
3944
+ [[package]]
3945
+ name = "werkzeug"
3946
+ version = "3.1.3"
3947
+ source = { registry = "https://pypi.org/simple" }
3948
+ dependencies = [
3949
+ { name = "markupsafe" },
3950
+ ]
3951
+ sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" }
3952
+ wheels = [
3953
+ { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" },
3954
+ ]
3955
+
3903
3956
  [[package]]
3904
3957
  name = "widgetsnbextension"
3905
3958
  version = "4.0.14"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes