diff --git a/yt_dlp/extractor/goplay.py b/yt_dlp/extractor/goplay.py
index dfe5afe63514..32300f75c2f5 100644
--- a/yt_dlp/extractor/goplay.py
+++ b/yt_dlp/extractor/goplay.py
@@ -5,56 +5,63 @@
 import hmac
 import json
 import os
+import re
+import urllib.parse

 from .common import InfoExtractor
 from ..utils import (
     ExtractorError,
+    int_or_none,
+    js_to_json,
+    remove_end,
     traverse_obj,
-    unescapeHTML,
 )

 class GoPlayIE(InfoExtractor):
-    _VALID_URL = r'https?://(www\.)?goplay\.be/video/([^/]+/[^/]+/|)(?P<display_id>[^/#]+)'
+    _VALID_URL = r'https?://(www\.)?goplay\.be/video/([^/?#]+/[^/?#]+/|)(?P<id>[^/#]+)'

     _NETRC_MACHINE = 'goplay'

     _TESTS = [{
-        'url': 'https://www.goplay.be/video/de-container-cup/de-container-cup-s3/de-container-cup-s3-aflevering-2#autoplay',
+        'url': 'https://www.goplay.be/video/de-slimste-mens-ter-wereld/de-slimste-mens-ter-wereld-s22/de-slimste-mens-ter-wereld-s22-aflevering-1',
         'info_dict': {
-            'id': '9c4214b8-e55d-4e4b-a446-f015f6c6f811',
+            'id': '2baa4560-87a0-421b-bffc-359914e3c387',
             'ext': 'mp4',
-            'title': 'S3 - Aflevering 2',
-            'series': 'De Container Cup',
-            'season': 'Season 3',
-            'season_number': 3,
-            'episode': 'Episode 2',
-            'episode_number': 2,
+            'title': 'S22 - Aflevering 1',
+            'description': r're:In aflevering 1 nemen Daan Alferink, Tess Elst en Xander De Rycke .{66}',
+            'series': 'De Slimste Mens ter Wereld',
+            'episode': 'Episode 1',
+            'season_number': 22,
+            'episode_number': 1,
+            'season': 'Season 22',
         },
+        'params': {'skip_download': True},
         'skip': 'This video is only available for registered users',
     }, {
-        'url': 'https://www.goplay.be/video/a-family-for-thr-holidays-s1-aflevering-1#autoplay',
+        'url': 'https://www.goplay.be/video/1917',
         'info_dict': {
-            'id': '74e3ed07-748c-49e4-85a0-393a93337dbf',
+            'id': '40cac41d-8d29-4ef5-aa11-75047b9f0907',
             'ext': 'mp4',
-            'title': 'A Family for the Holidays',
+            'title': '1917',
+            'description': r're:Op het hoogtepunt van de Eerste Wereldoorlog krijgen twee jonge .{94}',
         },
+        'params': {'skip_download': True},
         'skip': 'This video is only available for registered users',
     }, {
         'url': 'https://www.goplay.be/video/de-mol/de-mol-s11/de-mol-s11-aflevering-1#autoplay',
         'info_dict': {
-            'id': '03eb8f2f-153e-41cb-9805-0d3a29dab656',
+            'id': 'ecb79672-92b9-4cd9-a0d7-e2f0250681ee',
             'ext': 'mp4',
             'title': 'S11 - Aflevering 1',
+            'description': r're:Tien kandidaten beginnen aan hun verovering van Amerika en ontmoeten .{102}',
             'episode': 'Episode 1',
             'series': 'De Mol',
             'season_number': 11,
             'episode_number': 1,
             'season': 'Season 11',
         },
-        'params': {
-            'skip_download': True,
-        },
+        'params': {'skip_download': True},
         'skip': 'This video is only available for registered users',
     }]

@@ -69,27 +76,42 @@ def _real_initialize(self):
         if not self._id_token:
             raise self.raise_login_required(method='password')

+    def _find_json(self, s):
+        return self._search_json(
+            r'\w+\s*:\s*', s, 'next js data', None, contains_pattern=r'\[(?s:.+)\]', default=None)
+
     def _real_extract(self, url):
-        url, display_id = self._match_valid_url(url).group(0, 'display_id')
+        display_id = self._match_id(url)
         webpage = self._download_webpage(url, display_id)
-        video_data_json = self._html_search_regex(r'<div\s+data-hero="([^"]+)"', webpage, 'video_data')
-        video_data = self._parse_json(unescapeHTML(video_data_json), display_id).get('data')
-
-        movie = video_data.get('movie')
-        if movie:
-            video_id = movie['videoUuid']
-            info_dict = {
-                'title': movie.get('title'),
-            }
-        else:
-            episode = traverse_obj(video_data, ('playlists', ..., 'episodes', lambda _, v: v['pageInfo']['url'] == url), get_all=False)
-            video_id = episode['videoUuid']
-            info_dict = {
-                'title': episode.get('episodeTitle'),
-                'series': traverse_obj(episode, ('program', 'title')),
-                'season_number': episode.get('seasonNumber'),
-                'episode_number': episode.get('episodeNumber'),
-            }
+
+        nextjs_data = traverse_obj(
+            re.findall(r'<script[^>]*>\s*self\.__next_f\.push\(\s*(\[.+?\])\s*\);?\s*</script>', webpage),
+            (..., {js_to_json}, {json.loads}, ..., {self._find_json}, ...))
+        meta = traverse_obj(nextjs_data, (
+            ..., lambda _, v: v['meta']['path'] == urllib.parse.urlparse(url).path, 'meta', any))
+
+        video_id = meta['uuid']
+        info_dict = traverse_obj(meta, {
+            'title': ('title', {str}),
+            'description': ('description', {str.strip}),
+        })
+
+        if traverse_obj(meta, ('program', 'subtype')) != 'movie':
+            for season_data in traverse_obj(nextjs_data, (..., 'children', ..., 'playlists', ...)):
+                episode_data = traverse_obj(
+                    season_data, ('videos', lambda _, v: v['videoId'] == video_id, any))
+                if not episode_data:
+                    continue
+
+                episode_title = traverse_obj(
+                    episode_data, 'contextualTitle', 'episodeTitle', expected_type=str)
+                info_dict.update({
+                    'title': episode_title or info_dict.get('title'),
+                    'series': remove_end(info_dict.get('title'), f' - {episode_title}'),
+                    'season_number': traverse_obj(season_data, ('season', {int_or_none})),
+                    'episode_number': traverse_obj(episode_data, ('episodeNumber', {int_or_none})),
+                })
+                break

         api = self._download_json(
             f'https://api.goplay.be/web/v1/videos/long-form/{video_id}',

[GoPlay] ERROR: [GoPlay] Unable to extract video_data
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that yt-dlp is broken on a **supported** site
- [X] I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

Belgium

### Provide a description that is worded well enough to be understood

I cannot download a video from Goplay.be. ERROR: [GoPlay] Unable to extract video_data
Thank you in advance.

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', 'https://www.goplay.be/video/nonkels/nonkels-s2/nonkels-2-s2-aflevering-4', '--username', 'PRIVATE', '--password', 'PRIVATE']
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version master@2024.08.26.211743 from yt-dlp/yt-dlp-master-builds [41be32e78] (win_exe)
[debug] Python 3.8.10 (CPython AMD64 64bit) - Windows-10-10.0.19045-SP0 (OpenSSL 1.1.1k  25 Mar 2021)
[debug] exe versions: none
[debug] Optional libraries: Cryptodome-3.20.0, brotli-1.1.0, certifi-2024.07.04, curl_cffi-0.5.10, mutagen-1.47.0, requests-2.32.3, sqlite3-3.35.5, urllib3-2.2.2, websockets-13.0
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets, curl_cffi
[debug] Loaded 1831 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp-master-builds/releases/latest
Latest version: master@2024.08.26.211743 from yt-dlp/yt-dlp-master-builds
yt-dlp is up to date (master@2024.08.26.211743 from yt-dlp/yt-dlp-master-builds)
[GoPlay] Logging in
[GoPlay] Authenticating username
[GoPlay] Authenticating password
[GoPlay] Extracting URL: https://www.goplay.be/video/nonkels/nonkels-s2/nonkels-2-s2-aflevering-4
[GoPlay] nonkels-2-s2-aflevering-4: Downloading webpage
ERROR: [GoPlay] Unable to extract video_data; please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "yt_dlp\extractor\common.py", line 740, in extract
  File "yt_dlp\extractor\goplay.py", line 75, in _real_extract
  File "yt_dlp\extractor\common.py", line 1369, in _html_search_regex
  File "yt_dlp\extractor\common.py", line 1333, in _search_regex
```

Do **not** download the above spam links, they are malware
> Do not download these spam links, they are malware

The respond was too quick, so I didn't downloaded them. Thx :-)
Maybe some more information:
The video has many advertising at the begin and in the middle of the video.
> The video is protected by DRM

Then it won't be downloadable anyways. Does downloading non-DRM videos from this site still work?
> > The video is protected by DRM
>
> Then it won't be downloadable anyways. Does downloading non-DRM videos from this site still work?

After some researche, the video is actually NOT protected by DRM. So the error must be from something else. I tried 2 other videos, I got the same error: "ERROR: [GoPlay] Unable to extract video_data"...


diff --git a/src/viztracer/main.py b/src/viztracer/main.py
index 7cbf972c..eb996124 100644
--- a/src/viztracer/main.py
+++ b/src/viztracer/main.py
@@ -676,7 +676,7 @@ def exit_routine(self) -> None:
                 self.save()
                 if self.options.open:  # pragma: no cover
                     import subprocess
-                    subprocess.run(["vizviewer", "--once", os.path.abspath(self.ofile)])
+                    subprocess.run([sys.executable, "-m", "viztracer.viewer", "--once", os.path.abspath(self.ofile)])

 def main():

Cannot import name 'viewer_main' from 'viztracer' in 1.0.0
### Phenomenon:
I've been using viztracer through the viztracer plugin in vscode, but after upgrading to 1.0.0 ,viztracer doesn't work.

### Error message:
```powershell
C:\ProgramData\anaconda3\python.exe -m viztracer --ignore_frozen --open --log_print --quiet -u -- c:\...\something.py
Traceback (most recent call last):
  File "C:\ProgramData\anaconda3\Scripts\vizviewer-script.py", line 6, in <module>
    from viztracer import viewer_main
ImportError: cannot import name 'viewer_main' from 'viztracer' (C:\Users\29267\AppData\Roaming\Python\Python311\site-packages\viztracer\__init__.py)
```

### What I tried:
1. downgraded to 0.17.1 : **works fine**
2. upgraded to 1.0.0    : **bugs still there**

You have multiple versions of viztracers. The `vizviewer` viztracer tried to use is a different version. `viztracer` is from conda but seems like `vizviewer` used the version from your system Python.
But this is still partially my fault, `viztracer` should always use the same version `vizviewer`. For now you can either upgrade both version, or do not use `--open` option. Just do a vizviewer in the same environment as viztracer, which should work.


diff --git a/locust/runners.py b/locust/runners.py
index 9552d519c7..a4165cfa40 100644
--- a/locust/runners.py
+++ b/locust/runners.py
@@ -1025,7 +1025,9 @@ def client_listener(self) -> NoReturn:
                 # if abs(time() - msg.data["time"]) > 5.0:
                 #    warnings.warn("The worker node's clock seem to be out of sync. For the statistics to be correct the different locust servers need to have synchronized clocks.")
             elif msg.type == "locustfile":
-                if msg.data["version"][0:4] == __version__[0:4]:
+                if not msg.data["version"]:
+                    logger.error("A very old worker version requested locustfile. This probably won't work.")
+                elif msg.data["version"][0:4] == __version__[0:4]:
                     logger.debug(
                         f"A worker ({msg.node_id}) running a different patch version ({msg.data['version']}) connected, master version is {__version__}"
                     )

master crash with different version worker
### Prerequisites

- [X] I am using [the latest version of Locust](https://github.com/locustio/locust/releases/)
- [X] I am reporting a bug, not asking a question

### Description

I ran distributed locust with master node locust version 2.32.2 and some worker node locust version 2.25.0 (python3.8 default version).
The master node crash with the following message
```
➜  load-test locust -f locust.py --master
[2024-11-09 14:41:07,519] nasa33/INFO/locust.main: Starting Locust 2.32.2
[2024-11-09 14:41:07,524] nasa33/INFO/locust.main: Starting web interface at http://0.0.0.0:8089
Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 906, in gevent._gevent_cgreenlet.Greenlet.run
  File "/home/uniform64/.local/lib/python3.10/site-packages/locust/runners.py", line 1030, in client_listener
    if msg.data["version"][0:4] == __version__[0:4]:
TypeError: 'NoneType' object is not subscriptable
2024-11-09T06:41:13Z <Greenlet at 0x7f254a980cc0: <bound method MasterRunner.client_listener of <locust.runners.MasterRunner object at 0x7f254a963100>>> failed with TypeError

[2024-11-09 14:41:13,652] nasa33/CRITICAL/locust.runners: Unhandled exception in greenlet: <Greenlet at 0x7f254a980cc0: <bound method MasterRunner.client_listener of <locust.runners.MasterRunner object at 0x7f254a963100>>>
Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 906, in gevent._gevent_cgreenlet.Greenlet.run
  File "/home/uniform64/.local/lib/python3.10/site-packages/locust/runners.py", line 1030, in client_listener
    if msg.data["version"][0:4] == __version__[0:4]:
TypeError: 'NoneType' object is not subscriptable
```
when I use the following command on worker node.
```
~/.local/bin/locust -f - --worker --master-host 172.16.0.33 --processes -1
```

### Command line

locust -f locust.py --master

### Locustfile contents

```python3
import random
import string
from locust import HttpUser, task

def generate_random_string(length):
    return "".join(random.choices(string.ascii_lowercase+string.digits,
                                  k=length))

def generate_random_bytes(length):
    return random.randbytes(length)

class SimpleClient(HttpUser):
    @task
    def upload(self):
        # random generate a index and some data (both string)
        index = generate_random_string(random.randint(10, 20))
        data = generate_random_bytes(random.randint(100, 200))
        self.client.post("/upload", headers={"Index": index}, data=data)
```

### Python version

3.10

### Locust version

2.32.2

### Operating system

ubuntu22.04


diff --git a/yfinance/base.py b/yfinance/base.py
index 81733ba9..c3150759 100644
--- a/yfinance/base.py
+++ b/yfinance/base.py
@@ -30,7 +30,7 @@
 import pandas as pd
 import requests

-from . import utils, cache, Search
+from . import utils, cache
 from .data import YfData
 from .exceptions import YFEarningsDateMissing
 from .scrapers.analysis import Analysis
@@ -534,19 +534,45 @@ def get_isin(self, proxy=None) -> Optional[str]:
         self._isin = data.split(search_str)[1].split('"')[0].split('|')[0]
         return self._isin

-    def get_news(self, proxy=None) -> list:
+    def get_news(self, count=10, tab="news", proxy=None) -> list:
+        """Allowed options for tab: "news", "all", "press releases"""
         if self._news:
             return self._news

-        search = Search(
-            query=self.ticker,
-            news_count=10,
-            session=self.session,
-            proxy=proxy,
-            raise_errors=True
-        )
-        self._news = search.news
+        logger = utils.get_yf_logger()
+
+        tab_queryrefs = {
+            "all": "newsAll",
+            "news": "latestNews",
+            "press releases": "pressRelease",
+        }
+
+        query_ref = tab_queryrefs.get(tab.lower())
+        if not query_ref:
+            raise ValueError(f"Invalid tab name '{tab}'. Choose from: {', '.join(tab_queryrefs.keys())}")
+
+        url = f"{_ROOT_URL_}/xhr/ncp?queryRef={query_ref}&serviceKey=ncp_fin"
+        payload = {
+            "serviceConfig": {
+                "snippetCount": count,
+                "s": [self.ticker]
+            }
+        }
+
+        data = self._data.post(url, body=payload, proxy=proxy)
+        if data is None or "Will be right back" in data.text:
+            raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
+                               "Our engineers are working quickly to resolve "
+                               "the issue. Thank you for your patience.")
+        try:
+            data = data.json()
+        except _json.JSONDecodeError:
+            logger.error(f"{self.ticker}: Failed to retrieve the news and received faulty response instead.")
+            data = {}
+
+        news = data.get("data", {}).get("tickerStream", {}).get("stream", [])

+        self._news = [article for article in news if not article.get('ad', [])]
         return self._news

     @utils.log_indent_decorator

Any way to get more news?
`ticker.news` seems to return 8 to 10 news articles.

However, Yahoo Finance can offer many more than 8 to 10 news articles per ticker: https://finance.yahoo.com/quote/MSFT/news/ (keep scrolling down).

Is there a way to get more than 8 to 10 news articles with yfinance?

Someone began working on a solution but abandoned it: #1949


diff --git a/yfinance/utils.py b/yfinance/utils.py
index 0968f9d1..ebc8b99a 100644
--- a/yfinance/utils.py
+++ b/yfinance/utils.py
@@ -613,7 +613,7 @@ def fix_Yahoo_returning_live_separate(quotes, interval, tz_exchange, repair=Fals
             # - exception is volume, *slightly* greater on final row (and matches website)
             if dt1.date() == dt2.date():
                 # Last two rows are on same day. Drop second-to-last row
-                quotes = quotes.drop(quotes.index[n - 2])
+                quotes = _pd.concat([quotes.iloc[:-2], quotes.iloc[-1:]])
         else:
             if interval == "1wk":
                 last_rows_same_interval = dt1.year == dt2.year and dt1.week == dt2.week

0.2.42 and onwards fails to pull most recent trading days data for ASX stocks
### Describe bug

Pulling stock data using versions 0.2.42 and onwards fails to pull the last trading days data for ASX stocks. This could be related to timezones but the issue doesn't exist in 0.2.41.

### Simple code that reproduces your problem

`stock_data_daily = yf.download('CSL.AX', period='1y', interval='1d')`

### Debug log

DEBUG    Entering download()
DEBUG:yfinance:Entering download()
DEBUG     Disabling multithreading because DEBUG logging enabled
DEBUG:yfinance: Disabling multithreading because DEBUG logging enabled
DEBUG     Entering history()
DEBUG:yfinance: Entering history()
DEBUG      Entering history()
DEBUG:yfinance:  Entering history()
DEBUG       CSL.AX: Yahoo GET parameters: {'range': '1y', 'interval': '1d', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG:yfinance:   CSL.AX: Yahoo GET parameters: {'range': '1y', 'interval': '1d', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG       Entering get()
DEBUG:yfinance:   Entering get()
DEBUG        url=https://query2.finance.yahoo.com/v8/finance/chart/CSL.AX
DEBUG:yfinance:    url=https://query2.finance.yahoo.com/v8/finance/chart/CSL.AX
DEBUG        params={'range': '1y', 'interval': '1d', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG:yfinance:    params={'range': '1y', 'interval': '1d', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG        Entering _get_cookie_and_crumb()
DEBUG:yfinance:    Entering _get_cookie_and_crumb()
DEBUG         cookie_mode = 'basic'
DEBUG:yfinance:     cookie_mode = 'basic'
DEBUG         Entering _get_cookie_and_crumb_basic()
DEBUG:yfinance:     Entering _get_cookie_and_crumb_basic()
DEBUG          reusing cookie
DEBUG:yfinance:      reusing cookie
DEBUG          reusing crumb
DEBUG:yfinance:      reusing crumb
DEBUG         Exiting _get_cookie_and_crumb_basic()
DEBUG:yfinance:     Exiting _get_cookie_and_crumb_basic()
DEBUG        Exiting _get_cookie_and_crumb()
DEBUG:yfinance:    Exiting _get_cookie_and_crumb()
DEBUG        response code=200
DEBUG:yfinance:    response code=200
DEBUG       Exiting get()
DEBUG:yfinance:   Exiting get()
DEBUG       CSL.AX: yfinance received OHLC data: 2023-11-07 23:00:00 -> 2024-11-08 05:10:12
DEBUG:yfinance:   CSL.AX: yfinance received OHLC data: 2023-11-07 23:00:00 -> 2024-11-08 05:10:12
DEBUG       CSL.AX: OHLC after cleaning: 2023-11-08 10:00:00+11:00 -> 2024-11-08 16:10:12+11:00
DEBUG:yfinance:   CSL.AX: OHLC after cleaning: 2023-11-08 10:00:00+11:00 -> 2024-11-08 16:10:12+11:00
DEBUG       CSL.AX: OHLC after combining events: 2023-11-08 00:00:00+11:00 -> 2024-11-08 00:00:00+11:00
DEBUG:yfinance:   CSL.AX: OHLC after combining events: 2023-11-08 00:00:00+11:00 -> 2024-11-08 00:00:00+11:00
DEBUG       CSL.AX: yfinance returning OHLC: 2023-11-08 00:00:00+11:00 -> 2024-11-07 00:00:00+11:00
DEBUG:yfinance:   CSL.AX: yfinance returning OHLC: 2023-11-08 00:00:00+11:00 -> 2024-11-07 00:00:00+11:00
DEBUG      Exiting history()
DEBUG:yfinance:  Exiting history()
DEBUG     Exiting history()
DEBUG:yfinance: Exiting history()
DEBUG    Exiting download()
DEBUG:yfinance:Exiting download()

### Bad data proof

_No response_

### `yfinance` version

>= 0.2.42

### Python version

_No response_

### Operating system

_No response_


diff --git a/scipy/sparse/_construct.py b/scipy/sparse/_construct.py
index 0326c9963f0b..f483976badb7 100644
--- a/scipy/sparse/_construct.py
+++ b/scipy/sparse/_construct.py
@@ -349,7 +349,7 @@ def eye_array(m, n=None, *, k=0, dtype=float, format=None):

     Parameters
     ----------
-    m : int or tuple of ints
+    m : int
         Number of rows requested.
     n : int, optional
         Number of columns. Default: `m`.

DOC: sparse: `sparse.eye_array` does not accept `tuple[int, int]` as the docs say that it should
### Describe your issue.

`scipy.sparse.eye_array` does not accept `m: tuple[int, int]` as the docs suggest is should:

https://github.com/scipy/scipy/blob/964f0bb6701dc17b51b842382ced0fa2ee318377/scipy/sparse/_construct.py#L350-L353

This is the case with at least `1.14.1` and `1.15.0rc1`

### Reproducing Code Example

```python
from scipy.sparse import eye_array
eye_array((1, 1))
```

### Error message

```shell
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    eye_array((1, 1))
    ~~~~~~~~~^^^^^^^^
  File "/home/joren/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/sparse/_construct.py", line 377, in eye_array
    return _eye(m, n, k, dtype, format)
  File "/home/joren/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/sparse/_construct.py", line 394, in _eye
    m, n = int(m), int(n)
           ~~~^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'tuple'
```

### SciPy/NumPy/Python version and system information

```shell
1.15.0rc1 2.2.0 sys.version_info(major=3, minor=13, micro=1, releaselevel='final', serial=0)
Build Dependencies:
  blas:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/include
    lib directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Haswell MAX_THREADS=64
    pc file directory: /project
    version: 0.3.28
  lapack:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/include
    lib directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Haswell MAX_THREADS=64
    pc file directory: /project
    version: 0.3.28
  pybind11:
    detection method: config-tool
    include directory: unknown
    name: pybind11
    version: 2.13.6
Compilers:
  c:
    commands: cc
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  c++:
    commands: c++
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  cython:
    commands: cython
    linker: cython
    name: cython
    version: 3.0.11
  fortran:
    commands: gfortran
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  pythran:
    include directory: ../../tmp/pip-build-env-fa6gfmf0/overlay/lib/python3.13/site-packages/pythran
    version: 0.17.0
Machine Information:
  build:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
  cross-compiled: false
  host:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
Python Information:
  path: /opt/python/cp313-cp313/bin/python
  version: '3.13'
```

DOC: sparse: `sparse.eye_array` does not accept `tuple[int, int]` as the docs say that it should
### Describe your issue.

`scipy.sparse.eye_array` does not accept `m: tuple[int, int]` as the docs suggest is should:

https://github.com/scipy/scipy/blob/964f0bb6701dc17b51b842382ced0fa2ee318377/scipy/sparse/_construct.py#L350-L353

This is the case with at least `1.14.1` and `1.15.0rc1`

### Reproducing Code Example

```python
from scipy.sparse import eye_array
eye_array((1, 1))
```

### Error message

```shell
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    eye_array((1, 1))
    ~~~~~~~~~^^^^^^^^
  File "/home/joren/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/sparse/_construct.py", line 377, in eye_array
    return _eye(m, n, k, dtype, format)
  File "/home/joren/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/sparse/_construct.py", line 394, in _eye
    m, n = int(m), int(n)
           ~~~^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'tuple'
```

### SciPy/NumPy/Python version and system information

```shell
1.15.0rc1 2.2.0 sys.version_info(major=3, minor=13, micro=1, releaselevel='final', serial=0)
Build Dependencies:
  blas:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/include
    lib directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Haswell MAX_THREADS=64
    pc file directory: /project
    version: 0.3.28
  lapack:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/include
    lib directory: /opt/_internal/cpython-3.13.0/lib/python3.13/site-packages/scipy_openblas32/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.28 DYNAMIC_ARCH NO_AFFINITY Haswell MAX_THREADS=64
    pc file directory: /project
    version: 0.3.28
  pybind11:
    detection method: config-tool
    include directory: unknown
    name: pybind11
    version: 2.13.6
Compilers:
  c:
    commands: cc
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  c++:
    commands: c++
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  cython:
    commands: cython
    linker: cython
    name: cython
    version: 3.0.11
  fortran:
    commands: gfortran
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  pythran:
    include directory: ../../tmp/pip-build-env-fa6gfmf0/overlay/lib/python3.13/site-packages/pythran
    version: 0.17.0
Machine Information:
  build:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
  cross-compiled: false
  host:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
Python Information:
  path: /opt/python/cp313-cp313/bin/python
  version: '3.13'
```

Thank you for pointing this out!!
We should be using the [array_api specification](https://data-apis.org/array-api/latest/API_specification) for the [`eye` function](https://data-apis.org/array-api/latest/API_specification/generated/array_api.eye.html). That should also align us with the numpy interface.   The function name is not `eye`, but we intend to rename it after the spmatrix functions are deprecated and removed.

Luckily it looks like the code does the right thing. The docs are not what we want.  So we need to change the docs by removing ` or tuple of ints`.

And it would be good to backport the change to the maintenance branch (I believe it is far too late to update the 1.14.1 docs).
> Thank you for pointing this out!! We should be using the [array_api specification](https://data-apis.org/array-api/latest/API_specification) for the [`eye` function](https://data-apis.org/array-api/latest/API_specification/generated/array_api.eye.html). That should also align us with the numpy interface. The function name is not `eye`, but we intend to rename it after the spmatrix functions are deprecated and removed.
>
> Luckily it looks like the code does the right thing. The docs are not what we want. So we need to change the docs by removing ` or tuple of ints`.
>
> And it would be good to backport the change to the maintenance branch (I believe it is far too late to update the 1.14.1 docs).

I like that; it's easier to annotate that way :)
Thank you for pointing this out!!
We should be using the [array_api specification](https://data-apis.org/array-api/latest/API_specification) for the [`eye` function](https://data-apis.org/array-api/latest/API_specification/generated/array_api.eye.html). That should also align us with the numpy interface.   The function name is not `eye`, but we intend to rename it after the spmatrix functions are deprecated and removed.

Luckily it looks like the code does the right thing. The docs are not what we want.  So we need to change the docs by removing ` or tuple of ints`.

And it would be good to backport the change to the maintenance branch (I believe it is far too late to update the 1.14.1 docs).
> Thank you for pointing this out!! We should be using the [array_api specification](https://data-apis.org/array-api/latest/API_specification) for the [`eye` function](https://data-apis.org/array-api/latest/API_specification/generated/array_api.eye.html). That should also align us with the numpy interface. The function name is not `eye`, but we intend to rename it after the spmatrix functions are deprecated and removed.
>
> Luckily it looks like the code does the right thing. The docs are not what we want. So we need to change the docs by removing ` or tuple of ints`.
>
> And it would be good to backport the change to the maintenance branch (I believe it is far too late to update the 1.14.1 docs).

I like that; it's easier to annotate that way :)


diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py
index de7be104289b..71ae19acabc2 100644
--- a/scipy/stats/_stats_py.py
+++ b/scipy/stats/_stats_py.py
@@ -4298,7 +4298,7 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0):
         Axis along which to perform the calculation. Default is 0.
         If None, ravel both arrays before performing the calculation.

-        .. versionadded:: 1.13.0
+        .. versionadded:: 1.14.0
     alternative : {'two-sided', 'greater', 'less'}, optional
         Defines the alternative hypothesis. Default is 'two-sided'.
         The following options are available:

DOC: stats.pearsonr: incorrect `versionadded` for `axis` param
### Issue with current documentation:

Regarding the documentation of function scipy.stats.pearsonr. Typo in the version reference. The axis option is not in v1.13.0. It first appears in v1.14.0

### Idea or request for content:

Correct the version reference in the docstring. 1.13.0 --> 1.14.0

### Additional context (e.g. screenshots, GIFs)

```
def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0):
    r"""
    Pearson correlation coefficient and p-value for testing non-correlation.

    The Pearson correlation coefficient [1]_ measures the linear relationship
    between two datasets. Like other correlation
    coefficients, this one varies between -1 and +1 with 0 implying no
    correlation. Correlations of -1 or +1 imply an exact linear relationship.
    Positive correlations imply that as x increases, so does y. Negative
    correlations imply that as x increases, y decreases.

    This function also performs a test of the null hypothesis that the
    distributions underlying the samples are uncorrelated and normally
    distributed. (See Kowalski [3]_
    for a discussion of the effects of non-normality of the input on the
    distribution of the correlation coefficient.)
    The p-value roughly indicates the probability of an uncorrelated system
    producing datasets that have a Pearson correlation at least as extreme
    as the one computed from these datasets.

    Parameters
    ----------
    x : array_like
        Input array.
    y : array_like
        Input array.
    axis : int or None, default
        Axis along which to perform the calculation. Default is 0.
        If None, ravel both arrays before performing the calculation.

        .. versionadded:: 1.13.0
```

Thanks @biopzhang, agreed that this is a typo. Would you like to submit a PR to fix this?


diff --git a/scipy/sparse/linalg/_dsolve/linsolve.py b/scipy/sparse/linalg/_dsolve/linsolve.py
index d1ab77883163..560cb75bbf99 100644
--- a/scipy/sparse/linalg/_dsolve/linsolve.py
+++ b/scipy/sparse/linalg/_dsolve/linsolve.py
@@ -371,6 +371,10 @@ def splu(A, permc_spec=None, diag_pivot_thresh=None,

     Notes
     -----
+    When a real array is factorized and the returned SuperLU object's ``solve()`` method
+    is used with complex arguments an error is generated. Instead, cast the initial
+    array to complex and then factorize.
+
     This function uses the SuperLU library.

     References
@@ -468,6 +472,10 @@ def spilu(A, drop_tol=None, fill_factor=None, drop_rule=None, permc_spec=None,

     Notes
     -----
+    When a real array is factorized and the returned SuperLU object's ``solve()`` method
+    is used with complex arguments an error is generated. Instead, cast the initial
+    array to complex and then factorize.
+
     To improve the better approximation to the inverse, you may need to
     increase `fill_factor` AND decrease `drop_tol`.

sparse LU decomposition does not solve with complex right-hand side
The `solve` method of the sparse LU-decomposition `splu` or `spilu` throws a `TypeError` if called with a `numpy.array` of type `numpy.complex`. I am actually using `spilu` for preconditioning a gmres-solver required to perform a linear solve in a non-symmetric generalized eigenvalue problem. The eigenvectors are complex and hence the right-hand side for the linear solve can be complex.

### Reproducing code example:
```
import numpy as np
from scipy.sparse import csr_matrix
import scipy.sparse.linalg as sp_sparse_la

A = csr_matrix([[2.,-1.],[-1.,2.]])
n = A.shape[0]

v_real = np.random.randn(n)
v_cmplx = np.random.randn(n) + 1.0J * np.random.randn(n)

luA = sp_sparse_la.splu(A)
x_real = luA.solve(v_real)
x_cmplx = luA.solve(v_cmplx)
```

### Error message:
```
Traceback (most recent call last):
  File "dump.py", line 20, in <module>
    x_cmplx = luA.solve(v_cmplx)
TypeError: Cannot cast array data from dtype('complex128') to dtype('float64') according to the rule 'safe'
```

### Scipy/Numpy/Python version information:
```
('1.0.0', '1.13.3', sys.version_info(major=2, minor=7, micro=12, releaselevel='final', serial=0))
```

sparse LU decomposition does not solve with complex right-hand side
The `solve` method of the sparse LU-decomposition `splu` or `spilu` throws a `TypeError` if called with a `numpy.array` of type `numpy.complex`. I am actually using `spilu` for preconditioning a gmres-solver required to perform a linear solve in a non-symmetric generalized eigenvalue problem. The eigenvectors are complex and hence the right-hand side for the linear solve can be complex.

### Reproducing code example:
```
import numpy as np
from scipy.sparse import csr_matrix
import scipy.sparse.linalg as sp_sparse_la

A = csr_matrix([[2.,-1.],[-1.,2.]])
n = A.shape[0]

v_real = np.random.randn(n)
v_cmplx = np.random.randn(n) + 1.0J * np.random.randn(n)

luA = sp_sparse_la.splu(A)
x_real = luA.solve(v_real)
x_cmplx = luA.solve(v_cmplx)
```

### Error message:
```
Traceback (most recent call last):
  File "dump.py", line 20, in <module>
    x_cmplx = luA.solve(v_cmplx)
TypeError: Cannot cast array data from dtype('complex128') to dtype('float64') according to the rule 'safe'
```

### Scipy/Numpy/Python version information:
```
('1.0.0', '1.13.3', sys.version_info(major=2, minor=7, micro=12, releaselevel='final', serial=0))
```

if you cast your A matrix as complex, then it works in both cases. So probably when the LHS is real it selects a real-typed solver and complains.
Thank you, you are right. Maybe some comments regarding this issue should be added in the documentation.
Good first issue, depending on familiarity with the math.
Hi I'm working on it I'll try to do it by the end of next week
A note such as the following in the docstring of `splu` and `spilu` would close this issue

````When a real array is factorized and the returned SuperLU object ``solve()`` method is used with complex arguments an error is generated. Instead cast the initial matrix to complex and then factorize.````
Hi @j-bowhay, thanks for the comment, I am a first-time contributor to scipy, I would like to start from this issue
We don't assign issues to specific people but please feel free to have a go
if you cast your A matrix as complex, then it works in both cases. So probably when the LHS is real it selects a real-typed solver and complains.
Thank you, you are right. Maybe some comments regarding this issue should be added in the documentation.
Good first issue, depending on familiarity with the math.
Hi I'm working on it I'll try to do it by the end of next week
A note such as the following in the docstring of `splu` and `spilu` would close this issue

````When a real array is factorized and the returned SuperLU object ``solve()`` method is used with complex arguments an error is generated. Instead cast the initial matrix to complex and then factorize.````
Hi @j-bowhay, thanks for the comment, I am a first-time contributor to scipy, I would like to start from this issue
We don't assign issues to specific people but please feel free to have a go


diff --git a/docling/backend/msword_backend.py b/docling/backend/msword_backend.py
index 05508712..bab956a7 100644
--- a/docling/backend/msword_backend.py
+++ b/docling/backend/msword_backend.py
@@ -133,7 +133,6 @@ def get_level(self) -> int:
     def walk_linear(self, body, docx_obj, doc) -> DoclingDocument:
         for element in body:
             tag_name = etree.QName(element).localname
-
             # Check for Inline Images (blip elements)
             namespaces = {
                 "a": "http://schemas.openxmlformats.org/drawingml/2006/main",
@@ -153,6 +152,7 @@ def walk_linear(self, body, docx_obj, doc) -> DoclingDocument:
                 self.handle_pictures(element, docx_obj, drawing_blip, doc)
             # Check for Text
             elif tag_name in ["p"]:
+                # "tcPr", "sectPr"
                 self.handle_text_elements(element, docx_obj, doc)
             else:
                 _log.debug(f"Ignoring element in DOCX with tag: {tag_name}")
@@ -219,7 +219,6 @@ def handle_text_elements(self, element, docx_obj, doc):
         if paragraph.text is None:
             return
         text = paragraph.text.strip()
-        # if len(text)==0 # keep empty paragraphs, they seperate adjacent lists!

         # Common styles for bullet and numbered lists.
         # "List Bullet", "List Number", "List Paragraph"
@@ -291,9 +290,7 @@ def handle_text_elements(self, element, docx_obj, doc):
     def add_header(self, element, docx_obj, doc, curr_name, curr_level, text: str):
         level = self.get_level()
         if isinstance(curr_level, int):
-
             if curr_level > level:
-
                 # add invisible group
                 for i in range(level, curr_level):
                     self.parents[i] = doc.add_group(
@@ -301,9 +298,7 @@ def add_header(self, element, docx_obj, doc, curr_name, curr_level, text: str):
                         label=GroupLabel.SECTION,
                         name=f"header-{i}",
                     )
-
             elif curr_level < level:
-
                 # remove the tail
                 for key, val in self.parents.items():
                     if key >= curr_level:
@@ -314,7 +309,6 @@ def add_header(self, element, docx_obj, doc, curr_name, curr_level, text: str):
                 text=text,
                 level=curr_level,
             )
-
         else:
             self.parents[self.level] = doc.add_heading(
                 parent=self.parents[self.level - 1],
@@ -346,7 +340,7 @@ def add_listitem(
                 label=GroupLabel.LIST, name="list", parent=self.parents[level - 1]
             )

-            # TODO: Set marker and enumerated arguments if this is an enumeration element.
+            # Set marker and enumerated arguments if this is an enumeration element.
             self.listIter += 1
             if is_numbered:
                 enum_marker = str(self.listIter) + "."
@@ -365,8 +359,8 @@ def add_listitem(
                 self.level_at_new_list + self.prev_indent() + 1,
                 self.level_at_new_list + ilevel + 1,
             ):
-                # TODO: determine if this is an unordered list or an ordered list.
-                #  Set GroupLabel.ORDERED_LIST when it fits.
+                # Determine if this is an unordered list or an ordered list.
+                # Set GroupLabel.ORDERED_LIST when it fits.
                 self.listIter = 0
                 if is_numbered:
                     self.parents[i] = doc.add_group(
@@ -467,6 +461,19 @@ def get_rowspan(cell):
                 row_span = get_rowspan(cell)
                 col_span = get_colspan(cell)

+                cell_text = cell.text
+                # In case cell doesn't return text via docx library:
+                if len(cell_text) == 0:
+                    cell_xml = cell._element
+
+                    texts = [""]
+                    for elem in cell_xml.iter():
+                        if elem.tag.endswith("t"):  # <w:t> tags that contain text
+                            if elem.text:
+                                texts.append(elem.text)
+                    # Join the collected text
+                    cell_text = " ".join(texts).strip()
+
                 # Find the next available column in the grid
                 while table_grid[row_idx][col_idx] is not None:
                     col_idx += 1
@@ -477,15 +484,15 @@ def get_rowspan(cell):
                         table_grid[row_idx + i][col_idx + j] = ""

                 cell = TableCell(
-                    text=cell.text,
+                    text=cell_text,
                     row_span=row_span,
                     col_span=col_span,
                     start_row_offset_idx=row_idx,
                     end_row_offset_idx=row_idx + row_span,
                     start_col_offset_idx=col_idx,
                     end_col_offset_idx=col_idx + col_span,
-                    col_header=False,  # col_header,
-                    row_header=False,  # ((not col_header) and html_cell.name=='th')
+                    col_header=False,
+                    row_header=False,
                 )

                 data.table_cells.append(cell)

What is the meaning of `missing-text`?
### Question

When exporting docx documents as text, I always seem to get some `missing-text` in the output. I was not able to find this string in the project repository, `python-docx`, or documentation.

Snippet:

```py
doc_converter = DocumentConverter(allowed_formats=[InputFormat.DOCX])
conv_res = doc_converter.convert(input_doc_path)
print(conv_res.document.export_to_text())
```

Output:

```py
<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>

<missing-text>
```

Documents:

- Complete failure, all text is "missing-text": [doc.docx](https://github.com/user-attachments/files/17983955/doc.docx)
- Partial failure, only some of the text is "missing-text": [doc2.docx](https://github.com/user-attachments/files/17983962/doc2.docx)

Both documents are public.

What causes `missing-text`? What should be my mental model for it when processing documents?

Thanks!

@Belval, thanks for sharing with sample documents, I will check this!


diff --git a/docling/models/tesseract_ocr_cli_model.py b/docling/models/tesseract_ocr_cli_model.py
index 9a50eee0..a6b2f7fb 100644
--- a/docling/models/tesseract_ocr_cli_model.py
+++ b/docling/models/tesseract_ocr_cli_model.py
@@ -1,3 +1,4 @@
+import csv
 import io
 import logging
 import tempfile
@@ -95,7 +96,7 @@ def _run_tesseract(self, ifilename: str):
         # _log.info(decoded_data)

         # Read the TSV file generated by Tesseract
-        df = pd.read_csv(io.StringIO(decoded_data), sep="\t")
+        df = pd.read_csv(io.StringIO(decoded_data), quoting=csv.QUOTE_NONE, sep="\t")

         # Display the dataframe (optional)
         # _log.info("df: ", df.head())

pandas.errors.ParserError: Error tokenizing data. C error: EOF inside string starting at row 656
### Bug
Trying to convert a PDF I get the following error, the same options works on other PDFs.
**Seems related to `pandas.read_csv()` on the TSV output of Tesseract.**

```
Encountered an error during conversion of document b137be2685712845d8afee55fe6327d2901290f9a852a25b3f7b19010df64e10:
Traceback (most recent call last):

  File ".../docling/pipeline/base_pipeline.py", line 149, in _build_document
    for p in pipeline_pages:  # Must exhaust!
             ^^^^^^^^^^^^^^

  File ".../docling/pipeline/base_pipeline.py", line 116, in _apply_on_pages
    yield from page_batch

  File ".../docling/models/page_assemble_model.py", line 59, in __call__
    for page in page_batch:
                ^^^^^^^^^^

  File ".../docling/models/table_structure_model.py", line 93, in __call__
    for page in page_batch:
                ^^^^^^^^^^

  File ".../docling/models/layout_model.py", line 281, in __call__
    for page in page_batch:
                ^^^^^^^^^^

  File ".../docling/models/tesseract_ocr_cli_model.py", line 140, in __call__
    df = self._run_tesseract(fname)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^

  File ".../docling/models/tesseract_ocr_cli_model.py", line 98, in _run_tesseract
    df = pd.read_csv(io.StringIO(decoded_data), sep="\t")
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File ".../pandas/io/parsers/readers.py", line 1026, in read_csv
    return _read(filepath_or_buffer, kwds)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File ".../pandas/io/parsers/readers.py", line 626, in _read
    return parser.read(nrows)
           ^^^^^^^^^^^^^^^^^^

  File ".../pandas/io/parsers/readers.py", line 1923, in read
    ) = self._engine.read(  # type: ignore[attr-defined]
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File ".../pandas/io/parsers/c_parser_wrapper.py", line 234, in read
    chunks = self._reader.read_low_memory(nrows)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "parsers.pyx", line 838, in pandas._libs.parsers.TextReader.read_low_memory

  File "parsers.pyx", line 905, in pandas._libs.parsers.TextReader._read_rows

  File "parsers.pyx", line 874, in pandas._libs.parsers.TextReader._tokenize_rows

  File "parsers.pyx", line 891, in pandas._libs.parsers.TextReader._check_tokenize_status

  File "parsers.pyx", line 2061, in pandas._libs.parsers.raise_parser_error

pandas.errors.ParserError: Error tokenizing data. C error: EOF inside string starting at row 656
```

### Steps to reproduce
```
ocr_options = TesseractCliOcrOptions(force_full_page_ocr=True)

pipeline_options = PdfPipelineOptions()
pipeline_options.do_ocr = True
pipeline_options.do_table_structure = True
pipeline_options.table_structure_options.do_cell_matching = True
pipeline_options.ocr_options = ocr_options

converter = DocumentConverter(
    format_options={
        InputFormat.PDF: PdfFormatOption(
            pipeline_options=pipeline_options,
        )
    }
)

conv_res = converter.convert(Path(my_pdf_path))
```

### Docling version
```
Docling version: 2.5.2
Docling Core version: 2.4.0
Docling IBM Models version: 2.0.3
Docling Parse version: 2.0.4
```

### Python version
`Python 3.12.7`


diff --git a/docling/backend/msword_backend.py b/docling/backend/msword_backend.py
index 496bdb7b..05508712 100644
--- a/docling/backend/msword_backend.py
+++ b/docling/backend/msword_backend.py
@@ -507,18 +507,19 @@ def get_docx_image(element, drawing_blip):

         image_data = get_docx_image(element, drawing_blip)
         image_bytes = BytesIO(image_data)
+        level = self.get_level()
         # Open the BytesIO object with PIL to create an Image
         try:
             pil_image = Image.open(image_bytes)
             doc.add_picture(
-                parent=self.parents[self.level],
+                parent=self.parents[level - 1],
                 image=ImageRef.from_pil(image=pil_image, dpi=72),
                 caption=None,
             )
         except (UnidentifiedImageError, OSError) as e:
             _log.warning("Warning: image cannot be loaded by Pillow")
             doc.add_picture(
-                parent=self.parents[self.level],
+                parent=self.parents[level - 1],
                 caption=None,
             )
         return

Image location in Word Document is wrong
### Bug
The image placeholder in parsed docx documents is wrong. An incorrect index is used resulting in a wrong location for images in downstream export formats like markdown.

### Steps to reproduce
Parsing a simple .docx with docling
[image_within_text.docx](https://github.com/user-attachments/files/17919742/image_within_text.docx)

### Docling version
Docling version: 2.7.0
Docling Core version: 2.4.1
Docling IBM Models version: 2.0.6
Docling Parse version: 2.1.2

### Python version
3.12.4

<!-- ⚠ ATTENTION: When sharing screenshots, attachments, or other data make sure not to include any sensitive information. -->


diff --git a/docling/cli/main.py b/docling/cli/main.py
index c95128ac..a2a86bf4 100644
--- a/docling/cli/main.py
+++ b/docling/cli/main.py
@@ -1,6 +1,7 @@
 import importlib
 import json
 import logging
+import re
 import time
 import warnings
 from enum import Enum
@@ -129,6 +130,12 @@ def export_documents(
     )

+def _split_list(raw: Optional[str]) -> Optional[List[str]]:
+    if raw is None:
+        return None
+    return re.split(r"[;,]", raw)
+
+
 @app.command(no_args_is_help=True)
 def convert(
     input_sources: Annotated[
@@ -163,6 +170,13 @@ def convert(
     ocr_engine: Annotated[
         OcrEngine, typer.Option(..., help="The OCR engine to use.")
     ] = OcrEngine.EASYOCR,
+    ocr_lang: Annotated[
+        Optional[str],
+        typer.Option(
+            ...,
+            help="Provide a comma-separated list of languages used by the OCR engine. Note that each OCR engine has different values for the language names.",
+        ),
+    ] = None,
     pdf_backend: Annotated[
         PdfBackend, typer.Option(..., help="The PDF backend to use.")
     ] = PdfBackend.DLPARSE_V1,
@@ -248,6 +262,10 @@ def convert(
         case _:
             raise RuntimeError(f"Unexpected OCR engine type {ocr_engine}")

+    ocr_lang_list = _split_list(ocr_lang)
+    if ocr_lang_list is not None:
+        ocr_options.lang = ocr_lang_list
+
     pipeline_options = PdfPipelineOptions(
         do_ocr=ocr,
         ocr_options=ocr_options,
diff --git a/docling/datamodel/pipeline_options.py b/docling/datamodel/pipeline_options.py
index 2b9d228c..6c0711cc 100644
--- a/docling/datamodel/pipeline_options.py
+++ b/docling/datamodel/pipeline_options.py
@@ -22,6 +22,7 @@ class TableStructureOptions(BaseModel):

 class OcrOptions(BaseModel):
     kind: str
+    lang: List[str]
     force_full_page_ocr: bool = False  # If enabled a full page OCR is always applied
     bitmap_area_threshold: float = (
         0.05  # percentage of the area for a bitmap to processed with OCR

Specific language for easyOCR
### Question
Well, this library perform pretty good tasks, but for some cases, the languages is incorrect (in my Vietnamese language)
Just want to ask `How can I specific language parser to make result more concise?`

the same question?
Hey, I want to work on this. Can a mentor explain me please?
"Temporary use before completion of work"
You can change language at docling/datamodel/pipeline_options.py
ocr options class is not made yet, but you can change lang: list directly and save.

class EasyOcrOptions(OcrOptions):
    kind: Literal["easyocr"] = "easyocr"
    lang: List[str] = ['ko','en']#["fr", "de", "es", "en"]
Oh i see. Thank you
EasyOCR does NOT support Hebrew. Any work-around to process Hebrew image files in Docling? Thank you.

[https://www.jaided.ai/easyocr/](https://www.jaided.ai/easyocr/)
@danhertztech Docling supports multiple OCR engines (and the possibility to also bring your own). Out-of-the-box we have already Tesseract which could cover your use case. See more here https://ds4sd.github.io/docling/installation/


diff --git a/docling/models/ds_glm_model.py b/docling/models/ds_glm_model.py
index e63bad3a..0a066bfa 100644
--- a/docling/models/ds_glm_model.py
+++ b/docling/models/ds_glm_model.py
@@ -43,7 +43,8 @@ class GlmModel:
     def __init__(self, options: GlmOptions):
         self.options = options

-        load_pretrained_nlp_models()
+        if self.options.model_names != "":
+            load_pretrained_nlp_models()
         self.model = init_nlp_model(model_names=self.options.model_names)

     def _to_legacy_document(self, conv_res) -> DsDocument:

Unable to run.
### Bug
<!-- Describe the buggy behavior you have observed. -->
PS C:\Users\genco> & C:/ProgramData/anaconda3/envs/docling/python.exe c:/Users/genco/OneDrive/Documents/marker_new/docling_convertor_testing.py
Fetching 9 files: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 9/9 [00:00<?, ?it/s]
Traceback (most recent call last):
  File "c:\Users\genco\OneDrive\Documents\marker_new\docling_convertor_testing.py", line 5, in <module>
    result = converter.convert(source)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\pydantic\validate_call_decorator.py", line 60, in wrapper_function
    return validate_call_wrapper(*args, **kwargs)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\pydantic\_internal\_validate_call.py", line 96, in __call__
    res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs))
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 161, in convert
    return next(all_res)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 180, in convert_all
    for conv_res in conv_res_iter:
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 211, in _convert
    for item in map(
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 255, in _process_document
    conv_res = self._execute_pipeline(in_doc, raises_on_error=raises_on_error)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 263, in _execute_pipeline
    pipeline = self._get_pipeline(in_doc.format)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\document_converter.py", line 244, in _get_pipeline
    self.initialized_pipelines[pipeline_class] = pipeline_class(
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\pipeline\standard_pdf_pipeline.py", line 54, in __init__
    self.glm_model = GlmModel(options=GlmOptions())
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\docling\models\ds_glm_model.py", line 46, in __init__
    load_pretrained_nlp_models()
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\deepsearch_glm\utils\load_pretrained_models.py", line 120, in load_pretrained_nlp_models
    done, data = download_items(downloads)
  File "C:\ProgramData\anaconda3\envs\docling\lib\site-packages\deepsearch_glm\utils\load_pretrained_models.py", line 50, in download_items
    with target.open("wb") as fw:
  File "C:\ProgramData\anaconda3\envs\docling\lib\pathlib.py", line 1119, in open
    return self._accessor.open(self, mode, buffering, encoding, errors,
PermissionError: [Errno 13] Permission denied: 'C:\\ProgramData\\anaconda3\\envs\\docling\\lib\\site-packages\\deepsearch_glm\\resources\\models\\crf\\part-of-speech\\crf_pos_model_en.bin'
### Steps to reproduce
<!-- Describe the sequence of steps for reproducing the bug. -->
run code:
from docling.document_converter import DocumentConverter

source = "https://arxiv.org/pdf/2408.09869"  # PDF path or URL
converter = DocumentConverter()
result = converter.convert(source)
print(result.document.export_to_markdown())  # output: "### Docling Technical Report[...]"

### Docling version
<!-- Copy the output of `docling --version`. -->
latest version.

### Python version
<!-- Copy the output of `python --version`. -->
3.10.15

<!-- ⚠ ATTENTION: When sharing screenshots, attachments, or other data make sure not to include any sensitive information. -->

@ashunaveed Can you please tell us the exact version. There should be no need to download `crf_pos_model_en.bin`.

Please run,

```
docling --version
```

We suspect that you have by chance an older version, but we want to be 100% sure.
I'm trying to run Docling on a server without internet connection so I have downloaded the layout model and tableformer from Hugging Face and then I try to run with custom artifact path as per your documentation:
```
pipeline_options = PdfPipelineOptions(artifacts_path=artifacts_path)
doc_converter = DocumentConverter(
    format_options={
        InputFormat.PDF: PdfFormatOption(pipeline_options=pipeline_options)
    }
)
```
But I get an error similar to the OP (though for me the problem is timeout due to connection error).

I have tried with these versions:
Docling version: 2.5.1
Docling Core version: 2.3.2
Docling IBM Models version: 2.0.3
Docling Parse version: 2.0.3

and an older version:
Docling version: 2.3.1
Docling Core version: 2.3.1
Docling IBM Models version: 2.0.3
Docling Parse version: 2.0.2

And it tries to download the glm files in both versions.

I'm mostly curious to understand if the GLM files are needed as your answer above indicates that, at least crf_pos_model_en.bin, shouldn't be needed at all.


diff --git a/docling/backend/mspowerpoint_backend.py b/docling/backend/mspowerpoint_backend.py
index cbec761c..b71cd859 100644
--- a/docling/backend/mspowerpoint_backend.py
+++ b/docling/backend/mspowerpoint_backend.py
@@ -358,41 +358,36 @@ def walk_linear(self, pptx_obj, doc) -> DoclingDocument:

             size = Size(width=slide_width, height=slide_height)
             parent_page = doc.add_page(page_no=slide_ind + 1, size=size)
-            # parent_page = doc.add_page(page_no=slide_ind, size=size, hash=hash)
-
-            # Loop through each shape in the slide
-            for shape in slide.shapes:

+            def handle_shapes(shape, parent_slide, slide_ind, doc):
+                handle_groups(shape, parent_slide, slide_ind, doc)
                 if shape.has_table:
                     # Handle Tables
                     self.handle_tables(shape, parent_slide, slide_ind, doc)
-
                 if shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
-                    # Handle Tables
+                    # Handle Pictures
                     self.handle_pictures(shape, parent_slide, slide_ind, doc)
-
                 # If shape doesn't have any text, move on to the next shape
                 if not hasattr(shape, "text"):
-                    continue
+                    return
                 if shape.text is None:
-                    continue
+                    return
                 if len(shape.text.strip()) == 0:
-                    continue
+                    return
                 if not shape.has_text_frame:
-                    _log.warn("Warning: shape has text but not text_frame")
-                    continue
-
-                # if shape.is_placeholder:
-                # Handle Titles (Headers) and Subtitles
-                # Check if the shape is a placeholder (titles are placeholders)
-                # self.handle_title(shape, parent_slide, slide_ind, doc)
-                # self.handle_text_elements(shape, parent_slide, slide_ind, doc)
-                # else:
-
+                    _log.warning("Warning: shape has text but not text_frame")
+                    return
                 # Handle other text elements, including lists (bullet lists, numbered lists)
                 self.handle_text_elements(shape, parent_slide, slide_ind, doc)
+                return
+
+            def handle_groups(shape, parent_slide, slide_ind, doc):
+                if shape.shape_type == MSO_SHAPE_TYPE.GROUP:
+                    for groupedshape in shape.shapes:
+                        handle_shapes(groupedshape, parent_slide, slide_ind, doc)

-                # figures...
-                # doc.add_figure(data=BaseFigureData(), parent=self.parents[self.level], caption=None)
+            # Loop through each shape in the slide
+            for shape in slide.shapes:
+                handle_shapes(shape, parent_slide, slide_ind, doc)

         return doc

In a specific PowerPoint, an issue with missing text occurred during parsing.
### Bug
<!-- In a specific PowerPoint, an issue with missing text occurred during parsing. -->
...

[specific PowerPoint]
[powerpoint_sample.pptx](https://github.com/user-attachments/files/17694015/powerpoint_sample.pptx)

...

### Python version
docling                   2.4.0
Python version: 3.12.7
...

@Crespo522 I'm working on the fix, in short - we need to handle grouped elements correctly.


diff --git a/docling/backend/html_backend.py b/docling/backend/html_backend.py
index 7d14c2eb..9cd1e29b 100644
--- a/docling/backend/html_backend.py
+++ b/docling/backend/html_backend.py
@@ -120,6 +120,8 @@ def analyse_element(self, element, idx, doc):
             self.handle_header(element, idx, doc)
         elif element.name in ["p"]:
             self.handle_paragraph(element, idx, doc)
+        elif element.name in ["pre"]:
+            self.handle_code(element, idx, doc)
         elif element.name in ["ul", "ol"]:
             self.handle_list(element, idx, doc)
         elif element.name in ["li"]:
@@ -205,6 +207,16 @@ def handle_header(self, element, idx, doc):
                 level=hlevel,
             )

+    def handle_code(self, element, idx, doc):
+        """Handles monospace code snippets (pre)."""
+        if element.text is None:
+            return
+        text = element.text.strip()
+        label = DocItemLabel.CODE
+        if len(text) == 0:
+            return
+        doc.add_text(parent=self.parents[self.level], label=label, text=text)
+
     def handle_paragraph(self, element, idx, doc):
         """Handles paragraph tags (p)."""
         if element.text is None:

Unable to extract code block in HTML page
When I try to extract the content from a webpage using ```docling```,  I found it cannot extract **code blocks** in the webpage.

# Reproduce steps

HTML URL: https://requests.readthedocs.io/en/latest/user/quickstart/

```python
from docling.document_converter import DocumentConverter

converter = DocumentConverter()
result = converter.convert('https://requests.readthedocs.io/en/latest/user/quickstart/')
print(result.document.export_to_markdown())
````
The code blocks in the following picture cannot be extracted in the result markdown:
<img width="754" alt="image" src="https://github.com/user-attachments/assets/0175ddce-7516-4786-bdc7-95c3e830fad2">

The result markdown of this part in the above picture is :

```markdown
## Make a Request¶

Making a request with Requests is very simple.

Begin by importing the Requests module:

Now, let’s try to get a webpage. For this example, let’s get GitHub’s public
timeline:

Now, we have a Response object called r. We can
get all the information we need from this object.

Requests’ simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request:

Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple:

That’s all well and good, but it’s also only the start of what Requests can
do.

```


diff --git a/tools/finish_release.py b/tools/finish_release.py
index 958d7672bc..56b92d2a1d 100755
--- a/tools/finish_release.py
+++ b/tools/finish_release.py
@@ -111,7 +111,7 @@ def get_snap_revisions(snap, channel, version):
     print('Getting revision numbers for', snap, version)
     cmd = ['snapcraft', 'status', snap]
     process = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, universal_newlines=True)
-    pattern = f'^\s+{channel}\s+{version}\s+(\d+)\s*'
+    pattern = f'^\\s+{channel}\\s+{version}\\s+(\\d+)\\s*'
     revisions = re.findall(pattern, process.stdout, re.MULTILINE)
     assert len(revisions) == SNAP_ARCH_COUNT, f'Unexpected number of snaps found for {channel} {snap} {version} (expected {SNAP_ARCH_COUNT}, found {len(revisions)})'
     return revisions

Fix regex in finish_release.py
```
(venv) certbot [3.0.0] » python3 tools/finish_release.py
certbot/tools/finish_release.py:114: SyntaxWarning: invalid escape sequence '\s'
  pattern = f'^\s+{channel}\s+{version}\s+(\d+)\s*'
certbot/tools/finish_release.py:114: SyntaxWarning: invalid escape sequence '\s'
  pattern = f'^\s+{channel}\s+{version}\s+(\d+)\s*'
certbot/tools/finish_release.py:114: SyntaxWarning: invalid escape sequence '\s'
  pattern = f'^\s+{channel}\s+{version}\s+(\d+)\s*'
```


diff --git a/ninja/responses.py b/ninja/responses.py
index babd366e..6a0fd4ca 100644
--- a/ninja/responses.py
+++ b/ninja/responses.py
@@ -1,10 +1,11 @@
 from enum import Enum
-from ipaddress import IPv4Address, IPv6Address
+from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
 from typing import Any, FrozenSet

 from django.core.serializers.json import DjangoJSONEncoder
 from django.http import JsonResponse
 from pydantic import BaseModel
+from pydantic_core import Url

 __all__ = [
     "NinjaJSONEncoder",
@@ -21,7 +22,9 @@ class NinjaJSONEncoder(DjangoJSONEncoder):
     def default(self, o: Any) -> Any:
         if isinstance(o, BaseModel):
             return o.model_dump()
-        if isinstance(o, (IPv4Address, IPv6Address)):
+        if isinstance(o, Url):
+            return str(o)
+        if isinstance(o, (IPv4Address, IPv4Network, IPv6Address, IPv6Network)):
             return str(o)
         if isinstance(o, Enum):
             return str(o)

[BUG] Object of type Url is not JSON serializable
**Describe the bug**
django-ninja = "^1.3.0"

Using `HttpUrl` (or, I suspect, any *Url class) for a schema used in a response results in json serialization error. This is the same type of issue as #717.

```pytb
Traceback (most recent call last):
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/ninja/operation.py", line 121, in run
    return self._result_to_response(request, result, temporal_response)
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/ninja/operation.py", line 278, in _result_to_response
    return self.api.create_response(
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/ninja/main.py", line 453, in create_response
    content = self.renderer.render(request, data, response_status=status)
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/ninja/renderers.py", line 25, in render
    return json.dumps(data, cls=self.encoder_class, **self.json_dumps_params)
  File "/usr/lib/python3.10/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.10/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.10/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/ninja/responses.py", line 28, in default
    return super().default(o)
  File "/home/adys/.cache/pypoetry/virtualenvs/fabrile-backend-fklT6p4J-py3.10/lib/python3.10/site-packages/django/core/serializers/json.py", line 106, in default
    return super().default(o)
  File "/usr/lib/python3.10/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Url is not JSON serializable
```


diff --git a/pandas/io/sql.py b/pandas/io/sql.py
index 3c0c5cc64c24c..5652d7fab0c7c 100644
--- a/pandas/io/sql.py
+++ b/pandas/io/sql.py
@@ -241,7 +241,7 @@ def read_sql_table(  # pyright: ignore[reportOverlappingOverload]
     schema=...,
     index_col: str | list[str] | None = ...,
     coerce_float=...,
-    parse_dates: list[str] | dict[str, str] | None = ...,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = ...,
     columns: list[str] | None = ...,
     chunksize: None = ...,
     dtype_backend: DtypeBackend | lib.NoDefault = ...,
@@ -255,7 +255,7 @@ def read_sql_table(
     schema=...,
     index_col: str | list[str] | None = ...,
     coerce_float=...,
-    parse_dates: list[str] | dict[str, str] | None = ...,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = ...,
     columns: list[str] | None = ...,
     chunksize: int = ...,
     dtype_backend: DtypeBackend | lib.NoDefault = ...,
@@ -268,7 +268,7 @@ def read_sql_table(
     schema: str | None = None,
     index_col: str | list[str] | None = None,
     coerce_float: bool = True,
-    parse_dates: list[str] | dict[str, str] | None = None,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = None,
     columns: list[str] | None = None,
     chunksize: int | None = None,
     dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
@@ -372,7 +372,7 @@ def read_sql_query(  # pyright: ignore[reportOverlappingOverload]
     index_col: str | list[str] | None = ...,
     coerce_float=...,
     params: list[Any] | Mapping[str, Any] | None = ...,
-    parse_dates: list[str] | dict[str, str] | None = ...,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = ...,
     chunksize: None = ...,
     dtype: DtypeArg | None = ...,
     dtype_backend: DtypeBackend | lib.NoDefault = ...,
@@ -386,7 +386,7 @@ def read_sql_query(
     index_col: str | list[str] | None = ...,
     coerce_float=...,
     params: list[Any] | Mapping[str, Any] | None = ...,
-    parse_dates: list[str] | dict[str, str] | None = ...,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = ...,
     chunksize: int = ...,
     dtype: DtypeArg | None = ...,
     dtype_backend: DtypeBackend | lib.NoDefault = ...,
@@ -399,7 +399,7 @@ def read_sql_query(
     index_col: str | list[str] | None = None,
     coerce_float: bool = True,
     params: list[Any] | Mapping[str, Any] | None = None,
-    parse_dates: list[str] | dict[str, str] | None = None,
+    parse_dates: list[str] | dict[str, str] | dict[str, dict[str, Any]] | None = None,
     chunksize: int | None = None,
     dtype: DtypeArg | None = None,
     dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,

BUG: Type Annotation Inconsistency in read_sql_* Functions
### Pandas version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the [latest version](https://pandas.pydata.org/docs/whatsnew/index.html) of pandas.

- [X] I have confirmed this bug exists on the [main branch](https://pandas.pydata.org/docs/dev/getting_started/install.html#installing-the-development-version-of-pandas) of pandas.

### Reproducible Example

```python
import pandas as pd
import sqlite3

date_params = {"date_col": {"utc": True}}

with sqlite3.connect("blah") as con:
    # Fails type check.
    df = pd.read_sql_query("SELECT * FROM tablename", con, parse_dates=date_params)
    print(df)
```

### Issue Description

The pandas type annotations for the `parse_dates` argument in `read_sql_table()` and `read_sql_query()` is overly restrictive. It incorrectly causes type checkers to complain when using the `parse_dates` argument to pass keyword arguments to `to_datetime()` as documented [here](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_sql_query.html).

To solve this problem, the annotated type for `parse_date` just needs to be switched from `parse_dates: list[str] | dict[str, str] | None` to `list[str] | dict[str, str] | dict[str, dict[str, Any]] | None`.

This problem is not always visible because the corresponding `pandas-stubs` already does this. The inconsistency appears however in some type checkers when additional stubs are not available or configured though.

To illustrate, take the provided (valid) example and run `pyright` on it (with no arguments). It will output the following.

```
(bug_venv)$ pyright example.py
/home/user/Code/pandas_bug/example.py
/home/user/Code/pandas_bug/example.py:8:10 - error: No overloads for "read_sql_query" match the provided arguments (reportCallIssue)
/home/user/Code/pandas_bug/example.py:8:72 - error: Argument of type "dict[str, dict[str, bool]]" cannot be assigned to parameter "parse_dates" of type "list[str] |dict[str, str] | None" in function "read_sql_query"
Type "dict[str, dict[str, bool]]" is not assignable to type "list[str] | dict[str, str] | None"
"dict[str, dict[str, bool]]" is not assignable to "list[str]"
"dict[str, dict[str, bool]]" is not assignable to "dict[str, str]"
Type parameter "_VT@dict" is invariant, but "dict[str, bool]" is not the same as "str"
Consider switching from "dict" to "Mapping" which is covariant in the value type
"dict[str, dict[str, bool]]" is not assignable to "None" (reportArgumentType)
2 errors, 0 warnings, 0 informations
```

I am more than happy to submit a pull request for this is desired, but thought it best to put in this issue first in case I am missing something.

### Expected Behavior

import pandas as pd
import sqlite3

date_params = {"date_col": {"utc": True}}

with sqlite3.connect("blah") as con:
    # Type checks correctly
    df = pd.read_sql_query("SELECT * FROM tablename", con, parse_dates=date_params)
    print(df)

### Installed Versions

<details>

INSTALLED VERSIONS
------------------
commit                : 0691c5cf90477d3503834d983f69350f250a6ff7
python                : 3.12.6
python-bits           : 64
OS                    : Linux
OS-release            : 6.11.2-arch1-1
Version               : #1 SMP PREEMPT_DYNAMIC Fri, 04 Oct 2024 21:51:11 +0000
machine               : x86_64
processor             :
byteorder             : little
LC_ALL                : None
LANG                  : en_US.UTF-8
LOCALE                : en_US.UTF-8

pandas                : 2.2.3
numpy                 : 2.1.2
pytz                  : 2024.2
dateutil              : 2.9.0.post0
pip                   : 24.2
Cython                : None
sphinx                : None
IPython               : None
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : None
blosc                 : None
bottleneck            : None
dataframe-api-compat  : None
fastparquet           : None
fsspec                : None
html5lib              : None
hypothesis            : None
gcsfs                 : None
jinja2                : None
lxml.etree            : None
matplotlib            : None
numba                 : None
numexpr               : None
odfpy                 : None
openpyxl              : None
pandas_gbq            : None
psycopg2              : None
pymysql               : None
pyarrow               : None
pyreadstat            : None
pytest                : None
python-calamine       : None
pyxlsb                : None
s3fs                  : None
scipy                 : None
sqlalchemy            : None
tables                : None
tabulate              : None
xarray                : None
xlrd                  : None
xlsxwriter            : None
zstandard             : None
tzdata                : 2024.2
qtpy                  : None
pyqt5                 : None
</details>

Thanks for the report!

> This problem is not always visible because the corresponding `pandas-stubs` already does this. The inconsistency appears however in some type checkers when additional stubs are not available or configured though.

It seems to me this is not appropriate. PEP 561 makes this quite clear I think:

> Package maintainers who wish to support type checking of their code MUST add a marker file named py.typed to their package supporting typing.

Since pandas does not have a `py.typed` file, its type-hints should not be considered public. I only mention this to say that I think pandas should not be obligated to spend unnecessary effort in order to support third parties that use its internal type-hints.

Of course, in cases where the change would benefit pandas internal typing (as is the case here I believe), PRs are welcome!


diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py
index 6fa21d9410187..b0c8ec1ffc083 100644
--- a/pandas/core/dtypes/common.py
+++ b/pandas/core/dtypes/common.py
@@ -430,7 +430,7 @@ def is_period_dtype(arr_or_dtype) -> bool:
     Check whether an array-like or dtype is of the Period dtype.

     .. deprecated:: 2.2.0
-        Use isinstance(dtype, pd.Period) instead.
+        Use isinstance(dtype, pd.PeriodDtype) instead.

     Parameters
     ----------

DOC: Incorrect deprecation example for `is_period_dtype`
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)

### Location of the documentation

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.api.types.is_period_dtype.html#pandas.api.types.is_period_dtype

### Documentation problem

Suggests the user use `isinstance(dtype, pd.Period)` instead, when they really need to use `Use isinstance(dtype, pd.PeriodDtype)`

### Suggested fix for documentation

Update message to reference correct class


diff --git a/pandas/core/computation/expressions.py b/pandas/core/computation/expressions.py
index e2acd9a2c97c2..a2c3a706ae29c 100644
--- a/pandas/core/computation/expressions.py
+++ b/pandas/core/computation/expressions.py
@@ -65,23 +65,23 @@ def set_numexpr_threads(n=None) -> None:
         ne.set_num_threads(n)

-def _evaluate_standard(op, op_str, a, b):
+def _evaluate_standard(op, op_str, left_op, right_op):
     """
     Standard evaluation.
     """
     if _TEST_MODE:
         _store_test_result(False)
-    return op(a, b)
+    return op(left_op, right_op)

-def _can_use_numexpr(op, op_str, a, b, dtype_check) -> bool:
-    """return a boolean if we WILL be using numexpr"""
+def _can_use_numexpr(op, op_str, left_op, right_op, dtype_check) -> bool:
+    """return left_op boolean if we WILL be using numexpr"""
     if op_str is not None:
         # required min elements (otherwise we are adding overhead)
-        if a.size > _MIN_ELEMENTS:
+        if left_op.size > _MIN_ELEMENTS:
             # check for dtype compatibility
             dtypes: set[str] = set()
-            for o in [a, b]:
+            for o in [left_op, right_op]:
                 # ndarray and Series Case
                 if hasattr(o, "dtype"):
                     dtypes |= {o.dtype.name}
@@ -93,22 +93,22 @@ def _can_use_numexpr(op, op_str, a, b, dtype_check) -> bool:
     return False

-def _evaluate_numexpr(op, op_str, a, b):
+def _evaluate_numexpr(op, op_str, left_op, right_op):
     result = None

-    if _can_use_numexpr(op, op_str, a, b, "evaluate"):
+    if _can_use_numexpr(op, op_str, left_op, right_op, "evaluate"):
         is_reversed = op.__name__.strip("_").startswith("r")
         if is_reversed:
             # we were originally called by a reversed op method
-            a, b = b, a
+            left_op, right_op = right_op, left_op

-        a_value = a
-        b_value = b
+        left_value = left_op
+        right_value = right_op

         try:
             result = ne.evaluate(
-                f"a_value {op_str} b_value",
-                local_dict={"a_value": a_value, "b_value": b_value},
+                f"left_value {op_str} right_value",
+                local_dict={"left_value": left_value, "right_value": right_op},
                 casting="safe",
             )
         except TypeError:
@@ -116,20 +116,20 @@ def _evaluate_numexpr(op, op_str, a, b):
             # (https://github.com/pydata/numexpr/issues/379)
             pass
         except NotImplementedError:
-            if _bool_arith_fallback(op_str, a, b):
+            if _bool_arith_fallback(op_str, left_op, right_op):
                 pass
             else:
                 raise

         if is_reversed:
             # reverse order to original for fallback
-            a, b = b, a
+            left_op, right_op = right_op, left_op

     if _TEST_MODE:
         _store_test_result(result is not None)

     if result is None:
-        result = _evaluate_standard(op, op_str, a, b)
+        result = _evaluate_standard(op, op_str, left_op, right_op)

     return result

@@ -170,24 +170,24 @@ def _evaluate_numexpr(op, op_str, a, b):
 }

-def _where_standard(cond, a, b):
+def _where_standard(cond, left_op, right_op):
     # Caller is responsible for extracting ndarray if necessary
-    return np.where(cond, a, b)
+    return np.where(cond, left_op, right_op)

-def _where_numexpr(cond, a, b):
+def _where_numexpr(cond, left_op, right_op):
     # Caller is responsible for extracting ndarray if necessary
     result = None

-    if _can_use_numexpr(None, "where", a, b, "where"):
+    if _can_use_numexpr(None, "where", left_op, right_op, "where"):
         result = ne.evaluate(
             "where(cond_value, a_value, b_value)",
-            local_dict={"cond_value": cond, "a_value": a, "b_value": b},
+            local_dict={"cond_value": cond, "a_value": left_op, "b_value": right_op},
             casting="safe",
         )

     if result is None:
-        result = _where_standard(cond, a, b)
+        result = _where_standard(cond, left_op, right_op)

     return result

@@ -206,13 +206,13 @@ def _has_bool_dtype(x):
 _BOOL_OP_UNSUPPORTED = {"+": "|", "*": "&", "-": "^"}

-def _bool_arith_fallback(op_str, a, b) -> bool:
+def _bool_arith_fallback(op_str, left_op, right_op) -> bool:
     """
     Check if we should fallback to the python `_evaluate_standard` in case
     of an unsupported operation by numexpr, which is the case for some
     boolean ops.
     """
-    if _has_bool_dtype(a) and _has_bool_dtype(b):
+    if _has_bool_dtype(left_op) and _has_bool_dtype(right_op):
         if op_str in _BOOL_OP_UNSUPPORTED:
             warnings.warn(
                 f"evaluating in Python space because the {op_str!r} "
@@ -224,15 +224,15 @@ def _bool_arith_fallback(op_str, a, b) -> bool:
     return False

-def evaluate(op, a, b, use_numexpr: bool = True):
+def evaluate(op, left_op, right_op, use_numexpr: bool = True):
     """
-    Evaluate and return the expression of the op on a and b.
+    Evaluate and return the expression of the op on left_op and right_op.

     Parameters
     ----------
     op : the actual operand
-    a : left operand
-    b : right operand
+    left_op : left operand
+    right_op : right operand
     use_numexpr : bool, default True
         Whether to try to use numexpr.
     """
@@ -240,24 +240,24 @@ def evaluate(op, a, b, use_numexpr: bool = True):
     if op_str is not None:
         if use_numexpr:
             # error: "None" not callable
-            return _evaluate(op, op_str, a, b)  # type: ignore[misc]
-    return _evaluate_standard(op, op_str, a, b)
+            return _evaluate(op, op_str, left_op, right_op)  # type: ignore[misc]
+    return _evaluate_standard(op, op_str, left_op, right_op)

-def where(cond, a, b, use_numexpr: bool = True):
+def where(cond, left_op, right_op, use_numexpr: bool = True):
     """
-    Evaluate the where condition cond on a and b.
+    Evaluate the where condition cond on left_op and right_op.

     Parameters
     ----------
     cond : np.ndarray[bool]
-    a : return if cond is True
-    b : return if cond is False
+    left_op : return if cond is True
+    right_op : return if cond is False
     use_numexpr : bool, default True
         Whether to try to use numexpr.
     """
     assert _where is not None
-    return _where(cond, a, b) if use_numexpr else _where_standard(cond, a, b)
+    return _where(cond, left_op, right_op) if use_numexpr else _where_standard(cond, left_op, right_op)

 def set_test_mode(v: bool = True) -> None:

DOC: Update variables a and b to names consistent with comment documentation
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)

### Location of the documentation

https://github.com/pandas-dev/pandas/blob/main/pandas/core/computation/expressions.py

### Documentation problem

Lines 234 and 235 explain what a and b are in detail (left and right operands), but there are many of those same variables earlier in the file, making it harder to understand what they represent.

### Suggested fix for documentation

Assuming a and b represent right and left operands throughout each function, change these variable names to right_op and left_op instead throughout all functions to have more descriptive variable names


diff --git a/pandas/core/computation/pytables.py b/pandas/core/computation/pytables.py
index fe7e27f537b01..4a75acce46632 100644
--- a/pandas/core/computation/pytables.py
+++ b/pandas/core/computation/pytables.py
@@ -205,7 +205,7 @@ def generate(self, v) -> str:
         val = v.tostring(self.encoding)
         return f"({self.lhs} {self.op} {val})"

-    def convert_value(self, v) -> TermValue:
+    def convert_value(self, conv_val) -> TermValue:
         """
         convert the expression that is in the term to something that is
         accepted by pytables
@@ -219,44 +219,44 @@ def stringify(value):
         kind = ensure_decoded(self.kind)
         meta = ensure_decoded(self.meta)
         if kind == "datetime" or (kind and kind.startswith("datetime64")):
-            if isinstance(v, (int, float)):
-                v = stringify(v)
-            v = ensure_decoded(v)
-            v = Timestamp(v).as_unit("ns")
-            if v.tz is not None:
-                v = v.tz_convert("UTC")
-            return TermValue(v, v._value, kind)
+            if isinstance(conv_val, (int, float)):
+                conv_val = stringify(conv_val)
+            conv_val = ensure_decoded(conv_val)
+            conv_val = Timestamp(conv_val).as_unit("ns")
+            if conv_val.tz is not None:
+                conv_val = conv_val.tz_convert("UTC")
+            return TermValue(conv_val, conv_val._value, kind)
         elif kind in ("timedelta64", "timedelta"):
-            if isinstance(v, str):
-                v = Timedelta(v)
+            if isinstance(conv_val, str):
+                conv_val = Timedelta(conv_val)
             else:
-                v = Timedelta(v, unit="s")
-            v = v.as_unit("ns")._value
-            return TermValue(int(v), v, kind)
+                conv_val = Timedelta(conv_val, unit="s")
+            conv_val = conv_val.as_unit("ns")._value
+            return TermValue(int(conv_val), conv_val, kind)
         elif meta == "category":
             metadata = extract_array(self.metadata, extract_numpy=True)
             result: npt.NDArray[np.intp] | np.intp | int
-            if v not in metadata:
+            if conv_val not in metadata:
                 result = -1
             else:
-                result = metadata.searchsorted(v, side="left")
+                result = metadata.searchsorted(conv_val, side="left")
             return TermValue(result, result, "integer")
         elif kind == "integer":
             try:
-                v_dec = Decimal(v)
+                v_dec = Decimal(conv_val)
             except InvalidOperation:
                 # GH 54186
                 # convert v to float to raise float's ValueError
-                float(v)
+                float(conv_val)
             else:
-                v = int(v_dec.to_integral_exact(rounding="ROUND_HALF_EVEN"))
-            return TermValue(v, v, kind)
+                conv_val = int(v_dec.to_integral_exact(rounding="ROUND_HALF_EVEN"))
+            return TermValue(conv_val, conv_val, kind)
         elif kind == "float":
-            v = float(v)
-            return TermValue(v, v, kind)
+            conv_val = float(conv_val)
+            return TermValue(conv_val, conv_val, kind)
         elif kind == "bool":
-            if isinstance(v, str):
-                v = v.strip().lower() not in [
+            if isinstance(conv_val, str):
+                conv_val = conv_val.strip().lower() not in [
                     "false",
                     "f",
                     "no",
@@ -268,13 +268,13 @@ def stringify(value):
                     "",
                 ]
             else:
-                v = bool(v)
-            return TermValue(v, v, kind)
-        elif isinstance(v, str):
+                conv_val = bool(conv_val)
+            return TermValue(conv_val, conv_val, kind)
+        elif isinstance(conv_val, str):
             # string quoting
-            return TermValue(v, stringify(v), "string")
+            return TermValue(conv_val, stringify(conv_val), "string")
         else:
-            raise TypeError(f"Cannot compare {v} of type {type(v)} to {kind} column")
+            raise TypeError(f"Cannot compare {conv_val} of type {type(conv_val)} to {kind} column")

     def convert_values(self) -> None:
         pass

DOC: Convert v to conv_val in function for pytables.py
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)

### Location of the documentation

pandas\pandas\core\computation\pytables.py

### Documentation problem

Many instances of just v in this function. Wanted to clarify throughout

### Suggested fix for documentation

Change v to conv_val


diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index d1aa20501b060..de7fb3682fb4f 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -665,7 +665,7 @@ def size(self) -> int:

         See Also
         --------
-        ndarray.size : Number of elements in the array.
+        numpy.ndarray.size : Number of elements in the array.

         Examples
         --------

DOC: methods in see also section in the  pandas.DataFrame.size  are not hyperlinks
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)

### Location of the documentation

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.size.html

### Documentation problem

In the see also section the `ndarray.size` method is listed, but it is not hyperlinks and thus the reader cannot navigate with ease but has to look for them instead.

### Suggested fix for documentation

Add numpy.ndarray.size in the docstring.