Skip to content

Add collection list and manages block #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
142 changes: 84 additions & 58 deletions hydra_python_core/doc_writer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""API Doc templates generator."""
from typing import Any, Dict, List, Optional, Union
from urllib.parse import quote, urljoin


class HydraDoc():
Expand All @@ -9,9 +10,10 @@ def __init__(self, API: str, title: str, desc: str,
entrypoint: str, base_url: str) -> None:
"""Initialize the APIDoc."""
self.API = API
self.entrypoint_endpoint = entrypoint
self.title = title
self.base_url = base_url
self.context = Context("{}{}".format(base_url, API))
self.context = Context("{}".format(urljoin(base_url, API)))
self.parsed_classes = dict() # type: Dict[str, Any]
self.other_classes = list() # type: List[HydraClass]
self.collections = dict() # type: Dict[str, Any]
Expand All @@ -22,6 +24,7 @@ def __init__(self, API: str, title: str, desc: str,

def add_supported_class(
self, class_: 'HydraClass', collection: Union[bool, 'HydraCollection']=False,
collection_name: str=None,
collection_path: str=None, collectionGet: bool=True, collectionPost: bool=True,
collection_manages: Union[Dict[str, Any], List]=None) -> None:
"""Add a new supportedClass.
Expand All @@ -40,7 +43,8 @@ def add_supported_class(
}
if collection:
collection = HydraCollection(
class_, collection_path, collection_manages, collectionGet, collectionPost)
class_, collection_name, collection_path, collection_manages, collectionGet,
collectionPost)
self.collections[collection.path] = {
"context": Context(address="{}{}".format(self.base_url, self.API),
collection=collection), "collection": collection}
Expand Down Expand Up @@ -94,10 +98,11 @@ def generate(self) -> Dict[str, Any]:
for key in self.collections]
doc = {
"@context": self.context.generate(),
"@id": "{}{}/vocab".format(self.base_url, self.API),
"@id": "{}/vocab".format(urljoin(self.base_url, self.API)),
"@type": "ApiDocumentation",
"title": self.title,
"description": self.desc,
"entrypoint": urljoin(self.base_url, self.entrypoint_endpoint),
"supportedClass": [
x.generate() for x in parsed_classes +
self.other_classes + collections + [self.entrypoint]],
Expand Down Expand Up @@ -174,7 +179,7 @@ def __init__(self,
read: bool,
write: bool,
required: bool,
desc: str = "",
desc: str="",
) -> None:
"""Initialize the Hydra_Prop."""
self.prop = prop
Expand Down Expand Up @@ -210,8 +215,8 @@ def __init__(self,
method: str,
expects: Optional[str],
returns: Optional[str],
expects_header: List[str] = [],
returns_header: List[str] = [],
expects_header: List[str]=[],
returns_header: List[str]=[],
possible_status: List[Union['HydraStatus', 'HydraError']]=[],
) -> None:
"""Initialize the Hydra_Prop."""
Expand Down Expand Up @@ -254,51 +259,63 @@ class HydraCollection():

def __init__(
self, class_: HydraClass,
collection_path: str=None, manages: Union[Dict[str, Any], List]=None,
collection_name: str=None,
collection_path: str=None,
manages: Union[Dict[str, Any], List]=None,
get: bool=True, post: bool=True) -> None:
"""Generate Collection for a given class."""
self.class_ = class_
self.name = "{}Collection".format(class_.title)
self.name = "{}Collection".format(class_.title) \
if (collection_name is None) else collection_name
self.path = collection_path if collection_path else self.name
self.manages = manages
self.supportedOperation = list() # type: List
self.supportedProperty = [HydraClassProp("http://www.w3.org/ns/hydra/core#member",
"members",
False, False, False,
"The {}".format(self.class_.title.lower()))]
if manages is None:
# provide default manages block
self.manages = {
"property": "rdf:type",
"object": class_.id_,
}
else:
self.manages = manages

if get:
get_op = HydraCollectionOp("_:{}_collection_retrieve".format(self.class_.title.lower()),
"http://schema.org/FindAction",
"GET", "Retrieves all {} entities".format(
self.class_.title),
None, "vocab:{}".format(self.name), [], [], [])
self.class_.title),
None, "vocab:{}".format(self.name), [], [], [])
self.supportedOperation.append(get_op)

if post:
post_op = HydraCollectionOp("_:{}_create".format(self.class_.title.lower()),
"http://schema.org/AddAction",
"PUT", "Create new {} entity".format(
self.class_.title),
self.class_.id_, self.class_.id_, [], [],
[HydraStatus(code=201, desc="If the {} entity was created"
"successfully.".format(self.class_.title))]
)
self.class_.title),
self.class_.id_, self.class_.id_, [], [],
[HydraStatus(code=201, desc="If the {} entity was created"
"successfully.".format(self.class_.title))]
)
self.supportedOperation.append(post_op)

def generate(self) -> Dict[str, Any]:
"""Get as a python dict."""

# encode name because name might contain spaces or special Characters.
name = quote(self.name, safe='')
collection = {
"@id": "vocab:{}".format(self.name,),
"@type": "hydra:Class",
"@id": "vocab:{}".format(name),
"@type": "Collection",
"subClassOf": "http://www.w3.org/ns/hydra/core#Collection",
"title": "{}".format(self.name),
"description": "A collection of {}".format(self.class_.title.lower()),
"supportedOperation": [x.generate() for x in self.supportedOperation],
"supportedProperty": [x.generate() for x in self.supportedProperty]
"supportedProperty": [x.generate() for x in self.supportedProperty],
"manages": self.manages
}
if self.manages is not None:
collection["manages"] = self.manages
return collection


Expand All @@ -312,8 +329,8 @@ def __init__(self,
desc: str,
expects: Optional[str],
returns: Optional[str],
expects_header: List[str] = [],
returns_header: List[str] = [],
expects_header: List[str]=[],
returns_header: List[str]=[],
possible_status: List[Union['HydraStatus', 'HydraError']]=[],
) -> None:
"""Create method."""
Expand Down Expand Up @@ -361,6 +378,8 @@ def __init__(self, base_url: str, entrypoint: str) -> None:
entrypoint),
entrypoint=self)

self.collections: List[EntryPointCollection] = []

def add_Class(self, class_: HydraClass) -> None:
"""Add supportedProperty to the EntryPoint.

Expand All @@ -385,6 +404,7 @@ def add_Collection(self, collection: HydraCollection) -> None:
if not isinstance(collection, HydraCollection):
raise TypeError("Type is not <HydraCollection>")
entrypoint_collection = EntryPointCollection(collection)
self.collections.append(entrypoint_collection.generate())
self.entrypoint.add_supported_prop(entrypoint_collection)
self.context.add(entrypoint_collection.name, {
"@id": entrypoint_collection.id_, "@type": "@id"})
Expand All @@ -402,8 +422,22 @@ def get(self) -> Dict[str, str]:
}
for item in self.entrypoint.supportedProperty:
uri = item.id_
object_[item.name] = uri.replace(
"vocab:EntryPoint", "/{}".format(self.api))
if item.generate() in self.collections:
object_['collections'] = []
collection_returned = item.generate()
collection_id = uri.replace(
"vocab:EntryPoint", "/{}".format(self.api))
collection_to_append = {
"@id": collection_id,
'title': collection_returned['hydra:title'],
'@type': "Collection",
"supportedOperation": collection_returned['property']['supportedOperation'],
"manages": collection_returned['property']['manages']
}
object_['collections'].append(collection_to_append)
else:
object_[item.name] = uri.replace(
"vocab:EntryPoint", "/{}".format(self.api))

return object_

Expand All @@ -416,9 +450,10 @@ def __init__(self, collection: HydraCollection) -> None:
self.name = collection.name
self.supportedOperation = collection.supportedOperation
if collection.path:
self.id_ = "vocab:EntryPoint/{}".format(collection.path)
self.id_ = "vocab:EntryPoint/{}".format(
quote(collection.path, safe=''))
else:
self.id_ = "vocab:EntryPoint/{}".format(self.name)
self.id_ = "vocab:EntryPoint/{}".format(quote(self.name, safe=''))

def generate(self) -> Dict[str, Any]:
"""Get as a python dict."""
Expand All @@ -430,7 +465,7 @@ def generate(self) -> Dict[str, Any]:
"description": "The {} collection".format(self.name,),
"domain": "vocab:EntryPoint",
"range": "vocab:{}".format(self.name,),
"supportedOperation": []
"supportedOperation": [],
},
"hydra:title": self.name.lower(),
"hydra:description": "The {} collection".format(self.name,),
Expand Down Expand Up @@ -499,11 +534,11 @@ def __init__(self,
desc: str,
expects: Optional[str],
returns: Optional[str],
expects_header: List[str] = [],
returns_header: List[str] = [],
expects_header: List[str]=[],
returns_header: List[str]=[],
possible_status: List[Union['HydraStatus', 'HydraError']]=[],
type_: Optional[str] = None,
label: str = "",
type_: Optional[str]=None,
label: str="",
) -> None:
"""Create method."""
self.id_ = id_
Expand Down Expand Up @@ -554,7 +589,7 @@ class IriTemplateMapping():
def __init__(self,
variable: str,
prop: str,
required: bool = False):
required: bool=False):
self.variable = variable
self.prop = prop
self.required = required
Expand All @@ -576,7 +611,7 @@ class HydraIriTemplate():
def __init__(self,
template: str,
iri_mapping: List[IriTemplateMapping] = [],
basic_representation: bool = True):
basic_representation: bool=True):
self.template = template
if basic_representation:
self.variable_rep = "hydra:BasicRepresentation"
Expand All @@ -598,14 +633,14 @@ def generate(self) -> Dict[str, Any]:
class HydraStatus():
"""Class for possibleStatus in Hydra Doc."""

def __init__(self, code: int, id_: str = None, title: str = "", desc: str = "") -> None:
def __init__(self, code: int, id_: str=None, title: str="", desc: str="") -> None:
"""Create method."""
self.code = code
self.id_ = id_
self.title = title
self.desc = desc

def generate(self, status_type: str = "Status") -> Dict[str, Any]:
def generate(self, status_type: str="Status") -> Dict[str, Any]:
"""Get as Python dict."""
status = {
"@context": "http://www.w3.org/ns/hydra/context.jsonld",
Expand All @@ -622,7 +657,7 @@ def generate(self, status_type: str = "Status") -> Dict[str, Any]:
class HydraError(HydraStatus):
"""Class for Hydra Error to represent error details."""

def __init__(self, code: int, id_: str = None, title: str = "", desc: str = "") -> None:
def __init__(self, code: int, id_: str=None, title: str="", desc: str="") -> None:
"""Create method"""
super().__init__(code, id_, title, desc)

Expand All @@ -636,8 +671,8 @@ class HydraLink():
"""Template for a link property."""

def __init__(
self, id_: str, title: str = "",
desc: str = "", domain: str = "", range_: str = "") -> None:
self, id_: str, title: str="",
desc: str = "", domain: str="", range_: str="") -> None:
"""Initialize the Hydra_Link."""
self.id_ = id_ if "http" in id_ else "vocab:{}".format(id_)
self.range = range_
Expand Down Expand Up @@ -677,33 +712,25 @@ class Context():

def __init__(self,
address: str,
adders: Dict = {},
class_: Optional[HydraClass] = None,
collection: Optional[HydraCollection] = None,
entrypoint: Optional[HydraEntryPoint] = None,
class_: Optional[HydraClass]=None,
collection: Optional[HydraCollection]=None,
entrypoint: Optional[HydraEntryPoint]=None,
) -> None:
"""Initialize context."""
# NOTE: adders is a dictionary containing additional
# context elements to the base Hydra context
if class_ is not None:
self.context = {
"vocab": "{}/vocab#".format(address),
"hydra": "http://www.w3.org/ns/hydra/core#",
"members": "http://www.w3.org/ns/hydra/core#member",
"object": "http://schema.org/object",
} # type: Dict[str, Any]
self.context[class_.title] = class_.id_
self.context = {"vocab": "{}/vocab#".format(address), "hydra": "http://www.w3.org/ns/hydra/core#",
"members": "http://www.w3.org/ns/hydra/core#member", "object": "http://schema.org/object",
class_.title: class_.id_} # type: Dict[str, Any]
for prop in class_.supportedProperty:
self.context[prop.title] = prop.prop

elif collection is not None:
self.context = {
"vocab": "{}/vocab#".format(address),
"hydra": "http://www.w3.org/ns/hydra/core#",
"members": "http://www.w3.org/ns/hydra/core#member",
}
self.context[collection.name] = "vocab:{}".format(collection.name)
self.context[collection.class_.title] = collection.class_.id_
self.context = {"vocab": "{}/vocab#".format(address), "hydra": "http://www.w3.org/ns/hydra/core#",
"members": "http://www.w3.org/ns/hydra/core#member",
collection.name: "vocab:{}".format(collection.name),
collection.class_.title: collection.class_.id_}

elif entrypoint is not None:
self.context = {
Expand All @@ -724,7 +751,6 @@ def __init__(self,
"label": "rdfs:label",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"vocab": "{}/vocab#".format(address),
# "vocab": "localhost/api/vocab#",
"domain": {
"@type": "@id",
"@id": "rdfs:domain"
Expand Down
22 changes: 18 additions & 4 deletions samples/doc_writer_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# Creating the HydraDoc object, this is the primary class for the Doc
API_NAME = "api" # Name of the API, will serve as EntryPoint
BASE_URL = "https://hydrus.com/" # The base url at which the API is hosted
BASE_URL = "https://hydrus.com" # The base url at which the API is hosted
# NOTE: The API will be accessible at BASE_URL + ENTRY_POINT
# (http://hydrus.com/api/)

Expand Down Expand Up @@ -138,9 +138,23 @@
class_2.add_supported_op(class_2_op4)
class_1.add_supported_op(class_1_op1)

# add explicit statements about members of the collection
# Following manages block means every member of this collection is of type class_
collection_1_managed_by = {
"property": "rdf:type",
"object": 'vocab:' + class_uri,
}
# Following manages block means every member of this collection is of type class_3
collection_3_managed_by = {
"property": "rdf:type",
"object": 'vocab:' + class_3_uri,
}
# Add the classes to the HydraDoc
api_doc.add_supported_class(class_, collection=True, collection_path="DcTest")
api_doc.add_supported_class(class_3, collection=True, collection_path="EcTest")

api_doc.add_supported_class(class_, collection=True, collection_path="DcTest",
collection_manages=collection_1_managed_by)
api_doc.add_supported_class(class_3, collection=True, collection_path="EcTest",
collection_manages=collection_3_managed_by)
api_doc.add_supported_class(class_2, collection=False)
api_doc.add_supported_class(class_1, collection=False)
# NOTE: Using collection=True creates a HydraCollection for the class.
Expand Down Expand Up @@ -172,5 +186,5 @@
doc = doc.replace('true', '"true"')
doc = doc.replace('false', '"false"')
doc = doc.replace('null', '"null"')
with open("doc_writer_sample_output.py", "w") as f:
with open("samples/doc_writer_sample_output.py", "w") as f:
f.write(doc)
Loading