soaptest 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- soaptest/__init__.py +8 -0
- soaptest/__main__.py +26 -0
- soaptest/client/__init__.py +7 -0
- soaptest/client/client.py +107 -0
- soaptest/client/introspect.py +149 -0
- soaptest-0.1.0.dist-info/METADATA +149 -0
- soaptest-0.1.0.dist-info/RECORD +10 -0
- soaptest-0.1.0.dist-info/WHEEL +4 -0
- soaptest-0.1.0.dist-info/entry_points.txt +2 -0
- soaptest-0.1.0.dist-info/licenses/LICENSE +21 -0
soaptest/__init__.py
ADDED
soaptest/__main__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""CLI entry-point: ``soaptest <wsdl-url>`` prints the operation catalog."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
"""Print the operation catalog for a given WSDL URL."""
|
|
10
|
+
if len(sys.argv) < 2: # noqa: PLR2004
|
|
11
|
+
print("Usage: soaptest <wsdl-url-or-path>", file=sys.stderr)
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
|
|
14
|
+
from soaptest import Client # local import keeps startup fast when --help is enough
|
|
15
|
+
|
|
16
|
+
url = sys.argv[1]
|
|
17
|
+
try:
|
|
18
|
+
client = Client.from_wsdl(url)
|
|
19
|
+
client.describe()
|
|
20
|
+
except Exception as exc: # noqa: BLE001
|
|
21
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Client: thin Pythonic wrapper around zeep for SOAP service discovery and calling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from soaptest.client.introspect import Operation, build_catalog, format_catalog
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Client:
|
|
11
|
+
"""A thin wrapper around a zeep client for a single WSDL.
|
|
12
|
+
|
|
13
|
+
Typical usage::
|
|
14
|
+
|
|
15
|
+
from soaptest import Client
|
|
16
|
+
|
|
17
|
+
client = Client.from_wsdl("http://example.com/service?WSDL")
|
|
18
|
+
print(client.describe())
|
|
19
|
+
result = client.SomeOperation(param="value")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, zeep_client: Any, catalog: list[Operation]) -> None:
|
|
23
|
+
# Store under mangled names so __getattr__ never intercepts them
|
|
24
|
+
self._zeep_client = zeep_client
|
|
25
|
+
self._catalog = catalog
|
|
26
|
+
self._catalog_index: dict[str, Operation] = {op.name: op for op in catalog}
|
|
27
|
+
|
|
28
|
+
# ------------------------------------------------------------------
|
|
29
|
+
# Construction
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_wsdl(cls, url: str) -> Client:
|
|
34
|
+
"""Create a Client by loading a WSDL from *url* (http/https or file path).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
url: URL or local file path to the WSDL document.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A ready-to-use Client instance.
|
|
41
|
+
"""
|
|
42
|
+
import zeep # local import so zeep errors surface at call time, not module import
|
|
43
|
+
|
|
44
|
+
zeep_client: Any = zeep.Client(wsdl=url) # type: ignore[no-untyped-call]
|
|
45
|
+
catalog = build_catalog(zeep_client)
|
|
46
|
+
return cls(zeep_client, catalog)
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
# Discovery
|
|
50
|
+
# ------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def operations(self) -> list[Operation]:
|
|
54
|
+
"""Read-only list of Operation named-tuples discovered from the WSDL."""
|
|
55
|
+
return list(self._catalog)
|
|
56
|
+
|
|
57
|
+
def describe(self) -> str:
|
|
58
|
+
"""Return (and print) a formatted listing of all available operations.
|
|
59
|
+
|
|
60
|
+
The output groups operations by service/port::
|
|
61
|
+
|
|
62
|
+
NumberConversionSoap @ https://...
|
|
63
|
+
NumberToWords(ubiNum: nonNegativeInteger) -> string
|
|
64
|
+
NumberToDollars(dNum: decimal) -> string
|
|
65
|
+
"""
|
|
66
|
+
text = format_catalog(self._catalog)
|
|
67
|
+
print(text)
|
|
68
|
+
return text
|
|
69
|
+
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
# Dynamic dispatch
|
|
72
|
+
# ------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def __getattr__(self, name: str) -> Any:
|
|
75
|
+
"""Enable ``client.OperationName(**kwargs)`` for any catalogued operation.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
AttributeError: if *name* is not a known WSDL operation, so that
|
|
79
|
+
``hasattr(client, name)`` works correctly.
|
|
80
|
+
"""
|
|
81
|
+
# _catalog_index is set in __init__ via object.__setattr__-free assignment,
|
|
82
|
+
# so it IS in __dict__. But during pickling/copy Python may call __getattr__
|
|
83
|
+
# before __dict__ is fully restored — guard against that.
|
|
84
|
+
catalog_index = self.__dict__.get("_catalog_index", {})
|
|
85
|
+
if name not in catalog_index:
|
|
86
|
+
raise AttributeError(
|
|
87
|
+
f"{type(self).__name__!r} object has no attribute {name!r}. "
|
|
88
|
+
f"Available operations: {list(catalog_index)}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
service = self._zeep_client.service
|
|
92
|
+
|
|
93
|
+
def _call(**kwargs: Any) -> Any:
|
|
94
|
+
method = getattr(service, name)
|
|
95
|
+
return method(**kwargs)
|
|
96
|
+
|
|
97
|
+
_call.__name__ = name
|
|
98
|
+
_call.__qualname__ = f"Client.{name}"
|
|
99
|
+
return _call
|
|
100
|
+
|
|
101
|
+
# ------------------------------------------------------------------
|
|
102
|
+
# Representation
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def __repr__(self) -> str:
|
|
106
|
+
n = len(self._catalog)
|
|
107
|
+
return f"<soaptest.Client operations={n}>"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Operation catalog: walk zeep's WSDL bindings and build a structured list of operations.
|
|
2
|
+
|
|
3
|
+
Note: `binding._operations` is technically a private zeep API. It is the only
|
|
4
|
+
programmatic way to enumerate operations without parsing raw WSDL XML ourselves.
|
|
5
|
+
TODO: revisit if zeep exposes a stable public introspection API in a future release.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import contextlib
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from typing import NamedTuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Operation(NamedTuple):
|
|
16
|
+
"""A single WSDL operation with its signatures and provenance."""
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
input_signature: str
|
|
20
|
+
output_signature: str
|
|
21
|
+
service: str
|
|
22
|
+
port: str
|
|
23
|
+
port_url: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _format_element(element: object) -> str:
|
|
27
|
+
"""Return a human-readable type string for a zeep WSDL element."""
|
|
28
|
+
try:
|
|
29
|
+
# zeep elements have a .type attribute whose name we can read
|
|
30
|
+
type_obj = getattr(element, "type", None)
|
|
31
|
+
if type_obj is not None:
|
|
32
|
+
qname = getattr(type_obj, "name", None)
|
|
33
|
+
if qname:
|
|
34
|
+
return str(qname)
|
|
35
|
+
# Fall back to the element's own name
|
|
36
|
+
name = getattr(element, "name", None)
|
|
37
|
+
if name:
|
|
38
|
+
return str(name)
|
|
39
|
+
except Exception: # noqa: BLE001
|
|
40
|
+
pass
|
|
41
|
+
return "any"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _input_signature(op: object) -> str:
|
|
45
|
+
"""Produce 'param: type, ...' from a zeep operation's input message."""
|
|
46
|
+
try:
|
|
47
|
+
op_input = getattr(op, "input", None)
|
|
48
|
+
body = getattr(op_input, "body", None)
|
|
49
|
+
if body is None:
|
|
50
|
+
return ""
|
|
51
|
+
elements = getattr(body, "type", body)
|
|
52
|
+
# zeep exposes elements as an iterable of (name, element) pairs
|
|
53
|
+
parts = getattr(elements, "elements", None)
|
|
54
|
+
if parts:
|
|
55
|
+
return ", ".join(f"{n}: {_format_element(e)}" for n, e in parts)
|
|
56
|
+
# Some operations have a flat type with no sub-elements (scalar input)
|
|
57
|
+
name = getattr(body, "name", None)
|
|
58
|
+
if name:
|
|
59
|
+
return f"{name}: {_format_element(body)}"
|
|
60
|
+
except (AttributeError, TypeError):
|
|
61
|
+
pass
|
|
62
|
+
return ""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _output_signature(op: object) -> str:
|
|
66
|
+
"""Produce a type string from a zeep operation's output message."""
|
|
67
|
+
try:
|
|
68
|
+
op_output = getattr(op, "output", None)
|
|
69
|
+
body = getattr(op_output, "body", None)
|
|
70
|
+
if body is None:
|
|
71
|
+
return "any"
|
|
72
|
+
elements = getattr(body, "type", body)
|
|
73
|
+
parts = getattr(elements, "elements", None)
|
|
74
|
+
if parts:
|
|
75
|
+
pairs = list(parts)
|
|
76
|
+
if len(pairs) == 1:
|
|
77
|
+
return _format_element(pairs[0][1])
|
|
78
|
+
return ", ".join(f"{n}: {_format_element(e)}" for n, e in pairs)
|
|
79
|
+
return _format_element(body)
|
|
80
|
+
except (AttributeError, TypeError):
|
|
81
|
+
pass
|
|
82
|
+
return "any"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_catalog(zeep_client: object) -> list[Operation]:
|
|
86
|
+
"""Walk zeep_client.wsdl.services to build a list of Operation records."""
|
|
87
|
+
operations: list[Operation] = []
|
|
88
|
+
|
|
89
|
+
wsdl = getattr(zeep_client, "wsdl", None)
|
|
90
|
+
if wsdl is None:
|
|
91
|
+
return operations
|
|
92
|
+
|
|
93
|
+
services = getattr(wsdl, "services", {})
|
|
94
|
+
for service_name, service in services.items():
|
|
95
|
+
ports = getattr(service, "ports", {})
|
|
96
|
+
for port_name, port in ports.items():
|
|
97
|
+
binding = getattr(port, "binding", None)
|
|
98
|
+
if binding is None:
|
|
99
|
+
continue
|
|
100
|
+
# _operations: dict[str, zeep.wsdl.definitions.Operation]
|
|
101
|
+
# Private API — see module docstring.
|
|
102
|
+
raw_ops = getattr(binding, "_operations", {})
|
|
103
|
+
port_url: str = ""
|
|
104
|
+
with contextlib.suppress(AttributeError, TypeError):
|
|
105
|
+
port_url = str(port.binding_options.get("address", ""))
|
|
106
|
+
for op_name, op in raw_ops.items():
|
|
107
|
+
operations.append(
|
|
108
|
+
Operation(
|
|
109
|
+
name=str(op_name),
|
|
110
|
+
input_signature=_input_signature(op),
|
|
111
|
+
output_signature=_output_signature(op),
|
|
112
|
+
service=str(service_name),
|
|
113
|
+
port=str(port_name),
|
|
114
|
+
port_url=port_url,
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return operations
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def format_catalog(operations: list[Operation]) -> str:
|
|
122
|
+
"""Render the operation list as a human-readable multi-line string.
|
|
123
|
+
|
|
124
|
+
Output format::
|
|
125
|
+
|
|
126
|
+
ServiceName @ https://endpoint.example.com
|
|
127
|
+
OperationA(param: type) -> returnType
|
|
128
|
+
OperationB() -> returnType
|
|
129
|
+
"""
|
|
130
|
+
if not operations:
|
|
131
|
+
return "(no operations found)"
|
|
132
|
+
|
|
133
|
+
# Group by (service, port) preserving insertion order
|
|
134
|
+
groups: dict[tuple[str, str, str], list[Operation]] = defaultdict(list)
|
|
135
|
+
for op in operations:
|
|
136
|
+
groups[(op.service, op.port, op.port_url)].append(op)
|
|
137
|
+
|
|
138
|
+
lines: list[str] = []
|
|
139
|
+
for (_service, port, url), ops in groups.items():
|
|
140
|
+
header = f"{port}"
|
|
141
|
+
if url:
|
|
142
|
+
header += f" @ {url}"
|
|
143
|
+
lines.append(header)
|
|
144
|
+
for op in ops:
|
|
145
|
+
sig = op.input_signature or ""
|
|
146
|
+
ret = op.output_signature or "any"
|
|
147
|
+
lines.append(f" {op.name}({sig}) -> {ret}")
|
|
148
|
+
|
|
149
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: soaptest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Make SOAP services testable from pytest — point at a WSDL, get callable Python.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jaey8den/soap-tester
|
|
6
|
+
Project-URL: Repository, https://github.com/jaey8den/soap-tester
|
|
7
|
+
Project-URL: Issues, https://github.com/jaey8den/soap-tester/issues
|
|
8
|
+
Author-email: jaey8den <89349331+jaey8den@users.noreply.github.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: pytest,soap,testing,wsdl,zeep
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: lxml>=5.0
|
|
26
|
+
Requires-Dist: zeep>=4.2
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# soaptest
|
|
34
|
+
|
|
35
|
+
Make SOAP services testable from pytest. Point it at a WSDL, get callable Python.
|
|
36
|
+
|
|
37
|
+
## 30-second example
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install soaptest
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from soaptest import Client
|
|
45
|
+
|
|
46
|
+
client = Client.from_wsdl(
|
|
47
|
+
"http://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Discover what's available
|
|
51
|
+
print(client.describe())
|
|
52
|
+
# NumberConversionSoap @ http://www.dataaccess.com/webservicesserver/NumberConversion.wso
|
|
53
|
+
# NumberToWords(ubiNum: nonNegativeInteger) -> string
|
|
54
|
+
# NumberToDollars(dNum: decimal) -> string
|
|
55
|
+
|
|
56
|
+
# Call any operation by name
|
|
57
|
+
result = client.NumberToWords(ubiNum=42)
|
|
58
|
+
print(result) # "forty two "
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why
|
|
62
|
+
|
|
63
|
+
SOAP testing today means SoapUI (a 200 MB GUI) or hand-rolling `requests` with raw XML.
|
|
64
|
+
Neither fits naturally into a pytest suite.
|
|
65
|
+
|
|
66
|
+
`soaptest` is a thin Pythonic layer on top of [zeep](https://docs.python-zeep.org/).
|
|
67
|
+
It gives you WSDL discovery (`describe()`) and operation dispatch (`client.OpName(**kwargs)`)
|
|
68
|
+
with zero boilerplate. You write plain pytest test functions; soaptest handles the SOAP.
|
|
69
|
+
|
|
70
|
+
What it does not do yet: record/replay of responses, fault translation into typed Python
|
|
71
|
+
exceptions, type stubs for operations, or a pytest fixture plugin. Those are on the
|
|
72
|
+
[Roadmap](#roadmap). v0.1 is intentionally minimal — get the basics right first.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install soaptest
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Python 3.9+ is required. `zeep`, `lxml`, and `httpx` are installed automatically.
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
### Discover operations
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from soaptest import Client
|
|
88
|
+
|
|
89
|
+
client = Client.from_wsdl("https://yourservice.example.com/service?WSDL")
|
|
90
|
+
print(client.describe()) # pretty-printed operation list
|
|
91
|
+
ops = client.operations # list of Operation named-tuples
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Each `Operation` has: `name`, `input_signature`, `output_signature`, `service`, `port`, `port_url`.
|
|
95
|
+
|
|
96
|
+
### Call an operation
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
result = client.SomeOperation(param1="value", param2=123)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`__getattr__` dispatches to the underlying zeep service. The return value is whatever
|
|
103
|
+
zeep returns (usually a `zeep.objects.*` object or a plain Python scalar). No conversion
|
|
104
|
+
in v0.1 — that's a future feature.
|
|
105
|
+
|
|
106
|
+
Unknown operation names raise `AttributeError`, so `hasattr(client, "Op")` works.
|
|
107
|
+
|
|
108
|
+
### CLI
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
soaptest http://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Prints the operation catalog to stdout and exits.
|
|
115
|
+
|
|
116
|
+
### In pytest
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import pytest
|
|
120
|
+
from soaptest import Client
|
|
121
|
+
|
|
122
|
+
WSDL = "https://yourservice.example.com/service?WSDL"
|
|
123
|
+
|
|
124
|
+
@pytest.fixture(scope="session")
|
|
125
|
+
def soap_client():
|
|
126
|
+
return Client.from_wsdl(WSDL)
|
|
127
|
+
|
|
128
|
+
def test_something(soap_client):
|
|
129
|
+
result = soap_client.SomeOperation(param="value")
|
|
130
|
+
assert result.status == "OK"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Roadmap
|
|
134
|
+
|
|
135
|
+
v0.1 ships the discovery layer only. Planned for later releases:
|
|
136
|
+
|
|
137
|
+
- **Fault translation** — map SOAP faults to a typed `SoapFault` Python exception
|
|
138
|
+
- **pytest fixture plugin** — `@pytest.fixture` helpers and a `--wsdl` CLI option
|
|
139
|
+
- **Type-stub generation** — emit `.pyi` stubs so IDEs can autocomplete operations
|
|
140
|
+
- **Record/replay** — record live responses to YAML cassettes; replay offline
|
|
141
|
+
|
|
142
|
+
## Contributing
|
|
143
|
+
|
|
144
|
+
File a bug or feature request in the issue tracker. Pull requests are welcome once
|
|
145
|
+
an issue is open. For major changes, open an issue first to discuss scope.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
soaptest/__init__.py,sha256=25kzJGt9lM96IshbsK_aZN0seibLCjXl1CSR85ITyD0,176
|
|
2
|
+
soaptest/__main__.py,sha256=MCdo5NlmT--jXnbq3utDtgUI5yWSwWzAoq3Sq4_tgts,679
|
|
3
|
+
soaptest/client/__init__.py,sha256=6Afaf3rbcpPiWXYPj59X9eklAiF2vOI_XSc0ES4qXsE,163
|
|
4
|
+
soaptest/client/client.py,sha256=anv4IAjOxaWboXkP0ZzT5Xj8rR8ypIfVjcLiUG_dClI,3868
|
|
5
|
+
soaptest/client/introspect.py,sha256=PmOOHWVJX9g0umuQS3B2xzqyTIojPwuha8jtsOb28B4,5123
|
|
6
|
+
soaptest-0.1.0.dist-info/METADATA,sha256=0sDUuakIi4srZEYt0koHayGj3Yb20SIcSQnTmbaiy7o,4628
|
|
7
|
+
soaptest-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
soaptest-0.1.0.dist-info/entry_points.txt,sha256=nJZtYO_xEZbENhJMsdjA3_bRqvXh333u--rH8_nv4_M,52
|
|
9
|
+
soaptest-0.1.0.dist-info/licenses/LICENSE,sha256=TILopF8tBKvJWkTqy_7G0943ZQwINo2dE9lI5araqUc,1065
|
|
10
|
+
soaptest-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jaey8den
|
|
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.
|