langchain-webmetadata-extractor 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,6 @@
1
+ .venv/
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JosejuX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: langchain-webmetadata-extractor
3
+ Version: 0.1.0
4
+ Summary: LangChain tools for the Web Metadata, OpenGraph & Contact Extractor API — give your agent URL-to-structured-data extraction, AI-ready Markdown, contact discovery, and SEO auditing.
5
+ Project-URL: Homepage, https://github.com/JosejuX/rapidapi-metadata-extractor
6
+ Project-URL: RapidAPI Listing, https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor
7
+ Project-URL: PyPI SDK, https://pypi.org/project/webmetadata-extractor/
8
+ Author-email: Juanjo <josejuanjo.coding@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,langchain,langchain-tool,metadata-extractor,rag,seo,web-scraping
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.9
18
+ Requires-Dist: langchain-core>=0.2
19
+ Requires-Dist: pydantic>=2
20
+ Requires-Dist: webmetadata-extractor>=0.1.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Requires-Dist: responses>=0.23; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # langchain-webmetadata-extractor
28
+
29
+ LangChain tools for the [Web Metadata, OpenGraph & Contact Extractor API](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) — give an agent the ability to turn any URL into structured web intelligence, clean Markdown for RAG, contact signals for lead-gen, or a graded SEO audit.
30
+
31
+ ```bash
32
+ pip install langchain-webmetadata-extractor
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ Get a free API key on [RapidAPI](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) (1,000 requests/month, no credit card required).
38
+
39
+ ```python
40
+ from langchain_webmetadata_extractor import get_tools
41
+
42
+ tools = get_tools(api_key="YOUR_RAPIDAPI_KEY")
43
+ # or leave api_key out and set the WEBMETADATA_API_KEY environment variable instead
44
+
45
+ agent = create_react_agent(llm, tools) # or bind_tools(tools), or any LangGraph node
46
+ ```
47
+
48
+ You can also import and use tools individually:
49
+
50
+ ```python
51
+ from langchain_webmetadata_extractor import WebMarkdownTool
52
+
53
+ reader = WebMarkdownTool(api_key="YOUR_RAPIDAPI_KEY")
54
+ print(reader.run("https://example.com"))
55
+ ```
56
+
57
+ ## Tools included
58
+
59
+ | Tool | Use it when the agent needs to... |
60
+ |---|---|
61
+ | `WebMetadataExtractTool` | Get a broad, all-in-one picture of a page (SEO, contacts, tech stack, security, links) |
62
+ | `WebMarkdownTool` | Ingest a page's actual content into a RAG pipeline as clean Markdown |
63
+ | `WebContactsTool` | Find public emails/phones/social links for outreach or lead-gen workflows |
64
+ | `WebSEOAuditTool` | Grade a page's SEO with a score, warnings, and readability metrics |
65
+
66
+ Every tool supports both sync (`.run()` / `._run()`) and async (`.arun()` / `._arun()`) execution, and returns a JSON string — API errors come back as `{"error": true, "status_code": ..., "detail": ...}` instead of raising, so an agent loop can react to them without crashing.
67
+
68
+ ## Configuration
69
+
70
+ Pass `api_key` explicitly per tool, or set it once via the `WEBMETADATA_API_KEY` environment variable and omit it everywhere:
71
+
72
+ ```bash
73
+ export WEBMETADATA_API_KEY=your-rapidapi-key
74
+ ```
75
+
76
+ ## Links
77
+
78
+ - [Python SDK this wraps](https://pypi.org/project/webmetadata-extractor/) (`webmetadata-extractor`)
79
+ - [Full API documentation](https://github.com/JosejuX/rapidapi-metadata-extractor)
80
+ - [Live interactive demo](https://rapidapi-metadata-extractor.onrender.com) — no signup needed
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,58 @@
1
+ # langchain-webmetadata-extractor
2
+
3
+ LangChain tools for the [Web Metadata, OpenGraph & Contact Extractor API](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) — give an agent the ability to turn any URL into structured web intelligence, clean Markdown for RAG, contact signals for lead-gen, or a graded SEO audit.
4
+
5
+ ```bash
6
+ pip install langchain-webmetadata-extractor
7
+ ```
8
+
9
+ ## Quick start
10
+
11
+ Get a free API key on [RapidAPI](https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor) (1,000 requests/month, no credit card required).
12
+
13
+ ```python
14
+ from langchain_webmetadata_extractor import get_tools
15
+
16
+ tools = get_tools(api_key="YOUR_RAPIDAPI_KEY")
17
+ # or leave api_key out and set the WEBMETADATA_API_KEY environment variable instead
18
+
19
+ agent = create_react_agent(llm, tools) # or bind_tools(tools), or any LangGraph node
20
+ ```
21
+
22
+ You can also import and use tools individually:
23
+
24
+ ```python
25
+ from langchain_webmetadata_extractor import WebMarkdownTool
26
+
27
+ reader = WebMarkdownTool(api_key="YOUR_RAPIDAPI_KEY")
28
+ print(reader.run("https://example.com"))
29
+ ```
30
+
31
+ ## Tools included
32
+
33
+ | Tool | Use it when the agent needs to... |
34
+ |---|---|
35
+ | `WebMetadataExtractTool` | Get a broad, all-in-one picture of a page (SEO, contacts, tech stack, security, links) |
36
+ | `WebMarkdownTool` | Ingest a page's actual content into a RAG pipeline as clean Markdown |
37
+ | `WebContactsTool` | Find public emails/phones/social links for outreach or lead-gen workflows |
38
+ | `WebSEOAuditTool` | Grade a page's SEO with a score, warnings, and readability metrics |
39
+
40
+ Every tool supports both sync (`.run()` / `._run()`) and async (`.arun()` / `._arun()`) execution, and returns a JSON string — API errors come back as `{"error": true, "status_code": ..., "detail": ...}` instead of raising, so an agent loop can react to them without crashing.
41
+
42
+ ## Configuration
43
+
44
+ Pass `api_key` explicitly per tool, or set it once via the `WEBMETADATA_API_KEY` environment variable and omit it everywhere:
45
+
46
+ ```bash
47
+ export WEBMETADATA_API_KEY=your-rapidapi-key
48
+ ```
49
+
50
+ ## Links
51
+
52
+ - [Python SDK this wraps](https://pypi.org/project/webmetadata-extractor/) (`webmetadata-extractor`)
53
+ - [Full API documentation](https://github.com/JosejuX/rapidapi-metadata-extractor)
54
+ - [Live interactive demo](https://rapidapi-metadata-extractor.onrender.com) — no signup needed
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "langchain-webmetadata-extractor"
7
+ version = "0.1.0"
8
+ description = "LangChain tools for the Web Metadata, OpenGraph & Contact Extractor API — give your agent URL-to-structured-data extraction, AI-ready Markdown, contact discovery, and SEO auditing."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [{ name = "Juanjo", email = "josejuanjo.coding@gmail.com" }]
12
+ requires-python = ">=3.9"
13
+ keywords = ["langchain", "langchain-tool", "agent", "rag", "web-scraping", "seo", "metadata-extractor"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+ dependencies = [
22
+ "webmetadata-extractor>=0.1.0",
23
+ "langchain-core>=0.2",
24
+ "pydantic>=2",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest>=7.0", "pytest-asyncio>=0.21", "responses>=0.23"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/JosejuX/rapidapi-metadata-extractor"
32
+ "RapidAPI Listing" = "https://rapidapi.com/josejuanjocoding/api/web-metadata-and-contact-extractor"
33
+ "PyPI SDK" = "https://pypi.org/project/webmetadata-extractor/"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/langchain_webmetadata_extractor"]
@@ -0,0 +1,11 @@
1
+ from .tools import WebContactsTool, WebMarkdownTool, WebMetadataExtractTool, WebSEOAuditTool, get_tools
2
+
3
+ __all__ = [
4
+ "WebMetadataExtractTool",
5
+ "WebMarkdownTool",
6
+ "WebContactsTool",
7
+ "WebSEOAuditTool",
8
+ "get_tools",
9
+ ]
10
+
11
+ __version__ = "0.1.0"
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from typing import Optional, Type
6
+
7
+ from langchain_core.tools import BaseTool
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+ from webmetadata_extractor import AsyncWebMetadataClient, WebMetadataClient
11
+ from webmetadata_extractor.exceptions import WebMetadataAPIError
12
+
13
+
14
+ class _UrlInput(BaseModel):
15
+ url: str = Field(description="The fully-qualified URL to fetch and analyze, e.g. https://example.com")
16
+
17
+
18
+ def _dumps(result: dict) -> str:
19
+ return json.dumps(result, ensure_ascii=False)
20
+
21
+
22
+ def _error_payload(exc: WebMetadataAPIError) -> str:
23
+ return _dumps({"error": True, "status_code": exc.status_code, "detail": exc.body})
24
+
25
+
26
+ class _BaseWebMetadataTool(BaseTool):
27
+ """Shared config and dispatch logic for all tools in this package.
28
+
29
+ Subclasses only need to set `name`, `description`, and `client_method` (the
30
+ method name on `WebMetadataClient`/`AsyncWebMetadataClient` to call with `url`).
31
+ """
32
+
33
+ model_config = ConfigDict(arbitrary_types_allowed=True)
34
+ args_schema: Type[BaseModel] = _UrlInput
35
+ client_method: str = ""
36
+ api_key: Optional[str] = Field(
37
+ default=None,
38
+ exclude=True,
39
+ description="RapidAPI key. If omitted, reads WEBMETADATA_API_KEY from the environment.",
40
+ )
41
+
42
+ def _resolved_key(self) -> Optional[str]:
43
+ return self.api_key or os.environ.get("WEBMETADATA_API_KEY")
44
+
45
+ def _run(self, url: str) -> str:
46
+ client = WebMetadataClient(api_key=self._resolved_key())
47
+ try:
48
+ return _dumps(getattr(client, self.client_method)(url))
49
+ except WebMetadataAPIError as exc:
50
+ return _error_payload(exc)
51
+ finally:
52
+ client.close()
53
+
54
+ async def _arun(self, url: str) -> str:
55
+ async with AsyncWebMetadataClient(api_key=self._resolved_key()) as client:
56
+ try:
57
+ return _dumps(await getattr(client, self.client_method)(url))
58
+ except WebMetadataAPIError as exc:
59
+ return _error_payload(exc)
60
+
61
+
62
+ class WebMetadataExtractTool(_BaseWebMetadataTool):
63
+ """Full extraction: SEO/OpenGraph metadata, contacts, tech stack, schema.org, security, links."""
64
+
65
+ name: str = "web_metadata_extract"
66
+ description: str = (
67
+ "Extract the full structured web intelligence payload for a URL: SEO/OpenGraph metadata, "
68
+ "public contact info (emails/phones/social links), detected tech stack, Schema.org product data, "
69
+ "a graded security-headers audit, and internal/external links. Use this when you need a broad "
70
+ "picture of a page rather than one specific facet. Input is a single URL."
71
+ )
72
+ client_method: str = "extract"
73
+
74
+
75
+ class WebMarkdownTool(_BaseWebMetadataTool):
76
+ """Clean, AI/LLM-ready Markdown of a page's main content — for RAG ingestion pipelines."""
77
+
78
+ name: str = "web_markdown_reader"
79
+ description: str = (
80
+ "Fetch a URL and return its main article content as clean, LLM-ready Markdown (boilerplate, nav, "
81
+ "and ads stripped). Use this to ingest web pages into a RAG pipeline or to let an agent read a "
82
+ "page's actual content. Input is a single URL."
83
+ )
84
+ client_method: str = "markdown"
85
+
86
+
87
+ class WebContactsTool(_BaseWebMetadataTool):
88
+ """Public contact discovery (emails, phone numbers, social links) — a raw signal for lead-gen agents."""
89
+
90
+ name: str = "web_contacts_extract"
91
+ description: str = (
92
+ "Find public contact signals on a URL: email addresses, phone numbers, and social media profile "
93
+ "links present in the page's HTML. This is a raw signal for a lead-generation or outreach workflow, "
94
+ "not verified company/people intelligence. Input is a single URL."
95
+ )
96
+ client_method: str = "contacts"
97
+
98
+
99
+ class WebSEOAuditTool(_BaseWebMetadataTool):
100
+ """14-point automated SEO diagnostic with a warnings list and readability metrics."""
101
+
102
+ name: str = "web_seo_audit"
103
+ description: str = (
104
+ "Run a 14-point automated SEO audit on a URL: overall score, a list of warnings with severity and "
105
+ "evidence, and readability metrics (heading structure, sentence/paragraph counts). Use this when "
106
+ "asked to review, grade, or diagnose a page's SEO. Input is a single URL."
107
+ )
108
+ client_method: str = "seo_audit"
109
+
110
+
111
+ def get_tools(api_key: Optional[str] = None) -> list[BaseTool]:
112
+ """Convenience helper: instantiate all four tools with a shared api_key, ready to hand to an agent."""
113
+ return [
114
+ WebMetadataExtractTool(api_key=api_key),
115
+ WebMarkdownTool(api_key=api_key),
116
+ WebContactsTool(api_key=api_key),
117
+ WebSEOAuditTool(api_key=api_key),
118
+ ]
@@ -0,0 +1,110 @@
1
+ import json
2
+
3
+ import pytest
4
+ import responses
5
+ from webmetadata_extractor.client import DEFAULT_BASE_URL
6
+
7
+ from langchain_webmetadata_extractor import (
8
+ WebContactsTool,
9
+ WebMarkdownTool,
10
+ WebMetadataExtractTool,
11
+ WebSEOAuditTool,
12
+ get_tools,
13
+ )
14
+
15
+
16
+ @responses.activate
17
+ def test_extract_tool_run_returns_json_string():
18
+ responses.add(
19
+ responses.GET,
20
+ f"{DEFAULT_BASE_URL}/api/v1/extract",
21
+ json={"title": "GitHub"},
22
+ status=200,
23
+ )
24
+
25
+ tool = WebMetadataExtractTool(api_key="fake-key")
26
+ result = tool._run("https://github.com")
27
+
28
+ assert json.loads(result) == {"title": "GitHub"}
29
+ assert responses.calls[0].request.headers["X-RapidAPI-Key"] == "fake-key"
30
+
31
+
32
+ @responses.activate
33
+ def test_markdown_tool_hits_markdown_endpoint():
34
+ responses.add(
35
+ responses.GET,
36
+ f"{DEFAULT_BASE_URL}/api/v1/markdown",
37
+ json={"markdown_content": "# Hello"},
38
+ status=200,
39
+ )
40
+
41
+ tool = WebMarkdownTool(api_key="fake-key")
42
+ result = tool._run("https://example.com")
43
+
44
+ assert json.loads(result) == {"markdown_content": "# Hello"}
45
+
46
+
47
+ @responses.activate
48
+ def test_contacts_tool_returns_error_payload_on_api_error_instead_of_raising():
49
+ responses.add(
50
+ responses.GET,
51
+ f"{DEFAULT_BASE_URL}/api/v1/contacts",
52
+ json={"detail": "Invalid URL"},
53
+ status=400,
54
+ )
55
+
56
+ tool = WebContactsTool(api_key="fake-key")
57
+ result = tool._run("not-a-url")
58
+
59
+ parsed = json.loads(result)
60
+ assert parsed["error"] is True
61
+ assert parsed["status_code"] == 400
62
+
63
+
64
+ @responses.activate
65
+ def test_seo_audit_tool_reads_env_var_when_no_api_key_passed(monkeypatch):
66
+ monkeypatch.setenv("WEBMETADATA_API_KEY", "env-key")
67
+ responses.add(responses.GET, f"{DEFAULT_BASE_URL}/api/v1/seo-audit", json={"seo_score_percentage": 91.7}, status=200)
68
+
69
+ tool = WebSEOAuditTool()
70
+ tool._run("https://example.com")
71
+
72
+ assert responses.calls[0].request.headers["X-RapidAPI-Key"] == "env-key"
73
+
74
+
75
+ def test_get_tools_returns_all_four_with_shared_key():
76
+ tools = get_tools(api_key="shared-key")
77
+ assert {t.name for t in tools} == {
78
+ "web_metadata_extract",
79
+ "web_markdown_reader",
80
+ "web_contacts_extract",
81
+ "web_seo_audit",
82
+ }
83
+ assert all(t.api_key == "shared-key" for t in tools)
84
+
85
+
86
+ @pytest.mark.asyncio
87
+ async def test_arun_dispatches_to_the_configured_client_method(monkeypatch):
88
+ calls = {}
89
+
90
+ class FakeAsyncClient:
91
+ def __init__(self, api_key=None):
92
+ calls["api_key"] = api_key
93
+
94
+ async def __aenter__(self):
95
+ return self
96
+
97
+ async def __aexit__(self, *exc_info):
98
+ return False
99
+
100
+ async def extract(self, url):
101
+ calls["url"] = url
102
+ return {"title": "async result"}
103
+
104
+ monkeypatch.setattr("langchain_webmetadata_extractor.tools.AsyncWebMetadataClient", FakeAsyncClient)
105
+
106
+ tool = WebMetadataExtractTool(api_key="fake-key")
107
+ result = await tool._arun("https://example.com")
108
+
109
+ assert json.loads(result) == {"title": "async result"}
110
+ assert calls == {"api_key": "fake-key", "url": "https://example.com"}