diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 71c28d7..4532d63 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,6 +30,11 @@ jobs: run: | uv sync --group dev + - name: Copy README assets for MkDocs + run: | + mkdir -p docs/assets + cp assets/ai_quantum_*.png docs/assets/ + - name: Build docs run: uv run mkdocs build diff --git a/README.md b/README.md index f4428d8..3342af4 100644 --- a/README.md +++ b/README.md @@ -103,18 +103,22 @@ get_and_dump_arxiv_papers(..., backend='local') ### Publication keyword search Consider you want to perform a publication keyword search with the query: -`COVID-19` **AND** `Artificial Intelligence` **AND** `Medical Imaging`. +`Artificial Intelligence` **AND** `Quantum Computing` **AND** `Chemistry`. * Scrape papers from PubMed: ```py from paperscraper.pubmed import get_and_dump_pubmed_papers -covid19 = ['COVID-19', 'SARS-CoV-2'] -ai = ['Artificial intelligence', 'Deep learning', 'Machine learning'] -mi = ['Medical imaging'] -query = [covid19, ai, mi] +ai = ['Artificial intelligence', 'Machine learning'] +qc = [ + 'Quantum computing', 'Quantum computer', 'Quantum information', + 'Quantum algorithm', 'Quantum circuit', 'Quantum simulation', + 'Quantum machine learning', 'Qubit', 'Quantum annealing' +] +chemistry = ['Chemistry', 'Chemical', 'Molecule', 'Molecular', 'Materials science'] +query = [ai, qc, chemistry] -get_and_dump_pubmed_papers(query, output_filepath='covid19_ai_imaging.jsonl') +get_and_dump_pubmed_papers(query, output_filepath='ai_quantum_chemistry.jsonl') ``` * Scrape papers from arXiv: @@ -122,7 +126,7 @@ get_and_dump_pubmed_papers(query, output_filepath='covid19_ai_imaging.jsonl') ```py from paperscraper.arxiv import get_and_dump_arxiv_papers -get_and_dump_arxiv_papers(query, output_filepath='covid19_ai_imaging.jsonl') +get_and_dump_arxiv_papers(query, output_filepath='ai_quantum_chemistry.jsonl') ``` * Scrape papers from bioRiv, medRxiv or chemRxiv: @@ -131,7 +135,7 @@ get_and_dump_arxiv_papers(query, output_filepath='covid19_ai_imaging.jsonl') from paperscraper.xrxiv.xrxiv_query import XRXivQuery querier = XRXivQuery('server_dumps/chemrxiv_2020-11-10.jsonl') -querier.search_keywords(query, output_filepath='covid19_ai_imaging.jsonl') +querier.search_keywords(query, output_filepath='ai_quantum_chemistry.jsonl') ``` You can also use `dump_queries` to iterate over a bunch of queries for all available databases. @@ -139,7 +143,12 @@ You can also use `dump_queries` to iterate over a bunch of queries for all avail ```py from paperscraper import dump_queries -queries = [[covid19, ai, mi], [covid19, ai], [ai]] +physics = [ + 'Physics', 'Physical', 'Particle', 'Condensed matter', + 'Many-body', 'Fermion', 'Hamiltonian', 'Spin' +] +biology = ['Biology', 'Biological', 'Cellular', 'Genomics', 'Gene', 'Protein'] +queries = [[ai, qc, chemistry], [ai, qc, physics], [ai, qc, biology]] dump_queries(queries, '.') ``` @@ -148,8 +157,8 @@ Or use the harmonized interface of `QUERY_FN_DICT` to query multiple databases o from paperscraper.load_dumps import QUERY_FN_DICT print(QUERY_FN_DICT.keys()) -QUERY_FN_DICT['biorxiv'](query, output_filepath='biorxiv_covid_ai_imaging.jsonl') -QUERY_FN_DICT['medrxiv'](query, output_filepath='medrxiv_covid_ai_imaging.jsonl') +QUERY_FN_DICT['biorxiv'](query, output_filepath='biorxiv_ai_quantum_chemistry.jsonl') +QUERY_FN_DICT['chemrxiv'](query, output_filepath='chemrxiv_ai_quantum_chemistry.jsonl') ``` * Scrape papers from Google Scholar: @@ -185,7 +194,7 @@ To batch download full texts from your metadata search results: from paperscraper.pdf import save_pdf_from_dump # Save PDFs/XMLs in current folder and name the files by their DOI -save_pdf_from_dump('medrxiv_covid_ai_imaging.jsonl', pdf_path='.', key_to_save='doi') +save_pdf_from_dump('ai_quantum_chemistry.jsonl', pdf_path='.', key_to_save='doi') ``` #### Automatic Fallback Mechanisms @@ -304,19 +313,34 @@ automatically: Venn diagrams and bar plots. Compare the temporal evolution of different queries across different servers. ```py +import os + from paperscraper import QUERY_FN_DICT from paperscraper.postprocessing import aggregate_paper from paperscraper.utils import get_filename_from_query, load_jsonl # Define search terms and their synonyms -ml = ['Deep learning', 'Neural Network', 'Machine learning'] -mol = ['molecule', 'molecular', 'drug', 'ligand', 'compound'] -gnn = ['gcn', 'gnn', 'graph neural', 'graph convolutional', 'molecular graph'] -smiles = ['SMILES', 'Simplified molecular'] -fp = ['fingerprint', 'molecular fingerprint', 'fingerprints'] +ai = ['Artificial intelligence', 'Machine learning'] +qc = [ + 'Quantum computing', 'Quantum computer', 'Quantum information', + 'Quantum algorithm', 'Quantum circuit', 'Quantum simulation', + 'Quantum machine learning', 'Qubit', 'Quantum annealing' +] +chemistry = ['Chemistry', 'Chemical', 'Molecule', 'Molecular', 'Materials science'] +physics = [ + 'Physics', 'Physical', 'Particle', 'Condensed matter', + 'Many-body', 'Fermion', 'Hamiltonian', 'Spin' +] +biology = ['Biology', 'Biological', 'Cellular', 'Genomics', 'Gene', 'Protein'] +medicine = ['Medicine', 'Medical', 'Clinical', 'Disease', 'Patient', 'Health'] # Define queries -queries = [[ml, mol, smiles], [ml, mol, fp], [ml, mol, gnn]] +queries = [ + [ai, qc, chemistry], + [ai, qc, physics], + [ai, qc, biology], + [ai, qc, medicine], +] root = '../keyword_dumps' @@ -328,73 +352,80 @@ for query in queries: # Assuming the keyword search has been performed already data = load_jsonl(os.path.join(root, db, filename)) - # Unstructured matches are aggregated into 6 bins, 1 per year - # from 2015 to 2020. Sanity check is performed by having + # Unstructured matches are aggregated into 8 bins, 1 per year + # from 2019 to 2026. Sanity check is performed by having # `filtering=True`, removing papers that don't contain all of # the keywords in query. data_dict[filename][db], filtered = aggregate_paper( - data, 2015, bins_per_year=1, filtering=True, - filter_keys=query, return_filtered=True + data, 2019, bins_per_year=1, filtering=True, + filter_keys=query, return_filtered=True, last_year=2026 ) # Plotting is now very simple from paperscraper.plotting import plot_comparison data_keys = [ - 'deeplearning_molecule_fingerprint.jsonl', - 'deeplearning_molecule_smiles.jsonl', - 'deeplearning_molecule_gcn.jsonl' + 'artificialintelligence_quantumcomputing_chemistry.jsonl', + 'artificialintelligence_quantumcomputing_physics.jsonl', + 'artificialintelligence_quantumcomputing_biology.jsonl', + 'artificialintelligence_quantumcomputing_medicine.jsonl' ] plot_comparison( data_dict, data_keys, - title_text="'Deep Learning' AND 'Molecule' AND X", - keyword_text=['Fingerprint', 'SMILES', 'Graph'], - figpath='mol_representation' + x_ticks=[str(year) for year in range(2019, 2027)], + title_text="'Artificial intelligence' AND 'Quantum computing' AND X", + keyword_text=['Chemistry', 'Physics', 'Biology', 'Medicine'], + figpath='assets/ai_quantum_fields.png' ) ``` -![molreps](https://github.com/jannisborn/paperscraper/blob/main/assets/molreps.png?raw=true "MolReps") +![Artificial intelligence and quantum computing by field](assets/ai_quantum_fields.png) ### Venn Diagrams +The Venn diagrams below use the local arXiv, bioRxiv, ChemRxiv and medRxiv dumps. + ```py from paperscraper.plotting import ( plot_venn_two, plot_venn_three, plot_multiple_venn ) -sizes_2020 = (30842, 14474, 2292, 35476, 1904, 1408, 376) -sizes_2019 = (55402, 11899, 2563) -labels_2020 = ('Medical\nImaging', 'Artificial\nIntelligence', 'COVID-19') -labels_2019 = ['Medical Imaging', 'Artificial\nIntelligence'] +sizes_2024 = (18762, 6324, 624) +sizes_2025 = (18774, 6966, 775, 28652, 1965, 597, 77) +labels_2024 = ['Artificial\nIntelligence', 'Quantum\nComputing'] +labels_2025 = ('Artificial\nIntelligence', 'Quantum\nComputing', 'Chemistry') -plot_venn_two(sizes_2019, labels_2019, title='2019', figpath='ai_imaging.png') +plot_venn_two( + sizes_2024, labels_2024, title='2024', figpath='assets/ai_quantum_venn_2024.png' +) ``` -![2019](https://github.com/jannisborn/paperscraper/blob/main/assets/ai_imaging.png?raw=true "2019") +![2024 Venn diagram](assets/ai_quantum_venn_2024.png) ```py plot_venn_three( - sizes_2020, labels_2020, title='2020', figpath='ai_imaging_covid.png' + sizes_2025, labels_2025, title='2025', + figpath='assets/ai_quantum_chemistry_venn_2025.png' ) ``` -![2020](https://github.com/jannisborn/paperscraper/blob/main/assets/ai_imaging_covid.png?raw=true "2020") +![2025 Venn diagram](assets/ai_quantum_chemistry_venn_2025.png) Or plot both together: ```py plot_multiple_venn( - [sizes_2019, sizes_2020], [labels_2019, labels_2020], - titles=['2019', '2020'], suptitle='Keyword search comparison', + [sizes_2024, sizes_2025], [labels_2024, labels_2025], + titles=['2024', '2025'], suptitle='Keyword search comparison', gridspec_kw={'width_ratios': [1, 2]}, figsize=(10, 6), - figpath='both.png' + figpath='assets/ai_quantum_venn_both.png' ) ``` -![both](https://github.com/jannisborn/paperscraper/blob/main/assets/both.png?raw=true "Both") +![Venn diagram comparison](assets/ai_quantum_venn_both.png) diff --git a/assets/ai_imaging.png b/assets/ai_imaging.png deleted file mode 100644 index 5301794..0000000 Binary files a/assets/ai_imaging.png and /dev/null differ diff --git a/assets/ai_imaging_covid.png b/assets/ai_imaging_covid.png deleted file mode 100644 index 8507e6b..0000000 Binary files a/assets/ai_imaging_covid.png and /dev/null differ diff --git a/assets/ai_quantum_chemistry_venn_2025.png b/assets/ai_quantum_chemistry_venn_2025.png new file mode 100644 index 0000000..ae24516 Binary files /dev/null and b/assets/ai_quantum_chemistry_venn_2025.png differ diff --git a/assets/ai_quantum_fields.png b/assets/ai_quantum_fields.png new file mode 100644 index 0000000..9238ee7 Binary files /dev/null and b/assets/ai_quantum_fields.png differ diff --git a/assets/ai_quantum_venn_2024.png b/assets/ai_quantum_venn_2024.png new file mode 100644 index 0000000..d54af58 Binary files /dev/null and b/assets/ai_quantum_venn_2024.png differ diff --git a/assets/ai_quantum_venn_both.png b/assets/ai_quantum_venn_both.png new file mode 100644 index 0000000..e242981 Binary files /dev/null and b/assets/ai_quantum_venn_both.png differ diff --git a/assets/both.png b/assets/both.png deleted file mode 100644 index e196345..0000000 Binary files a/assets/both.png and /dev/null differ diff --git a/assets/molreps.png b/assets/molreps.png deleted file mode 100644 index bdd9816..0000000 Binary files a/assets/molreps.png and /dev/null differ diff --git a/codecov.yml b/codecov.yml index 2f3fdec..98ec592 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,7 @@ coverage: patch: off project: default: - target: 80% + target: 70% threshold: 2% # Up to 2% drop/fluctuation is OK ignore: diff --git a/paperscraper/plotting.py b/paperscraper/plotting.py index a514adf..3856e42 100644 --- a/paperscraper/plotting.py +++ b/paperscraper/plotting.py @@ -74,11 +74,13 @@ def plot_comparison( preprint.append(arxiv[-1] + biorxiv[-1] + medrxiv[-1] + chemrxiv[-1]) ind = np.arange(len(arxiv[0])) # the x locations for the groups - width = [0.2] * len(ind) # the width of the bars: can also be len(x) sequence - if len(keys) == 2: - pos = [-0.2, 0.2] - elif len(keys) == 3: - pos = [-0.3, 0.0, 0.3] + bar_width = min(0.8 / len(keys), 0.2) + width = [bar_width] * len(ind) + pos = np.linspace( + -bar_width * (len(keys) - 1) / 2, + bar_width * (len(keys) - 1) / 2, + len(keys), + ) plts = [] legend_plts = [] @@ -88,7 +90,7 @@ def plot_comparison( legend_platform = ["PubMed", "Preprint"] else: bars = [pubmed, arxiv, biorxiv, chemrxiv, medrxiv] - legend_platform = ["PubMed", "ArXiv", "BiorXiv", "ChemRxiv", "MedRxiv"] + legend_platform = ["PubMed", "ArXiv", "bioRxiv", "ChemRxiv", "medRxiv"] for idx in range(len(keys)): bottom = 0 @@ -252,7 +254,7 @@ def plot_single( else: bars = [pubmed, arxiv, biorxiv, chemrxiv, medrxiv] - legend_platform = ["PubMed", "ArXiv", "BiorXiv", "ChemRxiv", "MedRxiv"] + legend_platform = ["PubMed", "ArXiv", "bioRxiv", "ChemRxiv", "medRxiv"] if logscale: sums = ( np.array(pubmed) @@ -370,8 +372,6 @@ def plot_venn_two( subsets=sizes, linestyle="solid", linewidth=0.6, color="grey", **kwargs ) if kwargs.get("ax", False): - print(kwargs, type(kwargs)) - print(kwargs["ax"]) kwargs["ax"].set_title(title, fontdict={"fontweight": "bold"}, size=15) else: plt.title(title, fontdict={"fontweight": "bold"}, size=15) diff --git a/paperscraper/postprocessing.py b/paperscraper/postprocessing.py index 4c50585..d81fdc8 100644 --- a/paperscraper/postprocessing.py +++ b/paperscraper/postprocessing.py @@ -71,6 +71,8 @@ def aggregate_paper( year = int(date.split("-")[0]) if year < start_year or year > last_year: continue + title = str(paper.get("title") or "").lower() + abstract = str(paper.get("abstract") or "").lower() # At least one synonym per keyword needs to be in either title or # abstract. @@ -78,13 +80,9 @@ def aggregate_paper( # Filter out papers which undesired terms unwanted = False for unwanted_key in unwanted_keys: - if unwanted_key.lower() in paper["title"].lower(): + if unwanted_key.lower() in title: unwanted = True - if ( - filter_abstract - and paper["abstract"] is not None - and unwanted_key.lower() in paper["abstract"].lower() - ): + if filter_abstract and unwanted_key.lower() in abstract: unwanted = True if unwanted: continue @@ -95,13 +93,9 @@ def aggregate_paper( if not isinstance(key_term, list): key_term = [key_term] for key in key_term: - if key.lower() in paper["title"].lower(): + if key.lower() in title: got_key = True - if ( - filter_abstract - and paper["abstract"] is not None - and key.lower() in paper["abstract"].lower() - ): + if filter_abstract and key.lower() in abstract: got_key = True got_keys.append(got_key) diff --git a/paperscraper/utils.py b/paperscraper/utils.py index 5696f8e..1b87d50 100644 --- a/paperscraper/utils.py +++ b/paperscraper/utils.py @@ -72,5 +72,5 @@ def load_jsonl(filepath: str) -> List[Dict[str, str]]: """ with open(filepath, "r") as f: - data = [json.loads(line) for line in f.readlines()] + data = [json.loads(line) for line in f if line.strip()] return data diff --git a/pyproject.toml b/pyproject.toml index 727e147..4008944 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ dev = [ "mkdocstrings[python]>=0.24", "mkdocs-material-extensions", "pre-commit>=4.3.0", + "grip>=4.6.2", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index 4caabf1..2b6ab8f 100644 --- a/uv.lock +++ b/uv.lock @@ -163,6 +163,15 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/92/8d/e296c7af03757debd8fc80df2898cbed4fb69fc61ed2c9b4a1d42e923a9e/bibtexparser-1.4.3.tar.gz", hash = "sha256:a9c7ded64bc137720e4df0b1b7f12734edc1361185f1c9097048ff7c35af2b8f", size = 55582, upload-time = "2024-12-19T20:41:57.754Z" } +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + [[package]] name = "boto3" version = "1.42.34" @@ -929,6 +938,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + [[package]] name = "docutils" version = "0.21.2" @@ -1045,6 +1060,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, ] +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + [[package]] name = "fonttools" version = "4.60.2" @@ -1315,6 +1349,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] +[[package]] +name = "grip" +version = "4.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docopt" }, + { name = "flask" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "path-and-address" }, + { name = "pygments" }, + { name = "requests" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/3f/e8bc3ea1f24877292fa3962ad9e0234ad4bc787dc1eb5bd08c35afd0ceca/grip-4.6.2.tar.gz", hash = "sha256:3cf6dce0aa06edd663176914069af83f19dcb90f3a9c401271acfa71872f8ce3", size = 152280, upload-time = "2023-10-12T05:08:02.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/2c/ed06b092d21c66ea3b42408aecb62c3b16748f34205c27e6072186626088/grip-4.6.2-py3-none-any.whl", hash = "sha256:f2192e9d75b603d3de4a2c80ba70d82c7d9ebaade650306e41a7583966d0ed88", size = 138494, upload-time = "2023-10-12T05:07:59.686Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1529,6 +1582,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jaraco-classes" version = "3.4.0" @@ -2865,6 +2927,7 @@ dev = [ { name = "build" }, { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "coverage", version = "7.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "grip" }, { name = "isort", version = "6.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "isort", version = "8.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mkdocs-material" }, @@ -2905,6 +2968,7 @@ requires-dist = [ dev = [ { name = "build" }, { name = "coverage" }, + { name = "grip", specifier = ">=4.6.2" }, { name = "isort", specifier = ">=6" }, { name = "mkdocs-material", specifier = ">=9.6" }, { name = "mkdocs-material-extensions" }, @@ -2915,6 +2979,12 @@ dev = [ { name = "twine" }, ] +[[package]] +name = "path-and-address" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b5/749fab14d9e84257f3b0583eedb54e013422b6c240491a4ae48d9ea5e44f/path-and-address-2.0.1.zip", hash = "sha256:e96363d982b3a2de8531f4cd5f086b51d0248b58527227d43cf5014d045371b7", size = 6503, upload-time = "2016-07-21T02:56:09.794Z" } + [[package]] name = "pathspec" version = "1.0.3" @@ -4979,6 +5049,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + [[package]] name = "wrapt" version = "2.0.1"