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.


Reply via email to