Skip to content

Commit 463fc88

Browse files
authored
Merge pull request #5 from metaodi/develop
Release 0.1.0
2 parents 19aa6b3 + 141ef9f commit 463fc88

16 files changed

Lines changed: 2608 additions & 71 deletions

CHANGELOG.md

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

55
## [Unreleased]
66

7+
## [0.1.0] - 2021-09-16
8+
### Added
9+
- Add support for pagination
10+
- More examples and documentation
11+
- New error superclass
12+
713
## [0.0.3] - 2021-09-14
814
### Added
915
- Workflows to lint and publish the code
@@ -34,7 +40,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
3440
- `Fixed` for any bug fixes.
3541
- `Security` to invite users to upgrade in case of vulnerabilities.
3642

37-
[Unreleased]: https://github.com/metaodi/museumpy/compare/v0.0.3...HEAD
43+
[Unreleased]: https://github.com/metaodi/museumpy/compare/v0.1.0...HEAD
44+
[0.1.0]: https://github.com/metaodi/museumpy/compare/v0.0.3...v0.1.0
3845
[0.0.3]: https://github.com/metaodi/museumpy/compare/v0.0.2...v0.0.3
3946
[0.0.2]: https://github.com/metaodi/museumpy/compare/v0.0.1...v0.0.2
4047
[0.0.1]: https://github.com/metaodi/museumpy/releases/tag/v0.0.1

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ $ pip install museumpy
2424

2525
## Usage
2626

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

2929
### `search`
3030

@@ -96,7 +96,7 @@ import museumpy
9696

9797
id = '98977'
9898
module = 'Multimedia'
99-
# download attachment to a direcory called `files`
99+
# download attachment to a directory called `files`
100100
attachment_path = client.download_attachment(id, module, 'files')
101101
print(attachment_path)
102102
```
@@ -124,9 +124,10 @@ For convenience a default mapping is provided to access some fields more easily:
124124
```python
125125
for record in records:
126126
print(record['hasAttachments'])
127+
print(record['ObjObjectNumberTxt'])
127128
```
128129

129-
If you want to customize this mapping, you can pass a `map_function` to `search` and `fulltext_search`:
130+
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`:
130131

131132

132133
```python
@@ -149,6 +150,15 @@ client = museumpy.MuseumPlusClient(
149150
base_url='https://test.zetcom.com/MpWeb-mpTest',
150151
map_function=my_custom_map,
151152
)
153+
154+
records = client.search(
155+
base_url='https://test.zetcom.com/MpWeb-mpTest',
156+
query='Patolu',
157+
)
158+
for record in records:
159+
print(record['my_id'])
160+
print(record['my_title'])
161+
152162
```
153163

154164
## Release

examples/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Examples
2+
========
3+
4+
The examples in this directory make use of `.env` files to specify the username and password for a user.
5+
6+
You can either create a `.env` file (here or in the root of the repository) like this:
7+
8+
```bash
9+
MP_USER=my_username
10+
MP_PASS=supersecretpassword
11+
```
12+
13+
...or simply provide those values as environment variables.

examples/download_attachent.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,31 @@
22
import museumpy
33
from dotenv import load_dotenv, find_dotenv
44
import os
5+
from pprint import pprint
6+
import tempfile
57

68
load_dotenv(find_dotenv())
79
user = os.getenv('MP_USER')
810
pw = os.getenv('MP_PASS')
911

10-
client = museumpy.client(
12+
client = museumpy.MuseumPlusClient(
1113
base_url='https://mpzurichrietberg.zetcom.com/MpWeb-mpZurichRietberg',
1214
requests_kwargs={'auth': (user, pw)},
1315
)
1416

15-
group_result = client.search('ObjObjectGroupTxt', 'MyGroup')
17+
group_result = client.search(
18+
field='OgrNameTxt',
19+
value='Patolu, MAP',
20+
module='ObjectGroup'
21+
)
1622
group = group_result[0]['raw']
17-
ref = group['moduleIm']['moduleReference']
23+
ref = group['moduleItem']['moduleReference']
24+
1825

19-
for ref_item in ref['moduleReferenceItem']:
26+
for ref_item in ref['moduleReferenceItem'][:5]:
2027
item = client.module_item(ref_item['moduleItemId'], ref['targetModule'])
28+
pprint(item, depth=1)
2129
if item['hasAttachments'] == 'true':
22-
attachment_path = client.download_attachment(ref_item['moduleItemId'], ref['targetModule'], 'files')
23-
print(f"Attachment downloaded and saved at {attachment_path}")
30+
with tempfile.TemporaryDirectory() as tmpdir:
31+
attachment_path = client.download_attachment(ref_item['moduleItemId'], ref['targetModule'], tmpdir)
32+
print(f"Attachment downloaded and saved at {attachment_path}")

examples/pagination.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import museumpy
2+
from dotenv import load_dotenv, find_dotenv
3+
from pprint import pprint
4+
import os
5+
6+
load_dotenv(find_dotenv())
7+
user = os.getenv('MP_USER')
8+
pw = os.getenv('MP_PASS')
9+
10+
11+
client = museumpy.MuseumPlusClient(
12+
base_url='https://mpzurichrietberg.zetcom.com/MpWeb-mpZurichRietberg',
13+
requests_kwargs={'auth': (user, pw)}
14+
)
15+
16+
result = client.fulltext_search(
17+
query='Patolu',
18+
limit=2
19+
)
20+
21+
print(result)
22+
print(result.count)
23+
for rec in result[:5]:
24+
pprint(rec, depth=1)
25+
print(result)

examples/simple_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
)
1616

1717
pprint(records)
18-
print(len(records))
18+
print(records.count)
1919
pprint(records[0], depth=1)

museumpy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
__version__ = '0.0.3'
1+
__version__ = '0.1.0'
22
__all__ = ['client', 'errors', 'response', 'xmlparse']
33

4-
from .errors import MuseumPlusError # noqa
4+
from .errors import MuseumpyError # noqa
55
from .client import MuseumPlusClient
66

77
def fulltext_search(base_url, query, **kwargs): # noqa

museumpy/client.py

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,61 +45,61 @@ def __init__(self, base_url=None, map_function=None, requests_kwargs=None):
4545

4646
def fulltext_search(self, query, module='Object', limit=100, offset=0):
4747
url = f"{self.base_url}/ria-ws/application/module/{module}/search"
48-
data = FULLTEXT_TEMPLATE.format(
49-
module_name=module,
50-
limit=limit,
51-
offset=offset,
52-
query=query,
53-
)
54-
xml = data.encode("utf-8")
55-
xml_response = self._post_xml(url, xml)
56-
return response.SearchResponse(xml_response, self.map_function)
48+
params = {
49+
'module_name': module,
50+
'query': query,
51+
}
52+
data_loader = DataPoster(url, params, FULLTEXT_TEMPLATE, self.requests_kwargs)
53+
return response.SearchResponse(data_loader, limit, offset, self.map_function)
5754

5855
def search(self, field, value, module='Object', limit=100, offset=0):
5956
url = f"{self.base_url}/ria-ws/application/module/{module}/search"
60-
data = SEARCH_TEMPLATE.format(
61-
module_name=module,
62-
limit=limit,
63-
offset=offset,
64-
field=field,
65-
value=value,
66-
)
67-
xml = data.encode("utf-8")
68-
xml_response = self._post_xml(url, xml)
69-
return response.SearchResponse(xml_response, self.map_function)
57+
params = {
58+
'module_name': module,
59+
'field': field,
60+
'value': value,
61+
}
62+
data_loader = DataPoster(url, params, SEARCH_TEMPLATE, self.requests_kwargs)
63+
return response.SearchResponse(data_loader, limit, offset, self.map_function)
7064

7165
def module_item(self, id, module='Object'):
7266
url = f"{self.base_url}/ria-ws/application/module/{module}/{id}"
73-
xml_response = self._get_xml(url)
74-
resp = response.SearchResponse(xml_response)
75-
if len(resp) == 1:
67+
data_loader = DataLoader(url, self.requests_kwargs)
68+
resp = response.SearchResponse(data_loader)
69+
if resp.count == 1:
7670
return resp[0]
7771
return resp
7872

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

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

95-
def _get_xml(self, url):
96-
res = self._get_content(url)
79+
class DataPoster(object):
80+
def __init__(self, url, params=None, template=None, requests_kwargs=None):
81+
self.session = requests.Session()
82+
self.url = url
83+
self.params = params
84+
self.template = template
85+
self.xmlparser = xmlparse.XMLParser()
86+
self.requests_kwargs = requests_kwargs or {}
87+
88+
def load(self, **kwargs):
89+
self.params.update(kwargs)
90+
xml = self.template.format(**self.params).encode('utf-8')
91+
return self._post_xml(self.url, xml)
92+
93+
def _post_xml(self, url, xml):
94+
headers = {'Content-Type': 'application/xml'}
95+
res = self._post_content(url, xml, headers)
9796
return self.xmlparser.parse(res.content)
9897

99-
def _get_content(self, url, headers={}):
98+
def _post_content(self, url, data, headers):
10099
try:
101-
res = self.session.get(
100+
res = self.session.post(
102101
url,
102+
data=data,
103103
headers=headers,
104104
**self.requests_kwargs
105105
)
@@ -111,16 +111,38 @@ def _get_content(self, url, headers={}):
111111

112112
return res
113113

114-
def _post_xml(self, url, xml):
115-
headers = {'Content-Type': 'application/xml'}
116-
res = self._post_content(url, xml, headers)
114+
115+
class DataLoader(object):
116+
def __init__(self, url, requests_kwargs=None):
117+
self.session = requests.Session()
118+
self.url = url
119+
self.xmlparser = xmlparse.XMLParser()
120+
self.requests_kwargs = requests_kwargs or {}
121+
122+
def load(self, **kwargs):
123+
xml = self._get_xml(self.url)
124+
return xml
125+
126+
def download_file(self, url, dir):
127+
headers = {'Accept': 'application/octet-stream'}
128+
res = self._get_content(url, headers)
129+
d = res.headers.get('Content-Disposition')
130+
fname = re.findall("filename=(.+)", d)[0]
131+
assert fname, "Could not find filename in Content-Disposition header"
132+
path = os.path.join(dir, fname)
133+
with open(path, 'wb') as f:
134+
for chunk in res.iter_content(1024):
135+
f.write(chunk)
136+
return path
137+
138+
def _get_xml(self, url):
139+
res = self._get_content(url)
117140
return self.xmlparser.parse(res.content)
118141

119-
def _post_content(self, url, data, headers):
142+
def _get_content(self, url, headers={}):
120143
try:
121-
res = self.session.post(
144+
res = self.session.get(
122145
url,
123-
data=data,
124146
headers=headers,
125147
**self.requests_kwargs
126148
)
@@ -129,4 +151,5 @@ def _post_content(self, url, data, headers):
129151
raise errors.MuseumPlusError("HTTP error: %s" % e)
130152
except requests.exceptions.RequestException as e:
131153
raise errors.MuseumPlusError("Request error: %s" % e)
154+
132155
return res

museumpy/errors.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1-
class MuseumPlusError(Exception):
1+
class MuseumpyError(Exception):
22
"""
33
General MuseumPlus error class to provide a superclass for all other errors
44
"""
55

66

7-
class XMLParsingError(MuseumPlusError):
7+
class MuseumPlusError(MuseumpyError):
8+
"""
9+
MuseumPlus error raised when an error with the communication with MuseumPlus occurs
10+
"""
11+
12+
13+
class XMLParsingError(MuseumpyError):
814
"""
915
The error raised when parsing the XML.
1016
"""
17+
18+
19+
class NoMoreRecordsError(MuseumpyError):
20+
"""
21+
This error is raised if all records have been loaded (or no records are
22+
present)
23+
"""

0 commit comments

Comments
 (0)