pdfdata-api 0.2.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.
@@ -0,0 +1,31 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, yanglonggroup
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ For more information please email Prof. Long Yang at long_yang@tongji.edu.cn
@@ -0,0 +1,4 @@
1
+ """Compatibility import for pre-0.2 source users."""
2
+ from pdfdata_api import MaterialsAPI, PDFData_API, __version__
3
+
4
+ __all__ = ["PDFData_API", "MaterialsAPI", "__version__"]
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdfdata-api
3
+ Version: 0.2.0
4
+ Summary: Python client for the PDFData materials database API
5
+ Author-email: PDF Database Team <contact@pdfdata.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://pdfdata.org
8
+ Project-URL: Documentation, https://pdfdata.org/documents/api
9
+ Project-URL: Source, https://github.com/yanglonggroup/pdfDBflask
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.txt
13
+ Requires-Dist: requests>=2.25.0
14
+ Dynamic: license-file
15
+
16
+ # PDFData Python client
17
+
18
+ Standalone client for `https://pdfdata.org/api/v1`. Requires Python 3.9+ and
19
+ `requests`; Flask, MongoDB and scientific packages are not client dependencies.
20
+ Version 0.2.0 source and build configuration are in this repository. A public
21
+ PyPI release has not been made as part of this change.
22
+
23
+ Install from a local checkout (repository root):
24
+
25
+ ```sh
26
+ python -m pip install ./src/pdfdb_backend
27
+ ```
28
+
29
+ Or install the built wheel supplied with a release:
30
+
31
+ ```sh
32
+ python -m pip install /path/to/pdfdata_api-0.2.0-py3-none-any.whl
33
+ ```
34
+
35
+ Generate a key on the website's API page after signing in. Set it in an
36
+ environment variable and query:
37
+
38
+ ```python
39
+ import os
40
+ from pdfdata_api import PDFData_API
41
+
42
+ with PDFData_API(api_key=os.environ["PDFDATA_API_KEY"]) as client:
43
+ page = client.get_materials(elements="Si,O", per_page=20)
44
+ material = client.get_material("mp-10009-X")
45
+ curve = client.get_pdf_data("mp-10009-X")
46
+ all_matches = client.get_all_materials(elements="Si,O")
47
+ ```
48
+
49
+ `get_materials()` and `search()` return one page by default. Page size is 1–100.
50
+ `get_all_materials()` and `search_all()` collect successive pages. Legacy client
51
+ calls with `per_page=0` also collect successive pages; direct HTTP requests must
52
+ use page sizes 1–100. Collection is not a database snapshot while data changes.
53
+
54
+ Advanced search:
55
+
56
+ ```python
57
+ result = client.search({
58
+ "elements": {"all": ["Si"], "none": ["Au"]},
59
+ "qmax": "[10,30]",
60
+ "page": 1,
61
+ "per_page": 20,
62
+ })
63
+ ```
64
+
65
+ For applications already holding a website **access JWT**, `PDFData_API()` can
66
+ call `generate_api_key(jwt_token)`, `get_api_keys(jwt_token)` and
67
+ `delete_api_key(jwt_token, "Key 1")`. The generated key is set on the client.
68
+ Key management requires the login JWT; material queries require the API key.
69
+ `generate_api_key` creates a new key on each call and is not automatically retried.
70
+
71
+ `MaterialsAPI` remains an alias of `PDFData_API`. The old import
72
+ `from PDFData_API import PDFData_API` remains supported. Backend source imports
73
+ `from app.api.client import MaterialsAPI` forward to the same implementation.
74
+ The default timeout is 30 seconds and can be set with `timeout=60`. HTTP errors
75
+ raise `requests.HTTPError`; inspect `exc.response.status_code` and JSON `code`.
76
+
77
+ Build from this directory:
78
+
79
+ ```sh
80
+ python -m build
81
+ ```
82
+
83
+ Only `pdfdata_api` and the legacy `PDFData_API` module are included in the
84
+ distribution. Production environment files and backend code are excluded.
@@ -0,0 +1,69 @@
1
+ # PDFData Python client
2
+
3
+ Standalone client for `https://pdfdata.org/api/v1`. Requires Python 3.9+ and
4
+ `requests`; Flask, MongoDB and scientific packages are not client dependencies.
5
+ Version 0.2.0 source and build configuration are in this repository. A public
6
+ PyPI release has not been made as part of this change.
7
+
8
+ Install from a local checkout (repository root):
9
+
10
+ ```sh
11
+ python -m pip install ./src/pdfdb_backend
12
+ ```
13
+
14
+ Or install the built wheel supplied with a release:
15
+
16
+ ```sh
17
+ python -m pip install /path/to/pdfdata_api-0.2.0-py3-none-any.whl
18
+ ```
19
+
20
+ Generate a key on the website's API page after signing in. Set it in an
21
+ environment variable and query:
22
+
23
+ ```python
24
+ import os
25
+ from pdfdata_api import PDFData_API
26
+
27
+ with PDFData_API(api_key=os.environ["PDFDATA_API_KEY"]) as client:
28
+ page = client.get_materials(elements="Si,O", per_page=20)
29
+ material = client.get_material("mp-10009-X")
30
+ curve = client.get_pdf_data("mp-10009-X")
31
+ all_matches = client.get_all_materials(elements="Si,O")
32
+ ```
33
+
34
+ `get_materials()` and `search()` return one page by default. Page size is 1–100.
35
+ `get_all_materials()` and `search_all()` collect successive pages. Legacy client
36
+ calls with `per_page=0` also collect successive pages; direct HTTP requests must
37
+ use page sizes 1–100. Collection is not a database snapshot while data changes.
38
+
39
+ Advanced search:
40
+
41
+ ```python
42
+ result = client.search({
43
+ "elements": {"all": ["Si"], "none": ["Au"]},
44
+ "qmax": "[10,30]",
45
+ "page": 1,
46
+ "per_page": 20,
47
+ })
48
+ ```
49
+
50
+ For applications already holding a website **access JWT**, `PDFData_API()` can
51
+ call `generate_api_key(jwt_token)`, `get_api_keys(jwt_token)` and
52
+ `delete_api_key(jwt_token, "Key 1")`. The generated key is set on the client.
53
+ Key management requires the login JWT; material queries require the API key.
54
+ `generate_api_key` creates a new key on each call and is not automatically retried.
55
+
56
+ `MaterialsAPI` remains an alias of `PDFData_API`. The old import
57
+ `from PDFData_API import PDFData_API` remains supported. Backend source imports
58
+ `from app.api.client import MaterialsAPI` forward to the same implementation.
59
+ The default timeout is 30 seconds and can be set with `timeout=60`. HTTP errors
60
+ raise `requests.HTTPError`; inspect `exc.response.status_code` and JSON `code`.
61
+
62
+ Build from this directory:
63
+
64
+ ```sh
65
+ python -m build
66
+ ```
67
+
68
+ Only `pdfdata_api` and the legacy `PDFData_API` module are included in the
69
+ distribution. Production environment files and backend code are excluded.
@@ -0,0 +1,5 @@
1
+ """Standalone PDFData client; no Flask, MongoDB or scientific dependencies."""
2
+
3
+ from .client import MaterialsAPI, PDFData_API, __version__
4
+
5
+ __all__ = ["PDFData_API", "MaterialsAPI", "__version__"]
@@ -0,0 +1,143 @@
1
+ """One implementation for the public client and legacy import paths."""
2
+
3
+ from copy import deepcopy
4
+ import math
5
+ from urllib.parse import urlsplit
6
+
7
+ import requests
8
+
9
+
10
+ __version__ = "0.2.0"
11
+ DEFAULT_BASE_URL = "https://pdfdata.org/api/v1"
12
+
13
+
14
+ class PDFData_API:
15
+ """Query public materials with an API key; manage keys with a login JWT."""
16
+
17
+ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, *, timeout=30, session=None):
18
+ parsed = urlsplit(base_url)
19
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc or parsed.query or parsed.fragment:
20
+ raise ValueError("base_url must be an HTTP(S) API URL without a query or fragment")
21
+ if parsed.username or parsed.password:
22
+ raise ValueError("base_url must not contain credentials")
23
+ if isinstance(timeout, bool) or not isinstance(timeout, (int, float)) or not math.isfinite(timeout) or timeout <= 0:
24
+ raise ValueError("timeout must be a positive, finite number of seconds")
25
+ self.base_url = base_url.rstrip("/")
26
+ self.timeout = timeout
27
+ self._session = session if session is not None else requests.Session()
28
+ self.api_key = api_key
29
+ self.headers = {"Content-Type": "application/json",
30
+ "User-Agent": f"PDFData-Python-Client/{__version__}"}
31
+ if api_key is not None:
32
+ self._key(api_key)
33
+ self.headers["Authorization"] = f"Bearer {api_key}"
34
+
35
+ @staticmethod
36
+ def _key(value):
37
+ if not isinstance(value, str) or not value or len(value) > 512 or any(c.isspace() for c in value):
38
+ raise ValueError("A non-empty API key is required")
39
+ return value
40
+
41
+ def _request(self, method, path, *, params=None, body=None, jwt_token=None):
42
+ headers = dict(self.headers)
43
+ if jwt_token is not None:
44
+ if not isinstance(jwt_token, str) or not jwt_token or any(c.isspace() for c in jwt_token):
45
+ raise ValueError("A login access JWT is required")
46
+ headers["Authorization"] = f"Bearer {jwt_token}"
47
+ else:
48
+ headers["Authorization"] = f"Bearer {self._key(self.api_key)}"
49
+ response = self._session.request(method, f"{self.base_url}{path}", headers=headers,
50
+ params=params, json=body, timeout=self.timeout)
51
+ response.raise_for_status()
52
+ return response.json()
53
+
54
+ def get_materials(self, page=1, per_page=20, **filters):
55
+ """One page (1..100 items); legacy per_page=0 uses automatic pagination."""
56
+ if per_page == 0 and not isinstance(per_page, bool):
57
+ if not filters.get("detail") and filters.get("data_type") is None:
58
+ return self.get_all_materials(**filters)
59
+ per_page = 20
60
+ return self._request("GET", "/materials/search",
61
+ params={"page": page, "per_page": per_page, **filters})
62
+
63
+ def get_material(self, material_id):
64
+ return self.get_materials(material_id=material_id, detail="true")
65
+
66
+ def get_pdf_data(self, material_id):
67
+ return self.get_data(material_id, "pdf")
68
+
69
+ def get_data(self, material_id, data_type):
70
+ return self.get_materials(material_id=material_id, data_type=data_type)
71
+
72
+ def search(self, criteria):
73
+ """Advanced POST search; the caller's criteria are never modified."""
74
+ if not isinstance(criteria, dict):
75
+ raise ValueError("criteria must be a dictionary")
76
+ criteria = deepcopy(criteria)
77
+ if criteria.get("per_page") == 0 and not isinstance(criteria.get("per_page"), bool):
78
+ if not criteria.get("detail") and criteria.get("data_type") is None:
79
+ return self.search_all(criteria)
80
+ criteria["per_page"] = 20
81
+ return self._request("POST", "/materials/search", body=criteria)
82
+
83
+ @staticmethod
84
+ def _collect(fetch):
85
+ first = fetch(1)
86
+ if not isinstance(first.get("materials"), list):
87
+ # Legacy calls combining get_all with detail/data_type return one object.
88
+ return first
89
+ rows = list(first["materials"])
90
+ for page in range(2, first["pages"] + 1):
91
+ response = fetch(page)
92
+ rows.extend(response["materials"])
93
+ if not response["materials"]:
94
+ break
95
+ return {"materials": rows, "total": first["total"], "returned": len(rows),
96
+ "query": first.get("query", {})}
97
+
98
+ def get_all_materials(self, **filters):
99
+ """Fetch every page with bounded requests; datasets are not snapshot-isolated."""
100
+ filters = deepcopy(filters)
101
+ filters.pop("page", None)
102
+ filters.pop("per_page", None)
103
+ return self._collect(lambda page: self.get_materials(page=page, per_page=100, **filters))
104
+
105
+ def search_all(self, criteria):
106
+ if not isinstance(criteria, dict):
107
+ raise ValueError("criteria must be a dictionary")
108
+ criteria = deepcopy(criteria)
109
+ criteria["per_page"] = 100
110
+ return self._collect(lambda page: self.search({**criteria, "page": page}))
111
+
112
+ def generate_api_key(self, jwt_token):
113
+ """Works on PDFData_API() before the user has their first API key."""
114
+ if jwt_token is None:
115
+ raise ValueError("A login access JWT is required")
116
+ result = self._request("POST", "/user/create_key", jwt_token=jwt_token)
117
+ self.api_key = result["api_key"]
118
+ self.headers["Authorization"] = f"Bearer {self.api_key}"
119
+ return result
120
+
121
+ def get_api_keys(self, jwt_token):
122
+ if jwt_token is None:
123
+ raise ValueError("A login access JWT is required")
124
+ return self._request("GET", "/user/get_keys", jwt_token=jwt_token)
125
+
126
+ def delete_api_key(self, jwt_token, label):
127
+ if jwt_token is None:
128
+ raise ValueError("A login access JWT is required")
129
+ if not isinstance(label, str) or not label:
130
+ raise ValueError("label is required")
131
+ return self._request("DELETE", "/user/del_keys", body={"label": label}, jwt_token=jwt_token)
132
+
133
+ def close(self):
134
+ self._session.close()
135
+
136
+ def __enter__(self):
137
+ return self
138
+
139
+ def __exit__(self, *args):
140
+ self.close()
141
+
142
+
143
+ MaterialsAPI = PDFData_API
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdfdata-api
3
+ Version: 0.2.0
4
+ Summary: Python client for the PDFData materials database API
5
+ Author-email: PDF Database Team <contact@pdfdata.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://pdfdata.org
8
+ Project-URL: Documentation, https://pdfdata.org/documents/api
9
+ Project-URL: Source, https://github.com/yanglonggroup/pdfDBflask
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.txt
13
+ Requires-Dist: requests>=2.25.0
14
+ Dynamic: license-file
15
+
16
+ # PDFData Python client
17
+
18
+ Standalone client for `https://pdfdata.org/api/v1`. Requires Python 3.9+ and
19
+ `requests`; Flask, MongoDB and scientific packages are not client dependencies.
20
+ Version 0.2.0 source and build configuration are in this repository. A public
21
+ PyPI release has not been made as part of this change.
22
+
23
+ Install from a local checkout (repository root):
24
+
25
+ ```sh
26
+ python -m pip install ./src/pdfdb_backend
27
+ ```
28
+
29
+ Or install the built wheel supplied with a release:
30
+
31
+ ```sh
32
+ python -m pip install /path/to/pdfdata_api-0.2.0-py3-none-any.whl
33
+ ```
34
+
35
+ Generate a key on the website's API page after signing in. Set it in an
36
+ environment variable and query:
37
+
38
+ ```python
39
+ import os
40
+ from pdfdata_api import PDFData_API
41
+
42
+ with PDFData_API(api_key=os.environ["PDFDATA_API_KEY"]) as client:
43
+ page = client.get_materials(elements="Si,O", per_page=20)
44
+ material = client.get_material("mp-10009-X")
45
+ curve = client.get_pdf_data("mp-10009-X")
46
+ all_matches = client.get_all_materials(elements="Si,O")
47
+ ```
48
+
49
+ `get_materials()` and `search()` return one page by default. Page size is 1–100.
50
+ `get_all_materials()` and `search_all()` collect successive pages. Legacy client
51
+ calls with `per_page=0` also collect successive pages; direct HTTP requests must
52
+ use page sizes 1–100. Collection is not a database snapshot while data changes.
53
+
54
+ Advanced search:
55
+
56
+ ```python
57
+ result = client.search({
58
+ "elements": {"all": ["Si"], "none": ["Au"]},
59
+ "qmax": "[10,30]",
60
+ "page": 1,
61
+ "per_page": 20,
62
+ })
63
+ ```
64
+
65
+ For applications already holding a website **access JWT**, `PDFData_API()` can
66
+ call `generate_api_key(jwt_token)`, `get_api_keys(jwt_token)` and
67
+ `delete_api_key(jwt_token, "Key 1")`. The generated key is set on the client.
68
+ Key management requires the login JWT; material queries require the API key.
69
+ `generate_api_key` creates a new key on each call and is not automatically retried.
70
+
71
+ `MaterialsAPI` remains an alias of `PDFData_API`. The old import
72
+ `from PDFData_API import PDFData_API` remains supported. Backend source imports
73
+ `from app.api.client import MaterialsAPI` forward to the same implementation.
74
+ The default timeout is 30 seconds and can be set with `timeout=60`. HTTP errors
75
+ raise `requests.HTTPError`; inspect `exc.response.status_code` and JSON `code`.
76
+
77
+ Build from this directory:
78
+
79
+ ```sh
80
+ python -m build
81
+ ```
82
+
83
+ Only `pdfdata_api` and the legacy `PDFData_API` module are included in the
84
+ distribution. Production environment files and backend code are excluded.
@@ -0,0 +1,12 @@
1
+ LICENSE.txt
2
+ PDFData_API.py
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ pdfdata_api/__init__.py
7
+ pdfdata_api/client.py
8
+ pdfdata_api.egg-info/PKG-INFO
9
+ pdfdata_api.egg-info/SOURCES.txt
10
+ pdfdata_api.egg-info/dependency_links.txt
11
+ pdfdata_api.egg-info/requires.txt
12
+ pdfdata_api.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.25.0
@@ -0,0 +1,2 @@
1
+ PDFData_API
2
+ pdfdata_api
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pdfdata-api"
7
+ version = "0.2.0"
8
+ description = "Python client for the PDFData materials database API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [{name = "PDF Database Team", email = "contact@pdfdata.org"}]
12
+ dependencies = ["requests>=2.25.0"]
13
+ license = "BSD-3-Clause"
14
+ license-files = ["LICENSE.txt"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://pdfdata.org"
18
+ Documentation = "https://pdfdata.org/documents/api"
19
+ Source = "https://github.com/yanglonggroup/pdfDBflask"
20
+
21
+ [tool.setuptools]
22
+ packages = ["pdfdata_api"]
23
+ py-modules = ["PDFData_API"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Legacy setuptools entry point; metadata lives in pyproject.toml."""
2
+ from setuptools import setup
3
+
4
+ if __name__ == "__main__":
5
+ setup()