Skip to content

Commit 2f97c0f

Browse files
authored
Merge pull request #40 from metaodi/develop
Release 1.0.0
2 parents 2baa490 + 7533ffb commit 2f97c0f

20 files changed

+4971
-72
lines changed

.github/workflows/lint_python.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
timeout-minutes: 10
1010
strategy:
11+
fail-fast: false
1112
matrix:
1213
python-version: [3.6, 3.7, 3.8]
1314

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
44

55
## [Unreleased]
66

7+
## [1.0.0] - 2021-12-06
8+
### Added
9+
- Add support for SRU 1.1 by passing `sru_version='1.1'` to the client or the operation calls.
10+
11+
### Changed
12+
- Add MarcXchange (ISO 25577) namespace [#35](https://github.com/metaodi/sruthi/pull/35) (thanks [danmichaelo](https://github.com/danmichaelo)!)
13+
- Moved `sru` module in `__init__`
14+
- `explain` now returns a dict-like object (still with backwards-compatible attribute-access)
15+
16+
### Fixed
17+
- Fix parsing of non-standard namespaces for explain response
18+
719
## [0.1.2] - 2020-10-04
820
### Fixed
921
- Fix missing dependencies in setup.py
@@ -64,7 +76,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6476
- `Fixed` for any bug fixes.
6577
- `Security` to invite users to upgrade in case of vulnerabilities.
6678

67-
[Unreleased]: https://github.com/metaodi/sruthi/compare/v0.1.2...HEAD
79+
[Unreleased]: https://github.com/metaodi/sruthi/compare/v1.0.0...HEAD
80+
[1.0.0]: https://github.com/metaodi/sruthi/compare/v0.1.2...v1.0.0
6881
[0.1.2]: https://github.com/metaodi/sruthi/compare/v0.1.1...v0.1.2
6982
[0.1.1]: https://github.com/metaodi/sruthi/compare/v0.1.0...v0.1.1
7083
[0.1.0]: https://github.com/metaodi/sruthi/compare/v0.0.5...v0.1.0

CONTRIBUTING.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,24 @@ Install the dependencies using `pip`:
1313
```bash
1414
pip install -r requirements.txt
1515
pip install -r test-requirements.txt
16+
17+
# or use make
18+
make deps
1619
```
1720

1821
Make sure the tests pass:
1922

2023
```bash
21-
pytest
24+
make test
2225
```
2326

2427
To ensure a good quality of the code use `flake8` to check the code style:
2528

2629
```bash
2730
flake8 --install-hook git
31+
32+
# with make
33+
make lint
2834
```
2935

3036
## Create a pull request

Makefile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.DEFAULT_GOAL := help
2+
.PHONY: coverage deps help lint test
3+
4+
coverage: ## Run tests with coverage
5+
python -m coverage erase
6+
python -m coverage run --include=sruthi/* -m pytest -ra
7+
python -m coverage report -m
8+
9+
deps: ## Install dependencies
10+
python -m pip install --upgrade pip
11+
python -m pip install -r requirements.txt
12+
python -m pip install -r test-requirements.txt
13+
14+
lint: ## Linting of source code
15+
python -m flake8 --statistics --show-source .
16+
17+
test: ## Run tests
18+
python -m pytest --cov=sruthi tests/
19+
20+
help: SHELL := /bin/bash
21+
help: ## Show help message
22+
@IFS=$$'\n' ; \
23+
help_lines=(`fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##/:/'`); \
24+
printf "%s\n\n" "Usage: make [task]"; \
25+
printf "%-20s %s\n" "task" "help" ; \
26+
printf "%-20s %s\n" "------" "----" ; \
27+
for help_line in $${help_lines[@]}; do \
28+
IFS=$$':' ; \
29+
help_split=($$help_line) ; \
30+
help_command=`echo $${help_split[0]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
31+
help_info=`echo $${help_split[2]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
32+
printf '\033[36m'; \
33+
printf "%-20s %s" $$help_command ; \
34+
printf '\033[0m'; \
35+
printf "%s\n" $$help_info; \
36+
done

README.md

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
[![PyPI Version](https://img.shields.io/pypi/v/sruthi)](https://pypi.org/project/sruthi/)
2+
[![Tests + Linting Python](https://github.com/metaodi/sruthi/actions/workflows/lint_python.yml/badge.svg)](https://github.com/metaodi/sruthi/actions/workflows/lint_python.yml)
3+
14
# sruthi
25

36
**sru**thi is a client for python to make [SRU requests (Search/Retrieve via URL)](http://www.loc.gov/standards/sru/).
47

5-
Currently only SRU 1.2 is supported.
8+
Currently only **SRU 1.1 and 1.2** is supported.
69

710
## Table of Contents
811

912
* [Installation](#installation)
1013
* [Usage](#usage)
1114
* [`searchretrieve` operation](#searchretrieve-operation)
1215
* [`explain` operation](#explain-operation)
16+
* [Request for SRU 1.1](#request-for-sru-11)
1317
* [Schemas](#schemas)
18+
* [Development](#development)
1419
* [Release](#release)
1520

1621
## Installation
@@ -28,30 +33,38 @@ See the [`examples` directory](https://github.com/metaodi/sruthi/tree/master/exa
2833
### `searchretrieve` operation
2934

3035
```python
31-
import sruthi
32-
33-
records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Zurich')
34-
35-
for record in records:
36-
# print fields from schema
37-
print(record['reference'])
38-
print(record['title'])
39-
print(record['date'])
40-
print(record['extra']['link']) # extra record data is available at the 'extra' key
41-
```
42-
43-
```python
44-
# you can get more information at each step
45-
import sruthi
46-
47-
# note: records is an iterator
48-
records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Human')
49-
print(records.sru_version)
50-
print(records.count)
51-
52-
for record in records:
53-
print(record)
54-
print(record['schema'])
36+
>>> import sruthi
37+
>>> records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Brettspiel')
38+
>>> print(records)
39+
SearchRetrieveResponse(sru_version='1.2',count=500,next_start_record=11)
40+
>>> print(records.count)
41+
4
42+
>>> print(record[0])
43+
{'schema': 'isad', 'reference': 'PAT 2, 54 d, Nr. 253492', 'title': 'Schlumberger, Jean, Zürich: Brettspiel', 'date': '08.03.1946', 'descriptionlevel': 'Dossier', 'extent': None, 'creator': None, 'extra': {'score': '0.4', 'link': 'https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=1114641', 'beginDateISO': '1946-03-08', 'beginApprox': '0', 'endDateISO': '1946-03-08', 'endApprox': '0', 'hasDigitizedItems': '0'}}
44+
>>>
45+
>>> for record in records:
46+
... # print fields from schema
47+
... print(record['reference'])
48+
... print(record['title'])
49+
... print(record['date'])
50+
... print(record['extra']['link']) # extra record data is available at the 'extra' key
51+
PAT 2, 54 d, Nr. 253492
52+
Schlumberger, Jean, Zürich: Brettspiel
53+
08.03.1946
54+
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=1114641
55+
PAT 2, 54 d, Nr. 246025
56+
Frei, K. H., Weisslingen: Brettspiel
57+
26.10.1945
58+
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=1114639
59+
DS 107.2.37
60+
UZH Magazin
61+
Die Wissenschaftszeitschrift
62+
2019
63+
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=4612939
64+
G I 1, Nr. 34
65+
Verordnung der Stadt Zürich betreffend die Erfüllung von Amtspflichten durch die Chorherren des Grossmünsterstifts
66+
24.09.1485
67+
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=3796980
5568
```
5669

5770
The return value of `searchretrieve` is iterable, so you can easily loop over it. Or you can use indices to access elements, e.g. `records[1]` to get the second elemenet, or `records[-1]` to get the last one.
@@ -65,14 +78,48 @@ for records in records[:5]:
6578

6679
### `explain` operation
6780

81+
The `explain` operation returns a dict-like object.
82+
The values can either be accessed as keys `info['sru_version']` or as attributes `info.sru_version`.
83+
6884
```python
69-
import sruthi
85+
>>> import sruthi
86+
>>> info = sruthi.explain('https://suche.staatsarchiv.djiktzh.ch/SRU/')
87+
>>> info
88+
{'sru_version': '1.2', 'server': {'host': 'https://suche.staatsarchiv.djiktzh.ch/Sru', 'port': 80, 'database': 'sru'}, 'database': {'title': 'Staatsarchiv Zürich Online Search', 'description': 'Durchsuchen der Bestände des Staatsarchiv Zürichs.', 'contact': 'staatsarchivzh@ji.zh.ch'}, 'index': {'isad': {'title': 'Title', 'reference': 'Reference Code', 'date': 'Date', 'descriptionlevel': 'Level'}}, 'schema': {'isad': {'identifier': 'http://www.expertisecentrumdavid.be/xmlschemas/isad.xsd', 'name': 'isad', 'title': 'ISAD(G)'}}, 'config': {'maximumRecords': 99, 'defaults': {'numberOfRecords': 99}}}
89+
>>> info.server
90+
{'host': 'https://suche.staatsarchiv.djiktzh.ch/Sru', 'port': 80, 'database': 'sru'}
91+
>>> info.database
92+
{'title': 'Staatsarchiv Zürich Online Search', 'description': 'Durchsuchen der Bestände des Staatsarchiv Zürichs.', 'contact': 'staatsarchivzh@ji.zh.ch'}
93+
>>> info['index']
94+
{'isad': {'title': 'Title', 'reference': 'Reference Code', 'date': 'Date', 'descriptionlevel': 'Level'}}
95+
>>> info['schema']
96+
{'isad': {'identifier': 'http://www.expertisecentrumdavid.be/xmlschemas/isad.xsd', 'name': 'isad', 'title': 'ISAD(G)'}}
97+
```
98+
99+
### Request for SRU 1.1
100+
101+
By default sruthi uses SRU 1.2 to make requests, but you can specify the SRU version for each call or when you create a new client instance:
70102

71-
info = sruthi.explain('https://suche.staatsarchiv.djiktzh.ch/SRU/')
72-
print(info.server)
73-
print(info.database)
74-
print(info.index)
75-
print(info.schema)
103+
```python
104+
>>> import sruthi
105+
>>> # create a client
106+
>>> client = sruthi.Client(
107+
... 'https://services.dnb.de/sru/dnb',
108+
... record_schema='oai_dc',
109+
... sru_version='1.1'
110+
>>> )
111+
>>> records = client.searchretrieve(query="Zurich")
112+
>>> records.count
113+
8985
114+
>>> # ...or pass the version directly to the call
115+
>>> records = sruthi.searchretrieve(
116+
... 'https://services.dnb.de/sru/dnb',
117+
... query="Zurich",
118+
... record_schema='oai_dc',
119+
... sru_version='1.1'
120+
>>> )
121+
>>> records.count
122+
8985
76123
```
77124

78125
## Schemas
@@ -85,6 +132,13 @@ sruthi has been tested with the following schemas:
85132
* [MARCXML: The MARC 21 XML Schema](http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd) (marcxml)
86133
* [ISAD(G): General International Standard Archival Description, Second edition](http://www.expertisecentrumdavid.be/xmlschemas/isad.xsd) (isad)
87134

135+
## Development
136+
137+
To contribute to sruthi simply clone this repository and follow the instructions in [CONTRIBUTING.md](/CONTRIBUTING.md).
138+
139+
This project ha a Makefile with the most common commands.
140+
Type `make help` to get an overview.
141+
88142
## Release
89143

90144
To create a new release, follow these steps (please respect [Semantic Versioning](http://semver.org/)):

examples/explain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
'https://suche.staatsarchiv.djiktzh.ch/SRU/',
88
'https://amsquery.stadt-zuerich.ch/SRU/',
99
'http://lx2.loc.gov:210/LCDB?',
10-
'https://sru.swissbib.ch/sru/explain',
10+
'https://na01.alma.exlibrisgroup.com/view/sru/TR_INTEGRATION_INST',
1111
]
1212

1313

examples/library_of_congress.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import sruthi
2+
import sys
3+
4+
LOC_BASE = 'http://lx2.loc.gov:210/LCDB?'
5+
6+
7+
def loc_search(isbn, sru_base):
8+
loc_lcc = None
9+
try:
10+
records = sruthi.searchretrieve(sru_base, query=isbn)
11+
record = records[0]
12+
fields = record.get('datafield', [])
13+
for field in fields:
14+
if field['tag'] != '050':
15+
continue
16+
if len(field.get('subfield', [])) > 0:
17+
loc_lcc = (field['subfield'][0]['text'])
18+
break
19+
except Exception as e:
20+
print("Error: %s" % e, file=sys.stderr)
21+
return None
22+
return loc_lcc
23+
24+
25+
isbn = '0062509470'
26+
result = loc_search(isbn, LOC_BASE)
27+
print(f"Tag 050 of ISBN '{isbn}': {result}")

examples/sru1.1.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import sruthi
2+
from pprint import pprint
3+
4+
# check supported schemas of server
5+
server_url = 'https://services.dnb.de/sru/dnb'
6+
7+
# create sruthi client
8+
client = sruthi.Client(server_url, record_schema='oai_dc', sru_version='1.1')
9+
10+
explain = client.explain()
11+
print(f'SRU version: {explain.sru_version}')
12+
pprint(explain.server)
13+
pprint(explain.config)
14+
pprint(explain.index, depth=1)
15+
pprint(explain.schema, depth=1)
16+
pprint(explain.database)
17+
18+
19+
print(20 * '=')
20+
print('=')
21+
print(f"= Record with schema: {client.record_schema}")
22+
print('=')
23+
print(20 * '=')
24+
records = client.searchretrieve(
25+
query='Zurich'
26+
)
27+
print(f'Total records: {records.count}')
28+
pprint(records[0])

sruthi/__init__.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
1-
__version__ = '0.1.2'
2-
__all__ = ['client', 'errors', 'response', 'sru', 'xmlparse']
1+
__version__ = '1.0.0'
2+
__all__ = ['client', 'errors', 'response', 'xmlparse']
33

44
from .errors import SruthiError, ServerIncompatibleError, SruError, NoMoreRecordsError # noqa
55
from .errors import SruthiWarning, WrongNamespaceWarning # noqa
6-
from .sru import searchretrieve, explain # noqa
76
from .client import Client # noqa
7+
8+
9+
def searchretrieve(url, query, **kwargs):
10+
search_params = ['query', 'start_record', 'requests_kwargs']
11+
search_kwargs = {k: v for k, v in kwargs.items() if k in search_params}
12+
search_kwargs['query'] = query
13+
14+
# assume all others kwargs are for the client
15+
client_kwargs = {k: v for k, v in kwargs.items() if k not in search_params}
16+
client_kwargs['url'] = url
17+
18+
c = Client(**client_kwargs)
19+
return c.searchretrieve(**search_kwargs)
20+
21+
22+
def explain(url, **kwargs):
23+
c = Client(url, **kwargs)
24+
return c.explain()

sruthi/client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88

99
class Client(object):
10-
def __init__(self, url=None, maximum_records=10, record_schema=None):
10+
def __init__(self, url=None, maximum_records=10, record_schema=None, sru_version='1.2'):
1111
self.url = url
1212
self.maximum_records = maximum_records
13-
self.sru_version = '1.2'
13+
self.sru_version = sru_version
1414
self.record_schema = record_schema
1515

1616
def searchretrieve(self, query, start_record=1, requests_kwargs=None):
@@ -34,7 +34,8 @@ def explain(self, requests_kwargs=None):
3434
'version': self.sru_version,
3535
}
3636
data_loader = DataLoader(self.url, params, requests_kwargs)
37-
return response.ExplainResponse(data_loader)
37+
explain_response = response.ExplainResponse(data_loader)
38+
return explain_response.asdict()
3839

3940

4041
class DataLoader(object):

0 commit comments

Comments
 (0)