{"action":"created","comment":{"url":"https://api.github.com/repos/DOAJ/doaj/pulls/comments/37073658","id":37073658,"diff_hunk":"@@ -172,184 +178,731 @@ class DataSchemaException(Exception):\n \n class DataObj(object):\n     \"\"\"\n-    Class which stores data according to a defined structure and provides\n-    validation on setting any attribute. Recursive (if any of its attributes\n-    contain an object or a list of objects, these will be wrapped in a\n-    DataObj themselves and any actions delegated to the new DataObj).\n+    Class which provides services to other classes which store their internal data\n+    as a python data structure in the self.data field.\n     \"\"\"\n \n-    _coerce = {\n+    SCHEMA = None\n+\n+    DEFAULT_COERCE = {\n         \"unicode\": to_unicode(),\n         \"utcdatetime\": date_str(),\n         \"integer\": to_int(),\n         \"float\": to_float(),\n         \"isolang\": to_isolang(),\n         \"url\": to_url,\n-        \"bool\": to_bool()\n+        \"bool\": to_bool,\n+        \"isolang_2letter\": to_isolang(output_format=\"alpha2\"),\n+        \"country_code\" : to_country_code(),\n+        \"currency_code\" : to_currency_code\n     }\n \n-    _RESERVED_ATTR_NAMES = [\n-        '__class__',\n-        '__delattr__',\n-        '__dict__',\n-        '__doc__',\n-        '__format__',\n-        '__getattribute__',\n-        '__hash__',\n-        '__init__',\n-        '__members__',\n-        '__methods__',\n-        '__module__',\n-        '__new__',\n-        '__reduce__',\n-        '__reduce_ex__',\n-        '__repr__',\n-        '__setattr__',\n-        '__sizeof__',\n-        '__str__',\n-        '__subclasshook__',\n-        '__weakref__',\n-        '_add_struct',\n-        '_struct',\n-        '_type',\n-        '_silent_drop_extra_fields',\n-        '_RESERVED_ATTR_NAMES',\n-        '_coerce',\n-        '_data'\n-    ]\n-\n-    def __init__(self, raw=None, _struct=None, _type=None, _silent_drop_extra_fields=False):\n-        assert isinstance(raw, dict), \"The raw data passed in must be iterable as a dict.\"\n-\n-        self._silent_drop_extra_fields = _silent_drop_extra_fields\n-\n-        if _type:\n-            self._type = _type\n-\n-        if not self._type:\n-            raise ValueError(\"Can't create DataObj without a type\")\n-\n-        if _struct:\n-            self._struct = _struct\n+    def __init__(self, raw=None, struct=None, construct_raw=True, expose_data=False, properties=None, coerce_map=None, construct_silent_prune=False):\n+        # make a shortcut to the object.__getattribute__ function\n+        og = object.__getattribute__\n+\n+        # if no subclass has set the coerce, then set it from default\n+        try:\n+            og(self, \"_coerce_map\")\n+        except:\n+            self._coerce_map = coerce_map if coerce_map is not None else deepcopy(self.DEFAULT_COERCE)\n+\n+        # if no subclass has set the struct, initialise it\n+        try:\n+            og(self, \"_struct\")\n+        except:\n+            self._struct = struct\n+\n+        # assign the data if not already assigned by subclass\n+        # NOTE: data is not _data deliberately\n+        try:\n+            og(self, \"data\")\n+        except:\n+            self.data = {} if raw is None else raw\n+\n+        # properties to allow automatic object API construction\n+        # of the form\n+        #\n+        # {\"<public property name>\" : (\"<path.to.property>\", \"<data object wrapper>\")\n+        # e.g\n+        # {\"identifier\" : (\"bibjson.identifier\", DataObj))}\n+        try:\n+            og(self, \"_properties\")\n+        except:\n+            self._properties = properties if properties is not None else {}\n+\n+        # if no subclass has set expose_data, set it\n+        try:\n+            og(self, \"_expose_data\")\n+        except:\n+            self._expose_data = expose_data\n+\n+        # restructure the object based on the struct if requried\n+        if self._struct is not None and raw is not None and construct_raw:\n+            self.data = construct(self.data, self._struct, self._coerce_map, silent_prune=construct_silent_prune)\n+\n+        # run against the old validation routine\n+        # (now deprecated)\n+        self.validate()\n+\n+        # run the object's native validation routine\n+        self.custom_validate()\n+\n+    def __getattr__(self, name):\n+        props, data_attrs = self._list_dynamic_properties()\n+\n+        # if the name is not in the dynamic properties, raise an attribute error\n+        if name not in props and name not in data_attrs:\n+            raise AttributeError('{name} is not set'.format(name=name))\n+\n+        # otherwise, extract the path from the properties list or the internal data\n+        if name in props:\n+            path, wrapper = self._properties.get(name)\n         else:\n-            self._struct = {}\n+            path = name\n+            wrapper = DataObj\n+\n+        # request the internal property directly (which will in-turn raise the AttributeError if necessary)\n+        try:\n+            return self._get_internal_property(path, wrapper)\n+        except AttributeError:\n+            # re-wrap the attribute error with the name, rather than the path\n+            raise AttributeError('{name} is not set'.format(name=name))\n+\n+    def __setattr__(self, key, value):\n+        # first set the attribute on any explicitly defined property\n+        try:\n+            att = object.__getattribute__(self, key)\n+            return object.__setattr__(self, key, value)\n+        except AttributeError:\n+            pass\n+\n+        # this could be an internal attribute from the constructor, so we need to make\n+        # a special case\n+        if key in [\"_coerce_map\", \"_struct\", \"data\", \"_properties\", \"_expose_data\"]:\n+            return object.__setattr__(self, key, value)\n+\n+        props, data_attrs = self._list_dynamic_properties()\n+\n+        # extract the path from the properties list or the internal data\n+        path = None\n+        wrapper = None\n+        if key in props:\n+            path, wrapper = self._properties.get(key)\n+        elif key in data_attrs:\n+            path = key\n+            wrapper = DataObj\n+\n+        # try to set the property on othe internal object\n+        if path is not None:\n+            wasset = self._set_internal_property(path, value, wrapper)\n+            if wasset:\n+                return\n+\n+        # fall back to the default approach of allowing any attribute to be set on the object","path":"portality/lib/dataobj.py","position":297,"original_position":297,"commit_id":"badc8405954a58b52a16e554fd5c2ca2996c817a","original_commit_id":"badc8405954a58b52a16e554fd5c2ca2996c817a","user":{"login":"richard-jones","id":856138,"avatar_url":"https://avatars.githubusercontent.com/u/856138?v=3","gravatar_id":"","url":"https://api.github.com/users/richard-jones","html_url":"https://github.com/richard-jones","followers_url":"https://api.github.com/users/richard-jones/followers","following_url":"https://api.github.com/users/richard-jones/following{/other_user}","gists_url":"https://api.github.com/users/richard-jones/gists{/gist_id}","starred_url":"https://api.github.com/users/richard-jones/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/richard-jones/subscriptions","organizations_url":"https://api.github.com/users/richard-jones/orgs","repos_url":"https://api.github.com/users/richard-jones/repos","events_url":"https://api.github.com/users/richard-jones/events{/privacy}","received_events_url":"https://api.github.com/users/richard-jones/received_events","type":"User","site_admin":false},"body":"This is how python objects actually work, which is the reason I've done this.  Without this, it is not possible for a sub-class to define its own properties.","created_at":"2015-08-14T13:02:56Z","updated_at":"2015-08-14T13:02:56Z","html_url":"https://github.com/DOAJ/doaj/pull/841#discussion_r37073658","pull_request_url":"https://api.github.com/repos/DOAJ/doaj/pulls/841","_links":{"self":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/comments/37073658"},"html":{"href":"https://github.com/DOAJ/doaj/pull/841#discussion_r37073658"},"pull_request":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/841"}}},"pull_request":{"url":"https://api.github.com/repos/DOAJ/doaj/pulls/841","id":42372148,"html_url":"https://github.com/DOAJ/doaj/pull/841","diff_url":"https://github.com/DOAJ/doaj/pull/841.diff","patch_url":"https://github.com/DOAJ/doaj/pull/841.patch","issue_url":"https://api.github.com/repos/DOAJ/doaj/issues/841","number":841,"state":"open","locked":false,"title":"New data object and Application Create API","user":{"login":"richard-jones","id":856138,"avatar_url":"https://avatars.githubusercontent.com/u/856138?v=3","gravatar_id":"","url":"https://api.github.com/users/richard-jones","html_url":"https://github.com/richard-jones","followers_url":"https://api.github.com/users/richard-jones/followers","following_url":"https://api.github.com/users/richard-jones/following{/other_user}","gists_url":"https://api.github.com/users/richard-jones/gists{/gist_id}","starred_url":"https://api.github.com/users/richard-jones/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/richard-jones/subscriptions","organizations_url":"https://api.github.com/users/richard-jones/orgs","repos_url":"https://api.github.com/users/richard-jones/repos","events_url":"https://api.github.com/users/richard-jones/events{/privacy}","received_events_url":"https://api.github.com/users/richard-jones/received_events","type":"User","site_admin":false},"body":"This provides the following:\r\n* A new data object based on the previous iteration, but with more features\r\n* Revised model objects and tests for journal/vanilla data object\r\n* The Create portion of the Application CRUD API\r\n* Some additions to the api common library which may be shared over all object types\r\n* a README which outlines the API interactions planned for the Applications\r\n\r\nIt isn't the final Application CRUD but because of the new data object, and the overall pattern demonstrated around Application Create I think it is useful to merge this now.  It also has common code that other CRUD endpoints will want to make use of.","created_at":"2015-08-13T16:42:28Z","updated_at":"2015-08-14T13:02:56Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d7566a838b9d8cc764a1fae0b46e8dca46071224","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/DOAJ/doaj/pulls/841/commits","review_comments_url":"https://api.github.com/repos/DOAJ/doaj/pulls/841/comments","review_comment_url":"https://api.github.com/repos/DOAJ/doaj/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DOAJ/doaj/issues/841/comments","statuses_url":"https://api.github.com/repos/DOAJ/doaj/statuses/badc8405954a58b52a16e554fd5c2ca2996c817a","head":{"label":"DOAJ:feature/740_applications_crud","ref":"feature/740_applications_crud","sha":"badc8405954a58b52a16e554fd5c2ca2996c817a","user":{"login":"DOAJ","id":5971926,"avatar_url":"https://avatars.githubusercontent.com/u/5971926?v=3","gravatar_id":"","url":"https://api.github.com/users/DOAJ","html_url":"https://github.com/DOAJ","followers_url":"https://api.github.com/users/DOAJ/followers","following_url":"https://api.github.com/users/DOAJ/following{/other_user}","gists_url":"https://api.github.com/users/DOAJ/gists{/gist_id}","starred_url":"https://api.github.com/users/DOAJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DOAJ/subscriptions","organizations_url":"https://api.github.com/users/DOAJ/orgs","repos_url":"https://api.github.com/users/DOAJ/repos","events_url":"https://api.github.com/users/DOAJ/events{/privacy}","received_events_url":"https://api.github.com/users/DOAJ/received_events","type":"Organization","site_admin":false},"repo":{"id":14500626,"name":"doaj","full_name":"DOAJ/doaj","owner":{"login":"DOAJ","id":5971926,"avatar_url":"https://avatars.githubusercontent.com/u/5971926?v=3","gravatar_id":"","url":"https://api.github.com/users/DOAJ","html_url":"https://github.com/DOAJ","followers_url":"https://api.github.com/users/DOAJ/followers","following_url":"https://api.github.com/users/DOAJ/following{/other_user}","gists_url":"https://api.github.com/users/DOAJ/gists{/gist_id}","starred_url":"https://api.github.com/users/DOAJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DOAJ/subscriptions","organizations_url":"https://api.github.com/users/DOAJ/orgs","repos_url":"https://api.github.com/users/DOAJ/repos","events_url":"https://api.github.com/users/DOAJ/events{/privacy}","received_events_url":"https://api.github.com/users/DOAJ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/DOAJ/doaj","description":"The Directory of Open Access Journals - website and directory software","fork":false,"url":"https://api.github.com/repos/DOAJ/doaj","forks_url":"https://api.github.com/repos/DOAJ/doaj/forks","keys_url":"https://api.github.com/repos/DOAJ/doaj/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DOAJ/doaj/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DOAJ/doaj/teams","hooks_url":"https://api.github.com/repos/DOAJ/doaj/hooks","issue_events_url":"https://api.github.com/repos/DOAJ/doaj/issues/events{/number}","events_url":"https://api.github.com/repos/DOAJ/doaj/events","assignees_url":"https://api.github.com/repos/DOAJ/doaj/assignees{/user}","branches_url":"https://api.github.com/repos/DOAJ/doaj/branches{/branch}","tags_url":"https://api.github.com/repos/DOAJ/doaj/tags","blobs_url":"https://api.github.com/repos/DOAJ/doaj/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DOAJ/doaj/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DOAJ/doaj/git/refs{/sha}","trees_url":"https://api.github.com/repos/DOAJ/doaj/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DOAJ/doaj/statuses/{sha}","languages_url":"https://api.github.com/repos/DOAJ/doaj/languages","stargazers_url":"https://api.github.com/repos/DOAJ/doaj/stargazers","contributors_url":"https://api.github.com/repos/DOAJ/doaj/contributors","subscribers_url":"https://api.github.com/repos/DOAJ/doaj/subscribers","subscription_url":"https://api.github.com/repos/DOAJ/doaj/subscription","commits_url":"https://api.github.com/repos/DOAJ/doaj/commits{/sha}","git_commits_url":"https://api.github.com/repos/DOAJ/doaj/git/commits{/sha}","comments_url":"https://api.github.com/repos/DOAJ/doaj/comments{/number}","issue_comment_url":"https://api.github.com/repos/DOAJ/doaj/issues/comments{/number}","contents_url":"https://api.github.com/repos/DOAJ/doaj/contents/{+path}","compare_url":"https://api.github.com/repos/DOAJ/doaj/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DOAJ/doaj/merges","archive_url":"https://api.github.com/repos/DOAJ/doaj/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DOAJ/doaj/downloads","issues_url":"https://api.github.com/repos/DOAJ/doaj/issues{/number}","pulls_url":"https://api.github.com/repos/DOAJ/doaj/pulls{/number}","milestones_url":"https://api.github.com/repos/DOAJ/doaj/milestones{/number}","notifications_url":"https://api.github.com/repos/DOAJ/doaj/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DOAJ/doaj/labels{/name}","releases_url":"https://api.github.com/repos/DOAJ/doaj/releases{/id}","created_at":"2013-11-18T18:07:31Z","updated_at":"2015-06-03T03:16:30Z","pushed_at":"2015-08-13T16:42:29Z","git_url":"git://github.com/DOAJ/doaj.git","ssh_url":"git@github.com:DOAJ/doaj.git","clone_url":"https://github.com/DOAJ/doaj.git","svn_url":"https://github.com/DOAJ/doaj","homepage":null,"size":9998,"stargazers_count":5,"watchers_count":5,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":137,"forks":1,"open_issues":137,"watchers":5,"default_branch":"develop"}},"base":{"label":"DOAJ:develop","ref":"develop","sha":"b6ab4c5f663b3179baa6e56ccbee9a5c53b9b691","user":{"login":"DOAJ","id":5971926,"avatar_url":"https://avatars.githubusercontent.com/u/5971926?v=3","gravatar_id":"","url":"https://api.github.com/users/DOAJ","html_url":"https://github.com/DOAJ","followers_url":"https://api.github.com/users/DOAJ/followers","following_url":"https://api.github.com/users/DOAJ/following{/other_user}","gists_url":"https://api.github.com/users/DOAJ/gists{/gist_id}","starred_url":"https://api.github.com/users/DOAJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DOAJ/subscriptions","organizations_url":"https://api.github.com/users/DOAJ/orgs","repos_url":"https://api.github.com/users/DOAJ/repos","events_url":"https://api.github.com/users/DOAJ/events{/privacy}","received_events_url":"https://api.github.com/users/DOAJ/received_events","type":"Organization","site_admin":false},"repo":{"id":14500626,"name":"doaj","full_name":"DOAJ/doaj","owner":{"login":"DOAJ","id":5971926,"avatar_url":"https://avatars.githubusercontent.com/u/5971926?v=3","gravatar_id":"","url":"https://api.github.com/users/DOAJ","html_url":"https://github.com/DOAJ","followers_url":"https://api.github.com/users/DOAJ/followers","following_url":"https://api.github.com/users/DOAJ/following{/other_user}","gists_url":"https://api.github.com/users/DOAJ/gists{/gist_id}","starred_url":"https://api.github.com/users/DOAJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DOAJ/subscriptions","organizations_url":"https://api.github.com/users/DOAJ/orgs","repos_url":"https://api.github.com/users/DOAJ/repos","events_url":"https://api.github.com/users/DOAJ/events{/privacy}","received_events_url":"https://api.github.com/users/DOAJ/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/DOAJ/doaj","description":"The Directory of Open Access Journals - website and directory software","fork":false,"url":"https://api.github.com/repos/DOAJ/doaj","forks_url":"https://api.github.com/repos/DOAJ/doaj/forks","keys_url":"https://api.github.com/repos/DOAJ/doaj/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DOAJ/doaj/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DOAJ/doaj/teams","hooks_url":"https://api.github.com/repos/DOAJ/doaj/hooks","issue_events_url":"https://api.github.com/repos/DOAJ/doaj/issues/events{/number}","events_url":"https://api.github.com/repos/DOAJ/doaj/events","assignees_url":"https://api.github.com/repos/DOAJ/doaj/assignees{/user}","branches_url":"https://api.github.com/repos/DOAJ/doaj/branches{/branch}","tags_url":"https://api.github.com/repos/DOAJ/doaj/tags","blobs_url":"https://api.github.com/repos/DOAJ/doaj/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DOAJ/doaj/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DOAJ/doaj/git/refs{/sha}","trees_url":"https://api.github.com/repos/DOAJ/doaj/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DOAJ/doaj/statuses/{sha}","languages_url":"https://api.github.com/repos/DOAJ/doaj/languages","stargazers_url":"https://api.github.com/repos/DOAJ/doaj/stargazers","contributors_url":"https://api.github.com/repos/DOAJ/doaj/contributors","subscribers_url":"https://api.github.com/repos/DOAJ/doaj/subscribers","subscription_url":"https://api.github.com/repos/DOAJ/doaj/subscription","commits_url":"https://api.github.com/repos/DOAJ/doaj/commits{/sha}","git_commits_url":"https://api.github.com/repos/DOAJ/doaj/git/commits{/sha}","comments_url":"https://api.github.com/repos/DOAJ/doaj/comments{/number}","issue_comment_url":"https://api.github.com/repos/DOAJ/doaj/issues/comments{/number}","contents_url":"https://api.github.com/repos/DOAJ/doaj/contents/{+path}","compare_url":"https://api.github.com/repos/DOAJ/doaj/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DOAJ/doaj/merges","archive_url":"https://api.github.com/repos/DOAJ/doaj/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DOAJ/doaj/downloads","issues_url":"https://api.github.com/repos/DOAJ/doaj/issues{/number}","pulls_url":"https://api.github.com/repos/DOAJ/doaj/pulls{/number}","milestones_url":"https://api.github.com/repos/DOAJ/doaj/milestones{/number}","notifications_url":"https://api.github.com/repos/DOAJ/doaj/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DOAJ/doaj/labels{/name}","releases_url":"https://api.github.com/repos/DOAJ/doaj/releases{/id}","created_at":"2013-11-18T18:07:31Z","updated_at":"2015-06-03T03:16:30Z","pushed_at":"2015-08-13T16:42:29Z","git_url":"git://github.com/DOAJ/doaj.git","ssh_url":"git@github.com:DOAJ/doaj.git","clone_url":"https://github.com/DOAJ/doaj.git","svn_url":"https://github.com/DOAJ/doaj","homepage":null,"size":9998,"stargazers_count":5,"watchers_count":5,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":137,"forks":1,"open_issues":137,"watchers":5,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/841"},"html":{"href":"https://github.com/DOAJ/doaj/pull/841"},"issue":{"href":"https://api.github.com/repos/DOAJ/doaj/issues/841"},"comments":{"href":"https://api.github.com/repos/DOAJ/doaj/issues/841/comments"},"review_comments":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/841/comments"},"review_comment":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DOAJ/doaj/pulls/841/commits"},"statuses":{"href":"https://api.github.com/repos/DOAJ/doaj/statuses/badc8405954a58b52a16e554fd5c2ca2996c817a"}}}}