Skip to content

Fast way to implement common interfaces #5169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Aug 23, 2021

Conversation

smotornyuk
Copy link
Member

@smotornyuk smotornyuk commented Jan 22, 2020

@amercader , hope you'll be interested in this pr - I just have some spare time and put what you suggested yesterday into code.

TL;DR
It would be cool, to quickly implement IActions, ITemplateHelpers, etc, without using boilerplates in the form:

  • add plugin.implemens
  • add required method
  • create some file with definitions
  • import this file
  • so on.

Instead, let's just quickly decorate plugin's class(explicitly, so there is no change in existing plugins and no magic - the same as Ian, I believe that magic ruins code);

Usage:

  1. Register everything. Working on different extensions, I've noticed that people tend to use the same layout for introducing common entities: helpers reside in helpers.py, actions and auth functions in logic.{action, auth}.py or submodules, and for blueprints/cli we suggest to use views.py and cli.py respectively. If you are following such structure, just decorate your plugin with toolkit.blanket_implementation() and you'll get ITemplateHelpers, IAuthFunctions, IActions, IBlueprint, IClick as long as corresponding import paths available. These interfaces will use a dictionary(or list in case of blueprint/cli) of items, registered in __all__ attribute of import path. i.e ckanext.ext.views.__all__ defines registered blueprints.
  2. Cover single interface. If you want to do it in few iterations, just provide the first argument to decorator - it must be an item from toolkit.Blanket enumeration. Available options are all, helper, auth, action, blueprint, cli. all is used by default.
  3. Use custom import paths. Even if you don't follow the layout described above, just provide source module as the second argument to decorator and don't bother with the file structure.
  4. Add some dynamics with functions and lambdas. There are some cases, when one may want some actions available depending on some config value. In that case, put a function into the second slot of decorator and decide what to do in runtime.

Why "blanket"? Because it "covers" interface, covers implementation, covers the import path. I don't know - it's kind of cute and I'm pretty bad in naming.

@wardi
Copy link
Contributor

wardi commented Jan 23, 2020

Here's my suggested interface, instead of

@tk.blanket_implementation(tk.Blanket.cli)
class ExampleBlanketCliPlugin(p.SingletonPlugin):
    pass

How about:

@tk.blanket_cli
class ExampleBlanketCliPlugin(p.SingletonPlugin):
    pass

Then we just have blanket_helper, blanket_auth, blanket_action, blanket_blueprint defined the same way. This is explicit, concise, provides a clear pattern for future blankets and allows each blanket to have its own custom parameters

@smotornyuk
Copy link
Member Author

smotornyuk commented Jan 23, 2020

I thought about something similar - after the discussion on Thursday having separate decorators has much more sense for me both in terms of extensibility and unambiguity(and I'm going to remove all option, whatever implementation we'll choose - it will only create more possibilities for incompatible changes every time blanket will change). it won't be difficult to add multiple decorators to class and it shouldn't look too cumbersome as long as decorators are simple.

The only thing I'm concerning about when it comes to registering every blanket as toolkit item - won't there be an overwhelming number of toolkit members if we continue with adding everything there? So, instead of tk.blanket_cli, tk.blanket.cli form looks tidier from my point.

And some thoughts about the future evolution of this decorator. If there will be cases when the interface has few methods and we want to cover all of them, there should be some implementation aligned to single-method interfaces. For example, in the case of IFacet, it would be quite straightforward to create facets.py with three methods [dataset_facets, group_facets, organization_facets], or pass as decorator argument function, that'll return required functions. But is there any sense to account such options, or let's from the very beginning pin blankets to only simplest single-method interfaces and other interfaces will be considered as requiring classical implementation?

Personally, even though it sounds like "cool, they allow us to write less code", I'm leaning to restricted use-cases of blankets - initial idea was to provide a simple mechanism of reducing plain boilerplate. If this mechanism will be hard to get, will have some complex use-cases - we'll just end up with having two equal ways of doing the same thing, what I don't like at all.

@smotornyuk
Copy link
Member Author

Updated as per @wardi 's suggestion. I've added documentation, and if anyone is too lazy to build it locally, here's how it looks:
blanket_docs

@smotornyuk smotornyuk mentioned this pull request Feb 7, 2020
@wardi wardi removed the To Discuss label Jun 26, 2020
@wardi
Copy link
Contributor

wardi commented Jun 26, 2020

@amercader any comments on this?

@wardi wardi added this to the CKAN 3.0 milestone Jun 26, 2020
@wardi
Copy link
Contributor

wardi commented Mar 9, 2021

@smotornyuk sorry I let this sit, can you fix up the conflict so we can merge. I like this feature

@smotornyuk
Copy link
Member Author

No problem, I won't be able to use this feature in production till the official release anyway:)

Copy link
Member

@amercader amercader left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smotornyuk Apologies for the long overdue review. This looks great. I added some minor comments.

And also rewrote the docs 🙃 I think this gives a clearer idea for users on how to use them and removes internal implementation details, but feel free to pick only the bits you think are useful:

https://gist.github.com/amercader/857ee22e9ea1c913bee3133cae81b45f

blueprint = enum.auto()
cli = enum.auto()
validator = enum.auto()
_all = helper | auth | action | blueprint | cli | validator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove this? I think we agreed that "blanket all the things automatically" was bad so perhaps is best to just not offer the option.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, i'm done with testing so this line doesn't have any value anymore

Comment on lines 205 to 208
result = {
item: getattr(subject, item)
for item in getattr(subject, u"__all__", [])
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this exclude private functions from modules? eg functions starting with _

I think this would be reasonable, and should be documented

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a note about this point and updated logic a bit.

When __all__ attribute is not defined:

  • all locally defined members whose name doesn't start with an underscore are exported

When __all__ attribute is defined:

  • only items listed there are exported

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

Comment on lines 25 to 26
- ``tk.blanket.auth``: implemets ``IAuthFunctions``
- ``tk.blanket.action``: implemets ``IActions``
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the interface names, should we use tk.blanket.auth_functions and tk.blanket.actions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, updated

@smotornyuk smotornyuk force-pushed the blanket-implementations branch from 45308ac to be56844 Compare August 19, 2021 17:08
@amercader
Copy link
Member

Thanks @smotornyuk for the changes!

blanket

@amercader amercader merged commit 3a28581 into ckan:master Aug 23, 2021
@amercader
Copy link
Member

Sorry @smotornyuk , not sure why but when merging this PEP8 tests failed in master. Can you have a look?

@smotornyuk
Copy link
Member Author

Sure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants