index-now-for-python 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2025 – present, Jakob Bagterp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ include README.md
2
+ include LICENSE.md
3
+ include pyproject.toml
4
+ include setup.cfg
5
+
6
+ graft src
7
+
8
+ global-exclude __pycache__
9
+ global-exclude *.py[co]
10
+ global-exclude .DS_Store
11
+ global-exclude conftest.py
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: index-now-for-python
3
+ Version: 0.1.0
4
+ Summary: Lightweight Python package that makes it easy to submit URLs to the IndexNow API of various search engines
5
+ Home-page: https://jakob-bagterp.github.io/index-now-for-python/
6
+ Download-URL: https://pypi.org/project/index-now-for-python/
7
+ Author: Jakob Bagterp
8
+ Author-email: jakob_bagterp@hotmail.com
9
+ Maintainer: Jakob Bagterp
10
+ Maintainer-email: jakob_bagterp@hotmail.com
11
+ License: MIT License
12
+ Project-URL: Bug Tracker, https://github.com/jakob-bagterp/index-now-for-python/issues
13
+ Project-URL: Documentation, https://jakob-bagterp.github.io/index-now-for-python/
14
+ Project-URL: API Reference, https://jakob-bagterp.github.io/index-now-for-python/reference/
15
+ Project-URL: Source Code, https://github.com/jakob-bagterp/index-now-for-python
16
+ Project-URL: Release Notes, https://github.com/jakob-bagterp/index-now-for-python/releases
17
+ Keywords: python,timer
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Operating System :: OS Independent
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE.md
28
+ Requires-Dist: colorist==1.8.3
29
+ Requires-Dist: lxml==5.4.0
30
+ Requires-Dist: requests==2.32.3
31
+ Provides-Extra: testing
32
+ Requires-Dist: coverage==7.8.0; extra == "testing"
33
+ Requires-Dist: flake8==7.2.0; extra == "testing"
34
+ Requires-Dist: mypy==1.15.0; extra == "testing"
35
+ Requires-Dist: pytest==8.3.5; extra == "testing"
36
+ Requires-Dist: pytest-cov==6.0.0; extra == "testing"
37
+ Requires-Dist: tox==4.25.0; extra == "testing"
38
+ Dynamic: download-url
39
+ Dynamic: license-file
40
+
41
+ [![Latest version](https://img.shields.io/static/v1?label=version&message=0.1.0&color=yellowgreen)](https://github.com/jakob-bagterp/index-now-for-python/releases/latest)
42
+ [![Python 3.10 | 3.11 | 3.12 | 3.13+](https://img.shields.io/static/v1?label=python&message=3.10%20|%203.11%20|%203.12%20|%203.13%2B&color=blueviolet)](https://www.python.org)
43
+ [![MIT license](https://img.shields.io/static/v1?label=license&message=MIT&color=blue)](https://github.com/jakob-bagterp/index-now-for-python/blob/master/LICENSE.md)
44
+ [![Codecov](https://codecov.io/gh/jakob-bagterp/index-now-for-python/branch/master/graph/badge.svg?token=SGVMPJ1JWI)](https://codecov.io/gh/jakob-bagterp/index-now-for-python)
45
+ [![CodeQL](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml)
46
+ [![Test](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml)
47
+ [![Downloads](https://static.pepy.tech/badge/index-now-for-python)](https://pepy.tech/project/index-now-for-python)
48
+
49
+ # 🔍 Submit URLs to the IndexNow API of Various Search Enginges 🔎
50
+ Are you concerned about search engine optimization (SEO)? Do you want to make sure your website is indexed frequently by [Bing](https://www.bing.com/indexnow), [Yandex](https://yandex.com/indexnow), and other search engines?
51
+
52
+ IndexNow for Python is a lightweight Python package that makes it easy to submit URLs or entire sitemaps to the IndexNow API of various search engines, so your pages can be indexed faster.
53
+
54
+ Ready to try? Learn [how to install](https://jakob-bagterp.github.io/index-now-for-python/getting-started/installation/) and find tutorials in the [user guide](https://jakob-bagterp.github.io/index-now-for-python/user-guide/).
55
+
56
+ ## Getting Started
57
+ ### Basic Usage and Submit Individual URLs
58
+ Firstly, ensure that you have an [API key for IndexNow](https://www.indexnow.org/api-key). Hereafter, add your authentication details to the `IndexNowAuthentication` class, which will be used throughout the examples:
59
+
60
+ ```python
61
+ from index_now import submit_url_to_index_now, IndexNowAuthentication
62
+
63
+ authentication = IndexNowAuthentication(
64
+ host="example.com",
65
+ api_key="a1b2c3d4",
66
+ api_key_location="https://example.com/a1b2c3d4.txt",
67
+ )
68
+ ```
69
+
70
+ You can now submit individual URLs to the IndexNow API:
71
+
72
+ ```python
73
+ submit_url_to_index_now(authentication, "https://example.com/page1")
74
+ ```
75
+
76
+ ### Submit Multiple URLs in Bulk
77
+ How to submit multiple URLs in bulk to the IndexNow API:
78
+
79
+ ```python
80
+ from index_now import submit_urls_to_index_now, IndexNowAuthentication
81
+
82
+ authentication = IndexNowAuthentication(...)
83
+
84
+ urls = [
85
+ "https://example.com/page1",
86
+ "https://example.com/page2",
87
+ "https://example.com/page3",
88
+ ]
89
+
90
+ submit_urls_to_index_now(authentication, urls)
91
+ ```
92
+
93
+ ### Submit Entire Sitemap
94
+ How to submit an entire sitemap to the IndexNow API:
95
+
96
+ ```python
97
+ from index_now import submit_sitemap_to_index_now, IndexNowAuthentication
98
+
99
+ authentication = IndexNowAuthentication(...)
100
+
101
+ sitemap_url = "https://example.com/sitemap.xml"
102
+
103
+ submit_sitemap_to_index_now(authentication, sitemap_url)
104
+ ```
105
+
106
+ ### Submit to Specific Search Engines
107
+ How to use the default `SearchEngineEndpoint` options or a custom endpoint:
108
+
109
+ ```python
110
+ from index_now import submit_url_to_index_now, IndexNowAuthentication, SearchEngineEndpoint
111
+
112
+ authentication = IndexNowAuthentication(...)
113
+
114
+ endpoint_bing = SearchEngineEndpoint.MICROSOFT_BING
115
+ endpoint_custom = "https://example.com/indexnow"
116
+
117
+ for endpoint in [endpoint_bing, endpoint_custom]:
118
+ submit_url_to_index_now(authentication, "https://example.com/page1", endpoint)
119
+ ```
120
+
121
+ ## Become a Sponsor 🏅
122
+ If you find this project helpful, please consider supporting its development. Your donations will help keep it alive and growing. Every contribution, no matter the size, makes a difference.
123
+
124
+ [Donate on GitHub Sponsors](https://github.com/sponsors/jakob-bagterp)
125
+
126
+ Thank you for your support! 🙌
127
+
128
+ ## Contribute
129
+ If you have suggestions or changes to the module, feel free to add to the code and create a [pull request](https://github.com/jakob-bagterp/index-now-for-python/pulls).
130
+
131
+ ## Report Bugs
132
+ Report bugs and issues [here](https://github.com/jakob-bagterp/index-now-for-python/issues).
133
+
134
+ # MIT License
135
+
136
+ Copyright (c) 2025 – present, Jakob Bagterp
137
+
138
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
139
+
140
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
141
+
142
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,92 @@
1
+ [![Latest version](https://img.shields.io/static/v1?label=version&message=0.1.0&color=yellowgreen)](https://github.com/jakob-bagterp/index-now-for-python/releases/latest)
2
+ [![Python 3.10 | 3.11 | 3.12 | 3.13+](https://img.shields.io/static/v1?label=python&message=3.10%20|%203.11%20|%203.12%20|%203.13%2B&color=blueviolet)](https://www.python.org)
3
+ [![MIT license](https://img.shields.io/static/v1?label=license&message=MIT&color=blue)](https://github.com/jakob-bagterp/index-now-for-python/blob/master/LICENSE.md)
4
+ [![Codecov](https://codecov.io/gh/jakob-bagterp/index-now-for-python/branch/master/graph/badge.svg?token=SGVMPJ1JWI)](https://codecov.io/gh/jakob-bagterp/index-now-for-python)
5
+ [![CodeQL](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml)
6
+ [![Test](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml)
7
+ [![Downloads](https://static.pepy.tech/badge/index-now-for-python)](https://pepy.tech/project/index-now-for-python)
8
+
9
+ # 🔍 Submit URLs to the IndexNow API of Various Search Enginges 🔎
10
+ Are you concerned about search engine optimization (SEO)? Do you want to make sure your website is indexed frequently by [Bing](https://www.bing.com/indexnow), [Yandex](https://yandex.com/indexnow), and other search engines?
11
+
12
+ IndexNow for Python is a lightweight Python package that makes it easy to submit URLs or entire sitemaps to the IndexNow API of various search engines, so your pages can be indexed faster.
13
+
14
+ Ready to try? Learn [how to install](https://jakob-bagterp.github.io/index-now-for-python/getting-started/installation/) and find tutorials in the [user guide](https://jakob-bagterp.github.io/index-now-for-python/user-guide/).
15
+
16
+ ## Getting Started
17
+ ### Basic Usage and Submit Individual URLs
18
+ Firstly, ensure that you have an [API key for IndexNow](https://www.indexnow.org/api-key). Hereafter, add your authentication details to the `IndexNowAuthentication` class, which will be used throughout the examples:
19
+
20
+ ```python
21
+ from index_now import submit_url_to_index_now, IndexNowAuthentication
22
+
23
+ authentication = IndexNowAuthentication(
24
+ host="example.com",
25
+ api_key="a1b2c3d4",
26
+ api_key_location="https://example.com/a1b2c3d4.txt",
27
+ )
28
+ ```
29
+
30
+ You can now submit individual URLs to the IndexNow API:
31
+
32
+ ```python
33
+ submit_url_to_index_now(authentication, "https://example.com/page1")
34
+ ```
35
+
36
+ ### Submit Multiple URLs in Bulk
37
+ How to submit multiple URLs in bulk to the IndexNow API:
38
+
39
+ ```python
40
+ from index_now import submit_urls_to_index_now, IndexNowAuthentication
41
+
42
+ authentication = IndexNowAuthentication(...)
43
+
44
+ urls = [
45
+ "https://example.com/page1",
46
+ "https://example.com/page2",
47
+ "https://example.com/page3",
48
+ ]
49
+
50
+ submit_urls_to_index_now(authentication, urls)
51
+ ```
52
+
53
+ ### Submit Entire Sitemap
54
+ How to submit an entire sitemap to the IndexNow API:
55
+
56
+ ```python
57
+ from index_now import submit_sitemap_to_index_now, IndexNowAuthentication
58
+
59
+ authentication = IndexNowAuthentication(...)
60
+
61
+ sitemap_url = "https://example.com/sitemap.xml"
62
+
63
+ submit_sitemap_to_index_now(authentication, sitemap_url)
64
+ ```
65
+
66
+ ### Submit to Specific Search Engines
67
+ How to use the default `SearchEngineEndpoint` options or a custom endpoint:
68
+
69
+ ```python
70
+ from index_now import submit_url_to_index_now, IndexNowAuthentication, SearchEngineEndpoint
71
+
72
+ authentication = IndexNowAuthentication(...)
73
+
74
+ endpoint_bing = SearchEngineEndpoint.MICROSOFT_BING
75
+ endpoint_custom = "https://example.com/indexnow"
76
+
77
+ for endpoint in [endpoint_bing, endpoint_custom]:
78
+ submit_url_to_index_now(authentication, "https://example.com/page1", endpoint)
79
+ ```
80
+
81
+ ## Become a Sponsor 🏅
82
+ If you find this project helpful, please consider supporting its development. Your donations will help keep it alive and growing. Every contribution, no matter the size, makes a difference.
83
+
84
+ [Donate on GitHub Sponsors](https://github.com/sponsors/jakob-bagterp)
85
+
86
+ Thank you for your support! 🙌
87
+
88
+ ## Contribute
89
+ If you have suggestions or changes to the module, feel free to add to the code and create a [pull request](https://github.com/jakob-bagterp/index-now-for-python/pulls).
90
+
91
+ ## Report Bugs
92
+ Report bugs and issues [here](https://github.com/jakob-bagterp/index-now-for-python/issues).
@@ -0,0 +1,6 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools==80.4.0",
4
+ "wheel"
5
+ ]
6
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,86 @@
1
+ [metadata]
2
+ name = index-now-for-python
3
+ version = 0.1.0
4
+ author = Jakob Bagterp
5
+ author_email = jakob_bagterp@hotmail.com
6
+ maintainer = Jakob Bagterp
7
+ maintainer_email = jakob_bagterp@hotmail.com
8
+ description = Lightweight Python package that makes it easy to submit URLs to the IndexNow API of various search engines
9
+ long_description = file: README.md, LICENSE.md
10
+ long_description_content_type = text/markdown
11
+ keywords =
12
+ python
13
+ timer
14
+ url = https://jakob-bagterp.github.io/index-now-for-python/
15
+ download_url = https://pypi.org/project/index-now-for-python/
16
+ project_urls =
17
+ Bug Tracker = https://github.com/jakob-bagterp/index-now-for-python/issues
18
+ Documentation = https://jakob-bagterp.github.io/index-now-for-python/
19
+ API Reference = https://jakob-bagterp.github.io/index-now-for-python/reference/
20
+ Source Code = https://github.com/jakob-bagterp/index-now-for-python
21
+ Release Notes = https://github.com/jakob-bagterp/index-now-for-python/releases
22
+ classifiers =
23
+ Programming Language :: Python :: 3
24
+ Programming Language :: Python :: 3.10
25
+ Programming Language :: Python :: 3.11
26
+ Programming Language :: Python :: 3.12
27
+ Programming Language :: Python :: 3.13
28
+ License :: OSI Approved :: MIT License
29
+ Operating System :: OS Independent
30
+ license = MIT License
31
+ license_files = LICENSE.md
32
+
33
+ [options]
34
+ package_dir =
35
+ = src
36
+ packages = find:
37
+ python_requires = >=3.10
38
+ setup_requires =
39
+ colorist==1.8.3
40
+ lxml==5.4.0
41
+ requests==2.32.3
42
+ install_requires =
43
+ colorist==1.8.3
44
+ lxml==5.4.0
45
+ requests==2.32.3
46
+ zip_safe = no
47
+ include_package_data = True
48
+
49
+ [options.extras_require]
50
+ testing =
51
+ coverage==7.8.0
52
+ flake8==7.2.0
53
+ mypy==1.15.0
54
+ pytest==8.3.5
55
+ pytest-cov==6.0.0
56
+ tox==4.25.0
57
+
58
+ [options.packages.find]
59
+ where = src
60
+
61
+ [options.package_data]
62
+ colorist = py.typed
63
+
64
+ [mypy]
65
+ mypy_path = $MYPY_CONFIG_FILE_DIR/src
66
+ disallow_any_unimported = True
67
+ no_implicit_reexport = True
68
+ show_error_codes = True
69
+ strict = True
70
+ warn_unreachable = True
71
+
72
+ [pycodestyle]
73
+ max_line_length = 120
74
+
75
+ [flake8]
76
+ ignore =
77
+ E501
78
+ exclude =
79
+ .git
80
+ __pycache__
81
+ dist
82
+
83
+ [egg_info]
84
+ tag_build =
85
+ tag_date = 0
86
+
@@ -0,0 +1,7 @@
1
+ __all__ = ["IndexNowAuthentication", "SearchEngineEndpoint", "submit_url_to_index_now", "submit_urls_to_index_now", "submit_sitemap_to_index_now"]
2
+
3
+ from .authentication import IndexNowAuthentication
4
+ from .endpoint import SearchEngineEndpoint
5
+ from .sitemap import submit_sitemap_to_index_now
6
+ from .submit import submit_url_to_index_now, submit_urls_to_index_now
7
+ from .version import __version__ # noqa
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(slots=True, frozen=True)
5
+ class IndexNowAuthentication:
6
+ """Authentication data for the IndexNow API.
7
+
8
+ Args:
9
+ host (str): The host of the website to be indexed, e.g. `example.com`.
10
+ api_key (str): The IndexNow API key, e.g. `a1b2c3d4`.
11
+ api_key_location (str): The URL of the IndexNow API key file, e.g. `https://example.com/a1b2c3d4.txt`.
12
+
13
+ Example:
14
+ Basic usage:
15
+
16
+ ```python linenums="1" hl_lines="3-7"
17
+ from index_now import submit_url_to_index_now, IndexNowAuthentication
18
+
19
+ authentication = IndexNowAuthentication(
20
+ host="example.com",
21
+ api_key="a1b2c3d4",
22
+ api_key_location="https://example.com/a1b2c3d4.txt",
23
+ )
24
+
25
+ submit_url_to_index_now(authentication, "https://example.com/page1")
26
+ ```
27
+ """
28
+
29
+ host: str
30
+ api_key: str
31
+ api_key_location: str
@@ -0,0 +1,45 @@
1
+ from enum import Enum, unique
2
+
3
+
4
+ @unique
5
+ class SearchEngineEndpoint(Enum):
6
+ """Endpoint options for the [IndexNow API](https://www.indexnow.org/faq).
7
+
8
+ Attributes:
9
+ SearchEngineEndpoint.INDEXNOW (Enum): IndexNow default endpoint.
10
+ SearchEngineEndpoint.MICROSOFT_BING (Enum): [Microsoft Bing](https://www.bing.com).
11
+ SearchEngineEndpoint.NAVER (Enum): [Naver](https://www.naver.com).
12
+ SearchEngineEndpoint.SEZNAM (Enum): [Seznam.cz](https://www.seznam.cz).
13
+ SearchEngineEndpoint.YANDEX (Enum): [Yandex](https://yandex.com).
14
+ SearchEngineEndpoint.YEP (Enum): [Yep](https://yep.com).
15
+
16
+ Example:
17
+ How to submit a URL to the IndexNow API using different endpoint options or a custom endpoint:
18
+
19
+ ```python linenums="1" hl_lines="9-14"
20
+ from index_now import submit_url_to_index_now, IndexNowAuthentication, SearchEngineEndpoint
21
+
22
+ authentication = IndexNowAuthentication(
23
+ host="example.com",
24
+ api_key="a1b2c3d4",
25
+ api_key_location="https://example.com/a1b2c3d4.txt",
26
+ )
27
+
28
+ endpoint_bing = SearchEngineEndpoint.MICROSOFT_BING
29
+ endpoint_yandex = SearchEngineEndpoint.YANDEX
30
+ endpoint_custom = "https://example.com/indexnow"
31
+
32
+ for endpoint in [endpoint_bing, endpoint_yandex, endpoint_custom]:
33
+ submit_url_to_index_now(authentication, "https://example.com/page1", endpoint)
34
+ ```
35
+ """
36
+
37
+ INDEXNOW = "https://api.indexnow.org/indexnow"
38
+ MICROSOFT_BING = "https://www.bing.com/indexnow"
39
+ NAVER = "https://searchadvisor.naver.com/indexnow"
40
+ SEZNAM = "https://search.seznam.cz/indexnow"
41
+ YANDEX = "https://yandex.com/indexnow"
42
+ YEP = "https://indexnow.yep.com/indexnow"
43
+
44
+ def __str__(self) -> str:
45
+ return str(self.value)
@@ -0,0 +1,167 @@
1
+ import re
2
+ from typing import Any
3
+
4
+ import lxml.etree
5
+ import requests
6
+ from colorist import Color
7
+
8
+ from .authentication import IndexNowAuthentication
9
+ from .endpoint import SearchEngineEndpoint
10
+ from .submit import submit_urls_to_index_now
11
+
12
+
13
+ def get_urls_from_sitemap_xml(sitemap_url: str) -> list[str]:
14
+ """Get all URLs from a sitemap.xml file.
15
+
16
+ Args:
17
+ sitemap_url (str): The URL of the sitemap to get the URLs from.
18
+
19
+ Returns:
20
+ list[str] | None: List of URLs found in the sitemap.xml file, or empty list if no URLs are found.
21
+ """
22
+
23
+ response = requests.get(sitemap_url)
24
+ return parse_sitemap_xml_and_get_urls(response.content)
25
+
26
+
27
+ def parse_sitemap_xml_and_get_urls(sitemap_content: str | bytes | Any) -> list[str]:
28
+ """Parse the contents of a sitemap.xml file, e.g. from a response, and retrieve all the URLs from it.
29
+
30
+ Args:
31
+ content (str | bytes | Any): The content from the sitemap.xml file.
32
+
33
+ Returns:
34
+ list[str]: List of URLs found in the sitemap.xml file, or empty list if no URLs are found.
35
+ """
36
+
37
+ sitemap_tree = lxml.etree.fromstring(sitemap_content)
38
+ urls = sitemap_tree.xpath("//ns:url/ns:loc/text()", namespaces={"ns": "http://www.sitemaps.org/schemas/sitemap/0.9"})
39
+ return [str(url).strip() for url in urls] if isinstance(urls, list) and urls else []
40
+
41
+
42
+ def filter_urls(urls: list[str], contains: str | None = None, skip: int | None = None, take: int | None = None) -> list[str]:
43
+ """Filter URLs based on the given criteria.
44
+
45
+ Args:
46
+ urls (list[str]): List of URLs to be filtered.
47
+ contains (str | None): Optional filter for URLs. Can be simple string (e.g. `"section1"`) or regular expression (e.g. `r"(section1)|(section2)"`). Ignored by default and if set to `None`.
48
+ skip (int | None): Optional number of URLs to be skipped. Ignored by default and if set to `None`.
49
+ take (int | None): Optional limit of URLs to be taken. Ignored by default and if set to `None`.
50
+
51
+ Returns:
52
+ list[str]: Filtered list of URLs, or empty list if no URLs are found.
53
+ """
54
+
55
+ if not urls:
56
+ print(f"{Color.YELLOW}No URLs given before filtering.{Color.OFF}")
57
+ return []
58
+
59
+ if contains is not None:
60
+ pattern = re.compile(contains)
61
+ urls = [url for url in urls if pattern.search(url)]
62
+ if not urls:
63
+ print(f"{Color.YELLOW}No URLs contained the pattern \"{contains}\".{Color.OFF}")
64
+ return []
65
+
66
+ if skip is not None:
67
+ if skip >= len(urls):
68
+ print(f"{Color.YELLOW}No URLs left after skipping {skip} URL(s) from sitemap.{Color.OFF}")
69
+ return []
70
+ urls = urls[skip:]
71
+
72
+ if take is not None:
73
+ if take <= 0:
74
+ print(f"{Color.YELLOW}No URLs left. The value for take should be greater than 0.{Color.OFF}")
75
+ return []
76
+ urls = urls[:take]
77
+
78
+ return urls
79
+
80
+
81
+ def submit_sitemap_to_index_now(authentication: IndexNowAuthentication, sitemap_url: str, contains: str | None = None, skip: int | None = None, take: int | None = None, endpoint: SearchEngineEndpoint | str = SearchEngineEndpoint.INDEXNOW) -> None:
82
+ """Submit a sitemap to the IndexNow API of a search engine.
83
+
84
+ Args:
85
+ authentication (IndexNowAuthentication): Authentication data for the IndexNow API.
86
+ sitemap_url (str): The URL of the sitemap to submit, e.g. `https://example.com/sitemap.xml`.
87
+ contains (str | None): Optional filter for URLs. Can be simple string (e.g. `"section1"`) or regular expression (e.g. `r"(section1)|(section2)"`). Ignored by default and if set to `None`.
88
+ skip (int | None): Optional number of URLs from the sitemap to be skipped. Ignored by default and if set to `None`.
89
+ take (int | None): Optional limit of URLs from the sitemap to taken. Ignored by default and if set to `None`.
90
+ endpoint (SearchEngineEndpoint | str, optional): Select the search engine you want to submit to or use a custom URL as endpoint.
91
+
92
+ Example:
93
+ After adding your authentication details to the `IndexNowAuthentication` class, you can now submit an entire sitemap to the IndexNow API:
94
+
95
+ ```python linenums="1" hl_lines="11"
96
+ from index_now import submit_sitemap_to_index_now, IndexNowAuthentication
97
+
98
+ authentication = IndexNowAuthentication(
99
+ host="example.com",
100
+ api_key="a1b2c3d4",
101
+ api_key_location="https://example.com/a1b2c3d4.txt",
102
+ )
103
+
104
+ sitemap_url = "https://example.com/sitemap.xml"
105
+
106
+ submit_sitemap_to_index_now(authentication, sitemap_url)
107
+ ```
108
+
109
+ If you want to submit to a specific search engine, alternatively customize the endpoint:
110
+
111
+ ```python linenums="11" hl_lines="1-2" title=""
112
+ submit_sitemap_to_index_now(authentication, sitemap_url,
113
+ endpoint="https://www.bing.com/indexnow")
114
+ ```
115
+
116
+ If you want to only upload a portion of the sitemap URLs, alternatively use the `skip` and `take` parameters:
117
+
118
+ ```python linenums="1" hl_lines="11-12"
119
+ from index_now import submit_sitemap_to_index_now, IndexNowAuthentication
120
+
121
+ authentication = IndexNowAuthentication(
122
+ host="example.com",
123
+ api_key="a1b2c3d4",
124
+ api_key_location="https://example.com/a1b2c3d4.txt",
125
+ )
126
+
127
+ sitemap_url = "https://example.com/sitemap.xml"
128
+
129
+ submit_sitemap_to_index_now(authentication, sitemap_url,
130
+ skip=100, take=50)
131
+ ```
132
+
133
+ How to target URLs with a specific pattern by using the `contains` parameter:
134
+
135
+ ```python linenums="11" hl_lines="1-2" title=""
136
+ submit_sitemap_to_index_now(authentication, sitemap_url,
137
+ contains="section1")
138
+ ```
139
+
140
+ The `contains` parameter also accepts regular expressions for more advanced filtering:
141
+
142
+ ```python linenums="11" hl_lines="1-2" title=""
143
+ submit_sitemap_to_index_now(authentication, sitemap_url,
144
+ contains=r"(section1)|(section2)")
145
+ ```
146
+
147
+ Or combine the `contains`, `skip`, and `take` parameters to filter the URLs even further:
148
+
149
+ ```python linenums="11" hl_lines="1-3" title=""
150
+ submit_sitemap_to_index_now(authentication, sitemap_url,
151
+ contains=r"(section1)|(section2)",
152
+ skip=100, take=50)
153
+ ```
154
+ """
155
+
156
+ urls = get_urls_from_sitemap_xml(sitemap_url)
157
+ if not urls:
158
+ raise ValueError(f"No URLs found in sitemap. Please check the sitemap URL: {sitemap_url}")
159
+ print(f"Found {Color.GREEN}{len(urls)} URL(s){Color.OFF} in total from sitemap.")
160
+
161
+ if any([contains, skip, take]):
162
+ urls = filter_urls(urls, contains, skip, take)
163
+ if not urls:
164
+ raise ValueError("No URLs left after filtering. Please check your filter parameters.")
165
+ print(f"{Color.YELLOW}{len(urls)} URL(s) left after filtering.{Color.OFF}")
166
+
167
+ submit_urls_to_index_now(authentication, urls, endpoint)
@@ -0,0 +1,103 @@
1
+ import requests
2
+ from colorist import Color
3
+
4
+ from .authentication import IndexNowAuthentication
5
+ from .endpoint import SearchEngineEndpoint
6
+
7
+ ACCEPTED_STATUS_CODES = [200, 202]
8
+
9
+
10
+ def submit_url_to_index_now(authentication: IndexNowAuthentication, url: str, endpoint: SearchEngineEndpoint | str = SearchEngineEndpoint.INDEXNOW) -> None:
11
+ """Submits a list of URLs to the IndexNow API of a search engine.
12
+
13
+ Args:
14
+ authentication (IndexNowAuthentication): Authentication data for the IndexNow API.
15
+ url (str): URL to submit, e.g. `"https://example.com/page1"`.
16
+ endpoint (SearchEngineEndpoint | str, optional): Select the search engine you want to submit to or use a custom URL as endpoint.
17
+
18
+ Example:
19
+ After adding your authentication details to the `IndexNowAuthentication` class, you can now submit a single URL to the IndexNow API:
20
+
21
+ ```python linenums="1" hl_lines="11"
22
+ from index_now import submit_url_to_index_now, IndexNowAuthentication
23
+
24
+ authentication = IndexNowAuthentication(
25
+ host="example.com",
26
+ api_key="a1b2c3d4",
27
+ api_key_location="https://example.com/a1b2c3d4.txt",
28
+ )
29
+
30
+ url = "https://example.com/page1"
31
+
32
+ submit_url_to_index_now(authentication, url)
33
+ ```
34
+
35
+ If you want to submit to a specific search engine, alternatively customize the endpoint:
36
+
37
+ ```python linenums="11" hl_lines="1-2" title=""
38
+ submit_url_to_index_now(authentication, url,
39
+ endpoint="https://www.bing.com/indexnow")
40
+ ```
41
+ """
42
+
43
+ response = requests.get(url=str(endpoint), params={"url": url, "key": authentication.api_key, "keyLocation": authentication.api_key_location})
44
+
45
+ if response.status_code in ACCEPTED_STATUS_CODES:
46
+ print(f"{Color.GREEN}URL submitted successfully to the IndexNow API:{Color.OFF} {endpoint}")
47
+ print(f"Status code: {Color.GREEN}{response.status_code}{Color.OFF}")
48
+ else:
49
+ print("Failed to submit URL.")
50
+ print(f"Status code: {Color.RED}{response.status_code}{Color.OFF}. Response: {response.text}")
51
+
52
+
53
+ def submit_urls_to_index_now(authentication: IndexNowAuthentication, urls: list[str], endpoint: SearchEngineEndpoint | str = SearchEngineEndpoint.INDEXNOW) -> None:
54
+ """Submits a list of URLs to the IndexNow API of a search engine.
55
+
56
+ Args:
57
+ authentication (IndexNowAuthentication): Authentication data for the IndexNow API.
58
+ urls (list[str]): List of URLs to submit, e.g. `["https://example.com/page1", "https://example.com/page2"]`.
59
+ endpoint (SearchEngineEndpoint | str, optional): Select the search engine you want to submit to or use a custom URL as endpoint.
60
+
61
+ Example:
62
+ After adding your authentication details to the `IndexNowAuthentication` class, you can now submit multiple URLs to the IndexNow API:
63
+
64
+ ```python linenums="1" hl_lines="11"
65
+ from index_now import submit_urls_to_index_now, IndexNowAuthentication
66
+
67
+ authentication = IndexNowAuthentication(
68
+ host="example.com",
69
+ api_key="a1b2c3d4",
70
+ api_key_location="https://example.com/a1b2c3d4.txt",
71
+ )
72
+
73
+ urls = ["https://example.com/page1", "https://example.com/page2", "https://example.com/page3"]
74
+
75
+ submit_urls_to_index_now(authentication, urls)
76
+ ```
77
+
78
+ If you want to submit to a specific search engine, alternatively customize the endpoint:
79
+
80
+ ```python linenums="11" hl_lines="1-2" title=""
81
+ submit_urls_to_index_now(authentication, urls,
82
+ endpoint="https://www.bing.com/indexnow")
83
+ ```
84
+ """
85
+
86
+ payload: dict[str, str | list[str]] = {
87
+ "host": authentication.host,
88
+ "key": authentication.api_key,
89
+ "keyLocation": authentication.api_key_location,
90
+ "urlList": urls
91
+ }
92
+ response = requests.post(
93
+ url=str(endpoint),
94
+ json=payload,
95
+ headers={"Content-Type": "application/json; charset=utf-8"}
96
+ )
97
+
98
+ if response.status_code in ACCEPTED_STATUS_CODES:
99
+ print(f"{Color.GREEN}{len(urls)} URL(s) submitted successfully to the IndexNow API:{Color.OFF} {endpoint}")
100
+ print(f"Status code: {Color.GREEN}{response.status_code}{Color.OFF}")
101
+ else:
102
+ print("Failed to submit sitemap.")
103
+ print(f"Status code: {Color.RED}{response.status_code}{Color.OFF}. Response: {response.text}")
@@ -0,0 +1,2 @@
1
+ __version_info__ = (0, 1, 0)
2
+ __version__ = ".".join(map(str, __version_info__))
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: index-now-for-python
3
+ Version: 0.1.0
4
+ Summary: Lightweight Python package that makes it easy to submit URLs to the IndexNow API of various search engines
5
+ Home-page: https://jakob-bagterp.github.io/index-now-for-python/
6
+ Download-URL: https://pypi.org/project/index-now-for-python/
7
+ Author: Jakob Bagterp
8
+ Author-email: jakob_bagterp@hotmail.com
9
+ Maintainer: Jakob Bagterp
10
+ Maintainer-email: jakob_bagterp@hotmail.com
11
+ License: MIT License
12
+ Project-URL: Bug Tracker, https://github.com/jakob-bagterp/index-now-for-python/issues
13
+ Project-URL: Documentation, https://jakob-bagterp.github.io/index-now-for-python/
14
+ Project-URL: API Reference, https://jakob-bagterp.github.io/index-now-for-python/reference/
15
+ Project-URL: Source Code, https://github.com/jakob-bagterp/index-now-for-python
16
+ Project-URL: Release Notes, https://github.com/jakob-bagterp/index-now-for-python/releases
17
+ Keywords: python,timer
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Operating System :: OS Independent
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE.md
28
+ Requires-Dist: colorist==1.8.3
29
+ Requires-Dist: lxml==5.4.0
30
+ Requires-Dist: requests==2.32.3
31
+ Provides-Extra: testing
32
+ Requires-Dist: coverage==7.8.0; extra == "testing"
33
+ Requires-Dist: flake8==7.2.0; extra == "testing"
34
+ Requires-Dist: mypy==1.15.0; extra == "testing"
35
+ Requires-Dist: pytest==8.3.5; extra == "testing"
36
+ Requires-Dist: pytest-cov==6.0.0; extra == "testing"
37
+ Requires-Dist: tox==4.25.0; extra == "testing"
38
+ Dynamic: download-url
39
+ Dynamic: license-file
40
+
41
+ [![Latest version](https://img.shields.io/static/v1?label=version&message=0.1.0&color=yellowgreen)](https://github.com/jakob-bagterp/index-now-for-python/releases/latest)
42
+ [![Python 3.10 | 3.11 | 3.12 | 3.13+](https://img.shields.io/static/v1?label=python&message=3.10%20|%203.11%20|%203.12%20|%203.13%2B&color=blueviolet)](https://www.python.org)
43
+ [![MIT license](https://img.shields.io/static/v1?label=license&message=MIT&color=blue)](https://github.com/jakob-bagterp/index-now-for-python/blob/master/LICENSE.md)
44
+ [![Codecov](https://codecov.io/gh/jakob-bagterp/index-now-for-python/branch/master/graph/badge.svg?token=SGVMPJ1JWI)](https://codecov.io/gh/jakob-bagterp/index-now-for-python)
45
+ [![CodeQL](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/codeql.yml)
46
+ [![Test](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml/badge.svg)](https://github.com/jakob-bagterp/index-now-for-python/actions/workflows/test.yml)
47
+ [![Downloads](https://static.pepy.tech/badge/index-now-for-python)](https://pepy.tech/project/index-now-for-python)
48
+
49
+ # 🔍 Submit URLs to the IndexNow API of Various Search Enginges 🔎
50
+ Are you concerned about search engine optimization (SEO)? Do you want to make sure your website is indexed frequently by [Bing](https://www.bing.com/indexnow), [Yandex](https://yandex.com/indexnow), and other search engines?
51
+
52
+ IndexNow for Python is a lightweight Python package that makes it easy to submit URLs or entire sitemaps to the IndexNow API of various search engines, so your pages can be indexed faster.
53
+
54
+ Ready to try? Learn [how to install](https://jakob-bagterp.github.io/index-now-for-python/getting-started/installation/) and find tutorials in the [user guide](https://jakob-bagterp.github.io/index-now-for-python/user-guide/).
55
+
56
+ ## Getting Started
57
+ ### Basic Usage and Submit Individual URLs
58
+ Firstly, ensure that you have an [API key for IndexNow](https://www.indexnow.org/api-key). Hereafter, add your authentication details to the `IndexNowAuthentication` class, which will be used throughout the examples:
59
+
60
+ ```python
61
+ from index_now import submit_url_to_index_now, IndexNowAuthentication
62
+
63
+ authentication = IndexNowAuthentication(
64
+ host="example.com",
65
+ api_key="a1b2c3d4",
66
+ api_key_location="https://example.com/a1b2c3d4.txt",
67
+ )
68
+ ```
69
+
70
+ You can now submit individual URLs to the IndexNow API:
71
+
72
+ ```python
73
+ submit_url_to_index_now(authentication, "https://example.com/page1")
74
+ ```
75
+
76
+ ### Submit Multiple URLs in Bulk
77
+ How to submit multiple URLs in bulk to the IndexNow API:
78
+
79
+ ```python
80
+ from index_now import submit_urls_to_index_now, IndexNowAuthentication
81
+
82
+ authentication = IndexNowAuthentication(...)
83
+
84
+ urls = [
85
+ "https://example.com/page1",
86
+ "https://example.com/page2",
87
+ "https://example.com/page3",
88
+ ]
89
+
90
+ submit_urls_to_index_now(authentication, urls)
91
+ ```
92
+
93
+ ### Submit Entire Sitemap
94
+ How to submit an entire sitemap to the IndexNow API:
95
+
96
+ ```python
97
+ from index_now import submit_sitemap_to_index_now, IndexNowAuthentication
98
+
99
+ authentication = IndexNowAuthentication(...)
100
+
101
+ sitemap_url = "https://example.com/sitemap.xml"
102
+
103
+ submit_sitemap_to_index_now(authentication, sitemap_url)
104
+ ```
105
+
106
+ ### Submit to Specific Search Engines
107
+ How to use the default `SearchEngineEndpoint` options or a custom endpoint:
108
+
109
+ ```python
110
+ from index_now import submit_url_to_index_now, IndexNowAuthentication, SearchEngineEndpoint
111
+
112
+ authentication = IndexNowAuthentication(...)
113
+
114
+ endpoint_bing = SearchEngineEndpoint.MICROSOFT_BING
115
+ endpoint_custom = "https://example.com/indexnow"
116
+
117
+ for endpoint in [endpoint_bing, endpoint_custom]:
118
+ submit_url_to_index_now(authentication, "https://example.com/page1", endpoint)
119
+ ```
120
+
121
+ ## Become a Sponsor 🏅
122
+ If you find this project helpful, please consider supporting its development. Your donations will help keep it alive and growing. Every contribution, no matter the size, makes a difference.
123
+
124
+ [Donate on GitHub Sponsors](https://github.com/sponsors/jakob-bagterp)
125
+
126
+ Thank you for your support! 🙌
127
+
128
+ ## Contribute
129
+ If you have suggestions or changes to the module, feel free to add to the code and create a [pull request](https://github.com/jakob-bagterp/index-now-for-python/pulls).
130
+
131
+ ## Report Bugs
132
+ Report bugs and issues [here](https://github.com/jakob-bagterp/index-now-for-python/issues).
133
+
134
+ # MIT License
135
+
136
+ Copyright (c) 2025 – present, Jakob Bagterp
137
+
138
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
139
+
140
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
141
+
142
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ LICENSE.md
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.cfg
6
+ src/index_now/__init__.py
7
+ src/index_now/authentication.py
8
+ src/index_now/endpoint.py
9
+ src/index_now/sitemap.py
10
+ src/index_now/submit.py
11
+ src/index_now/version.py
12
+ src/index_now_for_python.egg-info/PKG-INFO
13
+ src/index_now_for_python.egg-info/SOURCES.txt
14
+ src/index_now_for_python.egg-info/dependency_links.txt
15
+ src/index_now_for_python.egg-info/not-zip-safe
16
+ src/index_now_for_python.egg-info/requires.txt
17
+ src/index_now_for_python.egg-info/top_level.txt
@@ -0,0 +1,11 @@
1
+ colorist==1.8.3
2
+ lxml==5.4.0
3
+ requests==2.32.3
4
+
5
+ [testing]
6
+ coverage==7.8.0
7
+ flake8==7.2.0
8
+ mypy==1.15.0
9
+ pytest==8.3.5
10
+ pytest-cov==6.0.0
11
+ tox==4.25.0