libib-client 1.0.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Masarik
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,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: libib-client
3
+ Version: 1.0.0
4
+ Summary: A synchronous, third-party client for the Libib API
5
+ Author: Michael Masarik
6
+ License: MIT
7
+ Project-URL: Homepage, https://michael-masarik.github.io/libib-client/
8
+ Project-URL: Repository, https://github.com/michael-masarik/libib-client
9
+ Project-URL: Issues, https://github.com/michael-masarik/libib-client/issues
10
+ Keywords: libib,library,api,client
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests>=2.31.0
15
+ Dynamic: license-file
16
+
17
+ # Libib-Client
18
+ A synchronous, third-party client for the [Libib API](https://support.libib.com/rest-api/introduction.html)
19
+
20
+ ## Install
21
+ ```shell
22
+ pip install libib-client
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ To initalize the client:
28
+
29
+ ```python
30
+ import Libib
31
+
32
+ # For Pro accounts
33
+ client = Libib("your-api-key", "your-user-id")
34
+
35
+ # For Ultimate accounts, also pass your Ultimate ID
36
+ client = Libib("your-api-key", "your-user-id", "your-ultimate-id")
37
+ ```
38
+
39
+ ## Documentation:
40
+ Documentaion can be [found here] (https://michael-masarik.github.io/libib-client/)
41
+
42
+ ## Note
43
+
44
+ I do not have an Ultimate account, so if the Ultimate features (or any features, for that matter) do not work, feel free to open an issue or a PR
@@ -0,0 +1,28 @@
1
+ # Libib-Client
2
+ A synchronous, third-party client for the [Libib API](https://support.libib.com/rest-api/introduction.html)
3
+
4
+ ## Install
5
+ ```shell
6
+ pip install libib-client
7
+ ```
8
+
9
+ ## Usage
10
+
11
+ To initalize the client:
12
+
13
+ ```python
14
+ import Libib
15
+
16
+ # For Pro accounts
17
+ client = Libib("your-api-key", "your-user-id")
18
+
19
+ # For Ultimate accounts, also pass your Ultimate ID
20
+ client = Libib("your-api-key", "your-user-id", "your-ultimate-id")
21
+ ```
22
+
23
+ ## Documentation:
24
+ Documentaion can be [found here] (https://michael-masarik.github.io/libib-client/)
25
+
26
+ ## Note
27
+
28
+ I do not have an Ultimate account, so if the Ultimate features (or any features, for that matter) do not work, feel free to open an issue or a PR
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "libib-client"
7
+ version = "1.0.0"
8
+ description = "A synchronous, third-party client for the Libib API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Michael Masarik" }
14
+ ]
15
+ keywords = ["libib", "library", "api", "client"]
16
+ dependencies = ["requests>=2.31.0"]
17
+
18
+ [project.urls]
19
+ Homepage = "https://michael-masarik.github.io/libib-client/"
20
+ Repository = "https://github.com/michael-masarik/libib-client"
21
+ Issues = "https://github.com/michael-masarik/libib-client/issues"
22
+
23
+ [tool.black]
24
+ line-length = 101
25
+ [tool.isort]
26
+ profile = "black"
27
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .main import Libib
2
+
3
+ __version__ = "0.0.1"
4
+
@@ -0,0 +1,454 @@
1
+ # Docs: https://support.libib.com/rest-api/introduction.html
2
+ import time
3
+
4
+ import requests
5
+
6
+ RATE = 2
7
+
8
+
9
+ class Libib:
10
+ """
11
+ Simple, Third-Party API Client for Libib
12
+ """
13
+ def __init__(self, apiKey: str, apiUserID: str, ultimateID: str | None = None) -> None:
14
+ self.apiHeaders = {}
15
+ self.apiHeaders["x-api-key"] = apiKey
16
+ self.apiHeaders["x-api-user"] = apiUserID
17
+ if ultimateID:
18
+ self.apiHeaders["x-api-ultimate"] = ultimateID
19
+ self.patrons = Patrons(self)
20
+ self.managers = Managers(self)
21
+ self.accounts = Accounts(self)
22
+
23
+ class Patrons:
24
+ """
25
+ Patron subclient
26
+ """
27
+ url = "https://api.libib.com/patrons"
28
+ patron_sig = {"barcode","first_name","last_name","email","notification_emails","tags","patron_id","phone","address1","address2","city","state","country","zip","freeze","password"}
29
+
30
+ def __init__(self, client: Libib) -> None:
31
+ self.client = client
32
+ self.headers = client.apiHeaders
33
+
34
+ def get_patrons(self) -> dict | list:
35
+ """
36
+ Retrieve a list of all existing patrons on your site. Returns 50 patrons at a time with pagination support.
37
+ For lists of patrons greater than 50, this function auto-handles pagination (with a 2 second pause between calls)
38
+
39
+ :param self: `Self@Libib`
40
+
41
+ :return success:
42
+ ```python
43
+ [
44
+ {
45
+ "barcode": "2020000000013",
46
+ "first_name": "Mary",
47
+ "last_name": "Shelley",
48
+ "email": "frankenstein@example.com",
49
+ "notification_emails": None,
50
+ "tags": None,
51
+ "patron_id": "mshelley",
52
+ "phone": "555-123-4567",
53
+ "address1": None,
54
+ "address2": None,
55
+ "city": "Augusta",
56
+ "state": "KS",
57
+ "country": "US",
58
+ "zip": None,
59
+ "freeze": None,
60
+ },
61
+ {
62
+ "barcode": "2020000000037",
63
+ "first_name": "Marcus",
64
+ "last_name": "Aurelius",
65
+ "email": "meditations@example.com",
66
+ "notification_emails": None,
67
+ "tags": None,
68
+ "patron_id": None,
69
+ "phone": None,
70
+ "address1": None,
71
+ "address2": None,
72
+ "city": None,
73
+ "state": None,
74
+ "country": "US",
75
+ "zip": None,
76
+ "freeze": 1,
77
+ }
78
+ ]
79
+ ```
80
+ :return error:
81
+ ```python
82
+ {"status": "error", "code": response.status_code, "body": response.json()}
83
+ ```
84
+ """
85
+ params = {
86
+ 'page': 1
87
+ }
88
+ response = requests.get(self.url, headers=self.headers, params=params)
89
+ data = response.json()
90
+ if response.status_code != 200:
91
+ return {"status": "error", "code": response.status_code, "body": data}
92
+ current_page = 1
93
+ pages_to_go = data.get("pages", 1)
94
+ patrons = []
95
+ errors = None
96
+
97
+ for patron in data.get("patrons", []):
98
+ patrons.append(patron)
99
+
100
+ while current_page < pages_to_go:
101
+ time.sleep(RATE) # Pause to avoid rate limiting
102
+ params["page"] += 1
103
+ response = requests.get(self.url, headers=self.headers, params=params)
104
+ data = response.json()
105
+ if response.status_code != 200:
106
+ errors = {"status": "error", "code": response.status_code, "body": data}
107
+ break
108
+ for patron in data.get("patrons", []):
109
+ patrons.append(patron)
110
+ current_page += 1
111
+ if errors:
112
+ return errors
113
+ return patrons
114
+
115
+ def get_patron_by_id(self, identifier: str) -> dict:
116
+ """
117
+ Retrieve a single patron by passing the patron's barcode or email as an identifier.
118
+
119
+ :param self: `Self@Libib`
120
+ :param identifier: patron email/barcode
121
+ :type identifier: str
122
+ :return success:
123
+ ```python
124
+ {
125
+ "barcode": "2020000000013",
126
+ "first_name": "Mary",
127
+ "last_name": "Shelley",
128
+ "email": "frankenstein@example.com",
129
+ "notification_emails": None,
130
+ "tags": None,
131
+ "patron_id": "mshelley",
132
+ "phone": "555-123-4567",
133
+ "address1": None,
134
+ "address2": None,
135
+ "city": "Augusta",
136
+ "state": "KS",
137
+ "country": "US",
138
+ "zip": None,
139
+ "freeze": None,
140
+ }
141
+ ```
142
+ :return error:
143
+ ```python
144
+ {"status": "error", "code": response.status_code, "body": response.json()}
145
+ ```
146
+ """
147
+ patronURL = f"{self.url}/{identifier}"
148
+ response = requests.get(patronURL, headers=self.headers)
149
+ data = response.json()
150
+ if response.status_code != 200:
151
+ return {"status": "error", "code": response.status_code, "body": data}
152
+ return data
153
+
154
+ def create_patron(self, **data) -> dict | bool:
155
+ """
156
+ Create a new patron in the account.
157
+
158
+ :param self: `Self@Libib`
159
+ :param **data: Patron fields as keyword arguments. Accepts any of:
160
+ barcode, first_name, last_name, email, notification_emails, tags, patron_id, phone, address1, address2, city, state, country, zip, freeze, password.
161
+ :return success:
162
+ ```python
163
+ True
164
+ ```
165
+ :return error:
166
+ ```python
167
+ {"status": "error", "code": response.status_code, "body": response.json()}
168
+ ```
169
+ """
170
+ valid_data = {k: v for k, v in data.items() if k in self.patron_sig}
171
+ invalid_data = {k: v for k, v in data.items() if k not in self.patron_sig}
172
+ if len(valid_data) == 0:
173
+ if len(invalid_data) > 0:
174
+ print("Data is invalid:", invalid_data)
175
+ return {"status": "error", "code": 400, "body": f"Data is invalid: {invalid_data}"}
176
+ else:
177
+ return {"status": "error", "code": 400, "body": "No Data Provided"}
178
+ params = valid_data
179
+ response = requests.post(self.url, headers=self.headers, params=params)
180
+ resp_data = response.json()
181
+ if response.status_code != 200:
182
+ return {"status": "error", "code": response.status_code, "body": resp_data}
183
+ return True
184
+
185
+ def update_patron(self, identifier: str, **data) -> bool | dict:
186
+ """
187
+ Update specific fields for an existing patron. Pass the patron's barcode or email as the identifier.
188
+
189
+ :param self: `Self@Libib`
190
+ :param identifier: Patron email/barcode
191
+ :type identifier: str
192
+ :param **data: Patron fields as keyword arguments. Accepts any of:
193
+ barcode, first_name, last_name, email, notification_emails, tags, patron_id, phone, address1, address2, city, state, country, zip, freeze, password.
194
+ :return success:
195
+ ```python
196
+ True
197
+ ```
198
+ :return error:
199
+ ```python
200
+ {"status": "error", "code": response.status_code, "body": response.json()}
201
+ ```
202
+ """
203
+ patronURL = f"{self.url}/{identifier}"
204
+ valid_data = {k: v for k, v in data.items() if k in self.patron_sig}
205
+ invalid_data = {k: v for k, v in data.items() if k not in self.patron_sig}
206
+ if len(valid_data) == 0:
207
+ if len(invalid_data) > 0:
208
+ print("Data is invalid:", invalid_data)
209
+ return {"status": "error", "code": 400, "body": f"Data is invalid: {invalid_data}"}
210
+ else:
211
+ return {"status": "error", "code": 400, "body": "No Data Provided"}
212
+ params = valid_data
213
+ response = requests.post(patronURL, headers=self.headers, params=params)
214
+ resp_data = response.json()
215
+ if response.status_code != 200:
216
+ return {"status": "error", "code": response.status_code, "body": resp_data}
217
+ return True
218
+
219
+ def restore_patron(self, identifier: str) -> bool | dict:
220
+ """
221
+ Restore a previously deleted patron. Patrons can be restored within 30 days of deletion. Pass the patron's barcode or email as the identifier.
222
+
223
+ :param self: `Self@Libib`
224
+ :param identifier: Patron email/barcode
225
+ :type identifier: str
226
+ :return success:
227
+ ```python
228
+ True
229
+ ```
230
+ :return error:
231
+ ```python
232
+ {"status": "error", "code": response.status_code, "body": response.json()}
233
+ ```
234
+ """
235
+ patronURL = f"{self.url}/{identifier}"
236
+ response = requests.patch(patronURL, headers=self.headers)
237
+ resp_data = response.json()
238
+ if response.status_code != 200:
239
+ return {"status": "error", "code": response.status_code, "body": resp_data}
240
+ return True
241
+
242
+ def delete_patron(self, identifier: str) -> bool | dict:
243
+ """
244
+ Remove a single patron by passing the patron's barcode or email as the identifier. Deleting a patron dissociates their entire lending/hold history.
245
+
246
+ :param self: `Self@Libib`
247
+ :param identifier: Patron email/barcode
248
+ :type identifier: str
249
+ :return success:
250
+ ```python
251
+ True
252
+ ```
253
+ :return error:
254
+ ```python
255
+ {"status": "error", "code": response.status_code, "body": response.json()}
256
+ ```
257
+ """
258
+ patronURL = f"{self.url}/{identifier}"
259
+ response = requests.delete(patronURL, headers=self.headers)
260
+ resp_data = response.json()
261
+ if response.status_code != 204:
262
+ return {"status": "error", "code": response.status_code, "body": resp_data}
263
+ return True
264
+
265
+
266
+ class Managers:
267
+ """
268
+ Manager subclient
269
+ """
270
+ url = "https://api.libib.com/managers"
271
+ roles = {"admin", "manager", "lender"}
272
+
273
+ def __init__(self, client: Libib) -> None:
274
+ self.client = client
275
+ self.headers = client.apiHeaders
276
+
277
+ def get_managers(self) -> dict | list:
278
+ """
279
+ Retrieve a list of all existing managers on your site – including the owner.
280
+
281
+ :param self: `Self@Libib`
282
+
283
+ :return success:
284
+ ```python
285
+ [
286
+ {
287
+ "first_name": "Larry",
288
+ "last_name": "McMurtry",
289
+ "email": "lonesome@example.com",
290
+ "role": "owner",
291
+ },
292
+ {
293
+ "first_name": "Samwise",
294
+ "last_name": "Gamgee",
295
+ "email": "samthewise@example.com",
296
+ "role": "lender",
297
+ },
298
+ {
299
+ "first_name": "Anaïs",
300
+ "last_name": "Nin",
301
+ "email": "angela@example.com",
302
+ "role": "admin",
303
+ },
304
+ ]
305
+ ```
306
+ :return error:
307
+ ```python
308
+ {"status": "error", "code": response.status_code, "body": response.json()}
309
+ ```
310
+ """
311
+ response = requests.get(self.url, headers=self.headers)
312
+ data = dict(response.json())
313
+ if response.status_code != 200:
314
+ return {"status": "error", "code": response.status_code, "body": data}
315
+ return data.get("managers", [])
316
+
317
+ def get_manager_by_id(self, email: str) -> dict:
318
+ """
319
+ Retrieve a single manager by passing the manager's email address as an identifier.
320
+
321
+ :param self: `Self@Libib`
322
+ :param email: Manager email
323
+ :type email: str
324
+ :return success:
325
+ ```python
326
+ {
327
+ "first_name": "Samwise",
328
+ "last_name": "Gamgee",
329
+ "email": "samthewise@example.com",
330
+ "role": "admin",
331
+ }
332
+ ```
333
+ :return error:
334
+ ```python
335
+ {"status": "error", "code": response.status_code, "body": response.json()}
336
+ ```
337
+ """
338
+ managerURL = f"{self.url}/{email}"
339
+ response = requests.get(managerURL, headers=self.headers)
340
+ data = response.json()
341
+ if response.status_code != 200:
342
+ return {"status": "error", "code": response.status_code, "body": data}
343
+ return data
344
+
345
+ def create_manager(self, first_name: str, last_name: str, email: str, password: str, role: str) -> dict | bool:
346
+ """
347
+ Create a new manager in the account. Must have open manager seats for this method to succeed.
348
+
349
+ > Assigning Collections:
350
+ > If user has a manager or lender role, assigning collections must be completed by the account owner using the website.
351
+
352
+ :param self: `Self@Libib`
353
+ :param first_name: Manager First Name
354
+ :type first_name: str
355
+ :param last_name: Manager Last Name
356
+ :type last_name: str
357
+ :param email: Manager Email
358
+ :type email: str
359
+ :param password: Manager Password
360
+ :type password: str
361
+ :param role: Manager Role
362
+ :type role: str
363
+ :rtype: dict[Any, Any]
364
+ :return success:
365
+ ```python
366
+ True
367
+ ```
368
+ :return error:
369
+ ```python
370
+ {"status": "error", "code": response.status_code, "body": response.json()}
371
+ ```
372
+ """
373
+ if role not in self.roles:
374
+ return {"status": "error", "code": 400, "body": f"Role is invalid: {role}"}
375
+
376
+ params = {
377
+ "first_name": first_name,
378
+ "last_name": last_name,
379
+ "email": email,
380
+ "password": password,
381
+ "role": role,
382
+ }
383
+
384
+ response = requests.post(url=self.url, headers=self.headers, params=params)
385
+ data = response.json()
386
+
387
+ if response.status_code != 200:
388
+ return {"status": "error", "code": response.status_code, "body": data}
389
+
390
+ return True
391
+
392
+ def delete_manager(self, email: str) -> bool | dict:
393
+ """
394
+ Remove a single manager by their email address. You cannot remove the owner account via this method. Owners must delete their entire account manually.
395
+
396
+ :param self: `Self@Libib`
397
+ :param email: Manager email
398
+ :type email: str
399
+ :return success:
400
+ ```python
401
+ True
402
+ ```
403
+ :return error:
404
+ ```python
405
+ {"status": "error", "code": response.status_code, "body": response.json()}
406
+ ```
407
+ """
408
+ managerURL = f"{self.url}/{email}"
409
+ response = requests.delete(managerURL, headers=self.headers)
410
+ resp_data = response.json()
411
+ if response.status_code != 204:
412
+ return {"status": "error", "code": response.status_code, "body": resp_data}
413
+ return True
414
+
415
+ class Accounts:
416
+ url = "https://api.libib.com/accounts"
417
+
418
+ def __init__(self, client: Libib) -> None:
419
+ self.client = client
420
+ self.headers = client.apiHeaders
421
+
422
+ def get_accounts(self) -> dict | list:
423
+ """
424
+ Retrieve a list of account information. For Pro users this will only return one account. For multi-account "Ultimate" users, this will return all accounts in their purview.
425
+
426
+ The API returns an object with an 'accounts' list; this method returns that inner list.
427
+
428
+ :param self: Self@Accounts
429
+
430
+ :return success:
431
+ ```python
432
+ [
433
+ {
434
+ "organization": "My Organization Name",
435
+ "api_id": "q44f0eb130194c31e9081bcc2412a7fe0a5b47ab",
436
+ "email": "sriddell@example.com",
437
+ "first_name": "Sepideh",
438
+ "last_name": "Riddell",
439
+ "manager_seats": 10,
440
+ "url": "sr-library",
441
+ "authorized": 1,
442
+ }
443
+ ]
444
+ ```
445
+ :return error:
446
+ ```python
447
+ {"status": "error", "code": response.status_code, "body": response.json()}
448
+ ```
449
+ """
450
+ response = requests.get(self.url, headers=self.headers)
451
+ data = dict(response.json())
452
+ if response.status_code != 200:
453
+ return {"status": "error", "code": response.status_code, "body": data}
454
+ return data.get("accounts", [])
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: libib-client
3
+ Version: 1.0.0
4
+ Summary: A synchronous, third-party client for the Libib API
5
+ Author: Michael Masarik
6
+ License: MIT
7
+ Project-URL: Homepage, https://michael-masarik.github.io/libib-client/
8
+ Project-URL: Repository, https://github.com/michael-masarik/libib-client
9
+ Project-URL: Issues, https://github.com/michael-masarik/libib-client/issues
10
+ Keywords: libib,library,api,client
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: requests>=2.31.0
15
+ Dynamic: license-file
16
+
17
+ # Libib-Client
18
+ A synchronous, third-party client for the [Libib API](https://support.libib.com/rest-api/introduction.html)
19
+
20
+ ## Install
21
+ ```shell
22
+ pip install libib-client
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ To initalize the client:
28
+
29
+ ```python
30
+ import Libib
31
+
32
+ # For Pro accounts
33
+ client = Libib("your-api-key", "your-user-id")
34
+
35
+ # For Ultimate accounts, also pass your Ultimate ID
36
+ client = Libib("your-api-key", "your-user-id", "your-ultimate-id")
37
+ ```
38
+
39
+ ## Documentation:
40
+ Documentaion can be [found here] (https://michael-masarik.github.io/libib-client/)
41
+
42
+ ## Note
43
+
44
+ I do not have an Ultimate account, so if the Ultimate features (or any features, for that matter) do not work, feel free to open an issue or a PR
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/libib-client/__init__.py
5
+ src/libib-client/main.py
6
+ src/libib_client.egg-info/PKG-INFO
7
+ src/libib_client.egg-info/SOURCES.txt
8
+ src/libib_client.egg-info/dependency_links.txt
9
+ src/libib_client.egg-info/requires.txt
10
+ src/libib_client.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.31.0
@@ -0,0 +1 @@
1
+ libib-client