{"action":"created","issue":{"url":"https://api.github.com/repos/coleifer/peewee/issues/804","labels_url":"https://api.github.com/repos/coleifer/peewee/issues/804/labels{/name}","comments_url":"https://api.github.com/repos/coleifer/peewee/issues/804/comments","events_url":"https://api.github.com/repos/coleifer/peewee/issues/804/events","html_url":"https://github.com/coleifer/peewee/issues/804","id":124333082,"number":804,"title":"Proposal for plugins support","user":{"login":"foxx","id":651797,"avatar_url":"https://avatars.githubusercontent.com/u/651797?v=3","gravatar_id":"","url":"https://api.github.com/users/foxx","html_url":"https://github.com/foxx","followers_url":"https://api.github.com/users/foxx/followers","following_url":"https://api.github.com/users/foxx/following{/other_user}","gists_url":"https://api.github.com/users/foxx/gists{/gist_id}","starred_url":"https://api.github.com/users/foxx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/foxx/subscriptions","organizations_url":"https://api.github.com/users/foxx/orgs","repos_url":"https://api.github.com/users/foxx/repos","events_url":"https://api.github.com/users/foxx/events{/privacy}","received_events_url":"https://api.github.com/users/foxx/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":15,"created_at":"2015-12-30T10:54:40Z","updated_at":"2016-01-08T12:22:40Z","closed_at":null,"body":"There is currently no easy way to build third party plugins within the same namespace as `peewee`, aside from monkeypatching.\r\n\r\nProjects such as [pytest][1] allow plugins to be registered into the Peewee namespace, giving a much cleaner syntax, as seen [here][2], for example;\r\n\r\n```python\r\nfrom pytest_raisesregexp import raisesregexp # without plugins\r\nfrom pytest import raises_regexp # with plugins\r\n```\r\n\r\nThis is apparently achieved via `entry_points` as seen [here][3], and allows the plugin to interact via hooks, as seen [here][4]. In particular, the plugin can access `pytest_namespace` which allows manipulation of the namespace.\r\n\r\nThe use case in this situation would be for providing third party plugins which are not suitable for the core or playhouse, whilst keeping everything in a single namespace.\r\n\r\n```python\r\nfrom peewee_dbmanager import DBManager # without plugins\r\nfrom peewee.dbmanager import DBManager # with plugins OR\r\nfrom peewee import DBManager # with plugins\r\n```\r\n\r\nThis could also open the door for potentially cleaning up Playhouse, allowing each of the `extensions` to be segregated into it's own plugin, keeping the core repo clean and lean.\r\n\r\nThoughts @coleifer ?\r\n\r\n[1]: https://pytest.org/latest/plugins.html\r\n[2]: https://github.com/kissgyorgy/pytest-raisesregexp\r\n[3]: http://pytest.org/2.2.4/plugins.html#making-your-plugin-installable-by-others\r\n[4]: http://pytest.org/2.2.4/plugins.html#well-specified-hooks"},"comment":{"url":"https://api.github.com/repos/coleifer/peewee/issues/comments/169984303","html_url":"https://github.com/coleifer/peewee/issues/804#issuecomment-169984303","issue_url":"https://api.github.com/repos/coleifer/peewee/issues/804","id":169984303,"user":{"login":"foxx","id":651797,"avatar_url":"https://avatars.githubusercontent.com/u/651797?v=3","gravatar_id":"","url":"https://api.github.com/users/foxx","html_url":"https://github.com/foxx","followers_url":"https://api.github.com/users/foxx/followers","following_url":"https://api.github.com/users/foxx/following{/other_user}","gists_url":"https://api.github.com/users/foxx/gists{/gist_id}","starred_url":"https://api.github.com/users/foxx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/foxx/subscriptions","organizations_url":"https://api.github.com/users/foxx/orgs","repos_url":"https://api.github.com/users/foxx/repos","events_url":"https://api.github.com/users/foxx/events{/privacy}","received_events_url":"https://api.github.com/users/foxx/received_events","type":"User","site_admin":false},"created_at":"2016-01-08T12:22:40Z","updated_at":"2016-01-08T12:22:40Z","body":"After some testing and consideration, I'm not convinced that `entry_points` is the correct approach for monkeypatch hooks. If this approach was used, then installing and importing a plugin would cause monkeypatching to happen behind the scenes, breaking the mantra of \"explicit is better than implicit\", meaning it's cleaner to call `someplugin.monkeypatch()`.\r\n\r\nThe problem in mine and @arikfr's use case is the inability to change Peewee internals without modifying global namespace, and a lack of hooks such as `before_execute` and `after_execute`. However even if we had those hooks, they would still impact global namespace due to the design of Peewee. Although we can subclass `Query` with the `_execute()` modifications, then apply this by subclassing `Model` and overloading `select()`, doing so could cause future problems because the entire `select()` class needs to be duplicated and modified.\r\n\r\nThe most cleanest implementation I can think of would be assigning class attributes to `Model` and `Database`, which are then used to determine the target class used. For example;\r\n\r\n```\r\nclass Model(with_metaclass(BaseModel)):\r\n    select_query_class = SelectQuery\r\n\r\n    @classmethod\r\n    def select(cls, *selection):\r\n        query = self.select_query_class(cls, *selection)\r\n\r\n```\r\n\r\nThis approach could be quite tedious however, resulting in a lot of boilerplate on each class. Instead it might be cleaner to use a single method which a subclass can implement;\r\n\r\n```\r\nclass Model(with_metaclass(BaseModel)):\r\n    def get_class(cls):\r\n        return cls\r\n\r\n    @classmethod\r\n    def select(cls, *selection):\r\n        query = self.get_class(SelectQuery)(cls, *selection)\r\n\r\nclass MySelectQuery(SelectQuery):\r\n    def _execute(self, *args, **kwargs):\r\n        print(\"do something\")\r\n        super()._execute(*args, **kwargs)\r\n\r\nclass MyModel(Model):\r\n    def get_class(cls):\r\n        if cls == SelectQuery:\r\n            return MySelectQuery\r\n        return cls\r\n```\r\n\r\nThe above would allow us to easily patch Peewee internals on a per instance basis, without resorting to monkeypatching or peewee core duplication.\r\n\r\nThoughts @arikfr / @coleifer ?"}}