Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from setuptools import setup, find_packages
import sys, os

version = '0.2.1'
version = "0.3.1"

setup(
name='sword2',
name="sword2",
version=version,
description="SWORD v2 python client",
long_description="""\
Expand All @@ -20,34 +20,34 @@
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Communications",
"Topic :: Internet",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords="sword-app atom sword2 http",
author="Ben O'Steen, Cottage Labs",
author_email='us@cottagelabs.com',
author_email="us@cottagelabs.com",
url="http://swordapp.org/",
license='Apache',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
license="Apache",
packages=find_packages(exclude=["ez_setup", "examples", "tests"]),
include_package_data=True,
zip_safe=False,
install_requires=[
"httplib2",
"lxml",
],
# Following left in as a memory aid for later-
#entry_points="""
# entry_points="""
# # -*- Entry points: -*-
# [console_scripts]
# cmd=module.path:func_name
#""",
# """,
)
1 change: 1 addition & 0 deletions sword2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
SWORD2 Python Client blurb
"""

from .service_document import ServiceDocument
from .collection import SDCollection, Collection_Feed
from .statement import Atom_Sword_Statement, Ore_Sword_Statement
Expand Down
142 changes: 77 additions & 65 deletions sword2/atom_objects.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Non-SWORD2 specific Atom/APP helper classes.
"""Non-SWORD2 specific Atom/APP helper classes.

Most often used class will be 'Entry' - it provides an easy means to make an atom:entry
Most often used class will be 'Entry' - it provides an easy means to make an atom:entry
document which can be used directly as the metadata entry.

Also provides Category, which is a convenience function to simplify reading in category information from an atom:entry
Expand All @@ -14,6 +14,7 @@

from .sword2_logging import logging
from .implementation_info import __version__

coll_l = logging.getLogger(__name__)


Expand All @@ -22,37 +23,34 @@

class Category(object):
"""Convenience class to aid in the intepreting of atom:category elements in XML. Currently, this is read-only.

Usage:

>>> from sword2 import Category

... # `Category` expects an etree.SubElement node (`c_node` in this example) referencing an <atom:category> element:
<atom:category term="...." scheme="...." label="....."> .... </atom:category>

# Load a `Category` instance:
>>> c = Category(dom = c_node)

# Overrides `__str__` to provide a simple means to view the content
>>> print c
"Category scheme:http://purl.org/net/sword/terms/ term:http://purl.org/net/sword/terms/originalDeposit label:Orignal Deposit text:'None'"

# Element attributes appear as object attibutes:
>>> c.scheme
'http://purl.org/net/sword/terms/'

# Element text will be in the text attribute, if text is present
>>> c.text
None

"""
def __init__(self, term=None,
scheme=None,
label=None,
text=None,
dom=None):

def __init__(self, term=None, scheme=None, label=None, text=None, dom=None):
"""Init a `Category` class - 99% of the time, this will be done by setting the dom parameter.

However, if (for testing) there is a need to 'fake' a `Category`, all the attributes can be set in the constructor."""
self.term = term
self.scheme = scheme
Expand All @@ -61,11 +59,9 @@ def __init__(self, term=None,
if dom != None:
self.dom = dom
self._from_element(self.dom)

def _from_element(self, e):
""" Load the `Category`'s internal attributes using the information within an `etree.SubElement`

"""
"""Load the `Category`'s internal attributes using the information within an `etree.SubElement`"""
for item in list(e.attrib.keys()):
if item.endswith("scheme"):
self.scheme = e.attrib[item]
Expand All @@ -78,16 +74,18 @@ def _from_element(self, e):

def __str__(self):
"""Rudimentary way to display the data held, in a way amenable to stdout."""
return "Category scheme:%s term:%s label:%s text:'%s'" % (self.scheme,
self.term,
self.label,
self.text)
return "Category scheme:%s term:%s label:%s text:'%s'" % (
self.scheme,
self.term,
self.label,
self.text,
)


class Entry(object):
"""Used to create `Entry`s - for multipart/metadata submission. Has a simple and extendable way to add in
namespace-aware key-value pairs.

Example of use:

>>> from sword2 import Entry
Expand All @@ -104,7 +102,7 @@ class Entry(object):


# Adding fields to the metadata entry
# dcterms (and other, non-atom fields) can be used by passing in a parameter with an underscore between the
# dcterms (and other, non-atom fields) can be used by passing in a parameter with an underscore between the
# prefix and element name, eg:
>>> e.add_fields(dcterms_title= "dcterms title", dcterms_some_other_field = "other")

Expand All @@ -125,7 +123,7 @@ class Entry(object):
<dcterms:some_other_field>other</dcterms:some_other_field>
<dcterms:title>dcterms title</dcterms:title>
</entry>
>>>
>>>

# Other namespaces - use `Entry.register_namespace` to add them to the list of those considered (prefix, URL):
>>> e.register_namespace("myschema", "http://example.org")
Expand All @@ -150,30 +148,40 @@ class Entry(object):

>>> len(e.entry.getchildren())
14
"""
atom_fields = ['title','id','updated','summary']
add_ns = ['dcterms', 'atom', 'app']
bootstrap = """<?xml version="1.0"?>
"""

atom_fields = ["title", "id", "updated", "summary"]
add_ns = ["dcterms", "atom", "app"]
bootstrap = (
"""<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:dcterms="http://purl.org/dc/terms/">
<generator uri="http://bitbucket.org/beno/python-sword2" version="%s"/>
</entry>""" % __version__
</entry>"""
% __version__
)

def __init__(self, atomEntryXml=None, **kw):
"""Create a basic `Entry` document, setting the generator and a timestamp for the updated element value.

Any keyword parameters passed in will be passed to the add_fields method and added to the entry
bootstrap document. It's currently not possible to add a namespace and use it within the init call."""

# create a namespace map which we'll use in all of the elements
self.nsmap = {"dcterms" : "http://purl.org/dc/terms/", "atom" : "http://www.w3.org/2005/Atom"}
self.entry = etree.fromstring(self.bootstrap if not atomEntryXml else atomEntryXml)
if not 'updated' in list(kw.keys()):
kw['updated'] = datetime.now().isoformat()
self.nsmap = {
"dcterms": "http://purl.org/dc/terms/",
"atom": "http://www.w3.org/2005/Atom",
}
self.entry = etree.fromstring(
self.bootstrap if not atomEntryXml else atomEntryXml
)
if not "updated" in list(kw.keys()):
kw["updated"] = datetime.now().isoformat()
self.add_fields(**kw)

def register_namespace(self, prefix, uri):
"""Registers a namespace,, making it available for use when adding subsequent fields to the entry.

Registration will also affect the XML export, adding in the xmlns:prefix="url" attribute when required."""
try:
etree.register_namespace(prefix, uri)
Expand All @@ -184,41 +192,45 @@ def register_namespace(self, prefix, uri):
self.add_ns.append(prefix)
if prefix not in list(NS.keys()):
NS[prefix] = "{%s}%%s" % uri

# we also have to handle namespaces internally, for etree implementations which
# don't support register_namespace
if prefix not in list(self.nsmap.keys()):
self.nsmap[prefix] = uri

def add_field(self, k, v, attrs=None):
"""Append a single key-value pair to the `Entry` document.
"""Append a single key-value pair to the `Entry` document.

eg

>>> e.add_field("myprefix_fooo", "value")

It is advisable to use the `Entry.add_fields` method instead as this is neater and simplifies element entry.

Note that the atom:author field is handled differently, as it requires certain fields from the author:

>>> e.add_field("author", {'name':".....",
'email':"....",
'uri':"...."} )

Note that this means of entry is not supported for other elements."""
if k in self.atom_fields:
# These should be unique!
old_e = self.entry.find(NS['atom'] % k)
old_e = self.entry.find(NS["atom"] % k)
if old_e == None:
e = etree.SubElement(self.entry, NS['atom'] % k, nsmap=self.nsmap) # Notice we explicitly declare the nsmap
e = etree.SubElement(
self.entry, NS["atom"] % k, nsmap=self.nsmap
) # Notice we explicitly declare the nsmap
e.text = v
else:
old_e.text = v
elif "_" in k:
# possible XML namespace, eg 'dcterms_title'
nmsp, tag = k.split("_", 1)
if nmsp in self.add_ns:
e = etree.SubElement(self.entry, NS[nmsp] % tag, nsmap=self.nsmap) # Notice we explicitly declare the nsmap
e = etree.SubElement(
self.entry, NS[nmsp] % tag, nsmap=self.nsmap
) # Notice we explicitly declare the nsmap
e.text = v
if attrs is not None:
for an, av in attrs.items():
Expand All @@ -227,40 +239,40 @@ def add_field(self, k, v, attrs=None):
self.add_author(**v)

def add_fields(self, **kw):
"""Add in multiple elements in one method call.
"""Add in multiple elements in one method call.

Eg:

>>> e.add_fields(dcterms_title="Origin of the Species",
dcterms_contributor="Darwin, Charles")
"""
for k,v in kw.items():
self.add_field(k,v)
for k, v in kw.items():
self.add_field(k, v)

def add_author(self, name, uri=None, email=None):
"""Convenience function to add in the atom:author elements in the fashion
required for Atom"""
a = etree.SubElement(self.entry, NS['atom'] % 'author', nsmap=self.nsmap)
n = etree.SubElement(a, NS['atom'] % 'name', nsmap=self.nsmap)
a = etree.SubElement(self.entry, NS["atom"] % "author", nsmap=self.nsmap)
n = etree.SubElement(a, NS["atom"] % "name", nsmap=self.nsmap)
n.text = name
if uri:
u = etree.SubElement(a, NS['atom'] % 'uri', nsmap=self.nsmap)
u = etree.SubElement(a, NS["atom"] % "uri", nsmap=self.nsmap)
u.text = uri
if email:
e = etree.SubElement(a, NS['atom'] % 'email', nsmap=self.nsmap)
e = etree.SubElement(a, NS["atom"] % "email", nsmap=self.nsmap)
e.text = email

def add_contributor(self, name, uri=None, email=None):
"""Convenience function to add in the atom:contributor elements in the fashion
required for Atom"""
a = etree.SubElement(self.entry, NS['atom'] % 'contributor', nsmap=self.nsmap)
n = etree.SubElement(a, NS['atom'] % 'name', nsmap=self.nsmap)
a = etree.SubElement(self.entry, NS["atom"] % "contributor", nsmap=self.nsmap)
n = etree.SubElement(a, NS["atom"] % "name", nsmap=self.nsmap)
n.text = name
if uri:
u = etree.SubElement(a, NS['atom'] % 'uri', nsmap=self.nsmap)
u = etree.SubElement(a, NS["atom"] % "uri", nsmap=self.nsmap)
u.text = uri
if email:
e = etree.SubElement(a, NS['atom'] % 'email', nsmap=self.nsmap)
e = etree.SubElement(a, NS["atom"] % "email", nsmap=self.nsmap)
e.text = email

def __str__(self):
Expand Down
Loading