mad-search 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.
- mad_search-0.1.0/.gitignore +13 -0
- mad_search-0.1.0/.python-version +1 -0
- mad_search-0.1.0/LICENSE +21 -0
- mad_search-0.1.0/PKG-INFO +92 -0
- mad_search-0.1.0/README.md +79 -0
- mad_search-0.1.0/pyproject.toml +25 -0
- mad_search-0.1.0/src/mad_search/__init__.py +5 -0
- mad_search-0.1.0/src/mad_search/__main__.py +65 -0
- mad_search-0.1.0/src/mad_search/config.py +49 -0
- mad_search-0.1.0/src/mad_search/formatter.py +53 -0
- mad_search-0.1.0/src/mad_search/journals.py +29 -0
- mad_search-0.1.0/src/mad_search/models.py +25 -0
- mad_search-0.1.0/src/mad_search/rerank.py +58 -0
- mad_search-0.1.0/src/mad_search/search.py +131 -0
- mad_search-0.1.0/uv.lock +166 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
mad_search-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The mad-search authors
|
|
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,92 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mad-search
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Search OpenAlex for academic articles using YAML-frontmatter markdown files
|
|
5
|
+
Project-URL: Homepage, https://codeberg.org/divinemadman/mad-search
|
|
6
|
+
Project-URL: Repository, https://codeberg.org/divinemadman/mad-search.git
|
|
7
|
+
Project-URL: Issues, https://codeberg.org/divinemadman/mad-search/issues
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Requires-Dist: pyalex>=0.21
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# mad-search
|
|
15
|
+
|
|
16
|
+
[](https://codeberg.org/divinemadman/mad-search)
|
|
17
|
+
|
|
18
|
+
Search OpenAlex for academic articles using YAML-frontmatter markdown files.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Python 3.13+
|
|
23
|
+
- A free [OpenAlex API key](https://openalex.org/settings/api)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# run directly with uv
|
|
29
|
+
uv run mad-search path/to/search.md
|
|
30
|
+
|
|
31
|
+
# or install globally
|
|
32
|
+
uv tool install mad-search
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Create a markdown file with YAML frontmatter:
|
|
38
|
+
|
|
39
|
+
```markdown
|
|
40
|
+
---
|
|
41
|
+
api_key: your_key_here
|
|
42
|
+
query: "(machine learning AND climate) OR large language models"
|
|
43
|
+
journals:
|
|
44
|
+
- Nature
|
|
45
|
+
- Science
|
|
46
|
+
year_from: 2020
|
|
47
|
+
year_to: 2025
|
|
48
|
+
---
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run it:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv run mad-search search.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Results are appended to the same file, overwriting any previous results.
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- **Boolean search** — `AND`, `OR`, `NOT` with parentheses, passed directly to OpenAlex's Elasticsearch backend
|
|
62
|
+
- **Journal filter** — names auto-resolved to ISSNs via the OpenAlex Sources endpoint
|
|
63
|
+
- **Year range** — optional `year_from` / `year_to` constraints
|
|
64
|
+
- **Result re-ranking** — results scored by title match (40%), abstract match (20%), and concept overlap (40%) after retrieval
|
|
65
|
+
- **Structured output** — summary table plus detail sections with DOI, citation count, FWCI, concepts (top 5, score ≥ 0.3), and abstract
|
|
66
|
+
- **Idempotent** — re-running replaces the previous results section
|
|
67
|
+
|
|
68
|
+
## Output
|
|
69
|
+
|
|
70
|
+
Results are appended as a markdown table with detail sections:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
## Results — 5 paper(s) found
|
|
74
|
+
|
|
75
|
+
| # | Title | DOI | Journal | Year | Citations | Authors |
|
|
76
|
+
|---|---|---|---|---|---|---|
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 1. Paper Title
|
|
81
|
+
|
|
82
|
+
- **DOI:** `10.xxx/...`
|
|
83
|
+
- **Authors:** ...
|
|
84
|
+
- **Journal:** ...
|
|
85
|
+
- **Citations:** 124 | FWCI: 3.2
|
|
86
|
+
- **Concepts:** Climate Science (0.92), Machine Learning (0.87)
|
|
87
|
+
- **Abstract:** ...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# mad-search
|
|
2
|
+
|
|
3
|
+
[](https://codeberg.org/divinemadman/mad-search)
|
|
4
|
+
|
|
5
|
+
Search OpenAlex for academic articles using YAML-frontmatter markdown files.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Python 3.13+
|
|
10
|
+
- A free [OpenAlex API key](https://openalex.org/settings/api)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# run directly with uv
|
|
16
|
+
uv run mad-search path/to/search.md
|
|
17
|
+
|
|
18
|
+
# or install globally
|
|
19
|
+
uv tool install mad-search
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Create a markdown file with YAML frontmatter:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
---
|
|
28
|
+
api_key: your_key_here
|
|
29
|
+
query: "(machine learning AND climate) OR large language models"
|
|
30
|
+
journals:
|
|
31
|
+
- Nature
|
|
32
|
+
- Science
|
|
33
|
+
year_from: 2020
|
|
34
|
+
year_to: 2025
|
|
35
|
+
---
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run it:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv run mad-search search.md
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Results are appended to the same file, overwriting any previous results.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **Boolean search** — `AND`, `OR`, `NOT` with parentheses, passed directly to OpenAlex's Elasticsearch backend
|
|
49
|
+
- **Journal filter** — names auto-resolved to ISSNs via the OpenAlex Sources endpoint
|
|
50
|
+
- **Year range** — optional `year_from` / `year_to` constraints
|
|
51
|
+
- **Result re-ranking** — results scored by title match (40%), abstract match (20%), and concept overlap (40%) after retrieval
|
|
52
|
+
- **Structured output** — summary table plus detail sections with DOI, citation count, FWCI, concepts (top 5, score ≥ 0.3), and abstract
|
|
53
|
+
- **Idempotent** — re-running replaces the previous results section
|
|
54
|
+
|
|
55
|
+
## Output
|
|
56
|
+
|
|
57
|
+
Results are appended as a markdown table with detail sections:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Results — 5 paper(s) found
|
|
61
|
+
|
|
62
|
+
| # | Title | DOI | Journal | Year | Citations | Authors |
|
|
63
|
+
|---|---|---|---|---|---|---|
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 1. Paper Title
|
|
68
|
+
|
|
69
|
+
- **DOI:** `10.xxx/...`
|
|
70
|
+
- **Authors:** ...
|
|
71
|
+
- **Journal:** ...
|
|
72
|
+
- **Citations:** 124 | FWCI: 3.2
|
|
73
|
+
- **Concepts:** Climate Science (0.92), Machine Learning (0.87)
|
|
74
|
+
- **Abstract:** ...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mad-search"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Search OpenAlex for academic articles using YAML-frontmatter markdown files"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pyalex>=0.21",
|
|
9
|
+
"pyyaml>=6.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Homepage = "https://codeberg.org/divinemadman/mad-search"
|
|
14
|
+
Repository = "https://codeberg.org/divinemadman/mad-search.git"
|
|
15
|
+
Issues = "https://codeberg.org/divinemadman/mad-search/issues"
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
mad-search = "mad_search.__main__:main"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/mad_search"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pyalex
|
|
5
|
+
|
|
6
|
+
from .config import parse_frontmatter
|
|
7
|
+
from .journals import resolve_journals
|
|
8
|
+
from .search import run_search
|
|
9
|
+
from .formatter import format_results
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
if len(sys.argv) != 2:
|
|
14
|
+
print("Usage: mad-search <path/to/search.md>")
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
filepath = Path(sys.argv[1])
|
|
18
|
+
if not filepath.exists():
|
|
19
|
+
print(f"Error: file not found: {filepath}")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
config, body = parse_frontmatter(filepath)
|
|
24
|
+
except ValueError as e:
|
|
25
|
+
print(f"Error: {e}")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
pyalex.config.api_key = config.api_key
|
|
29
|
+
|
|
30
|
+
print(f"Query: {config.query}")
|
|
31
|
+
if config.journals:
|
|
32
|
+
print(f"Journals: {', '.join(config.journals)}")
|
|
33
|
+
if config.year_from or config.year_to:
|
|
34
|
+
print(f"Year range: {config.year_from or 'any'} \u2014 {config.year_to or 'any'}")
|
|
35
|
+
|
|
36
|
+
issns = resolve_journals(config.journals)
|
|
37
|
+
if config.journals and not issns:
|
|
38
|
+
print("Error: none of the specified journals could be resolved.")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
if issns:
|
|
41
|
+
print(f"Resolved ISSNs: {', '.join(issns)}")
|
|
42
|
+
|
|
43
|
+
print("Searching OpenAlex\u2026")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
results = run_search(config, issns)
|
|
47
|
+
except RuntimeError as e:
|
|
48
|
+
print(f"Error: {e}")
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
print(f"Found {len(results)} result(s)")
|
|
52
|
+
|
|
53
|
+
output = format_results(results)
|
|
54
|
+
|
|
55
|
+
content = filepath.read_text(encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
if "## Results" in content:
|
|
58
|
+
content = content[: content.index("## Results")].rstrip()
|
|
59
|
+
|
|
60
|
+
filepath.write_text(content + "\n\n" + output + "\n", encoding="utf-8")
|
|
61
|
+
print(f"Written to {filepath}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from .models import SearchConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_frontmatter(path: Path) -> tuple[SearchConfig, str]:
|
|
10
|
+
content = path.read_text(encoding="utf-8")
|
|
11
|
+
|
|
12
|
+
match = re.match(r"^---\s*\n(.*?)\n\s*---\s*\n?(.*)", content, re.DOTALL)
|
|
13
|
+
if not match:
|
|
14
|
+
raise ValueError(
|
|
15
|
+
"No YAML frontmatter found. "
|
|
16
|
+
"File must start with `---\\n...\\n---`."
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
raw_yaml = match.group(1)
|
|
20
|
+
body = match.group(2)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
data = yaml.safe_load(raw_yaml)
|
|
24
|
+
except yaml.YAMLError as e:
|
|
25
|
+
raise ValueError(f"Invalid YAML frontmatter: {e}")
|
|
26
|
+
|
|
27
|
+
if not isinstance(data, dict):
|
|
28
|
+
raise ValueError("YAML frontmatter must be a mapping (key: value pairs)")
|
|
29
|
+
|
|
30
|
+
api_key = data.get("api_key")
|
|
31
|
+
if not api_key:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"`api_key` is required in frontmatter. "
|
|
34
|
+
"Get one at https://openalex.org/settings/api"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
query = data.get("query")
|
|
38
|
+
if not query:
|
|
39
|
+
raise ValueError("`query` is required in frontmatter")
|
|
40
|
+
|
|
41
|
+
config = SearchConfig(
|
|
42
|
+
api_key=str(api_key),
|
|
43
|
+
query=str(query),
|
|
44
|
+
journals=data.get("journals", []) or [],
|
|
45
|
+
year_from=data.get("year_from"),
|
|
46
|
+
year_to=data.get("year_to"),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return config, body
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from .models import WorkResult
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def format_results(results: list[WorkResult]) -> str:
|
|
5
|
+
lines: list[str] = []
|
|
6
|
+
|
|
7
|
+
if not results:
|
|
8
|
+
lines.append("## Results")
|
|
9
|
+
lines.append("")
|
|
10
|
+
lines.append("*No results found.*")
|
|
11
|
+
return "\n".join(lines)
|
|
12
|
+
|
|
13
|
+
lines.append(f"## Results \u2014 {len(results)} paper(s) found")
|
|
14
|
+
lines.append("")
|
|
15
|
+
|
|
16
|
+
lines.append("| # | Title | DOI | Journal | Year | Citations | Authors |")
|
|
17
|
+
lines.append("|---|---|---|---|---|---|---|")
|
|
18
|
+
for i, r in enumerate(results, start=1):
|
|
19
|
+
authors_str = ", ".join(r.authors[:3])
|
|
20
|
+
if len(r.authors) > 3:
|
|
21
|
+
authors_str += " et al."
|
|
22
|
+
title = (r.title or "N/A")[:80]
|
|
23
|
+
doi = r.doi or "N/A"
|
|
24
|
+
journal = (r.journal or "N/A")[:40]
|
|
25
|
+
year = str(r.year or "N/A")
|
|
26
|
+
citations = f"{r.cited_by_count:,}" if r.cited_by_count is not None else "N/A"
|
|
27
|
+
lines.append(f"| {i} | {title} | {doi} | {journal} | {year} | {citations} | {authors_str} |")
|
|
28
|
+
|
|
29
|
+
lines.append("")
|
|
30
|
+
lines.append("---")
|
|
31
|
+
lines.append("")
|
|
32
|
+
|
|
33
|
+
for i, r in enumerate(results, start=1):
|
|
34
|
+
lines.append(f"### {i}. {r.title or 'Untitled'}")
|
|
35
|
+
lines.append("")
|
|
36
|
+
lines.append(f"- **DOI:** `{r.doi or 'N/A'}`")
|
|
37
|
+
lines.append(f"- **Authors:** {', '.join(r.authors) or 'N/A'}")
|
|
38
|
+
lines.append(f"- **Journal:** {r.journal or 'N/A'} ({r.year or 'N/A'})")
|
|
39
|
+
if r.openalex_id:
|
|
40
|
+
lines.append(f"- **OpenAlex:** [{r.openalex_id}]({r.openalex_id})")
|
|
41
|
+
if r.cited_by_count is not None:
|
|
42
|
+
fwci_str = f" | FWCI: {r.fwci:.1f}" if r.fwci is not None else ""
|
|
43
|
+
lines.append(f"- **Citations:** {r.cited_by_count:,}{fwci_str}")
|
|
44
|
+
if r.concepts:
|
|
45
|
+
concepts_str = ", ".join(f"{c['name']} ({c['score']:.2f})" for c in r.concepts)
|
|
46
|
+
lines.append(f"- **Concepts:** {concepts_str}")
|
|
47
|
+
if r.abstract:
|
|
48
|
+
abstract = r.abstract[:1000]
|
|
49
|
+
suffix = "\u2026" if len(r.abstract) > 1000 else ""
|
|
50
|
+
lines.append(f"- **Abstract:** {abstract}{suffix}")
|
|
51
|
+
lines.append("")
|
|
52
|
+
|
|
53
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pyalex import Sources
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def resolve_journals(names: list[str]) -> list[str]:
|
|
5
|
+
if not names:
|
|
6
|
+
return []
|
|
7
|
+
|
|
8
|
+
resolved: list[str] = []
|
|
9
|
+
|
|
10
|
+
for name in names:
|
|
11
|
+
try:
|
|
12
|
+
results = list(Sources().search(name).filter(type="journal").get(per_page=3))
|
|
13
|
+
except Exception as e:
|
|
14
|
+
print(f" \u26a0 Error looking up journal '{name}': {e}")
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
if not results:
|
|
18
|
+
print(f" \u26a0 Could not resolve journal: '{name}'")
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
source = results[0]
|
|
22
|
+
issn = source.get("issn_l") or (source.get("issn") or [None])[0]
|
|
23
|
+
if issn:
|
|
24
|
+
resolved.append(issn)
|
|
25
|
+
print(f" \u2713 {name} \u2192 {issn}")
|
|
26
|
+
else:
|
|
27
|
+
print(f" \u26a0 Journal '{name}' found but has no ISSN")
|
|
28
|
+
|
|
29
|
+
return resolved
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class SearchConfig:
|
|
7
|
+
api_key: str
|
|
8
|
+
query: str
|
|
9
|
+
journals: list[str] = field(default_factory=list)
|
|
10
|
+
year_from: Optional[int] = None
|
|
11
|
+
year_to: Optional[int] = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class WorkResult:
|
|
16
|
+
doi: Optional[str]
|
|
17
|
+
title: Optional[str]
|
|
18
|
+
authors: list[str]
|
|
19
|
+
journal: Optional[str]
|
|
20
|
+
year: Optional[int]
|
|
21
|
+
openalex_id: Optional[str]
|
|
22
|
+
abstract: Optional[str]
|
|
23
|
+
concepts: list[dict] = field(default_factory=list)
|
|
24
|
+
cited_by_count: Optional[int] = None
|
|
25
|
+
fwci: Optional[float] = None
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from .models import WorkResult
|
|
4
|
+
|
|
5
|
+
STOP_WORDS = {"the", "a", "an", "of", "in", "on", "at", "to", "for", "and", "or", "not", "is", "by", "with",
|
|
6
|
+
"from", "as", "be", "this", "that", "it", "its"}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _extract_terms(query: str) -> list[str]:
|
|
10
|
+
cleaned = re.sub(r"\b(?:AND|OR|NOT)\b", " ", query)
|
|
11
|
+
cleaned = re.sub(r"[()\"'']", " ", cleaned)
|
|
12
|
+
tokens = [t.lower() for t in cleaned.split()
|
|
13
|
+
if t.lower() not in STOP_WORDS and len(t) > 1]
|
|
14
|
+
return list(set(tokens))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _title_score(title: str | None, terms: list[str]) -> float:
|
|
18
|
+
if not title or not terms:
|
|
19
|
+
return 0.0
|
|
20
|
+
t = title.lower()
|
|
21
|
+
return sum(1 for term in terms if term in t) / len(terms)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _abstract_score(abstract: str | None, terms: list[str]) -> float:
|
|
25
|
+
if not abstract or not terms:
|
|
26
|
+
return 0.0
|
|
27
|
+
a = abstract.lower()
|
|
28
|
+
return sum(1 for term in terms if term in a) / len(terms)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _concept_score(concepts: list[dict], terms: list[str]) -> float:
|
|
32
|
+
if not concepts or not terms:
|
|
33
|
+
return 0.0
|
|
34
|
+
total = 0.0
|
|
35
|
+
count = 0
|
|
36
|
+
for c in concepts:
|
|
37
|
+
name = c["name"].lower()
|
|
38
|
+
if any(term in name or name in term for term in terms):
|
|
39
|
+
total += c["score"]
|
|
40
|
+
count += 1
|
|
41
|
+
return total / count if count else 0.0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def rerank(results: list[WorkResult], query: str) -> list[WorkResult]:
|
|
45
|
+
terms = _extract_terms(query)
|
|
46
|
+
if not terms:
|
|
47
|
+
return results
|
|
48
|
+
|
|
49
|
+
scored: list[tuple[float, WorkResult]] = []
|
|
50
|
+
for r in results:
|
|
51
|
+
ts = _title_score(r.title, terms)
|
|
52
|
+
aps = _abstract_score(r.abstract, terms)
|
|
53
|
+
cs = _concept_score(r.concepts, terms)
|
|
54
|
+
combined = 0.4 * ts + 0.2 * aps + 0.4 * cs
|
|
55
|
+
scored.append((combined, r))
|
|
56
|
+
|
|
57
|
+
scored.sort(key=lambda x: x[0], reverse=True)
|
|
58
|
+
return [r for _, r in scored]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from pyalex import Works
|
|
4
|
+
|
|
5
|
+
from .models import SearchConfig, WorkResult
|
|
6
|
+
from .rerank import rerank
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def decode_abstract(inverted_index: dict | None) -> str | None:
|
|
10
|
+
if not inverted_index:
|
|
11
|
+
return None
|
|
12
|
+
max_pos = max(max(positions) for positions in inverted_index.values())
|
|
13
|
+
words = [""] * (max_pos + 1)
|
|
14
|
+
for word, positions in inverted_index.items():
|
|
15
|
+
for pos in positions:
|
|
16
|
+
words[pos] = word
|
|
17
|
+
return " ".join(words)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _split_or_query(query: str) -> list[str]:
|
|
21
|
+
parts = []
|
|
22
|
+
depth = 0
|
|
23
|
+
start = 0
|
|
24
|
+
i = 0
|
|
25
|
+
while i < len(query):
|
|
26
|
+
if query[i] == "(":
|
|
27
|
+
depth += 1
|
|
28
|
+
elif query[i] == ")":
|
|
29
|
+
depth -= 1
|
|
30
|
+
elif depth == 0 and query[i:i+4] == " OR ":
|
|
31
|
+
parts.append(query[start:i].strip())
|
|
32
|
+
start = i + 4
|
|
33
|
+
i += 3
|
|
34
|
+
i += 1
|
|
35
|
+
parts.append(query[start:].strip())
|
|
36
|
+
return [p for p in parts if p]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _strip_operators(s: str) -> str:
|
|
40
|
+
cleaned = re.sub(r"\b(?:AND|OR|NOT)\b", " ", s)
|
|
41
|
+
cleaned = re.sub(r"[()\"']", " ", cleaned)
|
|
42
|
+
return cleaned.strip()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _parse_work(w: dict) -> WorkResult:
|
|
46
|
+
authors = [
|
|
47
|
+
a["author"]["display_name"]
|
|
48
|
+
for a in w.get("authorships", [])
|
|
49
|
+
if a.get("author")
|
|
50
|
+
]
|
|
51
|
+
primary = w.get("primary_location") or {}
|
|
52
|
+
source = primary.get("source") or {}
|
|
53
|
+
journal = source.get("display_name")
|
|
54
|
+
abstract = decode_abstract(w.get("abstract_inverted_index"))
|
|
55
|
+
concepts_raw = w.get("concepts", [])
|
|
56
|
+
concepts = [
|
|
57
|
+
{"name": c["display_name"], "score": c["score"]}
|
|
58
|
+
for c in concepts_raw
|
|
59
|
+
if c.get("score", 0) >= 0.3
|
|
60
|
+
][:5]
|
|
61
|
+
return WorkResult(
|
|
62
|
+
doi=w.get("doi"),
|
|
63
|
+
title=w.get("title"),
|
|
64
|
+
authors=authors,
|
|
65
|
+
journal=journal,
|
|
66
|
+
year=w.get("publication_year"),
|
|
67
|
+
openalex_id=w.get("id"),
|
|
68
|
+
abstract=abstract,
|
|
69
|
+
concepts=concepts,
|
|
70
|
+
cited_by_count=w.get("cited_by_count"),
|
|
71
|
+
fwci=w.get("fwci"),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _search_branch(
|
|
76
|
+
search_q: str,
|
|
77
|
+
filters: dict,
|
|
78
|
+
seen_dois: set[str],
|
|
79
|
+
) -> list[WorkResult]:
|
|
80
|
+
works = (
|
|
81
|
+
Works()
|
|
82
|
+
.search(search_q)
|
|
83
|
+
.filter(**filters)
|
|
84
|
+
.select([
|
|
85
|
+
"id", "doi", "title", "authorships",
|
|
86
|
+
"primary_location", "publication_year",
|
|
87
|
+
"abstract_inverted_index",
|
|
88
|
+
"concepts", "cited_by_count", "fwci",
|
|
89
|
+
])
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
results: list[WorkResult] = []
|
|
93
|
+
for page in works.paginate(per_page=200, n_max=1000):
|
|
94
|
+
for w in page:
|
|
95
|
+
doi = w.get("doi")
|
|
96
|
+
if doi:
|
|
97
|
+
if doi in seen_dois:
|
|
98
|
+
continue
|
|
99
|
+
seen_dois.add(doi)
|
|
100
|
+
results.append(_parse_work(w))
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run_search(config: SearchConfig, issns: list[str]) -> list[WorkResult]:
|
|
105
|
+
filters: dict = {}
|
|
106
|
+
|
|
107
|
+
if config.year_from is not None:
|
|
108
|
+
filters["from_publication_date"] = f"{config.year_from}-01-01"
|
|
109
|
+
if config.year_to is not None:
|
|
110
|
+
filters["to_publication_date"] = f"{config.year_to}-12-31"
|
|
111
|
+
if issns:
|
|
112
|
+
filters["primary_location"] = {"source": {"issn": "|".join(issns)}}
|
|
113
|
+
|
|
114
|
+
branches = _split_or_query(config.query)
|
|
115
|
+
if not branches:
|
|
116
|
+
branches = [config.query]
|
|
117
|
+
|
|
118
|
+
all_results: list[WorkResult] = []
|
|
119
|
+
seen_dois: set[str] = set()
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
for branch in branches:
|
|
123
|
+
search_q = _strip_operators(branch)
|
|
124
|
+
if not search_q:
|
|
125
|
+
continue
|
|
126
|
+
all_results.extend(_search_branch(search_q, filters, seen_dois))
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise RuntimeError(f"OpenAlex API error: {e}")
|
|
129
|
+
|
|
130
|
+
all_results = rerank(all_results, config.query)
|
|
131
|
+
return all_results
|
mad_search-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.13"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "certifi"
|
|
7
|
+
version = "2026.6.17"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "charset-normalizer"
|
|
16
|
+
version = "3.4.7"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
|
|
21
|
+
{ url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
|
|
22
|
+
{ url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
|
|
23
|
+
{ url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
|
|
24
|
+
{ url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
|
|
25
|
+
{ url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
|
|
26
|
+
{ url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
|
|
27
|
+
{ url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
|
|
28
|
+
{ url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
|
|
29
|
+
{ url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
|
|
30
|
+
{ url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
|
|
31
|
+
{ url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
|
|
32
|
+
{ url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
|
|
33
|
+
{ url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
|
|
34
|
+
{ url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
|
|
35
|
+
{ url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
|
|
36
|
+
{ url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
|
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
|
|
38
|
+
{ url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
|
|
39
|
+
{ url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
|
|
45
|
+
{ url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
|
|
47
|
+
{ url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
|
|
48
|
+
{ url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
|
|
50
|
+
{ url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
|
|
51
|
+
{ url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
|
|
53
|
+
{ url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
|
|
56
|
+
{ url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
|
|
57
|
+
{ url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
|
|
59
|
+
{ url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
|
|
60
|
+
{ url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
|
|
62
|
+
{ url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[[package]]
|
|
72
|
+
name = "idna"
|
|
73
|
+
version = "3.18"
|
|
74
|
+
source = { registry = "https://pypi.org/simple" }
|
|
75
|
+
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
|
76
|
+
wheels = [
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[[package]]
|
|
81
|
+
name = "mad-search"
|
|
82
|
+
version = "0.1.0"
|
|
83
|
+
source = { editable = "." }
|
|
84
|
+
dependencies = [
|
|
85
|
+
{ name = "pyalex" },
|
|
86
|
+
{ name = "pyyaml" },
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
[package.metadata]
|
|
90
|
+
requires-dist = [
|
|
91
|
+
{ name = "pyalex", specifier = ">=0.21" },
|
|
92
|
+
{ name = "pyyaml", specifier = ">=6.0" },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pyalex"
|
|
97
|
+
version = "0.21"
|
|
98
|
+
source = { registry = "https://pypi.org/simple" }
|
|
99
|
+
dependencies = [
|
|
100
|
+
{ name = "requests" },
|
|
101
|
+
{ name = "urllib3" },
|
|
102
|
+
]
|
|
103
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c7/8a/5e270c3d6021a873667be7469fb95b00e0584593d57b7773ef0db6841cef/pyalex-0.21.tar.gz", hash = "sha256:39f470885187e0e411798d34163453361a3834c4dae53f0a18f272475b749741", size = 52593, upload-time = "2026-02-23T14:11:13.421Z" }
|
|
104
|
+
wheels = [
|
|
105
|
+
{ url = "https://files.pythonhosted.org/packages/c2/b6/714246176f5ad319dc1d06607a093570bc3d2c37de6d5583e106762883a5/pyalex-0.21-py3-none-any.whl", hash = "sha256:988c37eb31ee3d23176b431d37f7e18c37817317d976237abcc6f3c02f92396f", size = 15761, upload-time = "2026-02-23T14:11:12.523Z" },
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[[package]]
|
|
109
|
+
name = "pyyaml"
|
|
110
|
+
version = "6.0.3"
|
|
111
|
+
source = { registry = "https://pypi.org/simple" }
|
|
112
|
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
|
113
|
+
wheels = [
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
|
|
125
|
+
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
|
|
126
|
+
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
|
|
127
|
+
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
|
|
128
|
+
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
|
|
129
|
+
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
|
|
131
|
+
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
[[package]]
|
|
145
|
+
name = "requests"
|
|
146
|
+
version = "2.34.2"
|
|
147
|
+
source = { registry = "https://pypi.org/simple" }
|
|
148
|
+
dependencies = [
|
|
149
|
+
{ name = "certifi" },
|
|
150
|
+
{ name = "charset-normalizer" },
|
|
151
|
+
{ name = "idna" },
|
|
152
|
+
{ name = "urllib3" },
|
|
153
|
+
]
|
|
154
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
|
155
|
+
wheels = [
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
[[package]]
|
|
160
|
+
name = "urllib3"
|
|
161
|
+
version = "2.7.0"
|
|
162
|
+
source = { registry = "https://pypi.org/simple" }
|
|
163
|
+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
|
164
|
+
wheels = [
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
|
166
|
+
]
|