Skip to content

Commit

Permalink
2.0.0b7 (#112)
Browse files Browse the repository at this point in the history
- Add test for dispatcher [ Fixed #111 ]
- Fix a bug where TextInventoryCollection would export to TextInventoryCollection node in CTS XML [ Fixed #110 ]
- Add Dispatcher and TextInventoryCollection to documentation and example  [ Fixed #108 ]
  • Loading branch information
PonteIneptique committed Feb 16, 2017
1 parent 177846c commit a80e99a
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 21 deletions.
2 changes: 1 addition & 1 deletion MyCapytain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"""

__version__ = "2.0.0b6"
__version__ = "2.0.0b7"
5 changes: 5 additions & 0 deletions MyCapytain/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ class UnknownObjectError(ValueError):
class UnknownNamespace(ValueError):
""" This error is thrown when a namespace is unknown
"""


class UndispatchedTextError(Exception):
""" This error is thrown when a text has not been dispatched by a dispatcher
"""
10 changes: 7 additions & 3 deletions MyCapytain/resolvers/cts/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from MyCapytain.common.reference import URN, Reference
from MyCapytain.common.utils import xmlparser
from MyCapytain.errors import InvalidURN, UnknownObjectError
from MyCapytain.errors import InvalidURN, UnknownObjectError, UndispatchedTextError
from MyCapytain.resolvers.prototypes import Resolver
from MyCapytain.resources.collections.cts import TextInventory, TextGroup, Work, Citation, Text as InventoryText, \
Translation, Edition
Expand Down Expand Up @@ -38,7 +38,7 @@ class CTSCapitainsLocalResolver(Resolver):
TEXT_CLASS = Text
DEFAULT_PAGE = 1
PER_PAGE = (1, 10, 100) # Min, Default, Mainvex,

RAISE_ON_UNDISPATCHED = False
@property
def inventory(self):
return self.__inventory__
Expand Down Expand Up @@ -152,6 +152,10 @@ def parse(self, resource):
)
else:
self.logger.error("%s is not present", __text__.path)
except UndispatchedTextError as E:
self.logger.error("Error dispatching %s ", __cts__)
if self.RAISE_ON_UNDISPATCHED is True:
raise E
except Exception as E:
self.logger.error("Error parsing %s ", __cts__)

Expand All @@ -172,7 +176,7 @@ def __getText__(self, urn):
urn = [
t.id
for t in self.__texts__
if t.id.startswith(str(urn)) and isinstance(t, Edition) and not print(t.id)
if t.id.startswith(str(urn)) and isinstance(t, Edition)
]
if len(urn) > 0:
urn = URN(urn[0])
Expand Down
58 changes: 52 additions & 6 deletions MyCapytain/resolvers/utils.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,77 @@
from MyCapytain.errors import UndispatchedTextError


class CollectionDispatcher:
"""
""" Collection Dispatcher provides a utility to divide automatically texts and collections \
into different collections
:param collection:
:param default_inventory_name:
:param collection: The root collection
:param default_inventory_name: The default name of the default collection
"""
def __init__(self, collection, default_inventory_name=None):
self.collection = collection
if default_inventory_name is None:
default_inventory_name = list(self.collection.children.values())[0].id
self.__methods__ = [(default_inventory_name, lambda x, **k: True)]
self.__methods__ = [(default_inventory_name, lambda x, **kwargs: True)]

@property
def methods(self):
""" List of methods to dispatch resources.
Each element is a tuple with two elements :
- First one is the inventory identifier to dispatch to
- Second one is a function which, if returns true, will activate dispatching to given
:rtype: List
"""
return self.__methods__

def add(self, func, inventory_name=None):
def add(self, func, inventory_name):
""" Register given function as a filter.
If this function "func" returns True when given an object, said object will be dispatched to \
Collection(inventory_name)
:param func: Callable
:param inventory_name: Identifier of the collection to dispatch to
"""
self.methods.append((inventory_name, func))

def inventory(self, inventory_name):
""" Decorator to register filters for given inventory. For a function "abc", it has the same effect
:param inventory_name:
:return:
.. code-block:: python
tic = TextInventoryCollection()
latin = PrototypeTextInventory("urn:perseus:latinLit", parent=tic)
latin.set_label("Classical Latin", "eng")
dispatcher = CollectionDispatcher(tic)
@dispatcher.inventory("urn:perseus:latinLit")
def dispatchLatinLit(collection, path=None, **kwargs):
if collection.id.startswith("urn:cts:latinLit:"):
return True
return False
"""
def decorator(f):
self.add(func=f, inventory_name=inventory_name)
return f
return decorator

def dispatch(self, collection, **kwargs):
""" Dispatch a collection using internal filters
:param collection: Collection object
:param kwargs: Additional keyword arguments that could be used by internal filters
:return: None
:raises:
"""
for inventory, method in self.methods[::-1]:
if method(collection, **kwargs) is True:
collection.parent = self.collection.children[inventory]
return
raise Exception("Text not dispatched %s" % collection.id)
raise UndispatchedTextError("Text not dispatched %s" % collection.id)
8 changes: 5 additions & 3 deletions MyCapytain/resources/prototypes/cts/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ def __xml_export_generic__(self, attrs, namespaces=False, lines="\n", members=No
if namespaces is True:
attrs.update(self.__namespaces_header__())

strings = [make_xml_node(self.graph, self.TYPE_URI, close=False, attributes=attrs)]
TYPE_URI = self.TYPE_URI
if TYPE_URI == NAMESPACES.CTS.term("TextInventoryCollection"):
TYPE_URI = NAMESPACES.CTS.term("TextInventory")

strings = [make_xml_node(self.graph, TYPE_URI, close=False, attributes=attrs)]
for pred in self.CTS_PROPERTIES:
for obj in self.graph.objects(self.metadata, pred):
strings.append(
Expand All @@ -122,7 +125,7 @@ def __xml_export_generic__(self, attrs, namespaces=False, lines="\n", members=No
for obj in members:
strings.append(obj.export(Mimetypes.XML.CTS, namespaces=False))

strings.append(make_xml_node(self.graph, self.TYPE_URI, close=True))
strings.append(make_xml_node(self.graph, TYPE_URI, close=True))

return lines.join(strings)

Expand Down Expand Up @@ -648,4 +651,3 @@ def __export__(self, output=None, domain="", namespaces=True):
return Collection.__export__(self, output=output, domain=domain)
else:
return self.members[0].export(output=output, domain=domain)

65 changes: 65 additions & 0 deletions doc/Dispatcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from MyCapytain.resolvers.cts.local import CTSCapitainsLocalResolver
from MyCapytain.resolvers.utils import CollectionDispatcher
from MyCapytain.common.constants import Mimetypes, NS
from MyCapytain.resources.collections.cts import TextInventory
from MyCapytain.resources.prototypes.cts.inventory import TextInventoryCollection

# We set up a main collection
tic = TextInventoryCollection()
# We register sub collection we want to dispatch to
latin = TextInventory("urn:perseus:latinLit", parent=tic)
latin.set_label("Classical Latin", "eng")
farsi = TextInventory("urn:perseus:farsiLit", parent=tic)
farsi.set_label("Farsi", "eng")
gc = TextInventory("urn:perseus:greekLit", parent=tic)
gc.set_label("Ancient Greek", "eng")
gc.set_label("Grec Ancien", "fre")

# We create the dispatcher with the root collection
dispatcher = CollectionDispatcher(tic)

# And we record function for each given repository
# We could have two function dispatching for the same repository !
@dispatcher.inventory("urn:perseus:latinLit")
def dispatchLatinLit(collection, path=None, **kwargs):
if collection.id.startswith("urn:cts:latinLit:"):
return True
return False

@dispatcher.inventory("urn:perseus:farsiLit")
def dispatchfFarsiLit(collection, path=None, **kwargs):
if collection.id.startswith("urn:cts:farsiLit:"):
return True
return False

@dispatcher.inventory("urn:perseus:greekLit")
def dispatchGreekLit(collection, path=None, **kwargs):
if collection.id.startswith("urn:cts:greekLit:"):
return True
return False

# We set up a resolver which parses local file
NautilusDummy = CTSCapitainsLocalResolver(
resource=[
"./tests/testing_data/latinLit2"
],
# We give it the dispatcher
dispatcher=dispatcher
)

# If we want to read the main repository, we will have all children
all = NautilusDummy.getMetadata()

print(len(all.readableDescendants)) # 25 is the number of edition and translation
print([m.id for m in all.members]) # Direct members are dispatched-in collections
print(
all["urn:cts:latinLit:phi1294"] == all["urn:perseus:latinLit"]["urn:cts:latinLit:phi1294"]
) # Is true because they are dispatched this way

try:
all["urn:perseus:greekLit"]["urn:cts:latinLit:phi1294"]
except KeyError:
print("But this won't work because the object has been dispatched to latinLit !")

print(len(all["urn:perseus:greekLit"].readableDescendants)) # Is 6 because there is 6 recorded texts in __cts__
print(len(all["urn:perseus:latinLit"].readableDescendants)) # Is 19 because there is 6 recorded texts in __cts__
7 changes: 7 additions & 0 deletions doc/MyCapytain.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ Local CapiTainS Guidelines CTS Resolver
.. autoclass:: MyCapytain.resolvers.cts.local.CTSCapitainsLocalResolver
:members:

Dispatcher
**********

.. autoclass:: MyCapytain.resolvers.utils.CollectionDispatcher
:members:
:noindex:

Prototypes
**********

Expand Down
30 changes: 30 additions & 0 deletions doc/MyCapytain.classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,36 @@ Example
:language: python
:linenos:

Dispatchers
###########

Description
***********

Dispatcher are tools to be used to organize semi-automatically resources, most likely in the context of a resolver \
retrieving data from local directories.

Dispatcher has following properties :

- methods -> [tuple(Callable()->Bool, str)]
- add(func[Callable()], inventory_name[str])
- inventory(inventory_name[str]) -> @decorator
- dispatch(collection[Collection, **kwargs)
Implementation
**************

.. autoclass:: MyCapytain.resolvers.utils.CollectionDispatcher
:members:

Example
*******

.. literalinclude:: Dispatcher.py
:language: python
:linenos:


Resolvers
#########

Expand Down

0 comments on commit a80e99a

Please sign in to comment.