Skip to content

Commit

Permalink
Merge pull request #5 from metaodi/develop
Browse files Browse the repository at this point in the history
Release 0.1.0
  • Loading branch information
metaodi authored Sep 15, 2021
2 parents 19aa6b3 + 141ef9f commit 463fc88
Show file tree
Hide file tree
Showing 16 changed files with 2,608 additions and 71 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

## [0.1.0] - 2021-09-16
### Added
- Add support for pagination
- More examples and documentation
- New error superclass

## [0.0.3] - 2021-09-14
### Added
- Workflows to lint and publish the code
Expand Down Expand Up @@ -34,7 +40,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/museumpy/compare/v0.0.3...HEAD
[Unreleased]: https://github.com/metaodi/museumpy/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/metaodi/museumpy/compare/v0.0.3...v0.1.0
[0.0.3]: https://github.com/metaodi/museumpy/compare/v0.0.2...v0.0.3
[0.0.2]: https://github.com/metaodi/museumpy/compare/v0.0.1...v0.0.2
[0.0.1]: https://github.com/metaodi/museumpy/releases/tag/v0.0.1
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $ pip install museumpy

## Usage

See the [`examples` directory](https://github.com/metaodi/museumpy/tree/master/examples) for more scripts.
See the [`examples` directory](/examples) for more scripts.

### `search`

Expand Down Expand Up @@ -96,7 +96,7 @@ import museumpy

id = '98977'
module = 'Multimedia'
# download attachment to a direcory called `files`
# download attachment to a directory called `files`
attachment_path = client.download_attachment(id, module, 'files')
print(attachment_path)
```
Expand Down Expand Up @@ -124,9 +124,10 @@ For convenience a default mapping is provided to access some fields more easily:
```python
for record in records:
print(record['hasAttachments'])
print(record['ObjObjectNumberTxt'])
```

If you want to customize this mapping, you can pass a `map_function` to `search` and `fulltext_search`:
If you want to customize this mapping, you can pass a `map_function` to the client, which is then used on all subsequent calls to `search` and `fulltext_search`:


```python
Expand All @@ -149,6 +150,15 @@ client = museumpy.MuseumPlusClient(
base_url='https://test.zetcom.com/MpWeb-mpTest',
map_function=my_custom_map,
)

records = client.search(
base_url='https://test.zetcom.com/MpWeb-mpTest',
query='Patolu',
)
for record in records:
print(record['my_id'])
print(record['my_title'])

```

## Release
Expand Down
13 changes: 13 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Examples
========

The examples in this directory make use of `.env` files to specify the username and password for a user.

You can either create a `.env` file (here or in the root of the repository) like this:

```bash
MP_USER=my_username
MP_PASS=supersecretpassword
```

...or simply provide those values as environment variables.
21 changes: 15 additions & 6 deletions examples/download_attachent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@
import museumpy
from dotenv import load_dotenv, find_dotenv
import os
from pprint import pprint
import tempfile

load_dotenv(find_dotenv())
user = os.getenv('MP_USER')
pw = os.getenv('MP_PASS')

client = museumpy.client(
client = museumpy.MuseumPlusClient(
base_url='https://mpzurichrietberg.zetcom.com/MpWeb-mpZurichRietberg',
requests_kwargs={'auth': (user, pw)},
)

group_result = client.search('ObjObjectGroupTxt', 'MyGroup')
group_result = client.search(
field='OgrNameTxt',
value='Patolu, MAP',
module='ObjectGroup'
)
group = group_result[0]['raw']
ref = group['moduleIm']['moduleReference']
ref = group['moduleItem']['moduleReference']


for ref_item in ref['moduleReferenceItem']:
for ref_item in ref['moduleReferenceItem'][:5]:
item = client.module_item(ref_item['moduleItemId'], ref['targetModule'])
pprint(item, depth=1)
if item['hasAttachments'] == 'true':
attachment_path = client.download_attachment(ref_item['moduleItemId'], ref['targetModule'], 'files')
print(f"Attachment downloaded and saved at {attachment_path}")
with tempfile.TemporaryDirectory() as tmpdir:
attachment_path = client.download_attachment(ref_item['moduleItemId'], ref['targetModule'], tmpdir)
print(f"Attachment downloaded and saved at {attachment_path}")
25 changes: 25 additions & 0 deletions examples/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import museumpy
from dotenv import load_dotenv, find_dotenv
from pprint import pprint
import os

load_dotenv(find_dotenv())
user = os.getenv('MP_USER')
pw = os.getenv('MP_PASS')


client = museumpy.MuseumPlusClient(
base_url='https://mpzurichrietberg.zetcom.com/MpWeb-mpZurichRietberg',
requests_kwargs={'auth': (user, pw)}
)

result = client.fulltext_search(
query='Patolu',
limit=2
)

print(result)
print(result.count)
for rec in result[:5]:
pprint(rec, depth=1)
print(result)
2 changes: 1 addition & 1 deletion examples/simple_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
)

pprint(records)
print(len(records))
print(records.count)
pprint(records[0], depth=1)
4 changes: 2 additions & 2 deletions museumpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__version__ = '0.0.3'
__version__ = '0.1.0'
__all__ = ['client', 'errors', 'response', 'xmlparse']

from .errors import MuseumPlusError # noqa
from .errors import MuseumpyError # noqa
from .client import MuseumPlusClient

def fulltext_search(base_url, query, **kwargs): # noqa
Expand Down
111 changes: 67 additions & 44 deletions museumpy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,61 +45,61 @@ def __init__(self, base_url=None, map_function=None, requests_kwargs=None):

def fulltext_search(self, query, module='Object', limit=100, offset=0):
url = f"{self.base_url}/ria-ws/application/module/{module}/search"
data = FULLTEXT_TEMPLATE.format(
module_name=module,
limit=limit,
offset=offset,
query=query,
)
xml = data.encode("utf-8")
xml_response = self._post_xml(url, xml)
return response.SearchResponse(xml_response, self.map_function)
params = {
'module_name': module,
'query': query,
}
data_loader = DataPoster(url, params, FULLTEXT_TEMPLATE, self.requests_kwargs)
return response.SearchResponse(data_loader, limit, offset, self.map_function)

def search(self, field, value, module='Object', limit=100, offset=0):
url = f"{self.base_url}/ria-ws/application/module/{module}/search"
data = SEARCH_TEMPLATE.format(
module_name=module,
limit=limit,
offset=offset,
field=field,
value=value,
)
xml = data.encode("utf-8")
xml_response = self._post_xml(url, xml)
return response.SearchResponse(xml_response, self.map_function)
params = {
'module_name': module,
'field': field,
'value': value,
}
data_loader = DataPoster(url, params, SEARCH_TEMPLATE, self.requests_kwargs)
return response.SearchResponse(data_loader, limit, offset, self.map_function)

def module_item(self, id, module='Object'):
url = f"{self.base_url}/ria-ws/application/module/{module}/{id}"
xml_response = self._get_xml(url)
resp = response.SearchResponse(xml_response)
if len(resp) == 1:
data_loader = DataLoader(url, self.requests_kwargs)
resp = response.SearchResponse(data_loader)
if resp.count == 1:
return resp[0]
return resp

def download_attachment(self, id, module='Object', dir='.'):
url = f"{self.base_url}/ria-ws/application/module/{module}/{id}/attachment"
return self._download_file(url, dir)
data_loader = DataLoader(url, self.requests_kwargs)
return data_loader.download_file(url, dir)

def _download_file(self, url, dir):
headers = {'Accept': 'application/octet-stream'}
res = self._get_content(url, headers)
d = res.headers.get('Content-Disposition')
fname = re.findall("filename=(.+)", d)[0]
assert fname, "Could not find filename in Content-Disposition header"
path = os.path.join(dir, fname)
with open(path, 'wb') as f:
for chunk in res.iter_content(1024):
f.write(chunk)
return path

def _get_xml(self, url):
res = self._get_content(url)
class DataPoster(object):
def __init__(self, url, params=None, template=None, requests_kwargs=None):
self.session = requests.Session()
self.url = url
self.params = params
self.template = template
self.xmlparser = xmlparse.XMLParser()
self.requests_kwargs = requests_kwargs or {}

def load(self, **kwargs):
self.params.update(kwargs)
xml = self.template.format(**self.params).encode('utf-8')
return self._post_xml(self.url, xml)

def _post_xml(self, url, xml):
headers = {'Content-Type': 'application/xml'}
res = self._post_content(url, xml, headers)
return self.xmlparser.parse(res.content)

def _get_content(self, url, headers={}):
def _post_content(self, url, data, headers):
try:
res = self.session.get(
res = self.session.post(
url,
data=data,
headers=headers,
**self.requests_kwargs
)
Expand All @@ -111,16 +111,38 @@ def _get_content(self, url, headers={}):

return res

def _post_xml(self, url, xml):
headers = {'Content-Type': 'application/xml'}
res = self._post_content(url, xml, headers)

class DataLoader(object):
def __init__(self, url, requests_kwargs=None):
self.session = requests.Session()
self.url = url
self.xmlparser = xmlparse.XMLParser()
self.requests_kwargs = requests_kwargs or {}

def load(self, **kwargs):
xml = self._get_xml(self.url)
return xml

def download_file(self, url, dir):
headers = {'Accept': 'application/octet-stream'}
res = self._get_content(url, headers)
d = res.headers.get('Content-Disposition')
fname = re.findall("filename=(.+)", d)[0]
assert fname, "Could not find filename in Content-Disposition header"
path = os.path.join(dir, fname)
with open(path, 'wb') as f:
for chunk in res.iter_content(1024):
f.write(chunk)
return path

def _get_xml(self, url):
res = self._get_content(url)
return self.xmlparser.parse(res.content)

def _post_content(self, url, data, headers):
def _get_content(self, url, headers={}):
try:
res = self.session.post(
res = self.session.get(
url,
data=data,
headers=headers,
**self.requests_kwargs
)
Expand All @@ -129,4 +151,5 @@ def _post_content(self, url, data, headers):
raise errors.MuseumPlusError("HTTP error: %s" % e)
except requests.exceptions.RequestException as e:
raise errors.MuseumPlusError("Request error: %s" % e)

return res
17 changes: 15 additions & 2 deletions museumpy/errors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
class MuseumPlusError(Exception):
class MuseumpyError(Exception):
"""
General MuseumPlus error class to provide a superclass for all other errors
"""


class XMLParsingError(MuseumPlusError):
class MuseumPlusError(MuseumpyError):
"""
MuseumPlus error raised when an error with the communication with MuseumPlus occurs
"""


class XMLParsingError(MuseumpyError):
"""
The error raised when parsing the XML.
"""


class NoMoreRecordsError(MuseumpyError):
"""
This error is raised if all records have been loaded (or no records are
present)
"""
Loading

0 comments on commit 463fc88

Please sign in to comment.