Hello everybody. 1. I'm working on ticket #17093 [1]. You can see progress of work at github [2]. It's about rewritting django.template so it won't use global state. I wrote this post to tell you what I did so far, what issues I deal with and to discuss them. I hope that we will gain valuable experience in splitting Django into smaller parts.
2. This package has two kinds of global state. The first one are global attributes defined at module level of template package. These are base.builtins, base.libraries, loader.template_source_loaders and base.templatetags_modules. The second one are dependencies on settings and some other module-level attributes in template package like base.invalid_var_format_string (this is cache; it's a flag which means "is there '%s' in settings.TEMPLATE_STRING_IF_INVALID?") or context._standard_context_processors, which is cache set in context.get_standard_processors function which transform data from settings in some way. I decided to focus on the first kind of global state at first. 3. The goal is to write TemplateEngine class to encapsulate global state. For backward-compatibility, there will be one global instance of TemplateEngine and all functions like loader.get_template should delegate to the instance. Tests need to be rewritten. 4. What I did so far? I created base.TemplateEngine class and base.default_engine global instance. Some functions (mostly from loader module: get_template, find_template, select_templates and from base module: add_to_builtins, get_library, compile_string) delegates to appropriate methods of default_engine. I also partially rewrote tests (only tests/regressiontests/templates/tests.py) so they don't use default_engine directly. 5. The one issue is that the engine is needed everywhere. For example, consider creating base.Template instance. At the time of creation the template source is being compiled. The template may load a library, so the compilation cannot be done without list of all libraries defined in the engine. To sum up, we cannot just type Template(source), because this depends on template engine. I solved this issue by passing engine instance to Template constructor. In order not to break everything, I renamed Template class to _Template and created function called Template that does "return _Template(default_engine, **kwargs)". 6. The same issue occures when you consider including another template. loader_tags.IncludeNode needs to be able to load another template. I resolved this by passing an engine to base.Parser constructor. A parser passes itself to constructors of nodes, so IncludeNode can load another template in this way: "parser.engine.find_template(...)". 7. Then there is the issue of loaders. At the first, I thought that passing the engine will be not necessary if I change behaviour of loader.BaseLoader.load_template method so it will always return source of template instead of Template instance. So I did it for BaseLoader. Few days ago I did the same for loaders.cached.Loader, but I realised that this change means no caching at all -- the loader cached compiled templates. Now it caches nothing. The current architecture let us to cache template sources but not compiled templates. 8. There are two solutions. First, we can just pass the engine to every loader. Second, we can remove cache-loader and implement caching inside TemplateEngine. Both solutions are ugly. Do you see any better one? 9. Now, there is another issue. Changing API. It's obvious that changing base.Parser signature is OK since it's an internal structure. It's also easy to notice that base.Template is used everywhere so we cannot change API of Template. But there are many places where it's unclear if a method or class is public (it means that we promise we won't change its signature) or internal. For example, I don't know how much we can change loaders. I don't know if Django developers types "isinstance(obj, Template)" -- it won't work know. I don't know if I can change signature of SsiNode as I did. By the way, I think that we should make clear distinction between these two methods by using underscore convention. 10. As I said I'm trying to rewrite tests from tests.py file so they won't use the default engine. There left some sources of dependency on default engine. One of them are include tags in tests which load templates at the time of importing module [3] (or the source of problems is in base.Library.inclusion_tag?). Another one are template.response module [4] and all other test files (i. e. callables.py, custom.py). I didn't dig into these issues deeply yet since I wanted to publish my results and take some feedback. 11. A small issue is test regressiontests.views.tests.debug.DebugViewTests.test_template_loader_postmortem. The problem is in method django.views.debug.ExceptionReporter.get_traceback_data. It uses template_source_loaders global state which was removed. We can just use default_engine. I wonder when a Django developer uses a custom template engine instead of the global default one then they will get faulty error message. Is it important or negligible? 12. As I said there is another source of global-state -- django settings. I think we can deal with that by passing all necessary settings (like TEMPLATE_DEBUG, INSTALLED_APPS etc.) to TemplateEngine constructor. It means that we won't be able to create the default engine at the module-level since settings are not configured at the time of importing template package. We will have to make instancing default engine lazy. It means that there should be get_default_engine() function which creates the default engine at the very first call. I wonder if we will fall into circular imports hell or other problems... 13. At the end I would like to split the template package into new django-independent package (called i. e. 'template_engine') and django-specific package (named 'templates') which defines default_engine and all django-specific tags and filters. By django-independent I mean not depending on settings neigher other django modules excluding utils. I won't split tests in the same way - I think there is no need for that. 14. Is this plan good? What issues do you see? Could you look at my work and review it (I wonder especially if I'm using a python feature not supported by python version supported by Django?). If you have some experience at splitting Django into smaller parts, please share it with me. Tell me what I'm doing wrong (and what's good). If you are looking for having this ticket done, please tell me what is more important and what is negligible, what API do you expect the template engine? If you were working at the ticket, how would you complete the ticket? I hope that we can make Django better. [1] https://code.djangoproject.com/ticket/17093 [2] https://github.com/chrismedrela/django/tree/ticket_17093 [3] https://github.com/chrismedrela/django/blob/master/tests/regressiontests/templates/templatetags/custom.py#L106 [4] https://github.com/django/django/blob/master/django/template/response.py#L53 -- You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to django-developers@googlegroups.com. To unsubscribe from this group, send email to django-developers+unsubscr...@googlegroups.com. Visit this group at http://groups.google.com/group/django-developers?hl=en. For more options, visit https://groups.google.com/groups/opt_out.