diff options
| author | nitromaster101 <nitromaster101@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2> | 2009-07-09 01:36:29 +0000 |
|---|---|---|
| committer | nitromaster101 <nitromaster101@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2> | 2009-07-09 01:36:29 +0000 |
| commit | 637b2c5788657535ab05b6610f3b02d56ab957ec (patch) | |
| tree | e774b101c86e33e2fab7afaf9feec161b799edf5 | |
| parent | 8e71e49ea3708c30a4a0d3688cc9d643665d68be (diff) | |
add upload/revert schema manipulation, more tests, and other changes of the past few days
git-svn-id: http://freebase-python.googlecode.com/svn/trunk@172 5914aa95-5b3a-0410-a3b5-7b719e7fe9b2
| -rw-r--r-- | MANIFEST.in | 1 | ||||
| -rwxr-xr-x | freebase/api/__init__.py | 19 | ||||
| -rw-r--r-- | freebase/api/session.py | 104 | ||||
| -rw-r--r-- | freebase/schema.py | 571 | ||||
| -rw-r--r-- | schema-manipulation/get_type.py | 332 | ||||
| -rw-r--r-- | schema-manipulation/test_hardcore_schema_manipulation.py | 113 | ||||
| -rw-r--r-- | schema-manipulation/type_creation.py | 595 | ||||
| -rw-r--r-- | setup.py | 24 | ||||
| -rw-r--r-- | test/__init__.py | 0 | ||||
| -rw-r--r-- | test/runtests.py | 67 | ||||
| -rwxr-xr-x | test/test_freebase.py (renamed from test/test_all.py) | 41 | ||||
| -rw-r--r-- | test/test_schema_manipulation.py | 150 |
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' ) -''' @@ -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() + |
