{"action":"created","comment":{"url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/comments/130087869","pull_request_review_id":52899359,"id":130087869,"diff_hunk":"@@ -0,0 +1,323 @@\n+#\n+# interface.py:  support for generating XML with interface description\n+#\n+# Copyright (C) 2017\n+# Red Hat, Inc.  All rights reserved.\n+#\n+# This program is free software; you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation; either version 2 of the License, or\n+# (at your option) any later version.\n+#\n+# This program is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the GNU General Public License\n+# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n+#\n+# Author(s):  Vendula Poncova <vponcova@redhat.com>\n+#\n+# For more info about DBus specification see:\n+# https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format\n+#\n+import inspect\n+import re\n+\n+from inspect import Parameter\n+from typing import get_type_hints\n+from pydbus.generic import signal\n+\n+from pyanaconda.dbus.typing import get_dbus_type\n+from pyanaconda.dbus.xml import XMLGenerator\n+\n+__all__ = [\"dbus_class\", \"dbus_interface\", \"dbus_signal\"]\n+\n+\n+class dbus_signal(signal):\n+    \"\"\"DBus signal.\"\"\"\n+    def __init__(self, method=None):\n+        super(dbus_signal, self).__init__()\n+        self.declaration = method\n+\n+\n+def dbus_interface(interface_name):\n+    \"\"\"Generate DBus XML specification for given interface.\"\"\"\n+    def decorated(cls):\n+        generator = DBusSpecification()\n+        cls.dbus = generator.generate_specification(cls, interface_name)\n+        return cls\n+    return decorated\n+\n+\n+def dbus_class(cls):\n+    \"\"\"Generate DBus XML specification for given class.\"\"\"\n+    return dbus_interface(None)(cls)\n+\n+\n+class DBusSpecificationError(Exception):\n+    pass\n+\n+\n+class DBusSpecification(object):\n+    \"\"\"Class for generating DBus XML specification.\"\"\"\n+\n+    DIRECTION_IN = \"in\"\n+    DIRECTION_OUT = \"out\"\n+\n+    ACCESS_READ = \"read\"\n+    ACCESS_WRITE = \"write\"\n+    ACCESS_READWRITE = \"readwrite\"\n+\n+    RETURN_PARAMETER = \"return\"\n+\n+    NAME_PATTERN = re.compile(r'[A-Z][A-Za-z0-9]*')\n+\n+    def __init__(self, xml_generator=XMLGenerator()):\n+        self.xml_generator = xml_generator\n+\n+    def generate_specification(self, cls, interface_name=None):\n+        \"\"\"Generates DBus XML specification for given class.\n+\n+        If class defines a new interface, it will be added to\n+        the specification.\n+\n+        :param cls: class object to decorate\n+        :param str interface_name: name of the interface defined by class\n+        :return str: DBus specification in XML\n+        \"\"\"\n+        # Collect all interfaces that class inherits.\n+        interfaces = self._collect_interfaces(cls)\n+\n+        # Generate a new interface.\n+        if interface_name:\n+            interface = self._generate_interface(cls, interfaces, interface_name)\n+            interfaces[interface_name] = interface\n+\n+        # Generate XML specification for the given class.\n+        node = self._generate_node(cls, interfaces)\n+        return self.xml_generator.element_to_xml(node)\n+\n+    def _is_exportable(self, member_name):\n+        \"\"\"Is the name of a class member exportable?\n+\n+        The name is exportable if it follows the DBus specification.\n+        Only CamelCase names are allowed.\n+        \"\"\"\n+        return bool(self.NAME_PATTERN.fullmatch(member_name))\n+\n+    def _is_defined(self, interfaces, member_name):\n+        \"\"\"Is the member name defined in given interfaces?\n+\n+        :param interfaces: a dictionary of interfaces\n+        :param member_name: a name of the class member\n+        :return: True if the name is defined, otherwise False\n+        \"\"\"\n+        return any(self.xml_generator.is_member(member)\n+                   and self.xml_generator.has_name(member, member_name)\n+                   for interface in interfaces.values()\n+                   for member in interface)\n+\n+    def _iterate_parameters(self, member):\n+        \"\"\"Iterate over method parameters.\n+\n+        For every parameter returns its name, a type hint and a direction.\n+\n+        :param member: a method object\n+        :return: an iterator\n+        \"\"\"\n+        # Get type hints for parameters.\n+        type_hints = get_type_hints(member)\n+\n+        # Get parameters.\n+        parameters = inspect.signature(member).parameters\n+\n+        for index, name in enumerate(parameters):\n+            # Skip self parameter.\n+            if index < 1:\n+                continue\n+\n+            # Check the kind of the parameter\n+            if parameters[name].kind != Parameter.POSITIONAL_OR_KEYWORD:\n+                raise DBusSpecificationError(\"Only positional or keyword arguments are allowed.\")\n+\n+            # Check if the type is defined.\n+            if name not in type_hints:\n+                raise DBusSpecificationError(\"Parameter %s doesn't have defined type.\" % name)\n+\n+            yield name, type_hints[name], self.DIRECTION_IN\n+\n+        # Is the return type defined?\n+        if self.RETURN_PARAMETER not in type_hints:\n+            return\n+\n+        # Is the return type other than None?\n+        if type_hints[self.RETURN_PARAMETER] is None:\n+            return\n+\n+        yield self.RETURN_PARAMETER, type_hints[self.RETURN_PARAMETER], self.DIRECTION_OUT\n+\n+    def _is_method(self, member):\n+        \"\"\"Is the class member a DBus method?\"\"\"\n+        return inspect.ismethod(member) or inspect.isfunction(member)","path":"pyanaconda/dbus/interface.py","position":163,"original_position":163,"commit_id":"9c61e66b8436d1f49b2592b794e613b15e24ffd9","original_commit_id":"9c61e66b8436d1f49b2592b794e613b15e24ffd9","user":{"login":"jkonecny12","id":1643889,"avatar_url":"https://avatars0.githubusercontent.com/u/1643889?v=4","gravatar_id":"","url":"https://api.github.com/users/jkonecny12","html_url":"https://github.com/jkonecny12","followers_url":"https://api.github.com/users/jkonecny12/followers","following_url":"https://api.github.com/users/jkonecny12/following{/other_user}","gists_url":"https://api.github.com/users/jkonecny12/gists{/gist_id}","starred_url":"https://api.github.com/users/jkonecny12/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jkonecny12/subscriptions","organizations_url":"https://api.github.com/users/jkonecny12/orgs","repos_url":"https://api.github.com/users/jkonecny12/repos","events_url":"https://api.github.com/users/jkonecny12/events{/privacy}","received_events_url":"https://api.github.com/users/jkonecny12/received_events","type":"User","site_admin":false},"body":"I don't think we want to support `functions`. It needs to be part of the class all the time if I understand usage correctly.","created_at":"2017-07-28T13:23:24Z","updated_at":"2017-07-28T13:55:16Z","html_url":"https://github.com/rhinstaller/anaconda/pull/1130#discussion_r130087869","pull_request_url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130","_links":{"self":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/comments/130087869"},"html":{"href":"https://github.com/rhinstaller/anaconda/pull/1130#discussion_r130087869"},"pull_request":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130"}}},"pull_request":{"url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130","id":132514527,"html_url":"https://github.com/rhinstaller/anaconda/pull/1130","diff_url":"https://github.com/rhinstaller/anaconda/pull/1130.diff","patch_url":"https://github.com/rhinstaller/anaconda/pull/1130.patch","issue_url":"https://api.github.com/repos/rhinstaller/anaconda/issues/1130","number":1130,"state":"open","locked":false,"title":"Add support for automatic generating of DBus specification.","user":{"login":"poncovka","id":7396092,"avatar_url":"https://avatars3.githubusercontent.com/u/7396092?v=4","gravatar_id":"","url":"https://api.github.com/users/poncovka","html_url":"https://github.com/poncovka","followers_url":"https://api.github.com/users/poncovka/followers","following_url":"https://api.github.com/users/poncovka/following{/other_user}","gists_url":"https://api.github.com/users/poncovka/gists{/gist_id}","starred_url":"https://api.github.com/users/poncovka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poncovka/subscriptions","organizations_url":"https://api.github.com/users/poncovka/orgs","repos_url":"https://api.github.com/users/poncovka/repos","events_url":"https://api.github.com/users/poncovka/events{/privacy}","received_events_url":"https://api.github.com/users/poncovka/received_events","type":"User","site_admin":false},"body":"With provided generators dbus_signal, dbus_interface and dbus_class,\r\nit is possible to define a DBus interface with a Python class and\r\nprovide a DBus specification of a Python class. Types are derived\r\nfrom type hints using types from pyanaconda.dbus.typing. Only class\r\nmembers with CamelCase names are considered to be members of the DBus\r\ninterface.\r\n\r\nIt is supported the inheritance of DBus interfaces. Generated DBus\r\nspecification will contain all interfaces of the class ancestors.\r\nIf the class defines a new DBus interface, then class members with\r\nnames already defined in inherited interfaces are not considered to\r\nbe members of the new DBus interface.\r\n\r\nExample of a class that defines a new DBus interface with a method,\r\na signal and a property. Instances of this class can be also exported\r\nto DBus.\r\n\r\n```\r\n@dbus_interface(\"Interface\")\r\nclass NewInterface(object):\r\n\r\n  def Method(self, x: Int) -> List[Int]\r\n    return [x]\r\n\r\n  @dbus_signal\r\n  def Signal(self, y: Double)\r\n    pass\r\n\r\n  @property\r\n  def Property(self) -> Bool:\r\n    return False\r\n```\r\n\r\nExample of a class that doesn't define a new DBus interface, but\r\ninstances can be exported to DBus.\r\n```\r\n\r\n@dbus_class\r\nclass ExportableClass(NewInterface):\r\n  pass\r\n```\r\n\r\nIt is required DBus library pydbus.","created_at":"2017-07-26T13:24:51Z","updated_at":"2017-07-28T13:55:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f1a4ef377b5d08d78f901904a7d67671ffc3245d","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130/commits","review_comments_url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130/comments","review_comment_url":"https://api.github.com/repos/rhinstaller/anaconda/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rhinstaller/anaconda/issues/1130/comments","statuses_url":"https://api.github.com/repos/rhinstaller/anaconda/statuses/9c61e66b8436d1f49b2592b794e613b15e24ffd9","head":{"label":"poncovka:master-dbus","ref":"master-dbus","sha":"9c61e66b8436d1f49b2592b794e613b15e24ffd9","user":{"login":"poncovka","id":7396092,"avatar_url":"https://avatars3.githubusercontent.com/u/7396092?v=4","gravatar_id":"","url":"https://api.github.com/users/poncovka","html_url":"https://github.com/poncovka","followers_url":"https://api.github.com/users/poncovka/followers","following_url":"https://api.github.com/users/poncovka/following{/other_user}","gists_url":"https://api.github.com/users/poncovka/gists{/gist_id}","starred_url":"https://api.github.com/users/poncovka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poncovka/subscriptions","organizations_url":"https://api.github.com/users/poncovka/orgs","repos_url":"https://api.github.com/users/poncovka/repos","events_url":"https://api.github.com/users/poncovka/events{/privacy}","received_events_url":"https://api.github.com/users/poncovka/received_events","type":"User","site_admin":false},"repo":{"id":70139747,"name":"anaconda","full_name":"poncovka/anaconda","owner":{"login":"poncovka","id":7396092,"avatar_url":"https://avatars3.githubusercontent.com/u/7396092?v=4","gravatar_id":"","url":"https://api.github.com/users/poncovka","html_url":"https://github.com/poncovka","followers_url":"https://api.github.com/users/poncovka/followers","following_url":"https://api.github.com/users/poncovka/following{/other_user}","gists_url":"https://api.github.com/users/poncovka/gists{/gist_id}","starred_url":"https://api.github.com/users/poncovka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poncovka/subscriptions","organizations_url":"https://api.github.com/users/poncovka/orgs","repos_url":"https://api.github.com/users/poncovka/repos","events_url":"https://api.github.com/users/poncovka/events{/privacy}","received_events_url":"https://api.github.com/users/poncovka/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/poncovka/anaconda","description":"Graphical system installer","fork":true,"url":"https://api.github.com/repos/poncovka/anaconda","forks_url":"https://api.github.com/repos/poncovka/anaconda/forks","keys_url":"https://api.github.com/repos/poncovka/anaconda/keys{/key_id}","collaborators_url":"https://api.github.com/repos/poncovka/anaconda/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/poncovka/anaconda/teams","hooks_url":"https://api.github.com/repos/poncovka/anaconda/hooks","issue_events_url":"https://api.github.com/repos/poncovka/anaconda/issues/events{/number}","events_url":"https://api.github.com/repos/poncovka/anaconda/events","assignees_url":"https://api.github.com/repos/poncovka/anaconda/assignees{/user}","branches_url":"https://api.github.com/repos/poncovka/anaconda/branches{/branch}","tags_url":"https://api.github.com/repos/poncovka/anaconda/tags","blobs_url":"https://api.github.com/repos/poncovka/anaconda/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/poncovka/anaconda/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/poncovka/anaconda/git/refs{/sha}","trees_url":"https://api.github.com/repos/poncovka/anaconda/git/trees{/sha}","statuses_url":"https://api.github.com/repos/poncovka/anaconda/statuses/{sha}","languages_url":"https://api.github.com/repos/poncovka/anaconda/languages","stargazers_url":"https://api.github.com/repos/poncovka/anaconda/stargazers","contributors_url":"https://api.github.com/repos/poncovka/anaconda/contributors","subscribers_url":"https://api.github.com/repos/poncovka/anaconda/subscribers","subscription_url":"https://api.github.com/repos/poncovka/anaconda/subscription","commits_url":"https://api.github.com/repos/poncovka/anaconda/commits{/sha}","git_commits_url":"https://api.github.com/repos/poncovka/anaconda/git/commits{/sha}","comments_url":"https://api.github.com/repos/poncovka/anaconda/comments{/number}","issue_comment_url":"https://api.github.com/repos/poncovka/anaconda/issues/comments{/number}","contents_url":"https://api.github.com/repos/poncovka/anaconda/contents/{+path}","compare_url":"https://api.github.com/repos/poncovka/anaconda/compare/{base}...{head}","merges_url":"https://api.github.com/repos/poncovka/anaconda/merges","archive_url":"https://api.github.com/repos/poncovka/anaconda/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/poncovka/anaconda/downloads","issues_url":"https://api.github.com/repos/poncovka/anaconda/issues{/number}","pulls_url":"https://api.github.com/repos/poncovka/anaconda/pulls{/number}","milestones_url":"https://api.github.com/repos/poncovka/anaconda/milestones{/number}","notifications_url":"https://api.github.com/repos/poncovka/anaconda/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/poncovka/anaconda/labels{/name}","releases_url":"https://api.github.com/repos/poncovka/anaconda/releases{/id}","deployments_url":"https://api.github.com/repos/poncovka/anaconda/deployments","created_at":"2016-10-06T09:20:46Z","updated_at":"2016-10-06T09:20:52Z","pushed_at":"2017-07-27T12:43:18Z","git_url":"git://github.com/poncovka/anaconda.git","ssh_url":"git@github.com:poncovka/anaconda.git","clone_url":"https://github.com/poncovka/anaconda.git","svn_url":"https://github.com/poncovka/anaconda","homepage":null,"size":140331,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"rhinstaller:master","ref":"master","sha":"571914244fbb47c27b65d800ca4ff6edf49195cb","user":{"login":"rhinstaller","id":10549274,"avatar_url":"https://avatars1.githubusercontent.com/u/10549274?v=4","gravatar_id":"","url":"https://api.github.com/users/rhinstaller","html_url":"https://github.com/rhinstaller","followers_url":"https://api.github.com/users/rhinstaller/followers","following_url":"https://api.github.com/users/rhinstaller/following{/other_user}","gists_url":"https://api.github.com/users/rhinstaller/gists{/gist_id}","starred_url":"https://api.github.com/users/rhinstaller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rhinstaller/subscriptions","organizations_url":"https://api.github.com/users/rhinstaller/orgs","repos_url":"https://api.github.com/users/rhinstaller/repos","events_url":"https://api.github.com/users/rhinstaller/events{/privacy}","received_events_url":"https://api.github.com/users/rhinstaller/received_events","type":"Organization","site_admin":false},"repo":{"id":30210846,"name":"anaconda","full_name":"rhinstaller/anaconda","owner":{"login":"rhinstaller","id":10549274,"avatar_url":"https://avatars1.githubusercontent.com/u/10549274?v=4","gravatar_id":"","url":"https://api.github.com/users/rhinstaller","html_url":"https://github.com/rhinstaller","followers_url":"https://api.github.com/users/rhinstaller/followers","following_url":"https://api.github.com/users/rhinstaller/following{/other_user}","gists_url":"https://api.github.com/users/rhinstaller/gists{/gist_id}","starred_url":"https://api.github.com/users/rhinstaller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rhinstaller/subscriptions","organizations_url":"https://api.github.com/users/rhinstaller/orgs","repos_url":"https://api.github.com/users/rhinstaller/repos","events_url":"https://api.github.com/users/rhinstaller/events{/privacy}","received_events_url":"https://api.github.com/users/rhinstaller/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rhinstaller/anaconda","description":"Graphical system installer","fork":false,"url":"https://api.github.com/repos/rhinstaller/anaconda","forks_url":"https://api.github.com/repos/rhinstaller/anaconda/forks","keys_url":"https://api.github.com/repos/rhinstaller/anaconda/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rhinstaller/anaconda/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rhinstaller/anaconda/teams","hooks_url":"https://api.github.com/repos/rhinstaller/anaconda/hooks","issue_events_url":"https://api.github.com/repos/rhinstaller/anaconda/issues/events{/number}","events_url":"https://api.github.com/repos/rhinstaller/anaconda/events","assignees_url":"https://api.github.com/repos/rhinstaller/anaconda/assignees{/user}","branches_url":"https://api.github.com/repos/rhinstaller/anaconda/branches{/branch}","tags_url":"https://api.github.com/repos/rhinstaller/anaconda/tags","blobs_url":"https://api.github.com/repos/rhinstaller/anaconda/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rhinstaller/anaconda/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rhinstaller/anaconda/git/refs{/sha}","trees_url":"https://api.github.com/repos/rhinstaller/anaconda/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rhinstaller/anaconda/statuses/{sha}","languages_url":"https://api.github.com/repos/rhinstaller/anaconda/languages","stargazers_url":"https://api.github.com/repos/rhinstaller/anaconda/stargazers","contributors_url":"https://api.github.com/repos/rhinstaller/anaconda/contributors","subscribers_url":"https://api.github.com/repos/rhinstaller/anaconda/subscribers","subscription_url":"https://api.github.com/repos/rhinstaller/anaconda/subscription","commits_url":"https://api.github.com/repos/rhinstaller/anaconda/commits{/sha}","git_commits_url":"https://api.github.com/repos/rhinstaller/anaconda/git/commits{/sha}","comments_url":"https://api.github.com/repos/rhinstaller/anaconda/comments{/number}","issue_comment_url":"https://api.github.com/repos/rhinstaller/anaconda/issues/comments{/number}","contents_url":"https://api.github.com/repos/rhinstaller/anaconda/contents/{+path}","compare_url":"https://api.github.com/repos/rhinstaller/anaconda/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rhinstaller/anaconda/merges","archive_url":"https://api.github.com/repos/rhinstaller/anaconda/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rhinstaller/anaconda/downloads","issues_url":"https://api.github.com/repos/rhinstaller/anaconda/issues{/number}","pulls_url":"https://api.github.com/repos/rhinstaller/anaconda/pulls{/number}","milestones_url":"https://api.github.com/repos/rhinstaller/anaconda/milestones{/number}","notifications_url":"https://api.github.com/repos/rhinstaller/anaconda/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rhinstaller/anaconda/labels{/name}","releases_url":"https://api.github.com/repos/rhinstaller/anaconda/releases{/id}","deployments_url":"https://api.github.com/repos/rhinstaller/anaconda/deployments","created_at":"2015-02-02T21:56:45Z","updated_at":"2017-07-28T03:17:55Z","pushed_at":"2017-07-27T17:00:01Z","git_url":"git://github.com/rhinstaller/anaconda.git","ssh_url":"git@github.com:rhinstaller/anaconda.git","clone_url":"https://github.com/rhinstaller/anaconda.git","svn_url":"https://github.com/rhinstaller/anaconda","homepage":null,"size":140496,"stargazers_count":106,"watchers_count":106,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":112,"mirror_url":null,"open_issues_count":46,"forks":112,"open_issues":46,"watchers":106,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130"},"html":{"href":"https://github.com/rhinstaller/anaconda/pull/1130"},"issue":{"href":"https://api.github.com/repos/rhinstaller/anaconda/issues/1130"},"comments":{"href":"https://api.github.com/repos/rhinstaller/anaconda/issues/1130/comments"},"review_comments":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130/comments"},"review_comment":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rhinstaller/anaconda/pulls/1130/commits"},"statuses":{"href":"https://api.github.com/repos/rhinstaller/anaconda/statuses/9c61e66b8436d1f49b2592b794e613b15e24ffd9"}}}}
{"actor":{"display_login":"jkonecny12"}}