Skip to content

Commit

Permalink
Merge pull request #40 from priyanshunayan/add_collection_and_manages…
Browse files Browse the repository at this point in the history
…_block

Add collection list and manages block
  • Loading branch information
chrizandr authored Jul 20, 2020
2 parents 5ff1cb8 + dd6b739 commit 8b7f1b6
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 66 deletions.
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

0 comments on commit 8b7f1b6

Please sign in to comment.