Skip to content

Commit

Permalink
Merge pull request #40 from metaodi/develop
Browse files Browse the repository at this point in the history
Release 1.0.0
  • Loading branch information
metaodi authored Dec 6, 2021
2 parents 2baa490 + 7533ffb commit 2f97c0f
Show file tree
Hide file tree
Showing 20 changed files with 4,971 additions and 72 deletions.
1 change: 1 addition & 0 deletions .github/workflows/lint_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8]

Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

## [1.0.0] - 2021-12-06
### Added
- Add support for SRU 1.1 by passing `sru_version='1.1'` to the client or the operation calls.

### Changed
- Add MarcXchange (ISO 25577) namespace [#35](https://github.com/metaodi/sruthi/pull/35) (thanks [danmichaelo](https://github.com/danmichaelo)!)
- Moved `sru` module in `__init__`
- `explain` now returns a dict-like object (still with backwards-compatible attribute-access)

### Fixed
- Fix parsing of non-standard namespaces for explain response

## [0.1.2] - 2020-10-04
### Fixed
- Fix missing dependencies in setup.py
Expand Down Expand Up @@ -64,7 +76,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `Fixed` for any bug fixes.
- `Security` to invite users to upgrade in case of vulnerabilities.

[Unreleased]: https://github.com/metaodi/sruthi/compare/v0.1.2...HEAD
[Unreleased]: https://github.com/metaodi/sruthi/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/metaodi/sruthi/compare/v0.1.2...v1.0.0
[0.1.2]: https://github.com/metaodi/sruthi/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/metaodi/sruthi/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/metaodi/sruthi/compare/v0.0.5...v0.1.0
Expand Down
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,24 @@ Install the dependencies using `pip`:
```bash
pip install -r requirements.txt
pip install -r test-requirements.txt

# or use make
make deps
```

Make sure the tests pass:

```bash
pytest
make test
```

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

```bash
flake8 --install-hook git

# with make
make lint
```

## Create a pull request
Expand Down
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.DEFAULT_GOAL := help
.PHONY: coverage deps help lint test

coverage: ## Run tests with coverage
python -m coverage erase
python -m coverage run --include=sruthi/* -m pytest -ra
python -m coverage report -m

deps: ## Install dependencies
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -r test-requirements.txt

lint: ## Linting of source code
python -m flake8 --statistics --show-source .

test: ## Run tests
python -m pytest --cov=sruthi tests/

help: SHELL := /bin/bash
help: ## Show help message
@IFS=$$'\n' ; \
help_lines=(`fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##/:/'`); \
printf "%s\n\n" "Usage: make [task]"; \
printf "%-20s %s\n" "task" "help" ; \
printf "%-20s %s\n" "------" "----" ; \
for help_line in $${help_lines[@]}; do \
IFS=$$':' ; \
help_split=($$help_line) ; \
help_command=`echo $${help_split[0]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
help_info=`echo $${help_split[2]} | sed -e 's/^ *//' -e 's/ *$$//'` ; \
printf '\033[36m'; \
printf "%-20s %s" $$help_command ; \
printf '\033[0m'; \
printf "%s\n" $$help_info; \
done
116 changes: 85 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
[![PyPI Version](https://img.shields.io/pypi/v/sruthi)](https://pypi.org/project/sruthi/)
[![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)

# sruthi

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

Currently only SRU 1.2 is supported.
Currently only **SRU 1.1 and 1.2** is supported.

## Table of Contents

* [Installation](#installation)
* [Usage](#usage)
* [`searchretrieve` operation](#searchretrieve-operation)
* [`explain` operation](#explain-operation)
* [Request for SRU 1.1](#request-for-sru-11)
* [Schemas](#schemas)
* [Development](#development)
* [Release](#release)

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

```python
import sruthi

records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Zurich')

for record in records:
# print fields from schema
print(record['reference'])
print(record['title'])
print(record['date'])
print(record['extra']['link']) # extra record data is available at the 'extra' key
```

```python
# you can get more information at each step
import sruthi

# note: records is an iterator
records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Human')
print(records.sru_version)
print(records.count)

for record in records:
print(record)
print(record['schema'])
>>> import sruthi
>>> records = sruthi.searchretrieve('https://suche.staatsarchiv.djiktzh.ch/SRU/', query='Brettspiel')
>>> print(records)
SearchRetrieveResponse(sru_version='1.2',count=500,next_start_record=11)
>>> print(records.count)
4
>>> print(record[0])
{'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'}}
>>>
>>> for record in records:
... # print fields from schema
... print(record['reference'])
... print(record['title'])
... print(record['date'])
... print(record['extra']['link']) # extra record data is available at the 'extra' key
PAT 2, 54 d, Nr. 253492
Schlumberger, Jean, Zürich: Brettspiel
08.03.1946
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=1114641
PAT 2, 54 d, Nr. 246025
Frei, K. H., Weisslingen: Brettspiel
26.10.1945
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=1114639
DS 107.2.37
UZH Magazin
Die Wissenschaftszeitschrift
2019
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=4612939
G I 1, Nr. 34
Verordnung der Stadt Zürich betreffend die Erfüllung von Amtspflichten durch die Chorherren des Grossmünsterstifts
24.09.1485
https://suche.staatsarchiv.djiktzh.ch/detail.aspx?Id=3796980
```

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.
Expand All @@ -65,14 +78,48 @@ for records in records[:5]:

### `explain` operation

The `explain` operation returns a dict-like object.
The values can either be accessed as keys `info['sru_version']` or as attributes `info.sru_version`.

```python
import sruthi
>>> import sruthi
>>> info = sruthi.explain('https://suche.staatsarchiv.djiktzh.ch/SRU/')
>>> info
{'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': '[email protected]'}, '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}}}
>>> info.server
{'host': 'https://suche.staatsarchiv.djiktzh.ch/Sru', 'port': 80, 'database': 'sru'}
>>> info.database
{'title': 'Staatsarchiv Zürich Online Search', 'description': 'Durchsuchen der Bestände des Staatsarchiv Zürichs.', 'contact': '[email protected]'}
>>> info['index']
{'isad': {'title': 'Title', 'reference': 'Reference Code', 'date': 'Date', 'descriptionlevel': 'Level'}}
>>> info['schema']
{'isad': {'identifier': 'http://www.expertisecentrumdavid.be/xmlschemas/isad.xsd', 'name': 'isad', 'title': 'ISAD(G)'}}
```

### Request for SRU 1.1

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:

info = sruthi.explain('https://suche.staatsarchiv.djiktzh.ch/SRU/')
print(info.server)
print(info.database)
print(info.index)
print(info.schema)
```python
>>> import sruthi
>>> # create a client
>>> client = sruthi.Client(
... 'https://services.dnb.de/sru/dnb',
... record_schema='oai_dc',
... sru_version='1.1'
>>> )
>>> records = client.searchretrieve(query="Zurich")
>>> records.count
8985
>>> # ...or pass the version directly to the call
>>> records = sruthi.searchretrieve(
... 'https://services.dnb.de/sru/dnb',
... query="Zurich",
... record_schema='oai_dc',
... sru_version='1.1'
>>> )
>>> records.count
8985
```

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

## Development

To contribute to sruthi simply clone this repository and follow the instructions in [CONTRIBUTING.md](/CONTRIBUTING.md).

This project ha a Makefile with the most common commands.
Type `make help` to get an overview.

## Release

To create a new release, follow these steps (please respect [Semantic Versioning](http://semver.org/)):
Expand Down
2 changes: 1 addition & 1 deletion examples/explain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
'https://suche.staatsarchiv.djiktzh.ch/SRU/',
'https://amsquery.stadt-zuerich.ch/SRU/',
'http://lx2.loc.gov:210/LCDB?',
'https://sru.swissbib.ch/sru/explain',
'https://na01.alma.exlibrisgroup.com/view/sru/TR_INTEGRATION_INST',
]


Expand Down
27 changes: 27 additions & 0 deletions examples/library_of_congress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import sruthi
import sys

LOC_BASE = 'http://lx2.loc.gov:210/LCDB?'


def loc_search(isbn, sru_base):
loc_lcc = None
try:
records = sruthi.searchretrieve(sru_base, query=isbn)
record = records[0]
fields = record.get('datafield', [])
for field in fields:
if field['tag'] != '050':
continue
if len(field.get('subfield', [])) > 0:
loc_lcc = (field['subfield'][0]['text'])
break
except Exception as e:
print("Error: %s" % e, file=sys.stderr)
return None
return loc_lcc


isbn = '0062509470'
result = loc_search(isbn, LOC_BASE)
print(f"Tag 050 of ISBN '{isbn}': {result}")
28 changes: 28 additions & 0 deletions examples/sru1.1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import sruthi
from pprint import pprint

# check supported schemas of server
server_url = 'https://services.dnb.de/sru/dnb'

# create sruthi client
client = sruthi.Client(server_url, record_schema='oai_dc', sru_version='1.1')

explain = client.explain()
print(f'SRU version: {explain.sru_version}')
pprint(explain.server)
pprint(explain.config)
pprint(explain.index, depth=1)
pprint(explain.schema, depth=1)
pprint(explain.database)


print(20 * '=')
print('=')
print(f"= Record with schema: {client.record_schema}")
print('=')
print(20 * '=')
records = client.searchretrieve(
query='Zurich'
)
print(f'Total records: {records.count}')
pprint(records[0])
23 changes: 20 additions & 3 deletions sruthi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
__version__ = '0.1.2'
__all__ = ['client', 'errors', 'response', 'sru', 'xmlparse']
__version__ = '1.0.0'
__all__ = ['client', 'errors', 'response', 'xmlparse']

from .errors import SruthiError, ServerIncompatibleError, SruError, NoMoreRecordsError # noqa
from .errors import SruthiWarning, WrongNamespaceWarning # noqa
from .sru import searchretrieve, explain # noqa
from .client import Client # noqa


def searchretrieve(url, query, **kwargs):
search_params = ['query', 'start_record', 'requests_kwargs']
search_kwargs = {k: v for k, v in kwargs.items() if k in search_params}
search_kwargs['query'] = query

# assume all others kwargs are for the client
client_kwargs = {k: v for k, v in kwargs.items() if k not in search_params}
client_kwargs['url'] = url

c = Client(**client_kwargs)
return c.searchretrieve(**search_kwargs)


def explain(url, **kwargs):
c = Client(url, **kwargs)
return c.explain()
7 changes: 4 additions & 3 deletions sruthi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@


class Client(object):
def __init__(self, url=None, maximum_records=10, record_schema=None):
def __init__(self, url=None, maximum_records=10, record_schema=None, sru_version='1.2'):
self.url = url
self.maximum_records = maximum_records
self.sru_version = '1.2'
self.sru_version = sru_version
self.record_schema = record_schema

def searchretrieve(self, query, start_record=1, requests_kwargs=None):
Expand All @@ -34,7 +34,8 @@ def explain(self, requests_kwargs=None):
'version': self.sru_version,
}
data_loader = DataLoader(self.url, params, requests_kwargs)
return response.ExplainResponse(data_loader)
explain_response = response.ExplainResponse(data_loader)
return explain_response.asdict()


class DataLoader(object):
Expand Down
Loading

0 comments on commit 2f97c0f

Please sign in to comment.