gramps-web-api-client 0.1__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,18 @@
1
+ # Python
2
+ __pycache__
3
+
4
+ # setuptools
5
+ /dist
6
+ /src/*.egg-info
7
+
8
+ # mypy
9
+ .mypy_cache/
10
+
11
+ # IntelliJ/PyCharm
12
+ *.iml
13
+ .idea
14
+
15
+ # Miscelaneous
16
+ /build
17
+ /target
18
+ .DS_Store
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: gramps-web-api-client
3
+ Version: 0.1
4
+ Summary: A simple Python client to interact with Gramps Web API
5
+ Author-email: "David M. Straub" <straub@protonmail.com>
6
+ License: MIT
7
+ Project-URL: homepage, https://github.com/DavidMStraub/gramps-web-api-client
8
+ Project-URL: repository, https://github.com/DavidMStraub/gramps-web-api-client
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.31.0
11
+
12
+ # Gramps Web API Client
13
+
14
+
15
+ A simple Python client based on `requests` for interacting with a [Gramps Web API](https://github.com/gramps-project/gramps-webapi/) server.
16
+
17
+ ## Warning
18
+
19
+ This is an experimental project for advanced users. The goal is to allow powerful batch operations on Gramps objects via the API. But only use it if you know what you are doing &ndash; you can easily break your Gramps database with this tool!
20
+
21
+ ## Quick start
22
+
23
+ First, instantiate the API instance:
24
+
25
+ ```python
26
+ from gramps_web_api_client import API
27
+
28
+ api = API(
29
+ host="https://my-gramps-web-instance.com",
30
+ basic_auth("my_user", "my_password")
31
+ )
32
+ ```
33
+
34
+ Methods currently implemented:
35
+
36
+
37
+ | Method | description |
38
+ | ------------- | ------------- |
39
+ | `api.iter_people()` | Generator iterating over people |
40
+ | `api.iter_events()` | Generator iterating over events |
41
+ | `api.iter_places()` | Generator iterating over places |
42
+ | `api.get_person(handle)` | Get a single person by handle |
43
+ | `api.get_event(handle)` | Get a single event by handle |
44
+ | `api.get_place(handle)` | Get a single place by handle |
45
+ | `api.update_person(handle, data)` | Update a single person |
46
+ | `api.update_event(handle, data)` | Update a single event |
47
+ | `api.update_place(handle, data)` | Update a single place |
48
+ | `api.create_person(data)` | Create a new person |
49
+ | `api.create_event(data)` | Create a new evebt |
50
+ | `api.create_place(data)` | GCreateet a new place |
51
+
52
+
53
+ In all cases, `data` are dictionaries for JSON objects following the Gramps Web API conventions.
54
+
55
+ ## Examples
56
+
57
+ Removing all citations from an existing person:
58
+
59
+ ```python
60
+ api.update_person("somehandle", {"citation_list": []})
61
+ ```
62
+
63
+ In this example, all other properties of the person (except citations) will remain the same.
64
+
65
+ Creating a new place:
66
+
67
+ ```python
68
+ api.create_place({
69
+ "name": {
70
+ "_class": "PlaceName",
71
+ "value": "Gotham City"
72
+ }
73
+ })
74
+ ```
75
+
76
+ Iterating over people and adding a citation if a condition is satisfied:
77
+
78
+ ```python
79
+ for person in api.iter_people():
80
+ try:
81
+ surname = person["primary_name"]["surname_list"][0]["surname"]
82
+ except (KeyError, IndexError):
83
+ continue
84
+ if surname == "Garner":
85
+ api.update_person(
86
+ person["handle"],
87
+ {"citation_list": person["citation_list"] + ["some_citation_handle"]}
88
+ )
89
+ ```
@@ -0,0 +1,78 @@
1
+ # Gramps Web API Client
2
+
3
+
4
+ A simple Python client based on `requests` for interacting with a [Gramps Web API](https://github.com/gramps-project/gramps-webapi/) server.
5
+
6
+ ## Warning
7
+
8
+ This is an experimental project for advanced users. The goal is to allow powerful batch operations on Gramps objects via the API. But only use it if you know what you are doing &ndash; you can easily break your Gramps database with this tool!
9
+
10
+ ## Quick start
11
+
12
+ First, instantiate the API instance:
13
+
14
+ ```python
15
+ from gramps_web_api_client import API
16
+
17
+ api = API(
18
+ host="https://my-gramps-web-instance.com",
19
+ basic_auth("my_user", "my_password")
20
+ )
21
+ ```
22
+
23
+ Methods currently implemented:
24
+
25
+
26
+ | Method | description |
27
+ | ------------- | ------------- |
28
+ | `api.iter_people()` | Generator iterating over people |
29
+ | `api.iter_events()` | Generator iterating over events |
30
+ | `api.iter_places()` | Generator iterating over places |
31
+ | `api.get_person(handle)` | Get a single person by handle |
32
+ | `api.get_event(handle)` | Get a single event by handle |
33
+ | `api.get_place(handle)` | Get a single place by handle |
34
+ | `api.update_person(handle, data)` | Update a single person |
35
+ | `api.update_event(handle, data)` | Update a single event |
36
+ | `api.update_place(handle, data)` | Update a single place |
37
+ | `api.create_person(data)` | Create a new person |
38
+ | `api.create_event(data)` | Create a new evebt |
39
+ | `api.create_place(data)` | GCreateet a new place |
40
+
41
+
42
+ In all cases, `data` are dictionaries for JSON objects following the Gramps Web API conventions.
43
+
44
+ ## Examples
45
+
46
+ Removing all citations from an existing person:
47
+
48
+ ```python
49
+ api.update_person("somehandle", {"citation_list": []})
50
+ ```
51
+
52
+ In this example, all other properties of the person (except citations) will remain the same.
53
+
54
+ Creating a new place:
55
+
56
+ ```python
57
+ api.create_place({
58
+ "name": {
59
+ "_class": "PlaceName",
60
+ "value": "Gotham City"
61
+ }
62
+ })
63
+ ```
64
+
65
+ Iterating over people and adding a citation if a condition is satisfied:
66
+
67
+ ```python
68
+ for person in api.iter_people():
69
+ try:
70
+ surname = person["primary_name"]["surname_list"][0]["surname"]
71
+ except (KeyError, IndexError):
72
+ continue
73
+ if surname == "Garner":
74
+ api.update_person(
75
+ person["handle"],
76
+ {"citation_list": person["citation_list"] + ["some_citation_handle"]}
77
+ )
78
+ ```
@@ -0,0 +1,2 @@
1
+ [mypy]
2
+ strict = true
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "gramps-web-api-client"
3
+ description = "A simple Python client to interact with Gramps Web API"
4
+ authors = [
5
+ { name = "David M. Straub", email = "straub@protonmail.com" },
6
+ ]
7
+ license = { text = "MIT" }
8
+ readme = "README.md"
9
+ dynamic = ["version"]
10
+ dependencies = [
11
+ "requests>=2.31.0"
12
+ ]
13
+
14
+ [project.urls]
15
+ homepage = "https://github.com/DavidMStraub/gramps-web-api-client"
16
+ repository = "https://github.com/DavidMStraub/gramps-web-api-client"
17
+
18
+ [build-system]
19
+ requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"]
20
+ build-backend = "setuptools.build_meta"
21
+
22
+ [tool.isort]
23
+ profile = "black"
24
+
25
+ [tool.setuptools_scm]
26
+ # NOTE: Using a version file avoids some overhead
27
+ #
28
+ # This file is explicitly ignored by version control.
29
+ write_to = "src/gramps_web_api_client/_version.py"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ # Compatibility shim: Loads the real config from pyproject.toml
2
+ from setuptools import setup
3
+
4
+ setup()
@@ -0,0 +1,2 @@
1
+ # Generated by setuptools_scm, should be ignored by VCS
2
+ /_version.py
@@ -0,0 +1,17 @@
1
+ __version__: str | None
2
+ __version_tuple__: tuple | None
3
+
4
+ try:
5
+ # This file is auto-generated, and could be missing.
6
+ # Tell mypy to ignore it
7
+ from ._version import __version__, __version_tuple__ # type: ignore
8
+ except ImportError:
9
+ __version__ = None
10
+ __version_tuple__ = None
11
+
12
+ __all__ = (
13
+ "__version__",
14
+ "__version_tuple__",
15
+ )
16
+
17
+ from .api import API
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.1'
16
+ __version_tuple__ = version_tuple = (0, 1)
@@ -0,0 +1,165 @@
1
+ """A simple Gramps Web API Client."""
2
+
3
+ from typing import Optional, Tuple
4
+ from urllib.parse import urlencode
5
+
6
+ import requests
7
+
8
+ API_PREFIX = "/api"
9
+ ENDPOINT_TOKEN = "/token/"
10
+ ENDPOINT_PEOPLE = "/people/"
11
+ ENDPOINT_EVENTS = "/events/"
12
+ ENDPOINT_PLACES = "/places/"
13
+ PAGE_SIZE = 200
14
+
15
+
16
+ class API:
17
+ """API."""
18
+
19
+ def __init__(
20
+ self,
21
+ host: str = "http://127.0.0.1:5555",
22
+ basic_auth: Optional[Tuple[str, str]] = None,
23
+ ):
24
+ """Inititalize self."""
25
+ self.host = host.rstrip("/")
26
+ if basic_auth:
27
+ self.user, self.password = basic_auth
28
+ else:
29
+ raise ValueError("basic_auth is required")
30
+ self._access_token = ""
31
+ self._refresh_token = ""
32
+
33
+ def _endpoint_url(self, endpoint: str):
34
+ """Build a full URL for the given endpoint."""
35
+ return f"{self.host}{API_PREFIX}{endpoint}"
36
+
37
+ def _fetch_tokens(self):
38
+ """Fetch tokens."""
39
+ res = requests.post(
40
+ self._endpoint_url(ENDPOINT_TOKEN),
41
+ json={"username": self.user, "password": self.password},
42
+ )
43
+ res.raise_for_status()
44
+ data = res.json()
45
+ self._access_token = data["access_token"]
46
+ self._refresh_token = data["refresh_token"]
47
+
48
+ @property
49
+ def _auth_header(self):
50
+ """return the auth header."""
51
+ return {"Authorization": f"Bearer {self._access_token}"}
52
+
53
+ def _get(self, endpoint: str):
54
+ """Get data from a protected URL."""
55
+ if not self._access_token:
56
+ self._fetch_tokens()
57
+ url = self._endpoint_url(endpoint)
58
+ res = requests.get(url, headers=self._auth_header)
59
+ res.raise_for_status()
60
+ data = res.json()
61
+ return data
62
+
63
+ def _put(self, endpoint: str, data):
64
+ """Put data to a protected URL."""
65
+ if not self._access_token:
66
+ self._fetch_tokens()
67
+ url = self._endpoint_url(endpoint)
68
+ res = requests.put(url, headers=self._auth_header, json=data)
69
+ res.raise_for_status()
70
+ data = res.json()
71
+ return data
72
+
73
+ def _post(self, endpoint: str, data):
74
+ """Post data to a protected URL."""
75
+ if not self._access_token:
76
+ self._fetch_tokens()
77
+ url = self._endpoint_url(endpoint)
78
+ res = requests.post(url, headers=self._auth_header, json=data)
79
+ res.raise_for_status()
80
+ data = res.json()
81
+ return data
82
+
83
+ def _get_object(self, object_endpoint: str, handle: str):
84
+ """Get a single object."""
85
+ if not handle:
86
+ raise ValueError("handle is required")
87
+ endpoint = f"{object_endpoint}{handle}"
88
+ return self._get(endpoint)
89
+
90
+ def _put_object(self, object_endpoint: str, handle: str, data):
91
+ """Put a single object."""
92
+ if not handle:
93
+ raise ValueError("handle is required")
94
+ endpoint = f"{object_endpoint}{handle}"
95
+ return self._put(endpoint, data)
96
+
97
+ def _iter_objects(self, object_endpoint: str, **kwargs):
98
+ """Iterate over objects."""
99
+ page = 1
100
+ while True:
101
+ endpoint = f"{object_endpoint}?pagesize={PAGE_SIZE}&page={page}"
102
+ for key, value in kwargs.items():
103
+ if not isinstance(value, str):
104
+ value = urlencode(value)
105
+ endpoint += f"&{key}={value}"
106
+ data = self._get(endpoint)
107
+ if not len(data):
108
+ break
109
+ for obj in data:
110
+ yield obj
111
+ page += 1
112
+
113
+ def _update_object(self, object_endpoint: str, handle: str, data):
114
+ """Update an object."""
115
+ old_data = self._get_object(object_endpoint, handle)
116
+ new_data = {**old_data, **data}
117
+ return self._put_object(object_endpoint, handle, new_data)
118
+
119
+ def get_person(self, handle: str):
120
+ """Get a single person."""
121
+ return self._get_object(ENDPOINT_PEOPLE, handle)
122
+
123
+ def get_event(self, handle: str):
124
+ """Get a single event."""
125
+ return self._get_object(ENDPOINT_EVENTS, handle)
126
+
127
+ def get_place(self, handle: str):
128
+ """Get a single place."""
129
+ return self._get_object(ENDPOINT_PLACES, handle)
130
+
131
+ def iter_people(self, **kwargs):
132
+ """Iterate over people."""
133
+ return self._iter_objects(ENDPOINT_PEOPLE, **kwargs)
134
+
135
+ def iter_events(self, **kwargs):
136
+ """Iterate over events."""
137
+ return self._iter_objects(ENDPOINT_EVENTS, **kwargs)
138
+
139
+ def iter_places(self, **kwargs):
140
+ """Iterate over places."""
141
+ return self._iter_objects(ENDPOINT_PLACES, **kwargs)
142
+
143
+ def update_person(self, handle: str, data):
144
+ """Update a person."""
145
+ return self._update_object(ENDPOINT_PEOPLE, handle, data)
146
+
147
+ def update_event(self, handle: str, data):
148
+ """Update an event."""
149
+ return self._update_object(ENDPOINT_EVENTS, handle, data)
150
+
151
+ def update_place(self, handle: str, data):
152
+ """Update a place."""
153
+ return self._update_object(ENDPOINT_PLACES, handle, data)
154
+
155
+ def create_person(self, data):
156
+ """Create a person."""
157
+ return self._post(ENDPOINT_PEOPLE, data)
158
+
159
+ def create_event(self, data):
160
+ """Create ab event."""
161
+ return self._post(ENDPOINT_EVENTS, data)
162
+
163
+ def create_place(self, data):
164
+ """Create a place."""
165
+ return self._post(ENDPOINT_PLACES, data)
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.1
2
+ Name: gramps-web-api-client
3
+ Version: 0.1
4
+ Summary: A simple Python client to interact with Gramps Web API
5
+ Author-email: "David M. Straub" <straub@protonmail.com>
6
+ License: MIT
7
+ Project-URL: homepage, https://github.com/DavidMStraub/gramps-web-api-client
8
+ Project-URL: repository, https://github.com/DavidMStraub/gramps-web-api-client
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.31.0
11
+
12
+ # Gramps Web API Client
13
+
14
+
15
+ A simple Python client based on `requests` for interacting with a [Gramps Web API](https://github.com/gramps-project/gramps-webapi/) server.
16
+
17
+ ## Warning
18
+
19
+ This is an experimental project for advanced users. The goal is to allow powerful batch operations on Gramps objects via the API. But only use it if you know what you are doing &ndash; you can easily break your Gramps database with this tool!
20
+
21
+ ## Quick start
22
+
23
+ First, instantiate the API instance:
24
+
25
+ ```python
26
+ from gramps_web_api_client import API
27
+
28
+ api = API(
29
+ host="https://my-gramps-web-instance.com",
30
+ basic_auth("my_user", "my_password")
31
+ )
32
+ ```
33
+
34
+ Methods currently implemented:
35
+
36
+
37
+ | Method | description |
38
+ | ------------- | ------------- |
39
+ | `api.iter_people()` | Generator iterating over people |
40
+ | `api.iter_events()` | Generator iterating over events |
41
+ | `api.iter_places()` | Generator iterating over places |
42
+ | `api.get_person(handle)` | Get a single person by handle |
43
+ | `api.get_event(handle)` | Get a single event by handle |
44
+ | `api.get_place(handle)` | Get a single place by handle |
45
+ | `api.update_person(handle, data)` | Update a single person |
46
+ | `api.update_event(handle, data)` | Update a single event |
47
+ | `api.update_place(handle, data)` | Update a single place |
48
+ | `api.create_person(data)` | Create a new person |
49
+ | `api.create_event(data)` | Create a new evebt |
50
+ | `api.create_place(data)` | GCreateet a new place |
51
+
52
+
53
+ In all cases, `data` are dictionaries for JSON objects following the Gramps Web API conventions.
54
+
55
+ ## Examples
56
+
57
+ Removing all citations from an existing person:
58
+
59
+ ```python
60
+ api.update_person("somehandle", {"citation_list": []})
61
+ ```
62
+
63
+ In this example, all other properties of the person (except citations) will remain the same.
64
+
65
+ Creating a new place:
66
+
67
+ ```python
68
+ api.create_place({
69
+ "name": {
70
+ "_class": "PlaceName",
71
+ "value": "Gotham City"
72
+ }
73
+ })
74
+ ```
75
+
76
+ Iterating over people and adding a citation if a condition is satisfied:
77
+
78
+ ```python
79
+ for person in api.iter_people():
80
+ try:
81
+ surname = person["primary_name"]["surname_list"][0]["surname"]
82
+ except (KeyError, IndexError):
83
+ continue
84
+ if surname == "Garner":
85
+ api.update_person(
86
+ person["handle"],
87
+ {"citation_list": person["citation_list"] + ["some_citation_handle"]}
88
+ )
89
+ ```
@@ -0,0 +1,15 @@
1
+ .gitignore
2
+ README.md
3
+ mypy.cfg
4
+ pyproject.toml
5
+ setup.py
6
+ src/gramps_web_api_client/.gitignore
7
+ src/gramps_web_api_client/__init__.py
8
+ src/gramps_web_api_client/_version.py
9
+ src/gramps_web_api_client/api.py
10
+ src/gramps_web_api_client.egg-info/PKG-INFO
11
+ src/gramps_web_api_client.egg-info/SOURCES.txt
12
+ src/gramps_web_api_client.egg-info/dependency_links.txt
13
+ src/gramps_web_api_client.egg-info/requires.txt
14
+ src/gramps_web_api_client.egg-info/top_level.txt
15
+ tests/gramps_web_api_client/test_todo.py
@@ -0,0 +1 @@
1
+ gramps_web_api_client
@@ -0,0 +1,2 @@
1
+ def test_todo():
2
+ raise NotImplementedError("TODO: Implement tests")