Skip to content

Commit

Permalink
Client python dev 3 (#2957)
Browse files Browse the repository at this point in the history
* Fix date attribute type interaction

* Update python readme

* add port note

* Final readme change, update setup.py to 1.3.2
  • Loading branch information
flyingsilverfin authored and Marco Scoppetta committed Aug 15, 2018
1 parent 659f09e commit 409e187
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 30 deletions.
128 changes: 114 additions & 14 deletions client-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ In the interpreter or in your source, import `grakn`:
import grakn
```

You can then instantiate a client, open a session, and create transactions:
You can then instantiate a client, open a session, and create transactions.
_NOTE_: Grakn's default gRPC port is 48555 for versions >=1.3. Port 4567 (the old default REST endpoint) is deprecated for clients.

```
client = grakn.Grakn(uri="localhost:48555")
session = client.session(keyspace="mykeyspace")
Expand All @@ -46,19 +48,60 @@ with client.session(keyspace="mykeyspace") as session:
```
to automatically close sessions and transactions.

Credentials can be passed into the initial constructor as a dictionary:
Credentials can be passed into the initial constructor as a dictionary, if you are a KGMS user:
```
client = grakn.Grakn(uri='localhost:48555', credentials={'username': 'xxxx', 'password': 'yyyy'})
```

You can execute Graql queries and iterate through the answers as follows:
```
answer_iterator = tx.query("match $x isa person; limit 10; get;")
an_answer = next(answer)
person = an_answer.get('x')
# Perform a query that returns an iterator of ConceptMap answers
answer_iterator = tx.query("match $x isa person; limit 10; get;")
# Request first response
a_concept_map_answer = next(answer_iterator)
# Get the dictionary of variables : concepts, retrieve variable 'x'
person = a_concept_map_answer.map()['x']
# we can also iterate using a `for` loop
some_people = []
for concept_map in answer_iterator:
# Get 'x' again, without going through .map()
some_people.append(concept_map.get('x'))
break
# skip the iteration and .get('x') and extract all the concepts in one go
remaining_people = answer_iterator.collect_concepts()
# explicit close if not using `with` statements
tx.close()
```

_NOTE_: queries will return almost immediately -- this is because Grakn lazily evaluates the request on the server when
the local iterator is consumed, not when the request is created. Each time `next(iter)` is called, the client executes a fast RPC request
to the server to obtain the next concrete result.

You might also want to make some insertions using `.query()`
```
# Perform insert query that returns an iterator of ConceptMap of inserted concepts
insert_iterator = tx.query("insert $x isa person, has birth-date 2018-08-06;")
concepts = insert_iterator.collect_concepts()
print("Inserted a person with ID: {0}".format(concepts[0].id))
# Don't forget to commit() to persist changes
tx.commit()
```

Or you can use the methods available on Concept objects
```
person_type = tx.get_schema_concept("person") # retrieve the "person" schema type
person = person_type.create() # instantiate a person
birth_date_type = tx.get_schema_concept("birth-date") " retrieve the "birth-date" schema type
date = datetime.datetime(year=2018, month=8, day=6) # requires `import datetime`
birth_date = birth_date_type.create(date) # instantiate a date with a python datetime object
person.has(birth_date) # attach the birth_date concept to the person concept
tx.commit() # write changes to Grakn
```


# API reference


Expand All @@ -76,8 +119,8 @@ on the Grakn object the following methods are available:
| Method | Return type | Description |
| ---------------------------------------- | ----------------- | ---------------------------------------------------- |
| `session(String keyspace)` | *Session* | Return a new Session bound to the specified keyspace |
| `keyspaces.delete(String keyspace)` | *None* | Deletes the specified keyspace |
| `keyspaces.retrieve()` | List of *String* | Retrieves all available keyspaces |
| `keyspaces().delete(String keyspace)` | *None* | Deletes the specified keyspace |
| `keyspaces().retrieve()` | List of *String* | Retrieves all available keyspaces |



Expand Down Expand Up @@ -115,17 +158,74 @@ Some of the following Concept methods return a python iterator, which can be con

| Method | Return type | Description |
| ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `collect_concepts()` | List of *Concept* | Consumes the iterator and return list of Concepts. **This helper is useful on Iterator returned by transaction.query() method**. It is useful when one wants to work directly on Concepts without the need to traverse the result map or access the explanation. |
| `collect_concepts()` | List of *Concept* | Consumes the iterator and return list of Concepts. **This helper is useful on Iterator that return ConceptMap answer types**. It is useful when one wants to work directly on Concepts without the need to traverse the result map or access the explanation. |

_NOTE_: these iterators represent a lazy evaluation of a query or method on the Grakn server, and will be created very quickly. The actual work
is performed when the iterator is consumed, creating an RPC to the server to obtain the next concrete `Answer` or `Concept`.

**Answer**

This object represents a query answer and it is contained in the Iterator returned by `transaction.query()` method, the following methods are available:
**Answer**

| Method | Return type | Description |
| --------------- | --------------------------------------| ----------------------------------------------------------------------------------------------- |
| `get(var=None)` | Dict[str, *Concept*] or *Concept* | Returns result dictionary mapping variables (type `str`) to a *Concept*, or directly return a *Concept* if `var` is in the dict.|
| `explanation()` | *Explanation* or *None* | Returns an Explanation object if the current Answer contains inferred Concepts, None otherwise. |
This object represents a query answer and it is contained in the Iterator returned by `transaction.query()` method.
There are **different types of Answer**, based on the type of query executed a different type of Answer will be returned:

| Query Type | Answer Type |
|--------------------------------------|-------------------|
| `define` | ConceptMap |
| `undefine` | ConceptMap |
| `get` | ConceptMap |
| `insert` | ConceptMap |
| `delete` | ConceptMap |
| `aggregate count/min/max/sum/mean/std` | Value |
| `aggregate group` | AnswerGroup |
| `compute count/min/max/sum/mean/std` | Value |
| `compute path` | ConceptList |
| `compute cluster` | ConceptSet |
| `compute centrality` | ConceptSetMeasure |

**ConceptMap**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `map()` | Dict of *str* to *Concept* | Returns result dictionary in which every variable name (key) is linked to a Concept. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**Value**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `number()` | int or float | Returns numeric value of the Answer. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**ConceptList**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `list()` | Array of *String* | Returns list of Concept IDs. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**ConceptSet**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `set()` | Set of *String* | Returns a set containing Concept IDs. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**ConceptSetMeasure**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `measurement()` | int or float | Returns numeric value that is associated to the set of Concepts contained in the current Answer.|
| `set()` | Set of *String* | Returns a set containing Concept IDs. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**AnswerGroup**

| Method | Return type | Description |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
| `owner()` | *Concept* | Returns the Concepts which is the group owner. |
| `answers()` | List of *Answer* | Returns list of Answers that belongs to this group. |
| `explanation()` | *Explanation* or *null* | Returns an Explanation object if the current Answer contains inferred Concepts, null otherwise. |

**Explanation**

Expand Down
5 changes: 4 additions & 1 deletion client-python/grakn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ class Grakn(object):

def __init__(self, uri, credentials=None):
self.uri = uri
self.keyspaces = KeyspaceService(self.uri, credentials)
self._keyspace_service = KeyspaceService(self.uri, credentials)
self.credentials = credentials

def session(self, keyspace: str):
""" Open a session for a specific keyspace. Can be used as `with Grakn('localhost:48555').session(keyspace='test') as session: ... ` or as normal assignment"""
return Session(self.uri, keyspace, self.credentials)

def keyspaces(self):
return self._keyspace_service


class Session(object):
""" A session for a Grakn instance and a specific keyspace """
Expand Down
5 changes: 4 additions & 1 deletion client-python/grakn/service/Session/util/RequestBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@ def as_value_object(data, datatype: enums.DataType):
elif datatype == enums.DataType.DOUBLE:
msg.double = data
elif datatype == enums.DataType.DATE:
msg.date = data
# convert local datetime into long
epoch_seconds_utc = data.timestamp()
epoch_ms_long_utc = int(epoch_seconds_utc*1000)
msg.date = epoch_ms_long_utc
else:
# TODO specialize exception
raise Exception("Unknown attribute datatype: {}".format(datatype))
Expand Down
5 changes: 4 additions & 1 deletion client-python/grakn/service/Session/util/ResponseReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#

import abc
import datetime
from grakn.service.Session.util import enums
from grakn.service.Session.Concept import ConceptFactory
from grakn.exception.GraknError import GraknError
Expand Down Expand Up @@ -102,7 +103,9 @@ def from_grpc_value_object(grpc_value_object):
elif whichone == 'double':
return grpc_value_object.double
elif whichone == 'date':
return grpc_value_object.date
epoch_ms_utc = grpc_value_object.date
local_datetime_utc = datetime.datetime.fromtimestamp(float(epoch_ms_utc)/1000.)
return local_datetime_utc
else:
raise GraknError("Unknown datatype in enum but not handled in from_grpc_value_object")

Expand Down
2 changes: 1 addition & 1 deletion client-python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
setup(
name='grakn',
packages=pep420_package_finder.find('.', include=['grakn*']),
version='1.3.0',
version='1.3.2',
license='Apache-2.0',
description='A Python client for Grakn',
long_description=open('README.md').read(),
Expand Down
32 changes: 28 additions & 4 deletions client-python/tests/integration/test_concept.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import unittest
import grakn
import datetime
from grakn.exception.GraknError import GraknError


Expand Down Expand Up @@ -562,10 +563,33 @@ def test_value(self):
double = double_attr_type.create(43.1)
self.assertEqual(double.value(), 43.1)

def test_date_value(self):
# TODO
print(" ------ TODO ------ ")
pass
def test_get_date_value(self):
date_type = self.tx.put_attribute_type("birthdate", grakn.DataType.DATE)
person_type = self.tx.get_schema_concept("person")
person_type.has(date_type)
concepts = self.tx.query("insert $x isa person, has birthdate 2018-08-06;").collect_concepts()
person = concepts[0]
attrs_iter = person.attributes()
for attr_concept in attrs_iter:
# pick out the birthdate
if attr_concept.type().label() == "birthdate":
date = attr_concept.value()
self.assertIsInstance(date, datetime.datetime)
self.assertEqual(date.year, 2018)
self.assertEqual(date.month, 8)
self.assertEqual(date.day, 6)
return


def test_set_date_value(self):
date_type = self.tx.put_attribute_type("birthdate", grakn.DataType.DATE)
test_date = datetime.datetime(year=2018, month=6, day=6)
date_attr_inst = date_type.create(test_date)
value = date_attr_inst.value() # retrieve from server
self.assertIsInstance(value, datetime.datetime)
self.assertEqual(value.timestamp(), test_date.timestamp())



def test_owners(self):
""" Test retrieving entities that have an attribute """
Expand Down
8 changes: 4 additions & 4 deletions client-python/tests/integration/test_grakn.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def test_shortest_path_answer_ConceptList(self):

tx.close()
local_session.close()
inst.keyspaces.delete("shortestpath")
inst.keyspaces().delete("shortestpath")

def test_cluster_anwer_ConceptSet(self):
""" Test clustering with connected components response as ConceptSet """
Expand All @@ -229,7 +229,7 @@ def test_cluster_anwer_ConceptSet(self):
self.assertTrue(parentship_map['parentship'] in concept_set_answer.set())
tx.close()
local_session.close()
inst.keyspaces.delete("clusterkeyspace")
inst.keyspaces().delete("clusterkeyspace")


def test_compute_centrality_answer_ConceptSetMeasure(self):
Expand All @@ -246,7 +246,7 @@ def test_compute_centrality_answer_ConceptSetMeasure(self):
self.assertTrue(parentship_map['child'] in concept_set_measure_answer.set())
tx.close()
local_session.close()
inst.keyspaces.delete("centralitykeyspace")
inst.keyspaces().delete("centralitykeyspace")


def test_compute_aggregate_group_answer_AnswerGroup(self):
Expand All @@ -263,7 +263,7 @@ def test_compute_aggregate_group_answer_AnswerGroup(self):
self.assertEqual(answer_group.answers()[0].map()['y'].id, parentship_map['child'])
tx.close()
local_session.close()
inst.keyspaces.delete("aggregategroup")
inst.keyspaces().delete("aggregategroup")



Expand Down
8 changes: 4 additions & 4 deletions client-python/tests/integration/test_keyspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def test_retrieve_delete(self):
tx = session.transaction(grakn.TxType.WRITE)
tx.close()

keyspaces = client.keyspaces.retrieve()
keyspaces = client.keyspaces().retrieve()
self.assertGreater(len(keyspaces), 0)
self.assertTrue('keyspacetest' in keyspaces)

client.keyspaces.delete('keyspacetest')
post_delete_keyspaces = client.keyspaces.retrieve()
client.keyspaces().delete('keyspacetest')
post_delete_keyspaces = client.keyspaces().retrieve()
self.assertFalse('keyspacetest' in post_delete_keyspaces)

session.close()
#client.keyspaces.delete("keyspacetest")
#client.keyspaces().delete("keyspacetest")

0 comments on commit 409e187

Please sign in to comment.