secure-log2test 1.0.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.
- secure_log2test/__init__.py +9 -0
- secure_log2test/__main__.py +6 -0
- secure_log2test/cli.py +66 -0
- secure_log2test/core/__init__.py +0 -0
- secure_log2test/core/generator.py +46 -0
- secure_log2test/core/parser.py +73 -0
- secure_log2test/templates/test_module.py.j2 +20 -0
- secure_log2test-1.0.0.dist-info/METADATA +177 -0
- secure_log2test-1.0.0.dist-info/RECORD +12 -0
- secure_log2test-1.0.0.dist-info/WHEEL +4 -0
- secure_log2test-1.0.0.dist-info/entry_points.txt +2 -0
- secure_log2test-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""secure-log2test: convert Kibana API log exports into runnable pytest suites."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = _pkg_version("secure-log2test")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
# Running from a source checkout without an installed distribution.
|
|
9
|
+
__version__ = "0.0.0+unknown"
|
secure_log2test/cli.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""CLI entry for secure-log2test.
|
|
2
|
+
|
|
3
|
+
Wired through `console_scripts` entry в pyproject.toml so installs expose
|
|
4
|
+
a `secure-log2test` command. Also runnable via `python -m secure_log2test`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .core.generator import KibanaTestGenerator
|
|
15
|
+
from .core.parser import KibanaLogParser
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv: list[str] | None = None) -> int:
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="secure-log2test",
|
|
21
|
+
description="Convert Kibana API log export to executable pytest suite",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("input", type=Path, help="Path to Kibana JSON export")
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--output",
|
|
26
|
+
type=Path,
|
|
27
|
+
default=Path("tests_generated.py"),
|
|
28
|
+
help="Output pytest module (default: tests_generated.py)",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--base-url",
|
|
32
|
+
default="",
|
|
33
|
+
help="Base URL prefix for generated requests (default: empty)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--templates",
|
|
37
|
+
type=Path,
|
|
38
|
+
default=Path(__file__).parent / "templates",
|
|
39
|
+
help="Templates directory (default: bundled package templates)",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument("--verbose", action="store_true")
|
|
42
|
+
args = parser.parse_args(argv)
|
|
43
|
+
|
|
44
|
+
logging.basicConfig(
|
|
45
|
+
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
46
|
+
format="%(levelname)s %(message)s",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not args.input.exists():
|
|
50
|
+
print(f"Input file not found: {args.input}", file=sys.stderr)
|
|
51
|
+
return 1
|
|
52
|
+
|
|
53
|
+
log_parser = KibanaLogParser(args.input)
|
|
54
|
+
entries = log_parser.parse()
|
|
55
|
+
if not entries:
|
|
56
|
+
print("No entries parsed from input log.", file=sys.stderr)
|
|
57
|
+
return 1
|
|
58
|
+
|
|
59
|
+
generator = KibanaTestGenerator(args.templates)
|
|
60
|
+
generator.write(entries, args.output, base_url=args.base_url)
|
|
61
|
+
print(f"Generated {len(entries)} tests -> {args.output}")
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
6
|
+
|
|
7
|
+
from .parser import KibanaLogEntry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _slugify(value):
|
|
14
|
+
value = value.lower()
|
|
15
|
+
value = re.sub(r"[^a-z0-9]+", "_", value)
|
|
16
|
+
value = value.strip("_")
|
|
17
|
+
return value or "endpoint"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KibanaTestGenerator:
|
|
21
|
+
def __init__(self, templates_dir):
|
|
22
|
+
self.env = Environment(
|
|
23
|
+
loader=FileSystemLoader(str(templates_dir)),
|
|
24
|
+
autoescape=select_autoescape([]),
|
|
25
|
+
trim_blocks=True,
|
|
26
|
+
lstrip_blocks=True,
|
|
27
|
+
)
|
|
28
|
+
self.env.filters["slug"] = _slugify
|
|
29
|
+
|
|
30
|
+
def render(self, entries, base_url=""):
|
|
31
|
+
template = self.env.get_template("test_module.py.j2")
|
|
32
|
+
cleaned = []
|
|
33
|
+
for e in entries:
|
|
34
|
+
if isinstance(e, KibanaLogEntry):
|
|
35
|
+
cleaned.append(e)
|
|
36
|
+
else:
|
|
37
|
+
cleaned.append(KibanaLogEntry(**e))
|
|
38
|
+
return template.render(entries=cleaned, base_url=base_url)
|
|
39
|
+
|
|
40
|
+
def write(self, entries, output_path, base_url=""):
|
|
41
|
+
output_path = Path(output_path)
|
|
42
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
rendered = self.render(entries, base_url=base_url)
|
|
44
|
+
output_path.write_text(rendered, encoding="utf-8")
|
|
45
|
+
logger.info("Wrote %d entries to %s", len(entries), output_path)
|
|
46
|
+
return output_path
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SENSITIVE_HEADERS = frozenset({
|
|
13
|
+
"authorization",
|
|
14
|
+
"proxy-authorization",
|
|
15
|
+
"cookie",
|
|
16
|
+
"set-cookie",
|
|
17
|
+
"x-api-key",
|
|
18
|
+
"x-auth-token",
|
|
19
|
+
"authentication",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
REDACTED = "***REDACTED***"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def redact_headers(headers):
|
|
26
|
+
"""Replace sensitive header values with a fixed redaction marker.
|
|
27
|
+
|
|
28
|
+
Header name match is case-insensitive. Returns a new dict; the input
|
|
29
|
+
is not mutated.
|
|
30
|
+
"""
|
|
31
|
+
if not headers:
|
|
32
|
+
return {}
|
|
33
|
+
return {
|
|
34
|
+
name: (REDACTED if name.lower() in SENSITIVE_HEADERS else value)
|
|
35
|
+
for name, value in headers.items()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class KibanaLogEntry(BaseModel):
|
|
40
|
+
method: str
|
|
41
|
+
url: str
|
|
42
|
+
status: int
|
|
43
|
+
duration: int = 0
|
|
44
|
+
headers: dict[str, str] = Field(default_factory=dict)
|
|
45
|
+
body: Any = None
|
|
46
|
+
|
|
47
|
+
@field_validator("method")
|
|
48
|
+
@classmethod
|
|
49
|
+
def normalize_method(cls, v):
|
|
50
|
+
return v.upper()
|
|
51
|
+
|
|
52
|
+
@field_validator("headers")
|
|
53
|
+
@classmethod
|
|
54
|
+
def redact_sensitive_headers(cls, v):
|
|
55
|
+
return redact_headers(v)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class KibanaLogParser:
|
|
59
|
+
def __init__(self, path):
|
|
60
|
+
self.path = Path(path)
|
|
61
|
+
|
|
62
|
+
def parse(self):
|
|
63
|
+
with open(self.path) as f:
|
|
64
|
+
data = json.load(f)
|
|
65
|
+
|
|
66
|
+
entries = []
|
|
67
|
+
for hit in data.get("hits", {}).get("hits", []):
|
|
68
|
+
try:
|
|
69
|
+
entries.append(KibanaLogEntry(**hit["_source"]))
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning(f"Skipping bad entry: {e}")
|
|
72
|
+
|
|
73
|
+
return entries
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Auto-generated regression suite. Do not edit by hand."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BASE_URL = "{{ base_url }}"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
{% for entry in entries %}
|
|
11
|
+
def test_{{ entry.method | lower }}_{{ entry.url | slug }}_{{ loop.index }}():
|
|
12
|
+
response = requests.request(
|
|
13
|
+
method="{{ entry.method }}",
|
|
14
|
+
url=BASE_URL + "{{ entry.url }}",
|
|
15
|
+
timeout=10,
|
|
16
|
+
)
|
|
17
|
+
assert response.status_code == {{ entry.status }}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
{% endfor %}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: secure-log2test
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Convert Kibana API log exports into runnable pytest suites with auth header redaction.
|
|
5
|
+
Project-URL: Homepage, https://github.com/golikovichev/secure-log2test
|
|
6
|
+
Project-URL: Repository, https://github.com/golikovichev/secure-log2test
|
|
7
|
+
Project-URL: Issues, https://github.com/golikovichev/secure-log2test/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/golikovichev/secure-log2test/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Mikhail Golikov <golikovichev@gmail.com>
|
|
10
|
+
Maintainer-email: Mikhail Golikov <golikovichev@gmail.com>
|
|
11
|
+
License: MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Mikhail Golikov
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Keywords: api-testing,kibana,log-parsing,pytest,qa,test-generation
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
43
|
+
Classifier: Topic :: Software Development :: Testing
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Requires-Dist: jinja2>=3.1
|
|
46
|
+
Requires-Dist: pydantic>=2.0
|
|
47
|
+
Requires-Dist: requests>=2.31
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: twine>=4.0; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# secure-log2test
|
|
55
|
+
|
|
56
|
+
[](https://github.com/golikovichev/secure-log2test/actions/workflows/ci.yml)
|
|
57
|
+
[](https://pypi.org/project/secure-log2test/)
|
|
58
|
+
[](https://opensource.org/licenses/MIT)
|
|
59
|
+
|
|
60
|
+
Turn a Kibana API log export into an executable pytest suite. Auth headers redacted by default.
|
|
61
|
+
|
|
62
|
+
Status: alpha. v1.0.0 lands May 2026.
|
|
63
|
+
|
|
64
|
+
## Why
|
|
65
|
+
|
|
66
|
+
You have Kibana logs from staging or production. Each entry is a real request: method, URL, status, duration, headers, body. That's a regression suite waiting to happen. Most teams either ignore it, screenshot interesting failures into Jira, or hand-write pytest cases from log entries one at a time.
|
|
67
|
+
|
|
68
|
+
I needed a faster path. `secure-log2test` reads a Kibana JSON export and writes a pytest module you can run and commit. Auth values get replaced with `***REDACTED***` before they ever touch the output, so a generated suite is safe to push to a public repo.
|
|
69
|
+
|
|
70
|
+
The tool exists because at Лента I kept doing the same five steps by hand for every production incident: open Kibana, scroll, copy the failing request, paste into a new test, repeat. Five minutes per request times ten requests means an hour gone before any actual debugging starts.
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/golikovichev/secure-log2test
|
|
76
|
+
cd secure-log2test
|
|
77
|
+
python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
78
|
+
pip install -r requirements.txt
|
|
79
|
+
|
|
80
|
+
python main.py data/sample_kibana_export.json --output tests_generated.py
|
|
81
|
+
pytest tests_generated.py -v
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The sample export ships with the repo (`data/sample_kibana_export.json`), so you can see real output without setting up a Kibana instance first.
|
|
85
|
+
|
|
86
|
+
PyPI install (`pip install secure-log2test`) lands with v1.0.0.
|
|
87
|
+
|
|
88
|
+
## How it works
|
|
89
|
+
|
|
90
|
+
Two stages, kept separate.
|
|
91
|
+
|
|
92
|
+
**Parse** (`core/parser.py`). Reads the Kibana JSON and validates each entry through Pydantic v2. Sensitive headers are redacted before any further processing. The redaction list lives in `SENSITIVE_HEADERS`: `authorization`, `proxy-authorization`, `cookie`, `set-cookie`, `x-api-key`, `x-auth-token`, `authentication`. Match is case-insensitive. Header values get replaced with `***REDACTED***`. The original input dict is not mutated.
|
|
93
|
+
|
|
94
|
+
**Generate** (`core/generator.py`). Takes the cleaned entries and renders a Jinja2 template (`templates/test_module.py.j2`) into a pytest module. Each log entry becomes one `test_*` function. The slug filter turns `/api/v1/users/42` into a stable function name. A `--base-url` flag lets you target staging vs production at runtime.
|
|
95
|
+
|
|
96
|
+
The split lets you reuse the parser for other formats. If you want to generate Locust scripts, k6 scenarios, or an OpenAPI spec from the same logs, the parser stays. Only the template changes.
|
|
97
|
+
|
|
98
|
+
## Sample output
|
|
99
|
+
|
|
100
|
+
Given this Kibana log entry:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"method": "POST",
|
|
105
|
+
"url": "/api/v1/users",
|
|
106
|
+
"status": 201,
|
|
107
|
+
"headers": {"Authorization": "Bearer abc.xyz", "Content-Type": "application/json"},
|
|
108
|
+
"body": {"name": "Test", "email": "test@example.com"}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The generator emits something like:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
def test_post_api_v1_users():
|
|
116
|
+
response = requests.post(
|
|
117
|
+
f"{BASE_URL}/api/v1/users",
|
|
118
|
+
headers={"Authorization": "***REDACTED***", "Content-Type": "application/json"},
|
|
119
|
+
json={"name": "Test", "email": "test@example.com"},
|
|
120
|
+
)
|
|
121
|
+
assert response.status_code == 201, (
|
|
122
|
+
f"Expected 201, got {response.status_code}: {response.text[:200]}"
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The `Authorization` value never leaves the parser intact. You set the real token in your environment at run time.
|
|
127
|
+
|
|
128
|
+
## Limitations
|
|
129
|
+
|
|
130
|
+
This is what v0.2.0 does **not** handle. Calling them out so the tool stays trustworthy.
|
|
131
|
+
|
|
132
|
+
- OAuth flows. Only static `Authorization` headers, redacted to a placeholder.
|
|
133
|
+
- Multipart bodies and file uploads.
|
|
134
|
+
- Streaming responses or chunked transfer.
|
|
135
|
+
- Kibana exports older than the schema in `data/sample_kibana_export.json`. Fixture-driven; if your export looks different, open an issue with a redacted sample.
|
|
136
|
+
- Response body assertions. Status code only for now.
|
|
137
|
+
- Pre-request scripts or test scripts (the project takes only what is in the log entry).
|
|
138
|
+
|
|
139
|
+
If something on this list blocks you, open an issue. v1.0 stays small on purpose; the roadmap below is where new scope goes.
|
|
140
|
+
|
|
141
|
+
## Roadmap
|
|
142
|
+
|
|
143
|
+
| Version | Adds |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| 0.3 | Response body assertions, optional schema match. |
|
|
146
|
+
| 0.4 | Custom redaction rules via config file. |
|
|
147
|
+
| 0.5 | Multiple output formats (k6, Locust). |
|
|
148
|
+
| 1.0 | First stable API, PyPI release, full docs site. |
|
|
149
|
+
|
|
150
|
+
Target dates live in `CHANGELOG.md` once the work is in flight.
|
|
151
|
+
|
|
152
|
+
## Tests
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pytest tests/ -v
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Coverage as of 0.2.0:
|
|
159
|
+
- Parser unit tests for valid input, malformed input, header redaction, empty bodies.
|
|
160
|
+
- Edge cases for 5xx responses and missing fields.
|
|
161
|
+
- CI smoke test that runs the CLI end-to-end on the sample export and parses the generated Python with `ast.parse`.
|
|
162
|
+
|
|
163
|
+
CI runs on Python 3.10 and 3.11 via GitHub Actions.
|
|
164
|
+
|
|
165
|
+
## Security note
|
|
166
|
+
|
|
167
|
+
The default redaction list is conservative. If your team uses a custom auth header (`X-Company-Token`, anything not in the standard list), add it to `SENSITIVE_HEADERS` in `core/parser.py` before generating output. PRs welcome.
|
|
168
|
+
|
|
169
|
+
Never commit a generated suite that includes real production tokens. The redaction is a safety net, not a substitute for review.
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
Issue templates and PR guidance live in [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports with a redacted sample log are the most useful kind.
|
|
174
|
+
|
|
175
|
+
## Licence
|
|
176
|
+
|
|
177
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
secure_log2test/__init__.py,sha256=76BFWwHEySfATxwILmhgdq5QlFijtR2vO9Qw1xuaHw4,352
|
|
2
|
+
secure_log2test/__main__.py,sha256=WW2_Q71az5SeiIa9GKvlrJzPEerOEo8dzz9xoSJXW-M,132
|
|
3
|
+
secure_log2test/cli.py,sha256=YBEAwnNwUfnklwyzFQIK7B1UB4i4tXbQuGE1Cx1lrAk,1974
|
|
4
|
+
secure_log2test/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
secure_log2test/core/generator.py,sha256=kp8av-d8YMNJi0mk0iKlgqRxcZ-RrrA0mDgazCCMOIg,1416
|
|
6
|
+
secure_log2test/core/parser.py,sha256=ex15jC1v8KpqvTZW3I3unsEGXB25DEa9Trp4a5dyZ3g,1619
|
|
7
|
+
secure_log2test/templates/test_module.py.j2,sha256=XX6KOyYCopDIXT5yWvlQ9HPAG9cNJfIIv81QoIq941w,436
|
|
8
|
+
secure_log2test-1.0.0.dist-info/METADATA,sha256=PK3fXjfbxZJPhwyrv_fp77zUU8Oq_hPaIcBXiKTEL5o,8460
|
|
9
|
+
secure_log2test-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
10
|
+
secure_log2test-1.0.0.dist-info/entry_points.txt,sha256=yOS7yQSm0fwoD87Xjf5jDqHBjvdmIEtyVVMizlehaAI,61
|
|
11
|
+
secure_log2test-1.0.0.dist-info/licenses/LICENSE,sha256=JAoRnZLrd1Uavgc7CXfSQUM3qVL3FKNrLRsg5JP6ro8,1072
|
|
12
|
+
secure_log2test-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikhail Golikov
|
|
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.
|