sphinxcontrib-screenshot 0.1.2__py3-none-any.whl → 0.1.4__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.

Potentially problematic release.


This version of sphinxcontrib-screenshot might be problematic. Click here for more details.

@@ -13,17 +13,25 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import hashlib
16
+ import importlib
17
+ import importlib.metadata
16
18
  import os
19
+ import threading
17
20
  import typing
21
+ import wsgiref.simple_server
18
22
  from concurrent.futures import ThreadPoolExecutor
19
23
  from urllib.parse import urlparse
20
24
 
21
25
  from docutils import nodes
22
26
  from docutils.parsers.rst import directives
23
27
  from docutils.statemachine import ViewList
28
+ from playwright._impl._helper import ColorScheme
29
+ from playwright.sync_api import Browser, BrowserContext
24
30
  from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
25
31
  from playwright.sync_api import sync_playwright
32
+ from portpicker import pick_unused_port
26
33
  from sphinx.application import Sphinx
34
+ from sphinx.config import Config
27
35
  from sphinx.util.docutils import SphinxDirective
28
36
 
29
37
  Meta = typing.TypedDict('Meta', {
@@ -32,8 +40,6 @@ Meta = typing.TypedDict('Meta', {
32
40
  'parallel_write_safe': bool
33
41
  })
34
42
 
35
- __version__ = '0.1.2'
36
-
37
43
 
38
44
  class ScreenshotDirective(SphinxDirective):
39
45
  """Sphinx Screenshot Dirctive.
@@ -72,21 +78,47 @@ class ScreenshotDirective(SphinxDirective):
72
78
 
73
79
  document.querySelector('button').click();
74
80
  ```
81
+
82
+ Use `figclass` option if you want to specify a class name to the image.
83
+
84
+ ```rst
85
+ .. screenshot:: http://www.example.com
86
+ :figclass: foo
87
+ ```
88
+
89
+ It also generates a PDF file when `pdf` option is given, which might be
90
+ useful when you need scalable image assets.
91
+
92
+ ```rst
93
+ .. screenshot:: http://www.example.com
94
+ :pdf:
95
+ ```
75
96
  """
76
97
 
77
98
  required_arguments = 1 # URL
78
99
  has_content = True
79
100
  option_spec = {
101
+ 'browser': str,
80
102
  'height': directives.positive_int,
81
103
  'width': directives.positive_int,
82
104
  'caption': directives.unchanged,
83
105
  'figclass': directives.unchanged,
106
+ 'pdf': directives.flag,
107
+ 'color-scheme': str,
108
+ 'full-page': directives.flag,
109
+ 'context': str,
110
+ 'headers': directives.unchanged,
84
111
  }
85
112
  pool = ThreadPoolExecutor()
86
113
 
87
114
  @staticmethod
88
- def take_screenshot(url: str, width: int, height: int, filepath: str,
89
- init_script: str, interactions: str):
115
+ def take_screenshot(
116
+ url: str, browser_name: str, width: int, height: int, filepath: str,
117
+ init_script: str, interactions: str, generate_pdf: bool,
118
+ color_scheme: ColorScheme, full_page: bool,
119
+ context_builder: typing.Optional[typing.Callable[[Browser, str, str],
120
+ BrowserContext]],
121
+ headers: dict):
90
122
  """Takes a screenshot with Playwright's Chromium browser.
91
123
 
92
124
  Args:
@@ -99,15 +131,31 @@ class ScreenshotDirective(SphinxDirective):
99
131
  https://playwright.dev/python/docs/api/class-page#page-add-init-script
100
132
  interactions (str): JavaScript code to run before taking the screenshot
101
133
  after the page was loaded.
134
+ generate_pdf (bool): Generate a PDF file along with the screenshot.
135
+ color_scheme (str): The preferred color scheme. Can be 'light' or 'dark'.
136
+ context: A method to build the Playwright context.
102
137
  """
103
138
  with sync_playwright() as playwright:
104
- browser = playwright.chromium.launch()
105
- page = browser.new_page()
139
+ browser = getattr(playwright, browser_name).launch()
140
+
141
+ if context_builder:
142
+ try:
143
+ context = context_builder(browser, url, color_scheme)
144
+ except PlaywrightTimeoutError:
145
+ raise RuntimeError(
146
+ 'Timeout error occured at %s in executing py init script %s' %
147
+ (url, context_builder.__name__))
148
+ else:
149
+ context = browser.new_context(color_scheme=color_scheme)
150
+
151
+ page = context.new_page()
106
152
  page.set_default_timeout(10000)
107
153
  page.set_viewport_size({'width': width, 'height': height})
154
+
108
155
  try:
109
156
  if init_script:
110
157
  page.add_init_script(init_script)
158
+ page.set_extra_http_headers(headers)
111
159
  page.goto(url)
112
160
  page.wait_for_load_state('networkidle')
113
161
 
@@ -118,10 +166,20 @@ class ScreenshotDirective(SphinxDirective):
118
166
  except PlaywrightTimeoutError:
119
167
  raise RuntimeError('Timeout error occured at %s in executing\n%s' %
120
168
  (url, interactions))
121
- page.screenshot(path=filepath)
169
+ page.screenshot(path=filepath, full_page=full_page)
170
+ if generate_pdf:
171
+ page.emulate_media(media='screen')
172
+ root, ext = os.path.splitext(filepath)
173
+ page.pdf(width=f'{width}px', height=f'{height}px', path=root + '.pdf')
122
174
  page.close()
123
175
  browser.close()
124
176
 
177
+ def evaluate_substitutions(self, text: str) -> str:
178
+ substitutions = self.state.document.substitution_defs
179
+ for key, value in substitutions.items():
180
+ text = text.replace(f"|{key}|", value.astext())
181
+ return text
182
+
125
183
  def run(self) -> typing.List[nodes.Node]:
126
184
  screenshot_init_script: str = self.env.config.screenshot_init_script or ''
127
185
 
@@ -130,27 +188,56 @@ class ScreenshotDirective(SphinxDirective):
130
188
  os.makedirs(ss_dirpath, exist_ok=True)
131
189
 
132
190
  # Parse parameters
133
- url = self.arguments[0]
134
- height = self.options.get('height', 960)
135
- width = self.options.get('width', 1280)
191
+ raw_url = self.arguments[0]
192
+ url = self.evaluate_substitutions(raw_url)
193
+ browser = self.options.get('browser',
194
+ self.env.config.screenshot_default_browser)
195
+ height = self.options.get('height',
196
+ self.env.config.screenshot_default_height)
197
+ width = self.options.get('width', self.env.config.screenshot_default_width)
198
+ color_scheme = self.options.get(
199
+ 'color-scheme', self.env.config.screenshot_default_color_scheme)
136
200
  caption_text = self.options.get('caption', '')
137
201
  figclass = self.options.get('figclass', '')
202
+ pdf = 'pdf' in self.options
203
+ full_page = ('full-page' in self.options or
204
+ self.env.config.screenshot_default_full_page)
205
+ context = self.options.get('context', '')
138
206
  interactions = '\n'.join(self.content)
207
+ headers = self.options.get('headers', '')
208
+
209
+ request_headers = {**self.env.config.screenshot_default_headers}
210
+ if headers:
211
+ for header in headers.strip().split("\n"):
212
+ name, value = header.split(" ", 1)
213
+ request_headers[name] = value
139
214
 
140
215
  if urlparse(url).scheme not in {'http', 'https'}:
141
216
  raise RuntimeError(
142
217
  f'Invalid URL: {url}. Only HTTP/HTTPS URLs are supported.')
143
218
 
144
219
  # Generate filename based on hash of parameters
145
- hash_input = f'{url}_{height}_{width}_{interactions}'
220
+ hash_input = "_".join([
221
+ raw_url, browser,
222
+ str(height),
223
+ str(width), color_scheme, context, interactions,
224
+ str(full_page)
225
+ ])
146
226
  filename = hashlib.md5(hash_input.encode()).hexdigest() + '.png'
147
227
  filepath = os.path.join(ss_dirpath, filename)
148
228
 
229
+ if context:
230
+ context_builder_path = self.config.screenshot_contexts[context]
231
+ context_builder = resolve_python_method(context_builder_path)
232
+ else:
233
+ context_builder = None
234
+
149
235
  # Check if the file already exists. If not, take a screenshot
150
236
  if not os.path.exists(filepath):
151
- fut = self.pool.submit(ScreenshotDirective.take_screenshot, url, width,
152
- height, filepath, screenshot_init_script,
153
- interactions)
237
+ fut = self.pool.submit(ScreenshotDirective.take_screenshot, url, browser,
238
+ width, height, filepath, screenshot_init_script,
239
+ interactions, pdf, color_scheme, full_page,
240
+ context_builder, request_headers)
154
241
  fut.result()
155
242
 
156
243
  # Create image and figure nodes
@@ -173,11 +260,86 @@ class ScreenshotDirective(SphinxDirective):
173
260
  return [figure_node]
174
261
 
175
262
 
263
+ app_threads = {}
264
+
265
+
266
+ def resolve_python_method(import_path: str):
267
+ module_path, method_name = import_path.split(":")
268
+ module = importlib.import_module(module_path)
269
+ method = getattr(module, method_name)
270
+ return method
271
+
272
+
273
+ def setup_apps(app: Sphinx, config: Config):
274
+ """Start the WSGI application threads.
275
+
276
+ A new replacement is created for each WSGI app."""
277
+ for wsgi_app_name, wsgi_app_path in config.screenshot_apps.items():
278
+ port = pick_unused_port()
279
+ config.rst_prolog = (
280
+ config.rst_prolog or
281
+ "") + f"\n.. |{wsgi_app_name}| replace:: http://localhost:{port}\n"
282
+ app_builder = resolve_python_method(wsgi_app_path)
283
+ wsgi_app = app_builder(app)
284
+ httpd = wsgiref.simple_server.make_server("localhost", port, wsgi_app)
285
+ thread = threading.Thread(target=httpd.serve_forever)
286
+ thread.start()
287
+ app_threads[wsgi_app_name] = (httpd, thread)
288
+
289
+
290
+ def teardown_apps(app: Sphinx, exception: typing.Optional[Exception]):
291
+ """Shut down the WSGI application threads."""
292
+ for httpd, thread in app_threads.values():
293
+ httpd.shutdown()
294
+ thread.join()
295
+
296
+
176
297
  def setup(app: Sphinx) -> Meta:
177
298
  app.add_directive('screenshot', ScreenshotDirective)
178
299
  app.add_config_value('screenshot_init_script', '', 'env')
300
+ app.add_config_value(
301
+ 'screenshot_default_width',
302
+ 1280,
303
+ 'env',
304
+ description="The default width for screenshots")
305
+ app.add_config_value(
306
+ 'screenshot_default_height',
307
+ 960,
308
+ 'env',
309
+ description="The default height for screenshots")
310
+ app.add_config_value(
311
+ 'screenshot_default_browser',
312
+ 'chromium',
313
+ 'env',
314
+ description="The default browser for screenshots")
315
+ app.add_config_value(
316
+ 'screenshot_default_full_page',
317
+ False,
318
+ 'env',
319
+ description="Whether to take full page screenshots")
320
+ app.add_config_value(
321
+ 'screenshot_default_color_scheme',
322
+ 'null',
323
+ 'env',
324
+ description="The default color scheme for screenshots")
325
+ app.add_config_value(
326
+ 'screenshot_contexts', {},
327
+ 'env',
328
+ types=[dict[str, str]],
329
+ description="A dict of paths to Playwright context build methods")
330
+ app.add_config_value(
331
+ 'screenshot_default_headers', {},
332
+ 'env',
333
+ description="The default headers to pass in requests")
334
+ app.add_config_value(
335
+ 'screenshot_apps', {},
336
+ 'env',
337
+ types=[dict[str, str]],
338
+ description="A dict of WSGI apps")
339
+ app.connect('config-inited', setup_apps)
340
+ app.connect('build-finished', teardown_apps)
179
341
  return {
180
- 'version': __version__,
342
+ 'version': importlib.metadata.version('sphinxcontrib-screenshot'),
181
343
  'parallel_read_safe': True,
182
344
  'parallel_write_safe': True,
183
345
  }
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.2
2
+ Name: sphinxcontrib-screenshot
3
+ Version: 0.1.4
4
+ Summary: A Sphinx extension to embed webpage screenshots.
5
+ Author-email: Shuhei Iitsuka <tushuhei@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: repository, https://github.com/tushuhei/sphinxcontrib-screenshot/
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: playwright
20
+ Requires-Dist: sphinx
21
+ Requires-Dist: portpicker
22
+ Provides-Extra: dev
23
+ Requires-Dist: beautifulsoup4; extra == "dev"
24
+ Requires-Dist: build; extra == "dev"
25
+ Requires-Dist: flake8; extra == "dev"
26
+ Requires-Dist: flake8-pyproject; extra == "dev"
27
+ Requires-Dist: isort; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+ Requires-Dist: Pillow; extra == "dev"
30
+ Requires-Dist: pre-commit; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-regressions[image]; extra == "dev"
33
+ Requires-Dist: sphinx[test]; extra == "dev"
34
+ Requires-Dist: toml; extra == "dev"
35
+ Requires-Dist: tox; extra == "dev"
36
+ Requires-Dist: twine; extra == "dev"
37
+ Requires-Dist: types-beautifulsoup4; extra == "dev"
38
+ Requires-Dist: types-docutils; extra == "dev"
39
+ Requires-Dist: types-portpicker; extra == "dev"
40
+ Requires-Dist: types-Pillow; extra == "dev"
41
+ Requires-Dist: types-setuptools; extra == "dev"
42
+ Requires-Dist: user-agents; extra == "dev"
43
+ Requires-Dist: yapf; extra == "dev"
44
+ Provides-Extra: doc
45
+ Requires-Dist: myst-parser; extra == "doc"
46
+ Requires-Dist: shibuya; extra == "doc"
47
+ Requires-Dist: sphinx; extra == "doc"
48
+
49
+ # sphinxcontrib-screenshot
50
+
51
+ A Sphinx extension to embed website screenshots.
52
+
53
+ ```rst
54
+ .. screenshot:: http://www.example.com
55
+ :browser: chromium
56
+ :width: 1280
57
+ :height: 960
58
+ :color-scheme: dark
59
+ ```
60
+
61
+ Read more in the [documentation](https://sphinxcontrib-screenshot.readthedocs.io).
62
+
63
+ ## Notes
64
+
65
+ This extension uses [Playwright](https://playwright.dev) to capture a screenshot of the specified website only.
66
+ No data is sent to any other external server; the request is limited to the specified website.
67
+ Be cautious: avoid including sensitive information (such as authentication data) in the directive content.
68
+
69
+ ## Contributing
70
+
71
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details.
72
+
73
+ ## License
74
+
75
+ Apache 2.0; see [`LICENSE`](LICENSE) for details.
76
+
77
+ ## Disclaimer
78
+
79
+ This project is not an official Google project. It is not supported by
80
+ Google and Google specifically disclaims all warranties as to its quality,
81
+ merchantability, or fitness for a particular purpose.
@@ -0,0 +1,6 @@
1
+ sphinxcontrib/screenshot.py,sha256=eJEF1qfGGo1uwyTovYHXx4Ewz2fXmrxDSsQSdLJ0O-0,11863
2
+ sphinxcontrib_screenshot-0.1.4.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
3
+ sphinxcontrib_screenshot-0.1.4.dist-info/METADATA,sha256=XpzmVDGTDfL-AxKUv7jIV3wQ_phEGc-OLxWU-wG7-o0,2850
4
+ sphinxcontrib_screenshot-0.1.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
5
+ sphinxcontrib_screenshot-0.1.4.dist-info/top_level.txt,sha256=VJrV3_vaiKQVgVpR0I1iecxoO0drzGu-M0j40PVP2QQ,14
6
+ sphinxcontrib_screenshot-0.1.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,120 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: sphinxcontrib-screenshot
3
- Version: 0.1.2
4
- Summary: A Shpinx extension to embed webpage screenshots.
5
- Home-page: https://github.com/tushuhei/sphinxcontrib-screenshot/
6
- Author: Shuhei Iitsuka
7
- Author-email: tushuhei@gmail.com
8
- License: Apache-2.0
9
- Classifier: Development Status :: 3 - Alpha
10
- Classifier: Operating System :: OS Independent
11
- Classifier: License :: OSI Approved :: Apache Software License
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Requires-Python: >=3.9
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: playwright
19
- Requires-Dist: sphinx
20
- Provides-Extra: dev
21
- Requires-Dist: beautifulsoup4 ; extra == 'dev'
22
- Requires-Dist: build ; extra == 'dev'
23
- Requires-Dist: flake8 ; extra == 'dev'
24
- Requires-Dist: isort ; extra == 'dev'
25
- Requires-Dist: mypy ; extra == 'dev'
26
- Requires-Dist: Pillow ; extra == 'dev'
27
- Requires-Dist: pytest ; extra == 'dev'
28
- Requires-Dist: sphinx[test] ; extra == 'dev'
29
- Requires-Dist: toml ; extra == 'dev'
30
- Requires-Dist: twine ; extra == 'dev'
31
- Requires-Dist: types-beautifulsoup4 ; extra == 'dev'
32
- Requires-Dist: types-docutils ; extra == 'dev'
33
- Requires-Dist: types-Pillow ; extra == 'dev'
34
- Requires-Dist: types-setuptools ; extra == 'dev'
35
- Requires-Dist: yapf ; extra == 'dev'
36
-
37
- # sphinxcontrib-screenshot
38
-
39
- A Sphinx extension to embed website screenshots.
40
-
41
- ![Example screenshot](https://raw.githubusercontent.com/tushuhei/sphinxcontrib-screenshot/main/example.png)
42
-
43
- ## Install
44
-
45
- ```bash
46
- pip install sphinxcontrib-screenshot
47
- playwright install
48
- ```
49
-
50
- ## Usage
51
-
52
- Add `sphinxcontrib.screenshot` to your `conf.py`.
53
-
54
- ```py
55
- extensions = ["sphinxcontrib.screenshot"]
56
- ```
57
-
58
- Then use the `screenshot` directive in your Sphinx source file.
59
-
60
- ```rst
61
- .. screenshot:: http://www.example.com
62
- ```
63
-
64
- You can also specify the screen size for the screenshot with `width` and `height` parameters.
65
-
66
- ```rst
67
- .. screenshot:: http://www.example.com
68
- :width: 1280
69
- :height: 960
70
- ```
71
-
72
- You can include a caption for the screenshot's `figure` directive.
73
-
74
- ```rst
75
- .. screenshot:: http://www.example.com
76
- :caption: This is a screenshot for www.example.com
77
- ```
78
-
79
- You can describe the interaction that you want to have with the webpage before taking a screenshot in JavaScript.
80
-
81
- ```rst
82
- .. screenshot:: http://www.example.com
83
-
84
- document.querySelector('button').click();
85
- ```
86
-
87
- ## Pro tips
88
- `sphinxcontrib-screenshot` supports URLs with the HTTP and HTTPS protocols.
89
- To take screenshots of local files and build the document while running a local server for them, you can use the NPM library [concurrently](https://www.npmjs.com/package/concurrently) in the following way:
90
-
91
- ### Build the document
92
- ```bash
93
- npx --yes concurrently -k --success=first "make html" "python3 -m http.server 3000 --directory=examples"
94
- ```
95
-
96
- ### Watch and build the document
97
- ```bash
98
- npx --yes concurrently -k "make livehtml" "python3 -m http.server 3000 --directory=examples"
99
- ```
100
-
101
-
102
- ## Notes
103
-
104
- This extension uses [Playwright](https://playwright.dev) to capture a screenshot of the specified website only.
105
- No data is sent to any other external server; the request is limited to the specified website.
106
- Be cautious: avoid including sensitive information (such as authentication data) in the directive content.
107
-
108
- ## Contributing
109
-
110
- See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details.
111
-
112
- ## License
113
-
114
- Apache 2.0; see [`LICENSE`](LICENSE) for details.
115
-
116
- ## Disclaimer
117
-
118
- This project is not an official Google project. It is not supported by
119
- Google and Google specifically disclaims all warranties as to its quality,
120
- merchantability, or fitness for a particular purpose.
@@ -1,6 +0,0 @@
1
- sphinxcontrib/screenshot.py,sha256=Ri86PkBZ4EMYCj1BgKT9ZfQbEh6dVJRFr2n_E5cf9Ko,6137
2
- sphinxcontrib_screenshot-0.1.2.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
3
- sphinxcontrib_screenshot-0.1.2.dist-info/METADATA,sha256=lF-Qzmb4dvtCHvVzeqhvmfIdc4IUa7Sk-EVez6L4Dp8,3667
4
- sphinxcontrib_screenshot-0.1.2.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
5
- sphinxcontrib_screenshot-0.1.2.dist-info/top_level.txt,sha256=VJrV3_vaiKQVgVpR0I1iecxoO0drzGu-M0j40PVP2QQ,14
6
- sphinxcontrib_screenshot-0.1.2.dist-info/RECORD,,