langchain-openapi-tools 1.0.0__tar.gz → 1.0.2__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.
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/workflows/publish.yml +3 -2
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/PKG-INFO +29 -2
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/README.md +28 -1
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/__init__.py +2 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/loader.py +5 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/parser.py +13 -1
- langchain_openapi_tools-1.0.2/langchain_openapi/swagger.py +370 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/utils.py +7 -7
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/pyproject.toml +1 -1
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/api/loader/index.html +40 -20
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/api/parser/index.html +446 -434
- langchain_openapi_tools-1.0.2/site/search/search_index.json +1 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_loader.py +7 -9
- langchain_openapi_tools-1.0.2/tests/test_swagger.py +215 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/uv.lock +2 -2
- langchain_openapi_tools-1.0.0/site/search/search_index.json +0 -1
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/dependabot.yml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/workflows/ci.yml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/workflows/pages.yml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.github/workflows/release.yml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.gitignore +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/.pre-commit-config.yaml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/ARCHITECTURE.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/CHANGELOG.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/CODE_OF_CONDUCT.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/CONTRIBUTING.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/LICENSE +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/Makefile +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/benchmarks/benchmark.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/api/executor.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/api/loader.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/api/middleware.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/api/parser.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/api/toolkit.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/architecture.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/authentication.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/contributing.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/examples/crossref.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/examples/petstore.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/index.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/installation.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/middleware.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/quickstart.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/docs/toolkit.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/README.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/crossref/README.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/crossref/crossref.json +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/crossref/main.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/crossref/requirements.txt +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/petstore/README.md +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/petstore/main.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/petstore/petstore.json +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/examples/petstore/requirements.txt +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/enums.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/exceptions.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/executor.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/middleware.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/models.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/providers.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/py.typed +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/schema_converter.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/toolkit.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/mkdocs.yml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/404.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/api/executor/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/api/middleware/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/api/toolkit/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/architecture/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/_mkdocstrings.css +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/images/favicon.png +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/bundle.d7400e89.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/bundle.d7400e89.min.js.map +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/tinyseg.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/lunr/wordcut.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/workers/search.2c215733.min.js +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/stylesheets/main.ec1eaa64.min.css +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/stylesheets/main.ec1eaa64.min.css.map +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/authentication/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/contributing/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/examples/crossref/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/examples/petstore/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/installation/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/middleware/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/objects.inv +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/quickstart/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/sitemap.xml +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/sitemap.xml.gz +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/site/toolkit/index.html +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_e2e.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_executor.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_import.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_middleware.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_parser.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_providers.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_resolver.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_schema_converter.py +0 -0
- {langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/tests/test_toolkit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-openapi-tools
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Convert OpenAPI specifications into native, production-grade LangChain tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/abhaywani114/langchain-openapi
|
|
6
6
|
Project-URL: Documentation, https://abhaywani114.github.io/langchain-openapi/
|
|
@@ -79,12 +79,39 @@ No python code generation is required—tools are generated dynamically at runti
|
|
|
79
79
|
|
|
80
80
|
## Supported Specifications
|
|
81
81
|
|
|
82
|
+
- ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0)
|
|
82
83
|
- ✅ **OpenAPI 3.0.x** (JSON and YAML)
|
|
83
84
|
- ✅ **OpenAPI 3.1.x** (JSON and YAML)
|
|
84
|
-
- ❌ **Swagger 2.0** (Explicitly rejected with descriptive error)
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
## Architecture Pipeline
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Swagger 2.0 / OpenAPI 3.x
|
|
92
|
+
│
|
|
93
|
+
▼
|
|
94
|
+
Swagger Normalizer
|
|
95
|
+
│
|
|
96
|
+
▼
|
|
97
|
+
Normalized OpenAPI Model
|
|
98
|
+
│
|
|
99
|
+
▼
|
|
100
|
+
Existing Parser
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
Internal Operation Models
|
|
104
|
+
│
|
|
105
|
+
▼
|
|
106
|
+
Schema Converter
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
109
|
+
HTTP Executor
|
|
110
|
+
│
|
|
111
|
+
▼
|
|
112
|
+
LangChain StructuredTools
|
|
113
|
+
```
|
|
114
|
+
|
|
88
115
|
## Quick Start
|
|
89
116
|
|
|
90
117
|
### Installation
|
|
@@ -39,12 +39,39 @@ No python code generation is required—tools are generated dynamically at runti
|
|
|
39
39
|
|
|
40
40
|
## Supported Specifications
|
|
41
41
|
|
|
42
|
+
- ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0)
|
|
42
43
|
- ✅ **OpenAPI 3.0.x** (JSON and YAML)
|
|
43
44
|
- ✅ **OpenAPI 3.1.x** (JSON and YAML)
|
|
44
|
-
- ❌ **Swagger 2.0** (Explicitly rejected with descriptive error)
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
|
+
## Architecture Pipeline
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
Swagger 2.0 / OpenAPI 3.x
|
|
52
|
+
│
|
|
53
|
+
▼
|
|
54
|
+
Swagger Normalizer
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
Normalized OpenAPI Model
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
Existing Parser
|
|
61
|
+
│
|
|
62
|
+
▼
|
|
63
|
+
Internal Operation Models
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
Schema Converter
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
HTTP Executor
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
LangChain StructuredTools
|
|
73
|
+
```
|
|
74
|
+
|
|
48
75
|
## Quick Start
|
|
49
76
|
|
|
50
77
|
### Installation
|
{langchain_openapi_tools-1.0.0 → langchain_openapi_tools-1.0.2}/langchain_openapi/__init__.py
RENAMED
|
@@ -69,6 +69,7 @@ from langchain_openapi.schema_converter import (
|
|
|
69
69
|
SchemaConverter,
|
|
70
70
|
map_schema_type_to_python,
|
|
71
71
|
)
|
|
72
|
+
from langchain_openapi.swagger import SwaggerNormalizer
|
|
72
73
|
from langchain_openapi.toolkit import (
|
|
73
74
|
LangChainToolFactory,
|
|
74
75
|
OpenAPIToolCallbackHandler,
|
|
@@ -132,6 +133,7 @@ __all__ = [
|
|
|
132
133
|
"SchemaConverter",
|
|
133
134
|
"SpecLoadError",
|
|
134
135
|
"StaticHeadersProvider",
|
|
136
|
+
"SwaggerNormalizer",
|
|
135
137
|
"UnsupportedVersionError",
|
|
136
138
|
"__version__",
|
|
137
139
|
"build_tool_description",
|
|
@@ -137,6 +137,11 @@ class OpenAPILoader:
|
|
|
137
137
|
"""
|
|
138
138
|
raw_data = self._load_fn()
|
|
139
139
|
validate_raw_spec(raw_data)
|
|
140
|
+
swagger_ver = str(raw_data.get("swagger", ""))
|
|
141
|
+
if raw_data.get("swagger") == "2.0" or swagger_ver.startswith("2."):
|
|
142
|
+
from langchain_openapi.swagger import SwaggerNormalizer
|
|
143
|
+
|
|
144
|
+
raw_data = SwaggerNormalizer(raw_data).normalize()
|
|
140
145
|
spec = OpenAPISpec.from_dict(raw_data)
|
|
141
146
|
|
|
142
147
|
logger.info(
|
|
@@ -40,7 +40,13 @@ class OpenAPISpec:
|
|
|
40
40
|
@classmethod
|
|
41
41
|
def from_dict(cls, spec_dict: dict[str, Any]) -> "OpenAPISpec":
|
|
42
42
|
"""Construct an OpenAPISpec instance from a raw dictionary."""
|
|
43
|
-
|
|
43
|
+
swagger_ver = str(spec_dict.get("swagger", ""))
|
|
44
|
+
if spec_dict.get("swagger") == "2.0" or swagger_ver.startswith("2."):
|
|
45
|
+
from langchain_openapi.swagger import SwaggerNormalizer
|
|
46
|
+
|
|
47
|
+
spec_dict = SwaggerNormalizer(spec_dict).normalize()
|
|
48
|
+
|
|
49
|
+
version = str(spec_dict.get("openapi", "3.0.0"))
|
|
44
50
|
info = spec_dict.get("info")
|
|
45
51
|
if not isinstance(info, dict):
|
|
46
52
|
info = {}
|
|
@@ -170,6 +176,12 @@ class OpenAPIParser:
|
|
|
170
176
|
"""Parser for converting an OpenAPISpec into normalized internal Python models."""
|
|
171
177
|
|
|
172
178
|
def __init__(self, spec: OpenAPISpec) -> None:
|
|
179
|
+
swagger_ver = str(spec.raw.get("swagger", ""))
|
|
180
|
+
if spec.raw.get("swagger") == "2.0" or swagger_ver.startswith("2."):
|
|
181
|
+
from langchain_openapi.swagger import SwaggerNormalizer
|
|
182
|
+
|
|
183
|
+
normalized_raw = SwaggerNormalizer(spec.raw).normalize()
|
|
184
|
+
spec = OpenAPISpec.from_dict(normalized_raw)
|
|
173
185
|
self._spec = spec
|
|
174
186
|
self._resolver = ReferenceResolver(spec.raw)
|
|
175
187
|
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Swagger 2.0 to OpenAPI 3.0 specification normalizer."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SwaggerNormalizer:
|
|
8
|
+
"""Normalizes raw Swagger 2.0 specification dictionary into OpenAPI 3.0 format."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, raw_spec: dict[str, Any]) -> None:
|
|
11
|
+
self.raw = copy.deepcopy(raw_spec)
|
|
12
|
+
|
|
13
|
+
def normalize(self) -> dict[str, Any]:
|
|
14
|
+
"""Convert Swagger 2.0 spec dictionary into an OpenAPI 3.0 compliant dictionary.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Normalized OpenAPI 3.0 specification dictionary.
|
|
18
|
+
"""
|
|
19
|
+
spec = self.raw
|
|
20
|
+
version = str(spec.get("swagger", ""))
|
|
21
|
+
if not (spec.get("swagger") == "2.0" or version.startswith("2.")):
|
|
22
|
+
return spec
|
|
23
|
+
|
|
24
|
+
normalized: dict[str, Any] = {
|
|
25
|
+
"openapi": "3.0.3",
|
|
26
|
+
"info": spec.get("info", {}),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if "tags" in spec:
|
|
30
|
+
normalized["tags"] = spec["tags"]
|
|
31
|
+
|
|
32
|
+
# 1. Servers
|
|
33
|
+
servers = self._convert_servers(spec)
|
|
34
|
+
if servers:
|
|
35
|
+
normalized["servers"] = servers
|
|
36
|
+
|
|
37
|
+
# 2. Components
|
|
38
|
+
components = self._convert_components(spec)
|
|
39
|
+
if components:
|
|
40
|
+
normalized["components"] = components
|
|
41
|
+
|
|
42
|
+
# 3. Security Requirements
|
|
43
|
+
if "security" in spec:
|
|
44
|
+
normalized["security"] = spec["security"]
|
|
45
|
+
|
|
46
|
+
# 4. Paths
|
|
47
|
+
global_consumes = spec.get("consumes", ["application/json"])
|
|
48
|
+
global_produces = spec.get("produces", ["application/json"])
|
|
49
|
+
paths = spec.get("paths", {})
|
|
50
|
+
normalized["paths"] = self._convert_paths(
|
|
51
|
+
paths, global_consumes, global_produces
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 5. Reference Rewriting ($ref)
|
|
55
|
+
normalized = self._rewrite_references(normalized)
|
|
56
|
+
|
|
57
|
+
return normalized
|
|
58
|
+
|
|
59
|
+
def _convert_servers(self, spec: dict[str, Any]) -> list[dict[str, str]]:
|
|
60
|
+
host = spec.get("host", "").strip()
|
|
61
|
+
base_path = spec.get("basePath", "").strip()
|
|
62
|
+
schemes = spec.get("schemes", [])
|
|
63
|
+
|
|
64
|
+
if not isinstance(schemes, list) or not schemes:
|
|
65
|
+
schemes = ["https"]
|
|
66
|
+
|
|
67
|
+
if not base_path.startswith("/"):
|
|
68
|
+
base_path = f"/{base_path}" if base_path else ""
|
|
69
|
+
if base_path == "/":
|
|
70
|
+
base_path = ""
|
|
71
|
+
|
|
72
|
+
servers: list[dict[str, str]] = []
|
|
73
|
+
if host:
|
|
74
|
+
for scheme in schemes:
|
|
75
|
+
url = f"{scheme}://{host}{base_path}"
|
|
76
|
+
servers.append({"url": url})
|
|
77
|
+
elif base_path:
|
|
78
|
+
servers.append({"url": base_path})
|
|
79
|
+
|
|
80
|
+
return servers
|
|
81
|
+
|
|
82
|
+
def _convert_components(self, spec: dict[str, Any]) -> dict[str, Any]:
|
|
83
|
+
components: dict[str, Any] = {}
|
|
84
|
+
|
|
85
|
+
if "definitions" in spec and isinstance(spec["definitions"], dict):
|
|
86
|
+
components["schemas"] = spec["definitions"]
|
|
87
|
+
|
|
88
|
+
if "parameters" in spec and isinstance(spec["parameters"], dict):
|
|
89
|
+
converted_params: dict[str, Any] = {}
|
|
90
|
+
for param_name, param_obj in spec["parameters"].items():
|
|
91
|
+
if isinstance(param_obj, dict):
|
|
92
|
+
converted_params[param_name] = self._convert_parameter(param_obj)
|
|
93
|
+
components["parameters"] = converted_params
|
|
94
|
+
|
|
95
|
+
if "responses" in spec and isinstance(spec["responses"], dict):
|
|
96
|
+
converted_responses: dict[str, Any] = {}
|
|
97
|
+
global_produces = spec.get("produces", ["application/json"])
|
|
98
|
+
for resp_name, resp_obj in spec["responses"].items():
|
|
99
|
+
if isinstance(resp_obj, dict):
|
|
100
|
+
converted_responses[resp_name] = self._convert_response(
|
|
101
|
+
resp_obj, global_produces
|
|
102
|
+
)
|
|
103
|
+
components["responses"] = converted_responses
|
|
104
|
+
|
|
105
|
+
if "securityDefinitions" in spec and isinstance(
|
|
106
|
+
spec["securityDefinitions"], dict
|
|
107
|
+
):
|
|
108
|
+
components["securitySchemes"] = self._convert_security_definitions(
|
|
109
|
+
spec["securityDefinitions"]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return components
|
|
113
|
+
|
|
114
|
+
def _convert_security_definitions(self, sec_defs: dict[str, Any]) -> dict[str, Any]:
|
|
115
|
+
schemes: dict[str, Any] = {}
|
|
116
|
+
for name, sec in sec_defs.items():
|
|
117
|
+
if not isinstance(sec, dict):
|
|
118
|
+
continue
|
|
119
|
+
sec_type = sec.get("type")
|
|
120
|
+
if sec_type == "basic":
|
|
121
|
+
schemes[name] = {"type": "http", "scheme": "basic"}
|
|
122
|
+
elif sec_type == "apiKey":
|
|
123
|
+
schemes[name] = {
|
|
124
|
+
"type": "apiKey",
|
|
125
|
+
"name": sec.get("name", name),
|
|
126
|
+
"in": sec.get("in", "header"),
|
|
127
|
+
}
|
|
128
|
+
elif sec_type == "oauth2":
|
|
129
|
+
flow = sec.get("flow")
|
|
130
|
+
flow_key = flow
|
|
131
|
+
if flow == "accessCode":
|
|
132
|
+
flow_key = "authorizationCode"
|
|
133
|
+
elif flow == "application":
|
|
134
|
+
flow_key = "clientCredentials"
|
|
135
|
+
|
|
136
|
+
flow_obj: dict[str, Any] = {
|
|
137
|
+
"scopes": sec.get("scopes", {}),
|
|
138
|
+
}
|
|
139
|
+
if sec.get("authorizationUrl"):
|
|
140
|
+
flow_obj["authorizationUrl"] = sec["authorizationUrl"]
|
|
141
|
+
if sec.get("tokenUrl"):
|
|
142
|
+
flow_obj["tokenUrl"] = sec["tokenUrl"]
|
|
143
|
+
|
|
144
|
+
schemes[name] = {
|
|
145
|
+
"type": "oauth2",
|
|
146
|
+
"flows": {flow_key: flow_obj} if flow_key else {},
|
|
147
|
+
}
|
|
148
|
+
else:
|
|
149
|
+
schemes[name] = sec
|
|
150
|
+
if "description" in sec:
|
|
151
|
+
schemes[name]["description"] = sec["description"]
|
|
152
|
+
return schemes
|
|
153
|
+
|
|
154
|
+
def _convert_paths(
|
|
155
|
+
self,
|
|
156
|
+
paths: dict[str, Any],
|
|
157
|
+
global_consumes: list[str],
|
|
158
|
+
global_produces: list[str],
|
|
159
|
+
) -> dict[str, Any]:
|
|
160
|
+
converted_paths: dict[str, Any] = {}
|
|
161
|
+
|
|
162
|
+
for path_str, path_item in paths.items():
|
|
163
|
+
if not isinstance(path_item, dict):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
new_path_item: dict[str, Any] = {}
|
|
167
|
+
path_parameters = path_item.get("parameters", [])
|
|
168
|
+
|
|
169
|
+
for key, val in path_item.items():
|
|
170
|
+
if key in (
|
|
171
|
+
"get",
|
|
172
|
+
"post",
|
|
173
|
+
"put",
|
|
174
|
+
"delete",
|
|
175
|
+
"patch",
|
|
176
|
+
"options",
|
|
177
|
+
"head",
|
|
178
|
+
"trace",
|
|
179
|
+
):
|
|
180
|
+
if isinstance(val, dict):
|
|
181
|
+
new_path_item[key] = self._convert_operation(
|
|
182
|
+
val, global_consumes, global_produces, path_parameters
|
|
183
|
+
)
|
|
184
|
+
elif key != "parameters":
|
|
185
|
+
new_path_item[key] = val
|
|
186
|
+
|
|
187
|
+
converted_paths[path_str] = new_path_item
|
|
188
|
+
|
|
189
|
+
return converted_paths
|
|
190
|
+
|
|
191
|
+
def _convert_operation(
|
|
192
|
+
self,
|
|
193
|
+
operation: dict[str, Any],
|
|
194
|
+
global_consumes: list[str],
|
|
195
|
+
global_produces: list[str],
|
|
196
|
+
path_parameters: list[Any],
|
|
197
|
+
) -> dict[str, Any]:
|
|
198
|
+
op = copy.deepcopy(operation)
|
|
199
|
+
op_consumes = op.pop("consumes", global_consumes)
|
|
200
|
+
op_produces = op.pop("produces", global_produces)
|
|
201
|
+
|
|
202
|
+
# Merge path parameters and operation parameters
|
|
203
|
+
all_params = list(path_parameters) + op.get("parameters", [])
|
|
204
|
+
normal_params: list[dict[str, Any]] = []
|
|
205
|
+
body_param: dict[str, Any] | None = None
|
|
206
|
+
form_params: list[dict[str, Any]] = []
|
|
207
|
+
|
|
208
|
+
for p in all_params:
|
|
209
|
+
if not isinstance(p, dict):
|
|
210
|
+
continue
|
|
211
|
+
p_in = p.get("in")
|
|
212
|
+
if p_in == "body":
|
|
213
|
+
body_param = p
|
|
214
|
+
elif p_in == "formData":
|
|
215
|
+
form_params.append(p)
|
|
216
|
+
else:
|
|
217
|
+
normal_params.append(self._convert_parameter(p))
|
|
218
|
+
|
|
219
|
+
op["parameters"] = normal_params
|
|
220
|
+
|
|
221
|
+
# Convert Request Body (from in: body or in: formData)
|
|
222
|
+
if body_param:
|
|
223
|
+
op["requestBody"] = self._convert_body_parameter(body_param, op_consumes)
|
|
224
|
+
elif form_params:
|
|
225
|
+
op["requestBody"] = self._convert_form_parameters(form_params, op_consumes)
|
|
226
|
+
|
|
227
|
+
# Convert Responses
|
|
228
|
+
if "responses" in op and isinstance(op["responses"], dict):
|
|
229
|
+
converted_resps: dict[str, Any] = {}
|
|
230
|
+
for code, resp in op["responses"].items():
|
|
231
|
+
if isinstance(resp, dict):
|
|
232
|
+
converted_resps[str(code)] = self._convert_response(
|
|
233
|
+
resp, op_produces
|
|
234
|
+
)
|
|
235
|
+
op["responses"] = converted_resps
|
|
236
|
+
|
|
237
|
+
return op
|
|
238
|
+
|
|
239
|
+
def _convert_parameter(self, param: dict[str, Any]) -> dict[str, Any]:
|
|
240
|
+
p = copy.deepcopy(param)
|
|
241
|
+
if "schema" in p:
|
|
242
|
+
return p
|
|
243
|
+
|
|
244
|
+
# Move top-level schema attributes into schema dict
|
|
245
|
+
schema: dict[str, Any] = {}
|
|
246
|
+
for attr in (
|
|
247
|
+
"type",
|
|
248
|
+
"format",
|
|
249
|
+
"items",
|
|
250
|
+
"enum",
|
|
251
|
+
"default",
|
|
252
|
+
"minimum",
|
|
253
|
+
"maximum",
|
|
254
|
+
"minLength",
|
|
255
|
+
"maxLength",
|
|
256
|
+
"pattern",
|
|
257
|
+
"minItems",
|
|
258
|
+
"maxItems",
|
|
259
|
+
"uniqueItems",
|
|
260
|
+
"multipleOf",
|
|
261
|
+
):
|
|
262
|
+
if attr in p:
|
|
263
|
+
schema[attr] = p.pop(attr)
|
|
264
|
+
|
|
265
|
+
if schema:
|
|
266
|
+
p["schema"] = schema
|
|
267
|
+
return p
|
|
268
|
+
|
|
269
|
+
def _convert_body_parameter(
|
|
270
|
+
self, body_param: dict[str, Any], consumes: list[str]
|
|
271
|
+
) -> dict[str, Any]:
|
|
272
|
+
schema = body_param.get("schema", {})
|
|
273
|
+
content: dict[str, Any] = {}
|
|
274
|
+
|
|
275
|
+
media_types = consumes if consumes else ["application/json"]
|
|
276
|
+
for media_type in media_types:
|
|
277
|
+
content[media_type] = {"schema": schema}
|
|
278
|
+
|
|
279
|
+
req_body: dict[str, Any] = {"content": content}
|
|
280
|
+
if "description" in body_param:
|
|
281
|
+
req_body["description"] = body_param["description"]
|
|
282
|
+
if "required" in body_param:
|
|
283
|
+
req_body["required"] = body_param["required"]
|
|
284
|
+
|
|
285
|
+
return req_body
|
|
286
|
+
|
|
287
|
+
def _convert_form_parameters(
|
|
288
|
+
self, form_params: list[dict[str, Any]], consumes: list[str]
|
|
289
|
+
) -> dict[str, Any]:
|
|
290
|
+
properties: dict[str, Any] = {}
|
|
291
|
+
required: list[str] = []
|
|
292
|
+
has_file = False
|
|
293
|
+
|
|
294
|
+
for p in form_params:
|
|
295
|
+
name = p.get("name")
|
|
296
|
+
if not name:
|
|
297
|
+
continue
|
|
298
|
+
if p.get("required"):
|
|
299
|
+
required.append(name)
|
|
300
|
+
|
|
301
|
+
p_type = p.get("type", "string")
|
|
302
|
+
if p_type == "file":
|
|
303
|
+
has_file = True
|
|
304
|
+
prop_schema: dict[str, Any] = {"type": "string", "format": "binary"}
|
|
305
|
+
else:
|
|
306
|
+
prop_schema = {}
|
|
307
|
+
for attr in (
|
|
308
|
+
"type",
|
|
309
|
+
"format",
|
|
310
|
+
"items",
|
|
311
|
+
"enum",
|
|
312
|
+
"default",
|
|
313
|
+
"description",
|
|
314
|
+
):
|
|
315
|
+
if attr in p:
|
|
316
|
+
prop_schema[attr] = p[attr]
|
|
317
|
+
properties[name] = prop_schema
|
|
318
|
+
|
|
319
|
+
schema: dict[str, Any] = {
|
|
320
|
+
"type": "object",
|
|
321
|
+
"properties": properties,
|
|
322
|
+
}
|
|
323
|
+
if required:
|
|
324
|
+
schema["required"] = required
|
|
325
|
+
|
|
326
|
+
media_type = (
|
|
327
|
+
"multipart/form-data"
|
|
328
|
+
if has_file
|
|
329
|
+
else (consumes[0] if consumes else "application/x-www-form-urlencoded")
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
"required": bool(required),
|
|
334
|
+
"content": {media_type: {"schema": schema}},
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
def _convert_response(
|
|
338
|
+
self, resp: dict[str, Any], produces: list[str]
|
|
339
|
+
) -> dict[str, Any]:
|
|
340
|
+
r = copy.deepcopy(resp)
|
|
341
|
+
if "schema" in r:
|
|
342
|
+
schema = r.pop("schema")
|
|
343
|
+
content: dict[str, Any] = {}
|
|
344
|
+
media_types = produces if produces else ["application/json"]
|
|
345
|
+
for media_type in media_types:
|
|
346
|
+
content[media_type] = {"schema": schema}
|
|
347
|
+
r["content"] = content
|
|
348
|
+
return r
|
|
349
|
+
|
|
350
|
+
def _rewrite_references(self, obj: Any) -> Any:
|
|
351
|
+
if isinstance(obj, dict):
|
|
352
|
+
new_dict: dict[str, Any] = {}
|
|
353
|
+
for k, v in obj.items():
|
|
354
|
+
if k == "$ref" and isinstance(v, str):
|
|
355
|
+
ref_str = v
|
|
356
|
+
ref_str = ref_str.replace("#/definitions/", "#/components/schemas/")
|
|
357
|
+
ref_str = ref_str.replace(
|
|
358
|
+
"#/parameters/", "#/components/parameters/"
|
|
359
|
+
)
|
|
360
|
+
ref_str = ref_str.replace("#/responses/", "#/components/responses/")
|
|
361
|
+
ref_str = ref_str.replace(
|
|
362
|
+
"#/securityDefinitions/", "#/components/securitySchemes/"
|
|
363
|
+
)
|
|
364
|
+
new_dict[k] = ref_str
|
|
365
|
+
else:
|
|
366
|
+
new_dict[k] = self._rewrite_references(v)
|
|
367
|
+
return new_dict
|
|
368
|
+
elif isinstance(obj, list):
|
|
369
|
+
return [self._rewrite_references(item) for item in obj]
|
|
370
|
+
return obj
|
|
@@ -72,14 +72,14 @@ def validate_raw_spec(spec_dict: dict[str, Any]) -> None:
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
version_str = str(version)
|
|
75
|
-
if
|
|
75
|
+
if not (
|
|
76
|
+
version_str.startswith("2.0")
|
|
77
|
+
or version_str.startswith("2.")
|
|
78
|
+
or version_str.startswith("3.0")
|
|
79
|
+
or version_str.startswith("3.1")
|
|
80
|
+
):
|
|
76
81
|
raise UnsupportedVersionError(
|
|
77
|
-
f"
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if not (version_str.startswith("3.0") or version_str.startswith("3.1")):
|
|
81
|
-
raise UnsupportedVersionError(
|
|
82
|
-
f"OpenAPI version '{version_str}' is not supported."
|
|
82
|
+
f"Specification version '{version_str}' is not supported."
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
if "paths" not in spec_dict:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langchain-openapi-tools"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Convert OpenAPI specifications into native, production-grade LangChain tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|