I'm writing a website generator with various classes that represent the content in the webpages such as Page, NewsPost, Tag, Category etc.
I'd like to be able to construct these objects plainly, and I don't have a problem with that.
However, I'd also like to construct these objects within a certain context - say, the context of a website with a particular root URL. Let's say I put this context into an instance of a class ContentManager. This is the code I ultimately hope to end up with:
page = Page(title='Test Page', content='hello world!')
assert page.cm == None
cm = ContentManager(root_url='//localhost')
page = cm.Page(title='Test Page', content='hello world!')
assert page.cm == cm
I can easily manage this if page.cm is a per-instance property set in __init__, but I need to call class methods on cm.Page which need access to the cm object, so it has to be a static property.
If I just set it as a static property on the Page class, it would end up affecting other ContentManagers pages as well, which is not desirable.
How would I achieve this? Metaclasses? Or some sort of class factory function?
One solution could be creating a subclass of Page for every ContentManage instance:
class Page:
cm = None
def __init__(self, title, content):
self.title = title
self.content = content
class ContentManager:
def __init__(self, root_url):
class PerContentManagerPage(Page):
cm = self
self.Page = PerContentManagerPage
page0 = Page(title='Test Page', content='hello world!')
cm = ContentManager(root_url='//localhost')
page = cm.Page(title='Test Page', content='hello world!')
cm2 = ContentManager(root_url='//localhost')
page2 = cm2.Page(title='Test Page 2', content='hello world!')
assert page0.cm is None
assert page.cm == cm
assert page2.cm == cm2
In python a class is also an object (an instance of its metaclass). This solution creates a new subclass of Page every time you instantiate ContentManager. This means that the cm.Page class isn't the same as the cm2.Page class but both are the subclasses of Page. This is why it's possible that cm.Page.cm and cm2.Page.cm have different values, because these are two separate classes (or class objects).
Note: Although in python this could be solved by creating subclass objects dynamically, problems usually have better solutions. Creating classes/subclasses dynamically is a warning sign (HACK).
I'm still convinced that you shouldn't create a page subclass for each content manager instance. Instead I would simply use instances of the global ContentManager and Page classes by connecting them with references to each other in a suitable way and putting the data and the code into instance attributes/methods.
Setting everything else aside, you'll just need to dynamically construct a class to tie to each instance of ContentManager; we can do this using the built-in type function, which can either, with one argument, give us the type of an object, or, with three arguments (class name, base classes, and class dictionary) construct a new class.
Here's a sample of how that might look in your situation:
class Page(object):
# This is just a default value if we construct a Page
# outside the context of a ContentManager
cm = None
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
#classmethod
def do_class_thing(cls):
return cls.cm
class ContentManager(object):
def __init__(self, root_url):
self.url = root_url
"""
This is where the magic happens. We're telling type() to
construct a class, with the class name ContentManagerPage,
have it inherit from the above explicitly-declared Page
class, and then overriding its __dict__ such that the class
cm variable is set to be the ContentManager we're
constructing it from.
"""
self.Page = type(str('ContentManagerPage'), (Page,), {'cm': self})
Once you've got all this set up, it's simple enough to do exactly what you're trying to do, with cm as a class variable.
Related
More generally speaking I want add a custom admin Panel to list some related content. To lookup this related content I need to pass the current instance of the model or at least its ID to this panel. How can I do that within these lists in which these admin panels are noted?
Here is my specific example of an ArtistPage. In the editor I would like to add a panel to list WorkPages that are related to this ArtistPage:
from wagtail.models import Page
class ArtistPage(Page):
# ...
content_panels = [
# ...
ListWorksPanel(artist=self), # This doesn’t work
]
The panel itself is defined like that, mostly copied from the HelpPanel:
from wagtail.admin.panels import Panel
class ListWorksPanel(Panel):
def __init__(self, artist="", template="admin/list_works_panel.html", **kwargs,):
super().__init__(**kwargs)
self.artist = artist
self.template = template
def clone_kwargs(self):
kwargs = super().clone_kwargs()
del kwargs["help_text"]
kwargs.update(
artist=self.artist,
template=self.template,
)
return kwargs
class BoundPanel(Panel.BoundPanel):
def __init__(self, panel, instance, request, form):
super().__init__(panel, instance, request, form)
self.template_name = self.panel.template
self.artist = self.panel.artist
This is more a general Python question, I think. I know how to pass "self" in functions. But how does that work here with this class as element of a list? I reckon that the __init__() method of the ArtistPage is the way to go, but I cannot figure out how exactly.
What is the pythonic way of passing "self" to another class?
Update (Solution):
Following #gasman’s aswer, I just added the get_context_data method to the BoundPanel class. The works are accessible in the template of the panel now!
class ListWorksPanel(Panel):
def __init__(self, artist="", template="admin/list_works_panel.html", **kwargs,):
super().__init__(**kwargs)
self.artist = artist
self.template = template
def clone_kwargs(self):
kwargs = super().clone_kwargs()
del kwargs["help_text"]
kwargs.update(
artist=self.artist,
template=self.template,
)
return kwargs
class BoundPanel(Panel.BoundPanel):
def __init__(self, panel, instance, request, form):
super().__init__(panel, instance, request, form)
self.template_name = self.panel.template
self.artist = self.panel.artist
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context['works'] = self.instance.works.all() # exactly what I needed
return context
The ArtistPage instance is passed to BoundPanel.__init__ as the keyword argument instance. All code that deals with an individual ArtistPage needs to be written inside the BoundPanel class.
When you write ListWorksPanel() as part of a content_panels definition, you're creating a ListWorksPanel instance that then becomes part of the definition of the ArtistPage class. At this point in the code, no actual instance of ArtistPage exists, so there's no self to refer to. Effectively, there's a single ListWorksPanel object shared by all ArtistPage instances that will ever be created.
When the time comes to render the edit form for an individual page, Wagtail calls get_bound_panel on the ListWorksPanel object, passing the page instance along with the form and request objects. (The full process is explained here.) This returns an instance of BoundPanel, which is a template component that performs the final rendering. In this case, you probably want to define a get_context_data method on BoundPanel that does something like context['works'] = self.instance.works.all() - this will then make the variable works available on the template.
i'm trying to create back-end app using FastApi and sqlAchemy. I have a lot of entities which has relations with database. So, my question is: How to speed up development? Now i write for each entity code:
#app.get("/holidays")
def getHolidays():
session = Session(bind=engine)
holidays: List[Holiday] = session.query(Holiday).all()
return [x.to_json() for x in holidays]
#app.get("/exclusive_operations")
def getExclusiveOperations():
session = Session(bind=engine)
exclusive_operations: List[ExclusiveOperation] = session.query(ExclusiveOperation).all()
return [x.to_json() for x in exclusive_operations]
#app.get('/category_descriptions')
def getCategoryDescr():
session = Session(bind=engine)
category_descrs: List[CategoryDescr] = session.query(CategoryDescr).all()
return [x.to_json() for x in category_descrs]
So if i want to create all crud operations, i need to create 12 typical methods for 3 entities. Maybe another solution exists?
It is Python - as a dynamic language, the functions and methods are created at runtime. The "#app.get" decorator is what registers your views in the application, not their existence in the top level of a module.
Therefore, you can create a for loop that simply recreates and registers the view for each of your entities - it can be done either at the module level or inside a function.
(it is nice to have in mind that the "#xxxx" decorator syntax is just syntax sugar for calling the decorator passing the decorated function as its sole parameter)
for Entity, name in [(Holiday, "holidays"), (ExclusiveOperation, "exclusive_operations"), (CategoryDescr, "category_descriptions")]:
def freeze_var_wrapper(Entity, name):
# this intermediary function is needed, otherwise the Entity and name
# variables would be always up-to-date inside the view function
# and always point to the last value in the external for-loop after
# it finished execution:
def view():
session = Session(bind=engine)
entities = session.query(Entity).all()
return [x.to_json() for x in entities]
# optional, may facilitate debugging:
view.__name__ = f"get{Entity.__name__}s"
# actually registers the view function with the framework:
# (could be done in the same line, without the "view_registrer" var)
view_registrer = app.get(f"/{name}")
view_registrer(view)
freeze_var_wrapper(Entity, name)
There are other ways of doing this that might remove the boiler-plate and look more elegant - for example with class inheritance and an apropriate __init__subclass__in a base class (even if the framework does not use "class views", we will register the bound method for each class, which is just a callable):
class BaseView:
Entity: cls
view_name: str
def __init_subclass__(cls, *args, **kw):
super().__init_subclass__(*args, **kw)
app.get(f"/{cls.view_name}")(cls.view)
# above, cls.view is bound to the subclass being processed, therefore
# the class attributes as defined in each class body are used inside the method
# this could easily register post, delete and detail views as well
#classmethod
def view(cls);
session = Session(bind=engine)
entities = session.query(cls.Entity).all()
return [x.to_json() for x in entities]
class HolydayView(BaseView):
Entity = Holyday
view_name = "holydays"
# thats is just it.
class ExclusiveOperationView(BaseView):
Entity = ExclusiveOperation
view_name = "exclusive_operations"
class CatewgoryDescriptionView(BaseView):
Entity = CategoryDescription
view_name = "category_descriptions"
I'm writing a wrapper for the GMAIL API. In this wrapper, I am trying to include subattributes in the "main class" so it more closely follows the below:
Previously, I was use methods such as:
class Foo:
def __init__(self, ...):
# add some attributes
def get_method(self, ...):
return some_stuff
This allows me to do foo.get_method(...). To follow the GMAIL API, I try to do:
class Foo:
def __init__(self, ...):
# add some attributes
#property
def method(self):
class _Method:
#staticmethod
def get(self, ...):
return some_stuff
return _Method()
Which allows me to do foo.method.get(...). The above has some problems, it redefines the class every time, and I have to add #staticmethod above every method as part of it. I do realise that I could create the class at the outer class level, and set a hidden variable for each which then .method returns or creates, but this seems like too much workaround.
tldr: Is it possible to make the instance passed to the inner class as self be the instance of the outer class (I do not wish to have to pass the attributes of the outer class to each inner class).
Instead of sharing the self parameter between classes, you are probably better off just passing the things you need to the constructor of the class you instantiate.
class Messages:
def __init__(self, name):
self.name = name
def method(self, other_arg):
return self.name + other_arg
class Test:
name = "hi"
def __init__(self):
self.messages = Messages(name=self.name)
If you need to pass a lot of information to the constructor and it starts becoming unwieldy, you can do something like split the shared code into a third class, and then pass that between the Test and Messages classes as a single object.
In Python there are all sorts of clever things that you can do with metaclasses and magic methods, but in 99% of cases just refactoring things into different classes and functions will get you more readable and maintainable code.
Users should have an instance of messages, which allows method get. The scetch for code is:
class Messages:
...
def get()
...
class Users:
...
messages = Messages(...)
allows
users = Users()
users.messages.get()
The bad thing in this API is plural names, which is a bad sign for class. If done from scratch you would rather have classes User and Message, which make more sense.
If you have a closer look at GET/POST calls in the API you link provided, you would notice the urls are like UserId/settings, another hint to implement User class, not Users.
self in the methods reference the self of the outer class
maybe this is what you want factory-method
Although the example code I'll provide bellow might be similar to the already provided answers, and the link above to another answer might satify you wish, because it is slight different formed I'll still provide my vision on what you asked. The code is self explanatory.
class User:
def __init__(self, pk, name):
self.pk = pk
self.name = name
self._messages = None
def messages(self):
if self.messages is None:
self._messages = Messages(self.pk)
return self._messages
class Messages:
def __init__(self, usr):
self.usr = usr
def get(self):
return self._grab_data()
def _grab_data(self):
# grab the data from DB
if self.usr == 1:
print('All messages of usr 1')
elif self.usr == 2:
print('All messages of usr 2')
elif self.usr == 3:
print('All messages of usr 3')
one = User(1, 'One')
two = User(2, 'Two')
three = User(3, 'Three')
one.messages().get()
two.messages().get()
three.messages().get()
The messages method approach practical would be the same for labels, history etc.
Edit: I'll give one more try to myself trying to understand what you want to achieve, even though you said that
I have tried numerous things with defining the classes outside of the container class [...]
. I don't know if you tried inheritance, since your inner class me, despite it quite don't represent nothing here, but still looks like you want to make use of its functionality somehow. You said as well
self in the methods reference the self of the outer class
This sounds to me like you want inheritance at the end.
Then the way to go would be (a proximity idea by using inheritance):
class me(object):
def __init__(self):
self.__other_arg = None # private and hidden variable
# setter and getter methods
def set_other_arg(self, new_other_arg):
self.__other_arg = new_other_arg
def get_other_arg(self):
return self.__other_arg
class Test(me):
name = 'Class Test'
#property
def message(self):
other_arg = self.get_other_arg()
if other_arg is not None:
return '{} {}'.format(self.name, other_arg)
else:
return self.name
t = Test()
t.set_other_arg('said Hello')
print(t.message)
# output >>> Class Test said Hello
I think this could be a preferable way to go rather than your inner class approach, my opinion, you'll decide. Just one side note, look up for getter and setter in python, it might help you if you want to stick with the inheritance idea given.
I'm building an HTTP API and I factored out a lot of code into a superclass that handles requests to a collection of objects. In my subclass, I specify what database models the operation should work on and the superclass takes care of the rest.
This means that I don't need to re-implement the get, post, etc. methods from the superclass, however, I want to change their docstrings in the subclass so that I can have some documentation more specific to the actual model the endpoint is operating on.
What is the cleanest way to inherit the parent class's functionality but change the docstrings?
Example:
class CollectionApi(Resource):
"""Operate on a collection of something.
"""
class Meta(object):
model = None
schema = None
def get(self):
"""Return a list of collections.
"""
# snip
def post(self):
"""Create a new item in this collection.
"""
# snip
class ActivityListApi(CollectionApi):
"""Operations on the collection of Activities.
"""
class Meta(object):
model = models.Activity
schema = schemas.ActivitySchema
Specifically, I need ActivityListApi to have get and post run like in CollectionApi, but I want different docstrings (for automatic documentation's sake).
I can do this:
def get(self):
"""More detailed docs
"""
return super(ActivityListApi, self).get()
But this seems messy.
class CollectionApi(Resource):
"""Operate on a collection of something.
"""
def _get(self):
"""actual work... lotsa techy doc here!
the get methods only serve to have something to hang
their user docstrings onto
"""
pass
def get(self):
"""user-intended doc for CollectionApi"""
return self._get()
class ActivityListApi(CollectionApi):
def get(self):
"""user-intended doc for ActivityListApi"""
return self._get()
Using the django-tagging app as an example, I would like to change the manager of the Tag model so that I can replace it with an extended one:
# mytagging/models.py
from django.db import models
from tagging.models import TagManager, Tag
class MyTagManager(TagManager):
def update_tags(self, obj, tag_names):
# My actions
return super(MyTagManager, self).update_tags(obj, tag_names)
def add_tag(self, obj, tag_name):
# My actions
return super(MyTagManager, self).add_tag(obj, tag_name)
Tag.objects = MyTagManager
Now, Tag.objects = MyTagManager doesn't work, nor did I expect it to, but it illustrates what I would like to accomplish. I could very well create class MyTag(Tag) and set the manager that way, but then it would seem that I would also have to extend every other class that uses Tag and change it to MyTag.
I tried Tag.objects = MyTagManager() to initialize the class, but I get the error 'NoneType' object has no attribute '_meta' from a query object, it appears.
The above code renders this error when calling Tag.objects.update_tags(kwargs['instance'], tags) from admin page:
unbound method update_tags() must be called with MyTagManager instance
as first argument (got LibraryFile instance instead)
The LibraryFile model is the one I'm attempting to tag, and should therefore be the second argument instead of first (self being first).
Use proxy model with the different manager:
class MyTag(Tag):
objects = MyTagManager()
class Meta:
proxy = True
Using Secator's suggestion as part of the solution, I ended up just monkey-patching the Tag model itself where needed instead of the Tag.objects.
This being the final code:
from tagging.models import TagManager, Tag
import tagging
class MyTagManager(TagManager):
def update_tags(self, obj, tag_names):
# My actions
return super(MyTagManager, self).update_tags(obj, tag_names)
def add_tag(self, obj, tag_name):
# My actions
return super(MyTagManager, self).add_tag(obj, tag_name)
class MyTag(Tag):
objects = MyTagManager()
class Meta:
proxy = True
tagging.models.Tag = MyTag
tagging.fields.Tag = MyTag