summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rwxr-xr-xfreebase/api/__init__.py19
-rw-r--r--freebase/api/session.py104
-rw-r--r--freebase/schema.py571
-rw-r--r--schema-manipulation/get_type.py332
-rw-r--r--schema-manipulation/test_hardcore_schema_manipulation.py113
-rw-r--r--schema-manipulation/type_creation.py595
-rw-r--r--setup.py24
-rw-r--r--test/__init__.py0
-rw-r--r--test/runtests.py67
-rwxr-xr-xtest/test_freebase.py (renamed from test/test_all.py)41
-rw-r--r--test/test_schema_manipulation.py150
12 files changed, 1710 insertions, 307 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index e706d44..45f4a6e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,5 @@
recursive-include freebase *.py *.txt
recursive-include examples *.py *.txt
recursive-include test *.py *.txt
-include freebase-README
exclude metaweb.py
diff --git a/freebase/api/__init__.py b/freebase/api/__init__.py
index 9e7be3e..c48009d 100755
--- a/freebase/api/__init__.py
+++ b/freebase/api/__init__.py
@@ -1,19 +1,4 @@
-from session import HTTPMetawebSession, MetawebError, attrdict
+from session import HTTPMetawebSession, MetawebError, attrdict, LITERAL_TYPE_IDS
-from mqlkey import quotekey, unquotekey
-
-LITERAL_TYPE_IDS = set([
- "/type/int",
- "/type/float",
- "/type/boolean",
- "/type/rawstring",
- "/type/uri",
- "/type/text",
- "/type/datetime",
- "/type/bytestring",
- "/type/id",
- "/type/key",
- "/type/value",
- "/type/enumeration"
-])
+from mqlkey import quotekey, unquotekey \ No newline at end of file
diff --git a/freebase/api/session.py b/freebase/api/session.py
index b373209..d717d53 100644
--- a/freebase/api/session.py
+++ b/freebase/api/session.py
@@ -74,6 +74,22 @@ import pprint
import socket
import logging
+LITERAL_TYPE_IDS = set([
+ "/type/int",
+ "/type/float",
+ "/type/boolean",
+ "/type/rawstring",
+ "/type/uri",
+ "/type/text",
+ "/type/datetime",
+ "/type/bytestring",
+ "/type/id",
+ "/type/key",
+ "/type/value",
+ "/type/enumeration"
+])
+
+
class Delayed(object):
"""
Wrapper for callables in log statements. Avoids actually making
@@ -149,6 +165,7 @@ class attrdict(dict):
TypeError: 'int' object is not callable
"""
def __init__(self, *args, **kwargs):
+ # adds the *args and **kwargs to self (which is a dict)
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
@@ -342,7 +359,7 @@ class HTTPMetawebSession(MetawebSession):
msg = r.messages[0]
raise MetawebError(u'%s %s %r' % (msg.get('code',''), msg.message, msg.info))
- raise MetawebError, 'request failed: %s: %r %r' % (url, status, body)
+ raise MetawebError, 'request failed: %s: %s\n%s' % (url, status, body)
def _httpreq_json(self, *args, **kws):
resp, body = self._httpreq(*args, **kws)
@@ -843,13 +860,92 @@ class HTTPMetawebSession(MetawebSession):
#self._mqlresult(r)
return r
+ ### SCHEMA MANIPULATION ###
+ # Object helpers
+ def create_object(self, name="", path=None, key=None, namespace=None, included_types=None, create="unless_exists", extra=None):
+ if type(included_types) is str:
+ included_types = [included_types]
+
+ if path and (key or namespace):
+ raise Exception("You can't specify both the path and a key and namespace.")
+
+ if path:
+ key, namespace = get_key_namespace(path)
+
+
+ if included_types:
+ its = set(included_types)
+ q = [{
+ "id|=" : included_types,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }]
+ for res in self.mqlread(q):
+ its.update(map(lambda x: x["id"], res["/freebase/type_hints/included_types"]))
+
+ wq = {
+ "id" : None,
+ "name" : name,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ },
+ "create" : create
+ }
+
+ if included_types:
+ wq.update(type = [{ "id" : it, "connect" : "insert" } for it in its])
+
+ if extra:
+ wq.update(extra)
+
+ return self.mqlwrite(wq)
+
+ def connect_object(self, id, newpath, extra=None):
+
+ key, namespace = get_key_namespace(newpath)
+
+ wq = {
+ "id" : id,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ "connect" : "insert"
+ }
+ }
+
+ if extra: wq.update(extra)
+
+ return self.mqlwrite(wq)
+
+
+ def disconnect_object(self, id, extra=None):
+
+ key, namespace = get_key_namespace(id)
+
+ wq = {
+ "id" : id,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ "connect" : "delete"
+ }
+ }
+ if extra: wq.update(extra)
+ return self.mqlwrite(wq)
+
+ def move_object(self, oldpath, newpath):
+ a = self.connect_object(oldpath, newpath)
+ b = self.disconnect_object(oldpath)
+ return a, b
- # Special things in API
- def create_object(self, id):
- pass
+def get_key_namespace(path):
+ # be careful with /common
+ namespace, key = path.rsplit("/", 1)
+ return (key, namespace or "/")
+
if __name__ == '__main__':
console = logging.StreamHandler()
diff --git a/freebase/schema.py b/freebase/schema.py
new file mode 100644
index 0000000..5be9b4a
--- /dev/null
+++ b/freebase/schema.py
@@ -0,0 +1,571 @@
+
+from freebase.api.session import HTTPMetawebSession
+from freebase.api.session import get_key_namespace, LITERAL_TYPE_IDS
+
+def key_exists(s, k):
+ q = {
+ "id" : k,
+ "guid" : None
+ }
+ return not None == s.mqlread(q)
+
+
+def type_object(s, id, type_id):
+ q = {
+ "id" : type_id,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }
+ included_types = map(lambda x: x["id"], s.mqlread(q)["/freebase/type_hints/included_types"])
+
+ wq = {
+ "id" : id,
+ "type" : [{
+ "id" : it,
+ "connect" : "insert"
+ } for it in included_types + [type_id]]
+ }
+ return s.mqlwrite(wq)
+
+
+def copy_property(s, id, newid, **extra):
+ newname, newschema = get_key_namespace(newid)
+
+ info = get_property_info(s, id)
+ info["__raw"].update(extra)
+ create_property(s, info["name"], newname, newschema, info["expected_type"], info["unique"], info["/freebase/property_hints/disambiguator"],
+ info["/freebase/documented_object/tip"], info["__raw"])
+
+def move_property(s, id, newid, **extra):
+ copy_property(s, id, newid, **extra)
+ disconnect_schema = {"type" : "/type/property", "schema" : {"connect" : "delete", "id" : "/".join(id.split("/")[:-1]) }}
+ s.disconnect_object(id, extra = disconnect_schema)
+
+def get_property_info(s, prop_id):
+ q = PROPERTY_QUERY
+ q.update(id=prop_id)
+ res = s.mqlread(q)
+ info = {}
+
+ info["name"] = res["name"]["value"]
+ if res["schema"]:
+ info["schema"] = res["schema"]["id"]
+ else: info["schema"] = None
+
+ if res["key"]:
+ info["keys"] = map(lambda x: (x["value"], x["namespace"]), res["key"])
+ else: info["key"] = None
+
+ if res["/freebase/documented_object/tip"]:
+ info["/freebase/documented_object/tip"] = res["/freebase/documented_object/tip"]["value"]
+ else: info["/freebase/documented_object/tip"] = None
+
+ for i in ["delegated", "enumeration", "expected_type", "id", "master_property", "unique", "unit",
+ "/freebase/property_hints/disambiguator", "/freebase/property_hints/display_none",
+ "/freebase/property_hints/display_orientation","/freebase/property_hints/enumeration",
+ "/freebase/property_hints/dont_display_in_weblinks", "/freebase/property_hints/inverse_description"]:
+
+ if res[i]:
+ if isinstance(res[i], basestring):
+ info[i] = res[i]
+ elif isinstance(res[i], bool):
+ info[i] = res[i]
+ elif res[i].has_key("id"):
+ info[i] = res[i]["id"]
+ elif res[i].has_key("value"):
+ info[i] = res[i]["value"]
+ else:
+ raise Exception("There is a problem with getting the property value.")
+ else: info[i] = None
+
+ # delete the properties that are going to be asked for in create_property
+ del res["name"]
+ del res["schema"]
+ del res["key"]
+ del res["expected_type"]
+ del res["unique"]
+ del res["/freebase/property_hints/disambiguator"]
+ del res["/freebase/documented_object/tip"]
+
+ # delete other useless things...
+ del res["id"]
+
+ for i in [k for k, v in res.items() if v is None]:
+ del res[i]
+
+ info["__raw"] = res
+ return info
+
+
+# Create Type
+def create_type(s, name, key, ns, cvt=None, tip=None, included=None, extra=None):
+ if key_exists(s, ns + "/" + key ):
+ return
+
+ q = {
+ "create" : "unconditional",
+ "type" : "/type/type",
+ "/type/type/domain" : { "connect" : "insert", "id" : ns },
+ "name" : {"connect" : "insert", "value" : name, "lang" : "/lang/en" },
+ "key" : {
+ "connect" : "insert",
+ "value" : key,
+ "namespace" : ns
+ }
+ }
+
+ if included:
+ if isinstance(included, basestring):
+ included = [included]
+ itsq = [{
+ "id|=" : included,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }]
+ r = s.mqlread(itsq)
+ included_types = set(included)
+ if r:
+ for i in r:
+ included_types.update(map(lambda x: x["id"], i["/freebase/type_hints/included_types"]))
+
+ its = [{"connect" : "insert", "id" : t} for t in included_types]
+ q['/freebase/type_hints/included_types'] = its
+
+ # TODO: enum
+
+ if cvt:
+ q['/freebase/type_hints/mediator'] = { "connect" : "update", "value" : True }
+ if tip:
+ q['/freebase/documented_object/tip'] = { "connect" : "update", "value" : tip, "lang" : "/lang/en" }
+
+ if extra: q.update(extra)
+ return s.mqlwrite(q, use_permission_of=ns)
+
+
+# Create Property
+def create_property(s, name, key, schema, expected, unique=False, disambig=False, tip=None, extra=None):
+ if key_exists(s, schema + "/" + key):
+ raise Exception("The key \"%s\" already exists!" % (schema + "/" + key))
+ return
+
+ # validate parameters str
+
+ q = {
+ "create" : "unconditional",
+ "type" : "/type/property",
+ "name" : name,
+ "key" : {
+ "connect" : "insert",
+ "value" : key,
+ "namespace" : { "id" : schema },
+ },
+ "schema" : { "connect" : "insert", "id" : schema },
+ "expected_type" : { "connect" : "insert", "id" : expected }
+ }
+ if unique:
+ q['unique'] = { "connect" : "update", "value" : unique }
+ if tip:
+ q['/freebase/documented_object/tip'] = { "connect" : "update", "value" : tip, "lang" : "/lang/en" }
+ if disambig:
+ q['/freebase/property_hints/disambiguator'] = { "connect" : "update", "value" : True }
+ if extra:
+ q.update(extra)
+ #print json.dumps(q, indent=2)
+ return s.mqlwrite(q, use_permission_of=schema)
+
+def delegate_property(s, p, schema, name=None, key=None, expected=None, tip=None, extra=None):
+ q = {
+ "id" : p,
+ "type" : "/type/property",
+ "name" : None,
+ "unique" : None,
+ "expected_type" : {"id" : None},
+ "key" : None,
+ "/freebase/documented_object/tip" : None,
+ "/freebase/property_hints/disambiguator" : None
+ }
+ r = s.mqlread(q)
+
+ # If the expected_type of the delegator(master) is a primitive, the delegated's
+ # expected_type must be the same
+ if r["expected_type"]["id"] in LITERAL_TYPE_IDS:
+ if expected:
+ if expected != r["expected_type"]["id"]:
+ raise Exception("You can't set the expected_type if the expected_type of the delegated (master) is a primitive")
+ expected = r["expected_type"]["id"]
+ # If the expected_type of the delegator(master) is not a primitive, the delegated's
+ # expected_type can be different
+ elif expected is None:
+ expected = r["expected_type"]["id"]
+
+
+ if not tip and r["/freebase/documented_object/tip"]:
+ tip = r["/freebase/documented_object/tip"]
+
+ if name is None:
+ name = r["name"]
+ if key is None:
+ key = r["key"]
+
+ delegate = { "/type/property/delegated" : p}
+ if extra: delegate.update(extra)
+
+ return create_property(s, name, key, schema, expected, r['unique'],
+ r["/freebase/property_hints/disambiguator"],
+ tip,
+ delegate)
+
+def reciprocate_property(s, name, key, master, unique=False, disambig=False, tip=None, extra=None):
+ """ We're creating a reciprocate property of the master property. Let's illustrate
+ the idea behind the function with an example.
+
+ Say we examine the /visual_art/art_period_movement/associated_artworks property.
+ An example of an art_period_movement is the Renaissance, and once associated_artworks
+ could be the /en/mona_lisa. In this example, /visual_art/art_period_movement/associated_artworks
+ will be the master property, and /visual_art/artwork/period_or_movement will be the reciprocal.
+
+ In order to determine the characterists of the reciprocal property, we must examine the master.
+ associated_artworks property's schema is /visual_art/art_period_movement and its expected
+ type is /visual_art/artwork. Notice the similarity to /visual_art/artwork/period_or_movement.
+ period_or_movement's schema is /visual_art/artwork -- art_period_movement's expected type.
+ period_or_movement's expected type is /visual_art/art_period_movement -- art_period_movement's
+ schema!
+
+ So, given a master, the reciprocal's schema is the master's expected type and the reciprocal's
+ expected type is the master's schema. """
+
+
+ q = {
+ "id" : master,
+ "/type/property/expected_type" : None,
+ "/type/property/schema" : None }
+ r = s.mqlread(q)
+ ect = r["/type/property/expected_type"]
+ schema = r["/type/property/schema"]
+
+ master = {"master_property" : master}
+ if extra: master.update(extra)
+
+ # NOTE: swapping ect and schema; see comment above
+ return create_property(s, name, key, ect, schema, unique, disambig, tip,
+ extra = master)
+
+# upload / restore types
+def dump_base(s, base_id):
+ types = map(lambda x: x["id"], s.mqlread({"id" : base_id, "/type/domain/types":[{"id" : None}]})["/type/domain/types"])
+ graph = _get_graph(types)
+ graph["__follow_types"] = True
+
+ return graph
+
+def dump_type(s, type_id, follow_types=True):
+ types = [type_id]
+ graph = _get_graph(types, follow_types)
+ graph["__follow_types"] = follow_types
+ result = json.dumps(graph, indent=2)
+
+ fh = open("junk.json", "w")
+ fh.write(result)
+ fh.close()
+
+ return graph
+
+
+def upload_type(s, graph, new_location, ignore_types=None, debug=False):
+ follow_types = graph.get("__follow_types", True)
+ if debug: print "Following types:", follow_types
+
+ # create type dependencies
+ typegraph = {}
+ for tid, idres in graph.items():
+ if not tid.startswith("__"):
+ typegraph[tid] = idres["__requires"]
+
+ type_deps = map(lambda (name, x): (len(x), name), typegraph.items())
+ type_deps.sort()
+ if follow_types:
+ types_to_create = create_what(type_deps, typegraph)
+ else:
+ types_to_create = typegraph.keys()
+
+ # create property dependencies
+ propgraph = {}
+ proptotype = {}
+ for tid, idres in graph.items():
+ if not tid.startswith("__"):
+ for prop in idres["properties"]:
+ propgraph[prop["id"]] = prop["__requires"]
+ proptotype[prop["id"]] = tid
+ prop_deps = map(lambda (name, x): (len(x), name), propgraph.items())
+ prop_deps.sort()
+ if follow_types:
+ props_to_create = create_what(prop_deps, propgraph)
+ else:
+ props_to_create = propgraph.keys()
+
+ if debug: print "types", types_to_create
+ if debug: print "-----------------------"
+ if debug: print "props", props_to_create
+
+ base_id, domain_id = s.mqlreadmulti([{"id" : types_to_create[0], "type" : "/type/type", "domain" : {"id" : None}},
+ {"id" : new_location, "a:id" : None}])
+ base_id = base_id["domain"]["id"]
+ domain_id = domain_id["a:id"]
+
+ only_include = types_to_create + props_to_create
+
+ for type in types_to_create:
+ if debug: print type
+ key = ""
+ if len(graph[type]["key"]) == 1:
+ key = graph[type]["key"][0]["value"]
+ else:
+ expectedname = graph[type]["id"].split("/")[-1]
+ if base_id:
+ for group in graph[type]["key"]:
+ if group["namespace"] == base_id:
+ key = group["value"]
+ continue
+ if key is None:
+ key = expectedname
+ tip = None
+ if graph[type]["/freebase/documented_object/tip"]:
+ tip = graph[type]["/freebase/documented_object/tip"]["value"]
+
+ ignore = ("name", "domain", "key", "type", "id", "properties", "/freebase/type_hints/enumeration",
+ "/freebase/type_hints/included_types", "/freebase/type_hints/mediator", "/freebase/documented_object/tip")
+ extra = {}
+ for k, v in graph[type].items():
+ if k not in ignore and not k.startswith("__"):
+ if v:
+ if isinstance(v, basestring):
+ extra.update({k:v})
+ elif isinstance(v, bool):
+ extra.update({k:v})
+ elif v.has_key("id"):
+ extra.update({k:v["id"]})
+ elif v.has_key("value"):
+ extra.update({k:v["value"]})
+ else:
+ raise Exception("There is a problem with getting the property value.")
+
+ create_type(s, graph[type]["name"]["value"], key, domain_id,
+ included=map(lambda x: convert_name(x["id"], base_id, domain_id, only_include), graph[type]["/freebase/type_hints/included_types"]),
+ cvt=graph[type]["/freebase/type_hints/mediator"],
+ tip=tip, extra=extra)
+
+
+ if debug: print "--------------------------"
+
+ for prop in props_to_create:
+ info = graph[proptotype[prop]]["properties"]
+ for i in info:
+ if i["id"] == prop:
+
+ schema = convert_name(proptotype[prop], base_id, domain_id, only_include)
+ if debug: print prop
+ expected = None
+
+ if i["expected_type"]:
+ expected = convert_name(i["expected_type"], base_id, domain_id, only_include)
+ for k in i["key"]:
+ if k.namespace == proptotype[prop]:
+ key = k.value
+ if i["/freebase/documented_object/tip"]:
+ tip = graph[type]["/freebase/documented_object/tip"]
+
+ disambig = i["/freebase/property_hints/disambiguator"]
+
+ ignore = ("name", "expected_type", "key", "id", "master_property", "delegated", "unique", "type", "schema",
+ "/freebase/property_hints/disambiguator", "enumeration", "/freebase/property_hints/enumeration",
+ "/freebase/documented_object/tip")
+ extra = {}
+ for k, v in i.items():
+ if k not in ignore and not k.startswith("__"):
+ if v:
+ if isinstance(v, basestring):
+ extra.update({k:v})
+ elif isinstance(v, bool):
+ extra.update({k:v})
+ elif v.has_key("id"):
+ extra.update({k:v["id"]})
+ elif v.has_key("value"):
+ extra.update({k:v["value"]})
+ else:
+ raise Exception("There is a problem with getting the property value.")
+
+ # since we are creating a property, all these connect insert delicacies are unneccesary
+ """if isinstance(v, basestring) and v.startswith("/"): # an id
+ extra.update({k : {"connect" : "insert", "id" : v}})
+ elif isinstance(v, bool): # a bool value
+ extra.update({k : {"connect" : "insert", "value" : v}})
+ else: # an english value
+ extra.update({k : {"connect" : "insert", "value" : v, "lang" : "/lang/en"}})"""
+
+
+ if i['master_property']:
+ converted_master_property = convert_name(i["master_property"], base_id, domain_id, only_include)
+ if converted_master_property == i["master_property"]:
+ raise Exception("You can't set follow_types to False if there's a cvt. A cvt requires you get all the relevant types. Set follow_types to true.\n" + \
+ "The offending property was %s, whose master was %s." % (prop["id"], prop["master_property"]))
+ reciprocate_property(s, i["name"], key, converted_master_property,
+ i["unique"], disambig=disambig, tip=tip, extra=extra)
+
+ elif i['delegated']:
+ delegate_property(s, convert_name(i['delegated'], base_id, domain_id, only_include), schema,
+ expected=expected, tip=tip, extra=extra)
+
+ else:
+ create_property(s, i["name"], key, schema, expected, i["unique"],
+ disambig=disambig, tip=tip, extra=extra)
+
+
+
+
+
+
+def _get_graph(initial_types, follow_types):
+ """ get the graph of dependencies of all the types involved, starting with a list supplied """
+
+ assert isinstance(initial_types, (list, tuple))
+
+ graph = {}
+ to_update = set(initial_types)
+ done = set()
+ while len(to_update) > 0:
+ new = to_update.pop()
+ graph[new] = _get_needed(s, new)
+ if follow_types:
+ [to_update.add(b) for b in graph[new]["__related"] if b not in done]
+ done.update(graph[new]["__related"])
+ if not follow_types:
+ # we have to check that there are no cvts attached to us, or else
+ # ugly things happen (we can't include the cvt)
+ for prop in graph[new]["properties"]:
+ if prop["master_property"]:
+ raise Exception("You can't set follow_types to False if there's a cvt. A cvt requires you get all the relevant types. Set follow_types to true.\n" + \
+ "The offending property was %s, whose master was %s." % (prop["id"], prop["master_property"]))
+ return graph
+
+
+def convert_name(old_name, operating_base, new_base, only_include=None):
+ if old_name in only_include and old_name.startswith(operating_base):
+ return new_base + old_name.replace(operating_base, "", 1)
+ else:
+ return old_name
+
+def create_what(deps, graph):
+ create_list = []
+ while len(deps) > 0:
+ neediness, id = deps.pop(0)
+ if neediness == 0:
+ create_list.append(id)
+ continue
+ else:
+ work = True
+ for req in graph[id]:
+ if req not in create_list:
+ work = False
+ continue
+ if work:
+ create_list.append(id)
+ else:
+ deps.append((neediness, id))
+ return create_list
+
+
+
+def _get_needed(s, type_id):
+ q = TYPE_QUERY
+ q.update(id=type_id)
+
+ r = s.mqlread(q)
+ properties = r.properties
+
+ # let's identify who the parent is in order to only include
+ # other types in that domain. We don't want to go around including
+ # all of commons because someone's a /people/person
+ parents = [r["domain"]["id"]]
+
+ included_types = map(lambda x: x["id"], r["/freebase/type_hints/included_types"])
+ related_types = set(included_types)
+ for prop in properties:
+ if prop["expected_type"]:
+ related_types.add(prop["expected_type"])
+
+ # we have two different types of relationships: required and related.
+ # related can be used to generate subgraphs of types
+ # required is used to generate the dependency graph of types
+
+ related = return_relevant(related_types, parents)
+ requires = return_relevant(included_types, parents)
+
+ # get property information
+ properties = r["properties"]
+ for prop in properties:
+ dependent_on = set()
+ if prop["master_property"]:
+ dependent_on.add(prop["master_property"])
+ if prop["delegated"]:
+ dependent_on.add(prop["delegated"])
+
+ prop["__requires"] = return_relevant(dependent_on, parents)
+
+ # return all the information along with our special __* properties
+ info = r
+ info.update(__related=related, __requires=requires, __properties=properties)
+
+ return info
+
+
+def return_relevant(start_list, parents):
+ final = []
+ for item in start_list:
+ indomain = False
+ for parent in parents:
+ if item.startswith(parent):
+ indomain = True
+ continue
+ if indomain:
+ final.append(item)
+ return final
+
+
+
+PROPERTY_QUERY = {
+ "optional" : True,
+ "type" : "/type/property",
+ "delegated" : None,
+ "enumeration" : None,
+ "expected_type" : None,
+ "id" : None,
+ "key" : [{
+ "namespace" : None,
+ "value" : None
+ }],
+ #"link" : [{}],
+ "master_property" : None,
+ "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
+ "schema" : {"id" : None, "name" : None},
+ "unique" : None,
+ "unit" : None,
+ "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional" : True},
+ "/freebase/property_hints/disambiguator" : None,
+ "/freebase/property_hints/display_none" : None,
+ "/freebase/property_hints/display_orientation" : None,
+ "/freebase/property_hints/enumeration" : None,
+ "/freebase/property_hints/dont_display_in_weblinks" : None,
+ "/freebase/property_hints/inverse_description" : None,
+ }
+
+TYPE_QUERY = {
+ "type" : "/type/type",
+ "domain" : {},
+ "key" : [{"namespace" : None, "value" : None}],
+ "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
+ "/freebase/type_hints/included_types" : [{"id" : None, "optional" : True}],
+ "/freebase/type_hints/mediator" : None,
+ "/freebase/type_hints/enumeration" : None,
+ "/freebase/type_hints/minor" : None,
+ "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional":True},
+ }
+TYPE_QUERY.update(properties=[PROPERTY_QUERY])
+
diff --git a/schema-manipulation/get_type.py b/schema-manipulation/get_type.py
index af4554e..80def68 100644
--- a/schema-manipulation/get_type.py
+++ b/schema-manipulation/get_type.py
@@ -1,18 +1,217 @@
# TODO: copy /freebase/type_hints junk
+# TODO: preserve property order.
from pprint import pprint
import random
+import json
-from type_creation import create_type, create_property, delegate_property, reciprocate_property
-
+from freebase.schema import TYPE_QUERY, PROPERTY_QUERY
from freebase.api import HTTPMetawebSession, MetawebError
+from freebase.schema import create_type, create_property, reciprocate_property, delegate_property
+
s = HTTPMetawebSession("http://sandbox-freebase.com")
-s.login("username", "password")
+s.login("nitromaster101@gmail.com", "something")
import time
-def create_type_dependencies(s, base_id=None, type_id=None):
+def dump_base(s, base_id):
+ types = map(lambda x: x["id"], s.mqlread({"id" : base_id, "/type/domain/types":[{"id" : None}]})["/type/domain/types"])
+ graph = _get_graph(types)
+ graph["__follow_types"] = True
+
+ return graph
+
+def dump_type(s, type_id, follow_types=True):
+ types = [type_id]
+ graph = _get_graph(types, follow_types)
+ graph["__follow_types"] = follow_types
+ result = json.dumps(graph, indent=2)
+
+ fh = open("junk.json", "w")
+ fh.write(result)
+ fh.close()
+
+ return graph
+
+
+def upload_type(s, graph, new_location, ignore_types=None, debug=False):
+ follow_types = graph.get("__follow_types", True)
+ if debug: print "Following types:", follow_types
+
+ # create type dependencies
+ typegraph = {}
+ for tid, idres in graph.items():
+ if not tid.startswith("__"):
+ typegraph[tid] = idres["__requires"]
+
+ type_deps = map(lambda (name, x): (len(x), name), typegraph.items())
+ type_deps.sort()
+ if follow_types:
+ types_to_create = create_what(type_deps, typegraph)
+ else:
+ types_to_create = typegraph.keys()
+
+ # create property dependencies
+ propgraph = {}
+ proptotype = {}
+ for tid, idres in graph.items():
+ if not tid.startswith("__"):
+ for prop in idres["properties"]:
+ propgraph[prop["id"]] = prop["__requires"]
+ proptotype[prop["id"]] = tid
+ prop_deps = map(lambda (name, x): (len(x), name), propgraph.items())
+ prop_deps.sort()
+ if follow_types:
+ props_to_create = create_what(prop_deps, propgraph)
+ else:
+ props_to_create = propgraph.keys()
+
+ if debug: print "types", types_to_create
+ if debug: print "-----------------------"
+ if debug: print "props", props_to_create
+
+ base_id, domain_id = s.mqlreadmulti([{"id" : types_to_create[0], "type" : "/type/type", "domain" : {"id" : None}},
+ {"id" : new_location, "a:id" : None}])
+ base_id = base_id["domain"]["id"]
+ domain_id = domain_id["a:id"]
+
+ only_include = types_to_create + props_to_create
+
+ for type in types_to_create:
+ if debug: print type
+ key = ""
+ if len(graph[type]["key"]) == 1:
+ key = graph[type]["key"][0]["value"]
+ else:
+ expectedname = graph[type]["id"].split("/")[-1]
+ if base_id:
+ for group in graph[type]["key"]:
+ if group["namespace"] == base_id:
+ key = group["value"]
+ continue
+ if key is None:
+ key = expectedname
+ tip = None
+ if graph[type]["/freebase/documented_object/tip"]:
+ tip = graph[type]["/freebase/documented_object/tip"]["value"]
+
+ ignore = ("name", "domain", "key", "type", "id", "properties", "/freebase/type_hints/enumeration",
+ "/freebase/type_hints/included_types", "/freebase/type_hints/mediator", "/freebase/documented_object/tip")
+ extra = {}
+ for k, v in graph[type].items():
+ if k not in ignore and not k.startswith("__"):
+ if v:
+ if isinstance(v, basestring):
+ extra.update({k:v})
+ elif isinstance(v, bool):
+ extra.update({k:v})
+ elif v.has_key("id"):
+ extra.update({k:v["id"]})
+ elif v.has_key("value"):
+ extra.update({k:v["value"]})
+ else:
+ raise Exception("There is a problem with getting the property value.")
+
+ create_type(s, graph[type]["name"]["value"], key, domain_id,
+ included=map(lambda x: convert_name(x["id"], base_id, domain_id, only_include), graph[type]["/freebase/type_hints/included_types"]),
+ cvt=graph[type]["/freebase/type_hints/mediator"],
+ tip=tip, extra=extra)
+
+
+ if debug: print "--------------------------"
+
+ for prop in props_to_create:
+ info = graph[proptotype[prop]]["properties"]
+ for i in info:
+ if i["id"] == prop:
+
+ schema = convert_name(proptotype[prop], base_id, domain_id, only_include)
+ if debug: print prop
+ expected = None
+
+ if i["expected_type"]:
+ expected = convert_name(i["expected_type"], base_id, domain_id, only_include)
+ for k in i["key"]:
+ if k.namespace == proptotype[prop]:
+ key = k.value
+ if i["/freebase/documented_object/tip"]:
+ tip = graph[type]["/freebase/documented_object/tip"]
+
+ disambig = i["/freebase/property_hints/disambiguator"]
+
+ ignore = ("name", "expected_type", "key", "id", "master_property", "delegated", "unique", "type", "schema",
+ "/freebase/property_hints/disambiguator", "enumeration", "/freebase/property_hints/enumeration",
+ "/freebase/documented_object/tip")
+ extra = {}
+ for k, v in i.items():
+ if k not in ignore and not k.startswith("__"):
+ if v:
+ if isinstance(v, basestring):
+ extra.update({k:v})
+ elif isinstance(v, bool):
+ extra.update({k:v})
+ elif v.has_key("id"):
+ extra.update({k:v["id"]})
+ elif v.has_key("value"):
+ extra.update({k:v["value"]})
+ else:
+ raise Exception("There is a problem with getting the property value.")
+
+ # since we are creating a property, all these connect insert delicacies are unneccesary
+ """if isinstance(v, basestring) and v.startswith("/"): # an id
+ extra.update({k : {"connect" : "insert", "id" : v}})
+ elif isinstance(v, bool): # a bool value
+ extra.update({k : {"connect" : "insert", "value" : v}})
+ else: # an english value
+ extra.update({k : {"connect" : "insert", "value" : v, "lang" : "/lang/en"}})"""
+
+
+ if i['master_property']:
+ converted_master_property = convert_name(i["master_property"], base_id, domain_id, only_include)
+ if converted_master_property == i["master_property"]:
+ raise Exception("You can't set follow_types to False if there's a cvt. A cvt requires you get all the relevant types. Set follow_types to true.\n" + \
+ "The offending property was %s, whose master was %s." % (prop["id"], prop["master_property"]))
+ reciprocate_property(s, i["name"], key, converted_master_property,
+ i["unique"], disambig=disambig, tip=tip, extra=extra)
+
+ elif i['delegated']:
+ delegate_property(s, convert_name(i['delegated'], base_id, domain_id, only_include), schema,
+ expected=expected, tip=tip, extra=extra)
+
+ else:
+ create_property(s, i["name"], key, schema, expected, i["unique"],
+ disambig=disambig, tip=tip, extra=extra)
+
+
+
+
+
+
+def _get_graph(initial_types, follow_types):
+ """ get the graph of dependencies of all the types involved, starting with a list supplied """
+
+ assert isinstance(initial_types, (list, tuple))
+
+ graph = {}
+ to_update = set(initial_types)
+ done = set()
+ while len(to_update) > 0:
+ new = to_update.pop()
+ graph[new] = _get_needed(s, new)
+ if follow_types:
+ [to_update.add(b) for b in graph[new]["__related"] if b not in done]
+ done.update(graph[new]["__related"])
+ if not follow_types:
+ # we have to check that there are no cvts attached to us, or else
+ # ugly things happen (we can't include the cvt)
+ for prop in graph[new]["properties"]:
+ if prop["master_property"]:
+ raise Exception("You can't set follow_types to False if there's a cvt. A cvt requires you get all the relevant types. Set follow_types to true.\n" + \
+ "The offending property was %s, whose master was %s." % (prop["id"], prop["master_property"]))
+ return graph
+
+"""def create_type_dependencies(s, base_id=None, type_id=None, follow_types=True):
if base_id:
q = {"id" : base_id, "/type/domain/types" : [{"id" : None}] }
@@ -28,18 +227,13 @@ def create_type_dependencies(s, base_id=None, type_id=None):
if not base_id and not type_id:
raise Exception("You need to supply either a base_id or a type_id")
- graph = {}
- to_update = set(types)
- done = set()
- while len(to_update) > 0:
- new = to_update.pop()
- graph[new] = get_needed(s, new)
- [to_update.add(b) for b in graph[new]["related"] if b not in done]
- done.update(graph[new]["related"])
-
+ graph = get_graph(types, follow_types)
+
# find distinct subgraphs (looking at needs)
subgraphs = []
unknown = set(graph.keys())
+ if focus:
+ unknown = set([focus])
while len(unknown) > 0:
visited = set()
to_visit = set([list(unknown)[0]])
@@ -52,8 +246,11 @@ def create_type_dependencies(s, base_id=None, type_id=None):
pass
[to_visit.add(b) for b in graph[new]["related"] if b not in visited]
subgraphs.append(list(visited))
- #pprint(subgraphs)
+
+ print "SUBGRPAHS"
+ pprint(subgraphs)
+ print "GRAPH"
pprint(graph)
return
@@ -79,6 +276,8 @@ def create_type_dependencies(s, base_id=None, type_id=None):
props_to_create = create_what(least_needy, propgraph)
+ # CREATING STUFF
+
domainname = "awesome" + str(int(random.random() * 1e10))
print "\ndomainid", domainname
print "--------------------------"
@@ -142,10 +341,11 @@ def create_type_dependencies(s, base_id=None, type_id=None):
print "\ndomain id was", domainname
-
+"""
+
-def convert_name(old_name, operating_base, new_base):
- if old_name.startswith(operating_base):
+def convert_name(old_name, operating_base, new_base, only_include=None):
+ if old_name in only_include and old_name.startswith(operating_base):
return new_base + old_name.replace(operating_base, "", 1)
else:
return old_name
@@ -171,84 +371,55 @@ def create_what(deps, graph):
-def get_needed(s, type_id):
- #print type_id
- q = {
- "type" : "/type/type",
- "id" : type_id,
- "domain" : [{}],
- "expected_by" : [{}],
- "key" : [{"namespace" : None, "value" : None}],
- "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
- "/freebase/type_hints/included_types" : [{}],
- "/freebase/type_hints/mediator" : None,
- "/freebase/type_hints/enumeration" : None,
- "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional":True},
- "properties" : [{
- "optional" : True,
- "delegated" : {},
- "enumeration" : None,
- "expected_type" : {},
- "id" : None,
- "key" : [{
- "namespace" : None,
- "value" : None
- }],
- #"link" : [{}],
- "master_property" : {},
- "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
- "reverse_property" : {},
- "schema" : {"id" : None, "name" : None},
- "unique" : None,
- "unit" : None,
- "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional" : True},
- "/freebase/property_hints/disambiguator" : None
- }]
- }
+def _get_needed(s, type_id):
+ q = TYPE_QUERY
+ q.update(id=type_id)
r = s.mqlread(q)
properties = r.properties
- #print "************************************************" + type_id
- #pprint(r)
- #print "************************************************" + type_id
- # hopefully there's only one domain
- fathers = map(lambda x: x["id"], r["domain"])
+ # let's identify who the parent is in order to only include
+ # other types in that domain. We don't want to go around including
+ # all of commons because someone's a /people/person
+ parents = [r["domain"]["id"]]
- brothers = set()
included_types = map(lambda x: x["id"], r["/freebase/type_hints/included_types"])
- brothers.update(included_types)
+ related_types = set(included_types)
for prop in properties:
if prop["expected_type"]:
- brothers.add(prop["expected_type"]["id"])
-
+ related_types.add(prop["expected_type"])
- family = return_relevant(brothers, fathers)
- needed = return_relevant(included_types, fathers)
+ # we have two different types of relationships: required and related.
+ # related can be used to generate subgraphs of types
+ # required is used to generate the dependency graph of types
+
+ related = return_relevant(related_types, parents)
+ requires = return_relevant(included_types, parents)
# get property information
properties = r["properties"]
for prop in properties:
- needs = set()
+ dependent_on = set()
if prop["master_property"]:
- needs.add(prop["master_property"]["id"])
+ dependent_on.add(prop["master_property"])
if prop["delegated"]:
- needs.add(prop["delegated"]["id"])
+ dependent_on.add(prop["delegated"])
- prop["needs"] = return_relevant(needs, fathers)
+ prop["__requires"] = return_relevant(dependent_on, parents)
- answer = r
- answer.update(related=family, needs=needed, properties=properties)
+ # return all the information along with our special __* properties
+ info = r
+ info.update(__related=related, __requires=requires, __properties=properties)
- return answer
+ return info
-def return_relevant(start_list, fathers):
+def return_relevant(start_list, parents):
final = []
for item in start_list:
indomain = False
- for father in fathers:
- if item.startswith(father):
+ for parent in parents:
+ if item.startswith(parent):
indomain = True
continue
if indomain:
@@ -257,6 +428,21 @@ def return_relevant(start_list, fathers):
if __name__ == '__main__':
- create_type_dependencies(s, base_id="/people")
+ # create domain
+ name = str(int(random.random()*1e10))
+ domain_id = s.create_private_domain("coolcat" + name, "Coolcat" + name)["domain_id"]
+ domain_id = s.mqlread({"id" : domain_id, "a:id" : None})["a:id"]
+ print "domain name", domain_id
+
+ # dump information
+ #graph = dump_type(s, "/user/nitromaster101/coolcat3194242218/bridge_player", follow_types=True)
+ graph = dump_type(s, "/people/person", True)
+
+ # upload it somewhere else
+ upload_type(s, graph, domain_id, debug=True)
+
+
+ print "domain name", domain_id
+ #create_type_dependencies(s, type_id="/base/contractbridge/bridge_player")
diff --git a/schema-manipulation/test_hardcore_schema_manipulation.py b/schema-manipulation/test_hardcore_schema_manipulation.py
new file mode 100644
index 0000000..3ef04b1
--- /dev/null
+++ b/schema-manipulation/test_hardcore_schema_manipulation.py
@@ -0,0 +1,113 @@
+import unittest
+import sys, logging
+import freebase
+import random
+import time
+
+from freebase.api import HTTPMetawebSession, MetawebError
+
+from get_type import dump_type, dump_base, upload_type
+
+USERNAME = 'username'
+PASSWORD = 'password'
+API_HOST = 'sandbox.freebase.com'
+
+s = freebase.api.HTTPMetawebSession(API_HOST)
+
+# Sorry, this is just so annoying to type.
+f = lambda x: x["id"]
+
+class TestHardcoreSchemaManipulation(unittest.TestCase):
+
+ def test_copy_an_entire_domain(self):
+ domain_id = _create_domain()
+ ex_domain_id = "/film" # example domain id
+ ex_domain_type = "actor"
+ ex_domain_type_id = ex_domain_id + "/" + ex_domain_type
+
+ graph = dump_base(s, ex_domain_id)
+ upload_type(s, graph, domain_id)
+
+ newperson, realperson = s.mqlreadmulti([{"id" : domain_id + "/" + ex_domain_type, "/type/type/properties" : {"return" : "count" }},
+ {"id" : ex_domain_type_id, "/type/type/properties" : {"return" : "count" }}])
+ self.assertEqual(newperson["/type/type/properties"], realperson["/type/type/properties"])
+
+ # let's try and check everything.
+ # - check all the types are there
+ realtypes = s.mqlread([{"id" : None, "type" : "/type/type", "domain" : ex_domain_id}])
+ newtypes = s.mqlread([{"id" : None, "type" : "/type/type", "domain" : domain_id}])
+
+ l = lambda q: sorted(map(f, q))
+
+ realtypes = l(realtypes)
+ newtypes = l(newtypes)
+ print realtypes
+ print newtypes
+ self.assertEqual(len(realtypes), len(newtypes))
+ for i in range(len(realtypes)):
+ self.assertEqual(realtypes[i].lsplit("/", 1), newtypes[i].lsplit("/", 1))
+
+ # - check the properties are the same
+
+ def get_properties(types):
+ properties = set()
+ for i in s.mqlreadmulti([[{"id" : id, "/type/type/properties" : {"id" : None}}] for id in types]):
+ properties.update(map(f, i["/type/type/properties"]))
+ return properties
+
+ realproperties = sorted(list(get_properties(realtypes)))
+ newproperties = sorted(list(get_properties(newtypes)))
+
+ self.assertEqual(realproperties, newproperties)
+ for i in range(len(realproperties)):
+ self.assertEqual(realtypes[i].lsplit("/", 1), newtypes[i].lsplit("/", 1))
+
+ # - check the properties and type's attributes are the same
+
+
+
+ def test_try_copying_a_cvt(self):
+
+ # if follow_types is True, everything is kosher.
+ domain_id = _create_domain()
+ graph = dump_type(s, "/film/actor", follow_types=True)
+ upload_type(s, graph, domain_id)
+
+ newactor, realactor = s.mqlreadmulti([{"id" : domain_id + "/actor", "/type/type/properties" : {"return" : "count" }},
+ {"id" : "/film/actor", "/type/type/properties" : {"return" : "count" }}])
+ self.assertEqual(newactor["/type/type/properties"], realactor["/type/type/properties"])
+
+ # if follow_types is False, if we try to upload a cvt, it should whine
+ self.assertRaises(Exception, lambda: dump_type(s, "/film/actor", follow_types=False))
+
+
+
+def _create_domain():
+ domain_id = s.create_private_domain("test" + str(int(random.random() * 1e10)), "test")["domain_id"]
+ domain_id = s.mqlread({"id" : domain_id, "a:id" : None})["a:id"]
+ return domain_id
+
+if __name__ == '__main__':
+ if USERNAME == "username" and PASSWORD == "password":
+ try:
+ passwordfile = open(".password.txt", "r")
+ fh = passwordfile.read().split("\n")
+ USERNAME = fh[0]
+ PASSWORD = fh[1]
+ passwordfile.close()
+ s.login(USERNAME, PASSWORD)
+
+ except Exception, e:
+ print "In order to run the tests, we need to use a valid freebase username and password"
+ USERNAME = raw_input("Please enter your username: ")
+ PASSWORD = raw_input("Please enter your password (it'll appear in cleartext): ")
+ s.login(USERNAME, PASSWORD)
+ print "Thanks!"
+
+ else:
+ s.login(USERNAME, PASSWORD)
+
+ unittest.main()
+
+
+
diff --git a/schema-manipulation/type_creation.py b/schema-manipulation/type_creation.py
index c29eb5d..b940076 100644
--- a/schema-manipulation/type_creation.py
+++ b/schema-manipulation/type_creation.py
@@ -7,47 +7,278 @@ import urllib
from freebase.api import HTTPMetawebSession, MetawebError
from freebase.api import LITERAL_TYPE_IDS
+s = HTTPMetawebSession('sandbox.freebase.com', username, password)
+s.login()
-mss = HTTPMetawebSession('sandbox.freebase.com', USERNAME, PASSWORD)
-mss.login()
+def key_exists(s, k):
+ q = {
+ "id" : k,
+ "guid" : None
+ }
+ return not None == s.mqlread(q)
+
+
+# Object helpers
+def create_object(s, name="", path=None, key=None, namespace=None, included_types=[], create="unless_exists", extra=None):
+ if type(included_types) is str:
+ included_types = [included_types]
+
+ if path and (key or namespace):
+ raise Exception("You can't specify both the path and a key and namespace.")
+
+ if path:
+ key, namespace = get_key_namespace(path)
+
+ its = set(included_types)
+ if included_types:
+ q = [{
+ "id|=" : included_types,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }]
+ for res in s.mqlread(q):
+ its.update(map(lambda x: x["id"], res["/freebase/type_hints/included_types"]))
+
+ wq = {
+ "id" : None,
+ "name" : name,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ },
+ "create" : create
+ }
+
+ if included_types:
+ wq.update(type = [{ "id" : it, "connect" : "insert" } for it in its])
+
+ if extra:
+ wq.update(extra)
+
+ return s.mqlwrite(wq)
+
+
+def connect_object(s, id, newpath, extra=None):
+
+ key, namespace = get_key_namespace(newpath)
+
+ wq = {
+ "id" : id,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ "connect" : "insert"
+ }
+ }
+
+ if extra: wq.update(extra)
+
+ return s.mqlwrite(wq)
+
+
+def unconnect_object(s, id, extra=None):
+
+ key, namespace = get_key_namespace(id)
+
+ wq = {
+ "id" : id,
+ "key" : {
+ "namespace" : namespace,
+ "value" : key,
+ "connect" : "delete"
+ }
+ }
+ if extra: wq.update(extra)
+ return s.mqlwrite(wq)
+
+def move_object(s, oldpath, newpath):
+ a = connect_object(s, oldpath, newpath)
+ b = unconnect_object(s, oldpath)
+ return a, b
+
+def type_object(s, id, type_id):
+ q = {
+ "id" : type_id,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }
+ included_types = map(lambda x: x["id"], s.mqlread(q)["/freebase/type_hints/included_types"])
+
+ wq = {
+ "id" : id,
+ "type" : [{
+ "id" : it,
+ "connect" : "insert"
+ } for it in included_types + [type_id]]
+ }
+ return s.mqlwrite(wq)
+
+def get_key_namespace(path):
+ split = path.split("/")
+ return split[-1], "/".join(split[:-1]) or "/"
+
+
+# type moving
+
+
+# property moving
+
+def connect_property(s, id, newid, **extra):
+ split = newid.split("/")
+ newschema = "/".join(split[:-1])
+ newname = split[-1]
+
+ info = get_property_info(s, id)
+ info["__raw"].update(extra)
+ create_property(s, info["name"], newname, newschema, info["expected_type"], info["unique"], info["/freebase/property_hints/disambiguator"],
+ info["/freebase/documented_object/tip"], info["__raw"])
+
+def move_property(s, id, newid, **extra):
+ connect_property(s, id, newid, **extra)
+ disconnect_schema = {"type" : "/type/property", "schema" : {"connect" : "delete", "id" : "/".join(id.split("/")[:-1]) }}
+ unconnect_object(s, id, extra = disconnect_schema)
+
+PROPERTY_QUERY = {
+ "optional" : True,
+ "type" : "/type/property",
+ "delegated" : {},
+ "enumeration" : {},
+ "expected_type" : {},
+ "id" : {},
+ "key" : [{
+ "namespace" : None,
+ "value" : None
+ }],
+ #"link" : [{}],
+ "master_property" : {},
+ "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
+ "schema" : {"id" : None, "name" : None},
+ "unique" : {},
+ "unit" : {},
+ "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional" : True},
+ "/freebase/property_hints/disambiguator" : {},
+ "/freebase/property_hints/display_none" : {},
+ "/freebase/property_hints/display_orientation" : {},
+ "/freebase/property_hints/enumeration" : {},
+ "/freebase/property_hints/dont_display_in_weblinks" : {},
+ "/freebase/property_hints/inverse_description" : {},
+ }
+
+TYPE_QUERY = {
+ "type" : "/type/type",
+ "domain" : {},
+ "key" : [{"namespace" : None, "value" : None}],
+ "name" : {"value" : None, "lang" : "/lang/en", "optional":True},
+ "/freebase/type_hints/included_types" : [{}],
+ "/freebase/type_hints/mediator" : None,
+ "/freebase/type_hints/enumeration" : None,
+ "/freebase/type_hints/minor" : None,
+ "/freebase/documented_object/tip" : {"value" : None, "limit":1, "optional":True},
+ "properties" : {"optional" : True}
+ }
+TYPE_QUERY.update(properties=[PROPERTY_QUERY])
+
+
+def get_property_info(s, prop_id):
+ q = PROPERTY_QUERY
+ q.update(id=prop_id)
+ res = s.mqlread(q)
+ info = {}
-def key_exists( s, k ):
- q = {
- "id" : k,
- "guid" : None }
- return not None == s.mqlread( q )
+ info["name"] = res["name"]["value"]
+ if res["schema"]:
+ info["schema"] = res["schema"]["id"]
+ else: info["schema"] = None
+
+ if res["key"]:
+ info["keys"] = map(lambda x: (x["value"], x["namespace"]), res["key"])
+ else: info["key"] = None
+
+ if res["/freebase/documented_object/tip"]:
+ info["/freebase/documented_object/tip"] = res["/freebase/documented_object/tip"]["value"]
+ else: info["/freebase/documented_object/tip"] = None
-def create_type( s, name, key, ns, cvt=None, tip=None, enum=None, included=[]):
- if key_exists( s, ns + "/" + key ):
- return
- q = {
- "create" : "unconditional",
- "type" : "/type/type",
- "/type/type/domain" : { "connect" : "insert", "id" : ns },
- "name" : {"connect" : "insert", "value" : name, "lang" : "/lang/en" },
- "key" : {
- "connect" : "insert",
- "value" : key,
- "namespace" : ns }
- }
+ for i in ["delegated", "enumeration", "expected_type", "id", "master_property", "unique", "unit",
+ "/freebase/property_hints/disambiguator", "/freebase/property_hints/display_none",
+ "/freebase/property_hints/display_orientation","/freebase/property_hints/enumeration",
+ "/freebase/property_hints/dont_display_in_weblinks", "/freebase/property_hints/inverse_description"]:
+
+ if res[i]:
+ if isinstance(res[i], basestring):
+ info[i] = res[i]
+ elif res[i].has_key("id"):
+ info[i] = res[i]["id"]
+ elif res[i].has_key("value"):
+ info[i] = res[i]["value"]
+ else:
+ raise Exception("There is a problem with getting the property value.")
+ else: info[i] = None
+
+ # delete the properties that are going to be asked for in create_property
+ del res["name"]
+ del res["schema"]
+ del res["key"]
+ del res["expected_type"]
+ del res["unique"]
+ del res["/freebase/property_hints/disambiguator"]
+ del res["/freebase/documented_object/tip"]
+
+ # delete other useless things...
+ del res["id"]
+
+ for i in [k for k, v in res.items() if v is None]:
+ del res[i]
+
+ info["__raw"] = res
+ return info
+
+
+# Create Type
+def create_type(s, name, key, ns, cvt=None, tip=None, enum=None, included=[]):
+ if key_exists( s, ns + "/" + key ):
+ return
+
+ q = {
+ "create" : "unconditional",
+ "type" : "/type/type",
+ "/type/type/domain" : { "connect" : "insert", "id" : ns },
+ "name" : {"connect" : "insert", "value" : name, "lang" : "/lang/en" },
+ "key" : {
+ "connect" : "insert",
+ "value" : key,
+ "namespace" : ns
+ }
+ }
- if included:
- q['/freebase/type_hints/included_types'] = \
- [ {"connect":"insert",
- "id":included_id}
- for included_id in included ]
+ if included:
+ if isinstance(included, basestring):
+ included = [included]
+ q = {
+ "id|=" : included,
+ "/freebase/type_hints/included_types" : [{"id" : None}]
+ }
+ r = s.mqlread(q)
+ included_types = set()
+ for i in r:
+ included_types.append(map(lambda x: x["id"], i["/freebase/type_hints/included_types"]))
+
+ q['/freebase/type_hints/included_types'] = \
+ [ {"connect":"insert",
+ "id":included_id}
+ for included_id in included_types]
- if enum:
- pass # TODO. What is enumerator?
+ if enum:
+ pass # TODO. What is enumerator?
- if cvt:
- q['/freebase/type_hints/mediator'] = { "connect" : "update", "value" : True }
- if tip:
- q['/freebase/documented_object/tip'] = { "connect" : "update", "value" : tip, "lang" : "/lang/en" }
- s.mqlwrite( q )
+ if cvt:
+ q['/freebase/type_hints/mediator'] = { "connect" : "update", "value" : True }
+ if tip:
+ q['/freebase/documented_object/tip'] = { "connect" : "update", "value" : tip, "lang" : "/lang/en" }
+ return s.mqlwrite(q, use_permission_of=ns)
+
-def create_property( s, name, key, schema, expected, unique, disambig=False, tip=None, extra=None ):
- if key_exists( s, schema + "/" + key ):
+# Create Property
+def create_property(s, name, key, schema, expected, unique, disambig=False, tip=None, extra=None):
+ if key_exists(s, schema + "/" + key):
+ raise Exception("The key \"%s\" already exists!", schema + "/" + key)
return
q = {
"create" : "unconditional",
@@ -57,7 +288,7 @@ def create_property( s, name, key, schema, expected, unique, disambig=False, tip
"connect" : "insert",
"value" : key,
"namespace" : { "id" : schema },
- },
+ },
"schema" : { "connect" : "insert", "id" : schema },
"expected_type" : { "connect" : "insert", "id" : expected }
}
@@ -70,178 +301,170 @@ def create_property( s, name, key, schema, expected, unique, disambig=False, tip
if extra:
q.update(extra)
#print json.dumps(q, indent=2)
- s.mqlwrite(q)
+ return s.mqlwrite(q, use_permission_of=schema)
-def delegate_property( s, p, schema, name=None, key=None, expected=None, tip=None):
- q = {
- "id" : p,
- "type" : "/type/property",
- "name" : None,
- "unique" : None,
- "expected_type" : {"id" : None},
- "key" : None,
- "/freebase/documented_object/tip" : None,
- "/freebase/property_hints/disambiguator" : None }
- r = s.mqlread(q)
+def delegate_property(s, p, schema, name=None, key=None, expected=None, tip=None):
+ q = {
+ "id" : p,
+ "type" : "/type/property",
+ "name" : None,
+ "unique" : None,
+ "expected_type" : {"id" : None},
+ "key" : None,
+ "/freebase/documented_object/tip" : None,
+ "/freebase/property_hints/disambiguator" : None
+ }
+ r = s.mqlread(q)
- # If the expected_type of the delegator(master) is a primitive, the delegated's
- # expected_type must be the same
- if r["expected_type"]["id"] in LITERAL_TYPE_IDS:
- if expected:
- print "You can't set the expected_type if the expected_type of the delegated(master) is a primitive"
- expected = r["expected_type"]["id"]
- # If the expected_type of the delegator(master) is not a primitive, the delegated's
- # expected_type can be different
- elif expected is None:
- expected = r["expected_type"]["id"]
-
+ # If the expected_type of the delegator(master) is a primitive, the delegated's
+ # expected_type must be the same
+ if r["expected_type"]["id"] in LITERAL_TYPE_IDS:
+ if expected:
+ print "You can't set the expected_type if the expected_type of the delegated(master) is a primitive"
+ expected = r["expected_type"]["id"]
+ # If the expected_type of the delegator(master) is not a primitive, the delegated's
+ # expected_type can be different
+ elif expected is None:
+ expected = r["expected_type"]["id"]
- if not tip and r["/freebase/documented_object/tip"]:
- tip = r["/freebase/documented_object/tip"]
-
- if name is None:
- name = r["name"]
- if key is None:
- key = r["key"]
-
- create_property(s, name, key, schema, expected, r['unique'],
- r["/freebase/property_hints/disambiguator"],
- tip,
- { "/type/property/delegated" : p } )
-
-
-def reciprocate_property( s, name, key, master, unique, disambig=False, tip=None, extra=None ):
- """ We're creating a reciprocate property of the master property. Let's illustrate
- the idea behind the function with an example.
- Say we examine the /visual_art/art_period_movement/associated_artworks property.
- An example of an art_period_movement is the Renaissance, and once associated_artworks
- could be the /en/mona_lisa. In this example, /visual_art/art_period_movement/associated_artworks
- will be the master property, and /visual_art/artwork/period_or_movement will be the reciprocal.
+ if not tip and r["/freebase/documented_object/tip"]:
+ tip = r["/freebase/documented_object/tip"]
- In order to determine the characterists of the reciprocal property, we must examine the master.
- associated_artworks property's schema is /visual_art/art_period_movement and its expected
- type is /visual_art/artwork. Notice the similarity to /visual_art/artwork/period_or_movement.
- period_or_movement's schema is /visual_art/artwork -- art_period_movement's expected type.
- period_or_movement's expected type is /visual_art/art_period_movement -- art_period_movement's
- schema!
-
- So, given a master, the reciprocal's schema is the master's expected type and the reciprocal's
- expected type is the master's schema. """
+ if name is None:
+ name = r["name"]
+ if key is None:
+ key = r["key"]
+ return create_property(s, name, key, schema, expected, r['unique'],
+ r["/freebase/property_hints/disambiguator"],
+ tip,
+ { "/type/property/delegated" : p})
+
+def reciprocate_property(s, name, key, master, unique, disambig=False, tip=None):
+ """ We're creating a reciprocate property of the master property. Let's illustrate
+ the idea behind the function with an example.
+
+ Say we examine the /visual_art/art_period_movement/associated_artworks property.
+ An example of an art_period_movement is the Renaissance, and once associated_artworks
+ could be the /en/mona_lisa. In this example, /visual_art/art_period_movement/associated_artworks
+ will be the master property, and /visual_art/artwork/period_or_movement will be the reciprocal.
+
+ In order to determine the characterists of the reciprocal property, we must examine the master.
+ associated_artworks property's schema is /visual_art/art_period_movement and its expected
+ type is /visual_art/artwork. Notice the similarity to /visual_art/artwork/period_or_movement.
+ period_or_movement's schema is /visual_art/artwork -- art_period_movement's expected type.
+ period_or_movement's expected type is /visual_art/art_period_movement -- art_period_movement's
+ schema!
+
+ So, given a master, the reciprocal's schema is the master's expected type and the reciprocal's
+ expected type is the master's schema. """
- q = {
- "id" : master,
- "/type/property/expected_type" : None,
- "/type/property/schema" : None }
- r = s.mqlread(q)
- ect = r["/type/property/expected_type"]
- schema = r["/type/property/schema"]
- create_property(s, name, key, ect, schema, unique, disambig, tip,
- extra = { "master_property" : master })
+
+ q = {
+ "id" : master,
+ "/type/property/expected_type" : None,
+ "/type/property/schema" : None }
+ r = s.mqlread(q)
+ ect = r["/type/property/expected_type"]
+ schema = r["/type/property/schema"]
+ return create_property(s, name, key, ect, schema, unique, disambig, tip,
+ extra = { "master_property" : master })
+
+# ?
def make_subclass(s, sub, super):
- q = {
- "id" : super,
- "type" : "/type/type",
- "properties" : [{"id" : None}] }
- r = s.mqlread( q )
- for p in map(lambda x: x['id'], r['properties'] ):
- delegate_property(s, p, sub)
- w = {
- "id" : sub,
- "/freebase/type_hints/included_types" : { "connect" : "insert", "id" : super }}
- s.mqlwrite(w)
+ q = {
+ "id" : super,
+ "type" : "/type/type",
+ "properties" : [{"id" : None}]
+ }
+ r = s.mqlread( q )
+ for p in map(lambda x: x['id'], r['properties'] ):
+ delegate_property(s, p, sub)
+ w = {
+ "id" : sub,
+ "/freebase/type_hints/included_types" : { "connect" : "insert", "id" : super }
+ }
+ s.mqlwrite(w)
-def gogo(s, base="/base/bingbase"):
+def gogo(base="/base/bingbase"):
- create_type(s, "Bridge Player", "bridge_player", base, tip="A bridge player", included=["/people/person", "/common/topic", base+"/topic"])
- create_type(s, "Bridge Tournament", "bridge_tournament", base, tip="A bridge tournament", included=["/event/event", "/common/topic", base+"/topic"])
- create_type(s, "Bridge Tournament Standings", "bridge_tournament_standings", base, cvt=True, tip="Bridge Tournamen Results")
+
- player = base + "/bridge_player"
- tourney = base + "/bridge_tournament"
- standing = base + "/bridge_tournament_standings"
+ create_type(s, "Bridge Player", "bridge_player", base, tip="A bridge player", included=["/people/person", "/common/topic", base+"/topic"])
+ create_type(s, "Bridge Tournament", "bridge_tournament", base, tip="A bridge tournament", included=["/event/event", "/common/topic", base+"/topic"])
+ create_type(s, "Bridge Tournament Standings", "bridge_tournament_standings", base, cvt=True, tip="Bridge Tournamen Results")
- # tournament standings
- # ideas: create a cvt could be a function, disambig always True, same schema, iono, whatever
- create_property(s, "Year", "year", standing, "/type/datetime", True, disambig=True, tip="Year")
- create_property(s, "First Place", "first_place", standing, player, False, disambig=True)
- create_property(s, "Second Place", "second_place", standing, player, False, disambig=True)
- create_property(s, "Tournament", "tournament", standing, tourney, True, disambig=True)
+ player = base + "/bridge_player"
+ tourney = base + "/bridge_tournament"
+ standing = base + "/bridge_tournament_standings"
+ # tournament standings
+ # ideas: create a cvt could be a function, disambig always True, same schema, iono, whatever
+ create_property(s, "Year", "year", standing, "/type/datetime", True, disambig=True, tip="Year")
+ create_property(s, "First Place", "first_place", standing, player, False, disambig=True)
+ create_property(s, "Second Place", "second_place", standing, player, False, disambig=True)
+ create_property(s, "Tournament", "tournament", standing, tourney, True, disambig=True)
- # tournament
- reciprocate_property(s, "Standing", "standing", standing + "/tournament", False, disambig=True)
- create_property(s, "Location", "location", tourney, "/location/citytown", False, disambig=True)
- # bridge player
- # standings must be reverses!
- reciprocate_property(s, "First Place Finish", "first_place_finish", standing + "/first_place", False, False)
- reciprocate_property(s, "Second Place Finish", "second_place_finish", standing + "/second_place", False, False)
+ # tournament
+ reciprocate_property(s, "Standing", "standing", standing + "/tournament", False, disambig=True)
+ create_property(s, "Location", "location", tourney, "/location/citytown", False, disambig=True)
- #delegator property test
- delegate_property(mss, "/people/person/date_of_birth", player, "Date of Birth", "db")
+ # bridge player
+ # standings must be reverses!
+ reciprocate_property(s, "First Place Finish", "first_place_finish", standing + "/first_place", False, False)
+ reciprocate_property(s, "Second Place Finish", "second_place_finish", standing + "/second_place", False, False)
- # for cvt: expected_type: cvt, reciprocated property is the property on the cvt
-
-
-
-def create_chapter( s ):
- create_type( s, "Chapter", "chapter", "/base/testy", True, "A chapter in one of the books" )
- print "created book!"
- create_property( s, "Book", "book", "/base/testy/chapter",
- "/base/testy/book", True, True, "The book of which this chapter is a part" )
- print "created property!"
- reciprocate_property( s, "/base/testy/chapter/book",
- "Chapters", "chapters", "/base/testy/book", False, False, "Chapters in this book" )
+ #delegator property test
+ delegate_property(mss, "/people/person/date_of_birth", player, "Date of Birth", "db")
+
+ # for cvt: expected_type: cvt, reciprocated property is the property on the cvt
-def create_edition( s ):
- create_type( s, "Edition", "edition", "/base/testy", True, "An edition of the Aubrey-Maturin series" )
- create_property( s, "Examples", "examples", "/base/testy/edition",
- "/book/book_edition", False, False, "Book editions in this series" )
- w = {
- "id" : "/base/testy/edition",
- "/freebase/type_hints/enumeration" : { "value" : True, "connect" : "update" }}
- s.mqlwrite( w )
- w = {
- "create" : "unless_exists",
- "type" :"/base/testy/edition",
- "name" : "W. W. Norton Paperback" }
- s.mqlwrite( w )
-def create_page_ref( s ):
- create_type( s, "Page Reference", "page_ref", "/base/testy", True,
- "A reference to a page in a particular edition" )
- t = "/base/testy/page_ref"
- create_property( s, "Page Number", "page", t, "/type/int", True, True )
- create_property( s, "Edition", "edition", t, "/base/testy/edition", True, True )
+# another test... not sure it works
+def create_chapter(s):
+ create_type( s, "Chapter", "chapter", "/base/testy", True, "A chapter in one of the books" )
+ print "created book!"
+ create_property( s, "Book", "book", "/base/testy/chapter",
+ "/base/testy/book", True, True, "The book of which this chapter is a part" )
+ print "created property!"
+ reciprocate_property( s, "/base/testy/chapter/book",
+ "Chapters", "chapters", "/base/testy/book", False, False, "Chapters in this book" )
-def create_mention_properties( s, t ):
- create_property( s, "Circumstances", "circumstances", t, "/type/text", True, True,
- "Describe the circumstances of the mention" )
- create_property( s, "Quote", "quote", t, "/type/text", True, True,
- "A short quote from the mentioned location" )
- create_property( s, "Chapter", "chapter", t, "/base/testy/chapter", True, True,
- "The chapter in which the mention takes place" )
- create_property( s, "Page", "page", t, "/base/testy/page_ref", False, True,
- "Page number and Edition" )
- create_property( s, "What", "what", t, "/base/testy/topic", True, True,
- "What is being mentioned" )
+def create_edition(s):
+ create_type( s, "Edition", "edition", "/base/testy", True, "An edition of the Aubrey-Maturin series" )
+ create_property( s, "Examples", "examples", "/base/testy/edition",
+ "/book/book_edition", False, False, "Book editions in this series" )
+ w = {
+ "id" : "/base/testy/edition",
+ "/freebase/type_hints/enumeration" : { "value" : True, "connect" : "update" }
+ }
+ s.mqlwrite( w )
+ w = {
+ "create" : "unless_exists",
+ "type" :"/base/testy/edition",
+ "name" : "W. W. Norton Paperback"
+ }
+ s.mqlwrite( w )
-#def delegate_mention_properties( s ):
-# t = '/base/testy/mention'
-# delegate_property( s, '/base/testy/character_mention/character', t )
-# delegate_property( s, '/base/testy/dish_mention/dish', t )
-# delegate_property( s, '/base/testy/historical_event_mention/event', t )
-# delegate_property( s, '/base/testy/place_mention/place', t )
-# delegate_property( s, '/base/testy/ship_mention/ship', t )
-# delegate_property( s, '/base/testy/species_mention/species', t )
-# delegate_property( s, '/base/testy/written_work_mention/written_work', t )
- # nothing for surgical instruments
+def create_page_ref(s):
+ create_type( s, "Page Reference", "page_ref", "/base/testy", True,
+ "A reference to a page in a particular edition" )
+ t = "/base/testy/page_ref"
+ create_property( s, "Page Number", "page", t, "/type/int", True, True )
+ create_property( s, "Edition", "edition", t, "/base/testy/edition", True, True )
+def create_mention_properties(s, t):
+ create_property( s, "Circumstances", "circumstances", t, "/type/text", True, True,
+ "Describe the circumstances of the mention" )
+ create_property( s, "Quote", "quote", t, "/type/text", True, True,
+ "A short quote from the mentioned location" )
+ create_property( s, "Chapter", "chapter", t, "/base/testy/chapter", True, True,
+ "The chapter in which the mention takes place" )
+ create_property( s, "Page", "page", t, "/base/testy/page_ref", False, True,
+ "Page number and Edition" )
+ create_property( s, "What", "what", t, "/base/testy/topic", True, True,
+ "What is being mentioned" )
-'''
-create_type( s, "Brew Pub", "brew_pub", "/base/brew_pubs", False, "A brew pub is a pub which brews its own beer on the premises" )
-delegate_property( s, '/food/brewery_brand_of_beer/beers_produced', '/base/brewpubs/brew_pub', None, 'Beers produced on the premises' )
-'''
diff --git a/setup.py b/setup.py
index eb1b2b7..6270dc6 100644
--- a/setup.py
+++ b/setup.py
@@ -35,14 +35,19 @@ except ImportError:
from distutils.core import setup
-# if python version < 2.6, require simplejson
-# if python version >= 2.6, it comes with json
-
json = []
-major, minor, micro, releaselevel, serial = sys.version_info
-if major <= 2 and minor < 6:
- json.append("simplejson")
+# if jsonlib2 is already installed, then we're fine
+# we don't need anything else
+try:
+ import jsonlib2
+except ImportError:
+ # if python version < 2.6, require simplejson
+ # if python version >= 2.6, it comes with json
+
+ major, minor, micro, releaselevel, serial = sys.version_info
+ if major <= 2 and minor < 6:
+ json.append("simplejson")
setup(
name='freebase',
@@ -59,11 +64,14 @@ setup(
packages=['freebase', 'freebase.api', 'freebase.fcl'],
entry_points = {
'console_scripts': [
- 'fcl = freebase.fcl.fcl:main'
+ 'fcl = freebase.fcl.fcl:main',
+ 'fb_save_base = freebase.schema.cmd:fb_save_base',
+ 'fb_save_type = freebase.schena.cmd:fb_save_type',
+ 'fb_restore = freebase.schema.cmd:fb_restore'
]
},
+ test_suite = "test.runtests.main",
install_requires=[] + json,
- #download_url='xxx', # provided by cheeseshop?
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/__init__.py
diff --git a/test/runtests.py b/test/runtests.py
new file mode 100644
index 0000000..add2380
--- /dev/null
+++ b/test/runtests.py
@@ -0,0 +1,67 @@
+import unittest
+
+import os
+import os.path
+
+import freebase
+
+def main():
+ created = False
+ passwordfile = "test/.password.txt"
+
+ # setup password stuff
+ if not os.path.isfile(passwordfile):
+ created = True
+ USERNAME, PASSWORD = "", ""
+ print "RUNTESTSIn order to run the tests, we need to use a valid freebase username and password"
+ USERNAME = raw_input("Please enter your username: ")
+ PASSWORD = raw_input("Please enter your password (it'll appear in cleartext): ")
+
+ freebase.login(USERNAME, PASSWORD)
+
+ print "Thanks!"
+ fh = open(passwordfile, "w")
+ fh.write(USERNAME + "\n" + PASSWORD)
+ fh.close()
+
+ # run tests
+ import test_freebase
+ import test_schema_manipulation
+
+ # test_freebase.TestFreebase,
+ testcases = [test_schema_manipulation.TestSchemaManipulation, test_freebase.TestFreebase]
+ testcases_dumb = [test_freebase.TestFreebase, test_schema_manipulation.TestSchemaManipulation]
+
+
+ suites = [unittest.TestLoader().loadTestsFromTestCase(x)
+ for x in testcases]
+
+
+ suites_dumb = [unittest.TestLoader().loadTestsFromTestCase(x)
+ for x in testcases_dumb]
+
+ print sorted(suites)
+ print sorted(suites_dumb)
+ print sorted(suites) == sorted(suites_dumb)
+
+ print
+
+ print "SUITES", suites
+
+
+ s1 = unittest.TestLoader().loadTestsFromTestCase(test_freebase.TestFreebase)
+ s2 = unittest.TestLoader().loadTestsFromTestCase(test_schema_manipulation.TestSchemaManipulation)
+
+ anotherrun = unittest.TestSuite([s1, s2])
+
+ #run = unittest.TestSuite(suites)
+
+ # delete password stuff
+
+ if created: os.remove(passwordfile)
+
+ return anotherrun
+
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/test/test_all.py b/test/test_freebase.py
index cfe83f8..32517fc 100755
--- a/test/test_all.py
+++ b/test/test_freebase.py
@@ -40,8 +40,28 @@ PASSWORD = 'password'
API_HOST = 'sandbox.freebase.com'
TEST_QUERY = {'id': 'null', 'name': 'Sting'}
+s = HTTPMetawebSession(API_HOST)
+
+if USERNAME == "username" and PASSWORD == "password":
+ try:
+ passwordfile = open("test/.password.txt", "r")
+ fh = passwordfile.read().split("\n")
+ USERNAME = fh[0]
+ PASSWORD = fh[1]
+ passwordfile.close()
+ s.login(USERNAME, PASSWORD)
+
+ except Exception, e:
+ print "FREEBASEIn order to run the tests, we need to use a valid freebase username and password"
+ USERNAME = raw_input("Please enter your username: ")
+ PASSWORD = raw_input("Please enter your password (it'll appear in cleartext): ")
+ s.login(USERNAME, PASSWORD)
+ print "Thanks!"
+
+else:
+ s.login(USERNAME, PASSWORD)
+
class TestFreebase(unittest.TestCase):
-
def test_freebase_dot_login_logout(self):
freebase.login(username=USERNAME, password=PASSWORD)
self.assertNotEqual(freebase.user_info(), None)
@@ -128,8 +148,7 @@ class TestFreebase(unittest.TestCase):
def test_write(self):
read_query = {'type':'/music/artist','name':'Yanni\'s Cousin Tom', 'id':{}}
- mss = HTTPMetawebSession(API_HOST, username=USERNAME,
- password=PASSWORD)
+ mss = HTTPMetawebSession(API_HOST, username=USERNAME, password=PASSWORD)
result = mss.mqlread(read_query)
self.assertEqual(None, result)
@@ -284,19 +303,5 @@ class TestFreebase(unittest.TestCase):
if __name__ == '__main__':
- if USERNAME == "username" and PASSWORD == "password":
-
- try:
- passwordfile = open("test/.password.txt", "r")
- fh = passwordfile.read().split("\n")
- USERNAME = fh[0]
- PASSWORD = fh[1]
- passwordfile.close()
-
- except Exception, e:
- print "In order to run the tests, we need to use a valid freebase username and password"
- USERNAME = raw_input("Please enter your username: ")
- PASSWORD = raw_input("Please enter your password (it'll appear in cleartext): ")
-
unittest.main()
-
+
diff --git a/test/test_schema_manipulation.py b/test/test_schema_manipulation.py
new file mode 100644
index 0000000..968e429
--- /dev/null
+++ b/test/test_schema_manipulation.py
@@ -0,0 +1,150 @@
+import unittest
+import sys, logging
+import freebase
+import random
+import time
+
+from freebase.api import HTTPMetawebSession, MetawebError
+from freebase.schema import create_type, reciprocate_property, delegate_property
+from freebase.schema import create_property, type_object, copy_property, move_property
+
+USERNAME = 'username'
+PASSWORD = 'password'
+API_HOST = 'sandbox.freebase.com'
+
+s = freebase.api.HTTPMetawebSession(API_HOST)
+domain_id = None
+
+if USERNAME == "username" and PASSWORD == "password":
+ try:
+ passwordfile = open("test/.password.txt", "r")
+ fh = passwordfile.read().split("\n")
+ USERNAME = fh[0]
+ PASSWORD = fh[1]
+ passwordfile.close()
+ s.login(USERNAME, PASSWORD)
+
+ except Exception, e:
+ print "SCHEMAIn order to run the tests, we need to use a valid freebase username and password"
+ USERNAME = raw_input("Please enter your username: ")
+ PASSWORD = raw_input("Please enter your password (it'll appear in cleartext): ")
+ s.login(USERNAME, PASSWORD)
+ print "Thanks!"
+
+else:
+ s.login(USERNAME, PASSWORD)
+
+r = s.create_private_domain("test" + str(int(random.random() * 1e10)), "test")["domain_id"]
+domain_id = s.mqlread({"id" : r, "a:id" : None})["a:id"]
+
+# Sorry, this is just so annoying to type.
+f = lambda x: x["id"]
+
+class TestSchemaManipulation(unittest.TestCase):
+
+ def test_make_and_type_object(self):
+ print domain_id
+ a = s.create_object("A", path=domain_id + "/a")
+ self.assertEqual(a.create, "created")
+
+ b = s.create_object("B", path=domain_id + "/b", included_types=["/people/person"])
+ q = { "id" : b.id, "type" : [{"id" : None}] }
+ types = map(f, s.mqlread(q)["type"])
+ self.assertEqual("/common/topic" in types, True)
+ self.assertEqual("/people/person" in types, True)
+ self.assertEqual("/film/actor" in types, False)
+ s.touch(), time.sleep(2), s.touch();
+ type_object(s, b.id, "/film/film_genre")
+ s.touch(), time.sleep(2), s.touch();
+ s.mqlwrite({"id" : b.id, "/film/film_genre/films_in_this_genre" : {"id" : "/en/the_taking_of_pelham_1_2_3", "connect" : "insert"}})
+ types = map(f, s.mqlread(q)["type"])
+ s.touch(), time.sleep(2), s.touch();
+ print types
+
+ self.assertEqual("/film/film_genre" in types, True)
+ self.assertEqual("/media_common/media_genre" in types, True)
+
+ def test_move_object(self):
+ print domain_id
+ old = s.create_object("old", domain_id + "/old")
+ s.move_object(domain_id + "/old", domain_id + "/new")
+ is_old = { "id" : domain_id + "/old", "key" : [{"value" : None}]}
+ is_new = { "id" : domain_id + "/new", "key" : [{"value" : None}]}
+ s.touch()
+ o = s.mqlread(is_old)
+ n = s.mqlread(is_new)
+ # we don't just check to see if the id exists, as freebase caches id -> guid lookups
+ if o:
+ self.assertEqual(len([b for b in o["key"] if b["value"] == "old"]), 0)
+ self.assertEqual(len([b for b in n["key"]]), 1)
+
+ def test_property_moving(self):
+ person = create_type(s, "Person", "person", domain_id)
+ date_of_birth = create_property(s, "DB", "db", domain_id + "/person", "/type/datetime", False, tip="Date of Birth of a Person")
+
+ goblin = create_type(s, "Goblin", "goblin", domain_id)
+ self.assertEqual(s.mqlread({"id" : domain_id + "/goblin/db"}), None)
+ copy_property(s, domain_id + "/person/db", domain_id + "/goblin/db")
+ self.assertNotEqual(s.mqlread({"id" : domain_id + "/goblin/db"}), None)
+
+ self.assertEqual(s.mqlread({"id" : domain_id + "/goblin/db", "/freebase/documented_object/tip" : None})["/freebase/documented_object/tip"], "Date of Birth of a Person")
+
+ # But we don't actually want that to be the tip, we should be able to change it
+ dragon = create_type(s, "Dragon", "dragon", domain_id)
+ copy_property(s, domain_id + "/person/db", domain_id + "/dragon/db", **{"/freebase/documented_object/tip": "Date of Birth of a Dragon!"})
+ self.assertEqual(s.mqlread({"id" : domain_id + "/dragon/db", "/freebase/documented_object/tip" : None})["/freebase/documented_object/tip"], "Date of Birth of a Dragon!")
+
+ # let's test with a slightly move obtuse property, unit!
+ create_property(s, "Magnetic Moment", "mmoment", domain_id + "/person", "/type/float", True,
+ extra={"unit" : {"connect" : "insert", "id" : "/en/nuclear_magneton"}})
+ copy_property(s, domain_id + "/person/mmoment", domain_id + "/goblin/mmoment")
+ self.assertEqual(s.mqlread({"id" : domain_id + "/goblin/mmoment", "/type/property/unit" : {"id" : None}})["/type/property/unit"]["id"], "/en/nuclear_magneton")
+
+
+ def test_rename_property(self):
+ grendel = create_type(s, "Grendel", "grendel", domain_id)
+ # a mistake
+ date_of_bbirth = create_property(s, "Date of Bbirth", "dbb", domain_id + "/grendel", "/type/datetime", False, tip="Date of Bbirth of Grendel")
+ self.assertEqual(s.mqlread({"id" : domain_id + "/grendel/dbb", "name" : None})["name"], "Date of Bbirth")
+
+ # let's fix it
+ move_property(s, domain_id + "/grendel/dbb", domain_id + "/grendel/db", name="Date of Birth", **{"/freebase/documented_object/tip":"Date of Birth of Grendel"})
+ self.assertEqual(s.mqlread({"id" : domain_id + "/grendel/db", "name" : None})["name"], "Date of Birth")
+ # sometimes freebase still thinks /grendel/dbb exists... i hate id -> guid caches.
+ self.assertEqual(s.mqlread({"id" : domain_id + "/grendel/dbb", "key" : [{"value" : None}]}), None)
+
+ def test_recreate_contractbridge_base(self):
+
+ base = domain_id
+
+ create_type(s, "Bridge Player", "bridge_player", base, tip="A bridge player", included=["/people/person", "/common/topic"])
+ create_type(s, "Bridge Tournament", "bridge_tournament", base, tip="A bridge tournament", included=["/event/event", "/common/topic"])
+ create_type(s, "Bridge Tournament Standings", "bridge_tournament_standings", base, cvt=True, tip="Bridge Tournamen Results")
+
+ player = base + "/bridge_player"
+ tourney = base + "/bridge_tournament"
+ standing = base + "/bridge_tournament_standings"
+
+ # tournament standings
+ # ideas: create a cvt could be a function, disambig always True, same schema, iono, whatever
+ create_property(s, "Year", "year", standing, "/type/datetime", True, disambig=True, tip="Year")
+ create_property(s, "First Place", "first_place", standing, player, False, disambig=True)
+ create_property(s, "Second Place", "second_place", standing, player, False, disambig=True)
+ create_property(s, "Tournament", "tournament", standing, tourney, True, disambig=True)
+
+
+ # tournament
+ reciprocate_property(s, "Standing", "standing", standing + "/tournament", False, disambig=True)
+ create_property(s, "Location", "location", tourney, "/location/citytown", False, disambig=True)
+
+ # bridge player
+ # standings must be reverses!
+ reciprocate_property(s, "First Place Finish", "first_place_finish", standing + "/first_place", False, False)
+ reciprocate_property(s, "Second Place Finish", "second_place_finish", standing + "/second_place", False, False)
+
+ #delegator property test
+ delegate_property(s, "/people/person/date_of_birth", player, "Date of Birth", "db")
+
+if __name__ == '__main__':
+ unittest.main()
+