Package: mixt
Write html components directly in python and you have a beautiful but controversial MIXTure.Yes,controversial.If you don’t like it, ignore it(but you can use this without the html-in-python part, see below ;))Basedonpyxl. Python 3.6+ only, and use typing for data validation.Once you have your html, you can do whatever you want with it. Think of it as a replacement for your classic template engine.Source code:https://github.com/twidi/mixt/Documentation:https://twidi.github.io/mixt/PyPI:https://pypi.org/project/mixt/CI(CircleCi):https://circleci.com/gh/twidi/workflows/mixt/Basic usageLet’s create a fileexample.py# coding: mixtfrommixtimporthtml,Element,RequiredclassHello(Element):classPropTypes:name:Required[str]defrender(self,context):return<div>Hello,{self.name}</div>print(<Helloname="World"/>)And execute it:$pythonexample.py<div>Hello,World</div>If you don’t like to write html in python, you can still use it:frommixtimporthtml,Element,RequiredclassHello(Element):classPropTypes:name:Required[str]defrender(self,context):returnhtml.Div()("Hello, ",self.name)print(Hello(name="World"))FeaturesYes it is inspired by React (in fact, mainly JSX) and we borrow some of the concept:props and PropTypes with validationdev-mode to validate props in dev but not in production to speed things up (your tests should have tested that everything is ok)contextclass components or simple function componentshigh order componentsAnd we added:write css using pythoncss/js collectorsproxy componentsInstallationRun these two commands. The second one will tell python how to understand files with html inside.pipinstallmixtmixt-post-installTo check that everything is ready, run:python-mmixt.examples.simpleYou should have this output:<divtitle="Greeting">Hello, World</div>If you don’t want to use the html-in-python stuff, don’t runmixt-post-install. And then test with (to have the same output):python-mmixt.examples.simple_pure_pythonContributeClone the git project then:makedevTo check that everything is ready, run:python-mmixt.examples.simpleYou should have this output:<divtitle="Greeting">Hello, World</div>After having done some code:maketestsmakelintIf you touch things in thecodecdirectory, you’ll have to runmake dev(or at leastmakefull-clean) to purge thepycpython files.Note that our CI will check that every commit passes themake lint,make testsandmakecheck-doc. So don’t forget to run these for each commit.One way to do it before pushing is:gitrebasedevelop--exec'git log -n 1; make checks'User GuideNote: You can find thefinalcode of this user guide insrc/mixt/examples/user_guide(you’ll findmixt.pyandpure_python.py).Run it with:python-mmixt.examples.user_guideStartLet’s create a… todo list, yeah!But before, remember. This is not React, it’s not on the browser and there is no Javascript involved here. We only talk about rendering some HTML.But you can do what you want with it. Add javascript handlers, simple forms…Talking about forms…In a todo list, we want to be able to add a todo. It’s a simple text input.So let’s create our first component, theTodoForm. We want a form with an input text and a button.A component is a subclass of theElementclass, with arendermethod you have to write.# coding: mixtfrommixtimportElement,html# html is mandatory to resolve html tagsclassTodoForm(Element):defrender(self,context):# Ignore the ``context`` argument for now.return\# The ``\`` is only for a better indentation below<formmethod="post"action="???"><label>NewTodo:</label><itextname="todo"/><buttontype="submit">Add</button></form>Note that this could have been written as a simple function:# coding: mixtfrommixtimportElement,htmldefTodoForm():return\<formmethod="post"action="???"><label>NewTodo:</label><itextname="todo"/><buttontype="submit">Add</button></form>When print the component, these two will give the same result:print(<TodoForm/>)<formmethod="post"action="???"><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>SpacingNotice how it is formatted: no space between tags. In fact, it’slike in JSX:JSX removes whitespace at the beginning and ending of a line. It also removes blank lines. New lines adjacent to tags are removed; new lines that occur in the middle of string literals are condensed into a single spaceTo add a space, or a newline, you can pass some python. Let’s, as an example, add a newline before the label:#...<formmethod="post"action="???">{'\n'}<label>NewTodo:</label><itextname="todo"/>#...Now we have this output:<formmethod="post"action="/todo/add"><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>PropsNow let’s go further.Notice theactionattribute of the form. We need to pass something. But hard-coding it does not sound right. Wwe need to pass it to the component.Mixthas, likeReact, the concept of properties, aka “props”.PropTypes classInMixt, we define them with a type, in a class inside our component, namedPropTypes:classTodoForm(Element):classPropTypes:add_url:strdefrender(self,context):return\<formmethod="post"action={self.add_url}><label>NewTodo:</label><itextname="todo"/><buttontype="submit">Add</button></form>Here we defined a prop namedadd_urlwhich must be a string (str). This uses thepython typing syntax.And notice how we changed theactionattribute of theformtag. It’s now{self.add_url}instead of"???".When attributes are passed between curly braces, they are interpreted as pure python at run-time. In fact, as themixtparser will convert the whole file to pure python before letting the python interpreter running it, it will stay the same, only the html around will be converted. So there is no penalty to do this.Props and childrenLook how this would look like if our component was written in pure python:frommixtimportElement,htmlclassTodoForm(Element):classPropTypes:add_url:strdefrender(self,context):returnhtml.Form(method='post',action=self.add_url)(html.Label()(html.Raw("New Todo: ")),html.InputText(name='todo'),html.Button(type='submit')(html.Raw("Add")# or html.Rawhtml(text="Add")),)Notice how the props are passed to a component as named arguments and howactionis passed:action=self.add_url.This pure-python component also shows you how it works: props are passed as named argument to the component class, then this component is called, passing children components as positional arguments to the call:ComponentClass(prop1="val1",prop2="val2")(Children1(),Children2(),)What are children? Children are tags inside other tags.In<divid="divid"><span/><p>foo</p></div>, we have:ahtml.Divcomponent, with a propidand two children:ahtml.Spancomponent, without childrenahtml.Pcomponent with one child:ahtml.RawHtmlcomponent with the text “foo”Note that you can play with props and children. First the version in pure-python to show how it works:defrender(self,context):props={"prop1":"val1","prop2":"val2"}children=[Children1(),Children2()]returnComponentClass(**props)(*children)# You can pass a list of children to to the call, so this would produce the same result:# ComponentClass(**props)(children)Then themixtversion:defrender(self,context):props={"prop1":"val1","prop2":"val2"}children=[<Children1/>,<Children2/>]return<ComponentClass{**props}>{*children}</ComponentClass># or, the same, passing the children as a list:# return <ComponentClass {**props}>{children}</ComponentClass>Passing propsNow let’s go back to our propsadd_url.How to pass it to the component?The exact same way we passed attributes to HTML tags: they are in fact props defined in the HTML compoments (defined inmixt.html). We support every HTML tags that, at the time of the writing, are valid (not deprecated) in HTML5, with their attributes (excluding the deprecated ones).So let’s do this:print(<TodoFormadd_url="/todo/add"/>)<formmethod="post"action="/todo/add"><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>OK, we have our prop present in the rendered HTML.ValidationWhat if we don’t pass a string? We said inPropTypesthat we wanted a string…NumbersLet’s try it:print(<TodoFormadd_url=1/>)<formmethod="post"action="1"><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It works! But… it’s not a string!! In fact, there is a special case for numbers, you can pass them as numbers instead of strings and they are converted if needed…Booleans and other special casesSo let’s try something else.print(<TodoFormadd_url=True/>)mixt.exceptions.InvalidPropValueError:<TodoForm>.add_url:`True`isnotavalidvalueforthisprop(type:<class'bool'>, expected: <class 'str'>)And it’s the same if we passTruein pythonprint(<TodoFormadd_url={True}/>)mixt.exceptions.InvalidPropValueError:<TodoForm>.add_url:`True`isnotavalidvalueforthisprop(type:<class'bool'>, expected: <class 'str'>)Ok, let’s trick the system and pass"True", as a string.print(<TodoFormadd_url="True"/>)mixt.exceptions.InvalidPropValueError:<TodoForm>.add_url:`True`isnotavalidvalueforthisprop(type:<class'bool'>, expected: <class 'str'>)Still the same, but here we passed a string! Yes but there are 4 values that are always evaluated to what they seems to be:TrueFalseNoneNotProvided (a special value meaning “not set” which is different thatNone)The only way to pass one of these values as a string, is to pass them via python, as a string:print(<TodoFormadd_url={"True"}/>)<formmethod="post"action="True"><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>Except these 4 values, and numbers, every value that is passed to an attribute is considered a string. Even if there is no quotes, like in html in HTML5, where quotes are not mandatory for strings without some characters (no spaces, no/…).To pass something else, you must surround the value in curly braces (and in this cases there is no need for quotes around the curly braces.Ok, now we are sure that we only accept string…. but what if I pass nothing? And… what is “nothing”?Let’s start with an empty string in python:print(<TodoFormadd_url={""}/>)<formmethod="post"action=""><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>Ok it works, we wanted a string, we have a string.Now let’s pass this empty string directly:print(<TodoFormadd_url=""/>)<formmethod="post"action=""><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It still works, because it’s still a string. Let’s remove the quotes, to see.print(<TodoFormadd_url=/>)mixt.exceptions.GeneralParserError:<mixtparser>UnclosedTags:<TodoForm>Hum yeah, this is not valid HTML. So let’s remove the=:print(<TodoFormadd_url/>)mixt.exceptions.InvalidPropValueError:<TodoForm>.add_url:`True`isnotavalidvalueforthisprop(type:<class'bool'>, expected: <class 'str'>)WHAT? Yes, think about HTML5 attributes likerequired,checked… They only need to be present as an attribute, without value, to be consideredTrue. So when an attribute doesn’t have any value, it’s a boolean, and it’sTrue.In addition to not pass a value, those two other ways are valid in HTML5 for a boolean to byTrue:pass an empty string:required=""pass the name of the attribute:required="required"For your convenience, we added another way:passTrue(case does not matter), as python or as a string:required=True,required={True},required="true"And its counterpart, to passFalse:passFalse(case does not matter), as python or as a string:required=False,required={False},required="false"Required propsOk for the boolean attributes. It’s not our case. The last thing we can do is to not set the attribute at all:print(<TodoForm/>)# this is the same: ``print(<TodoForm add_url=NotProvided />)```# (``NotProvided`` must be imported from ``mixt``)mixt.exceptions.UnsetPropError:<TodoForm>.add_url:propisnotsetIt’s understandable: we try to access a prop that is not set, of course we cannot use it.But what if we don’t access it? If we don’t print the component, it won’t be rendered:<TodoForm/><TodoFormat0x7fbd18ea5630>So we can create an instance but it will fail at render time. But there is a way to prevent that.By default, all properties are optional. And you don’t have to use theOptionaltype from the pythontypingmodule for that, it would be cumbersome to do it for each prop.Instead,mixtprovides a type namedRequiredthat you use the same way thanOptionnal.frommixtimportElement,Required,htmlclassTodoForm(Element):classPropTypes:add_url:Required[str]defrender(self,context):# ...So we just said we wanted a string, and that it is required.Let’s try again to create it without the prop:<TodoForm/>mixt.exceptions.RequiredPropError:<TodoForm>.add_url:isarequiredpropbutisnotsetNow we have the exception raised earlier in our program.Default propsTo see other possibilities in props, let’s add a new one to change the text label. But we don’t want to make it required, and instead have a default value.For this, it’s as easy as adding a value to the prop in thePropTypesclass:classTodoForm(Element):classPropTypes:add_url:Required[str]label:str='New Todo'defrender(self,context):return\<formmethod="post"action={self.add_url}><label>{self.label}:</label><itextname="todo"/><buttontype="submit">Add</button></form>Now let’s try it without passing the prop:print(<TodoFormadd_url="/todo/add"/>)<formmethod="post"action=""><label>New Todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>And if we pass one:print(<TodoFormadd_url="/todo/add"label="Thing to do"/>)<formmethod="post"action="/todo/add"><label>Thing to do:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It works as expected.Note that you cannot give a default value while having the propRequired. It makes no sense, so it’s checked as soon as possible, while theclassis constructed:classTodoForm(Element):classPropTypes:add_url:Required[str]label:Required[str]='New Todo'mixt.exceptions.PropTypeRequiredError:<TodoForm>.label:apropwithadefaultvaluecannotberequiredAnd of course the default value must match the type!classTodoForm(Element):classPropTypes:add_url:Required[str]label:str={'label':'foo'}mixt.exceptions.InvalidPropValueError:<TodoForm>.label:`{'label':'foo'}`isnotavalidvalueforthisprop(type:<class'dict'>, expected: <class 'str'>)ChoicesAnother thing we want to do in our component is to let it construct the label, passing it a “type” of todo, but limiting the choices. For this we can use theChoicestype:frommixtimportChoices,Element,Required,htmlclassTodoForm(Element):classPropTypes:add_url:Required[str]type:Choices=['todo','thing']defrender(self,context):return\<formmethod="post"action={self.add_url}><label>New{self.type}:</label><itextname="todo"/><buttontype="submit">Add</button></form>Let’s try it:print(<TodoFormadd_url="/todo/add"type="todo"/>)print(<TodoFormadd_url="/todo/add"type="thing"/>)<formmethod="post"action="/todo/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><formmethod="post"action="/todo/add"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>And what if we try to pass something else than the available choices? It fails, as expected:print(<TodoFormadd_url="/todo/add"type="stuff"/>)mixt.exceptions.InvalidPropChoiceError:<TodoForm>.type:`stuff`isnotavalidchoiceforthisprop(mustbein['todo','thing'])Default choicesBut maybe we don’t want to pass it and use a default value. What would the result be?print(<TodoFormadd_url="/todo/add"/>)mixt.exceptions.UnsetPropError:<TodoForm>.type:propisnotsetSo we have to mark thetypeprop as required:classPropTypes:add_url:Required[str]type:Required[Choices]=['todo','thing']So if we don’t pass it, it fails earlier:print(<TodoFormadd_url="/todo/add"/>)mixt.exceptions.RequiredPropError:<TodoForm>.type:isarequiredpropbutisnotsetBut it’s not what we want, we want a default value.In fact, you noticed that for types other thanChoices, setting a value inPropTypesgives us a default value. But forChoicesit’s different, as the value is the list of choices.For this, we haveDefaultChoices: it work the same asChoices, but use the first entry in the list as the default value. And of course, as with other types having default, it cannot beRequired.Let’s try it:frommixtimportDefaultChoices,Element,Required,htmlclassTodoForm(Element):classPropTypes:add_url:Required[str]type:DefaultChoices=['todo','thing']print(<TodoFormadd_url="/todo/add"/>)<formmethod="post"action="/todo/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It works as expected.Advanced typesUntil then, we used simple types, but you can use more complicated ones.So for example, we’ll make theadd_urlprop to accept a function that will compute the url for us based on thetypeprop. But we also want to allow strings, and with a default value.We can do that, with typing. Our function will take a string, thetypeand will return a string, the url.So thesyntaxisCallable[[str],str]for the callable, and we useUnionto accept things of typeCallableorstr:fromtypingimportUnion,CallablefrommixtimportDefaultChoices,Element,Required,htmlclassTodoForm(Element):classPropTypes:add_url:Union[Callable[[str],str],str]="/todo/add"type:DefaultChoices=['todo','thing']defrender(self,context):ifcallable(self.add_url):add_url=self.add_url(self.type)else:add_url=self.add_urlreturn\<formmethod="post"action={add_url}><label>New{self.type}:</label><itextname="todo"/><buttontype="submit">Add</button></form>First, let’s try it without theadd_urlprop, as we have a default:print(<TodoForm/>)<formmethod="post"action="/todo/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It should work too if we pass a string:print(<TodoFormadd_url="/todolist/add"/>)<formmethod="post"action="/todolist/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>And now we can pass a function:defmake_url(type):returnf"/{type}/add"print(<TodoFormadd_url={make_url}/>)mixt.exceptions.InvalidPropValueError:<TodoForm>.add_url:`<functionmake_urlat0x7fe2ae87be18>`isnotavalidvalueforthisprop(type:<class'function'>, expected: Union[Callable[[str], str], str])Oh? Why? I passed a function accepting a string as argument and returning a string. Yes, but don’t forget that types are checked! So we must add types to our function:defmake_url(type:str)->str:returnf"/{type}/add"print(<TodoFormadd_url={make_url}/>)<formmethod="post"action="/todo/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>And if we pass another type, the url should change accordingly:print(<TodoFormadd_url={make_url}type="thing"/>)<formmethod="post"action="/thing/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>We can even make this function the default value for our prop:fromtypingimportUnion,CallablefrommixtimportDefaultChoices,Element,Required,htmldefmake_url(type:str)->str:returnf"/{type}/add"classTodoForm(Element):classPropTypes:add_url:Union[Callable[[str],str],str]=make_urltype:DefaultChoices=['todo','thing']defrender(self,context):ifcallable(self.add_url):add_url=self.add_url(self.type)else:add_url=self.add_urlreturn\<formmethod="post"action={add_url}><label>New{self.type}:</label><itextname="todo"/><buttontype="submit">Add</button></form>print(<TodoForm/>)<formmethod="post"action="/todo/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>dev-modeNow you may start wondering… python typing is cumbersome and validating may take away some of our precious time.Let’s me answer that:No, typing is not cumbersome. It’s really useful to spot bugs and add some self-documentation.Yes, it takes away some of our precious time. But we got you covered.By default,mixtrun in “dev-mode”. And in dev-mode, props are validated when passed to a component. When you are NOT in “dev-mode”, the validation is skipped. So in production, you can deactivate the dev-mode (we’ll see how in a minute) and pass props very fast:we don’t check required props (but that would fail if you try to use it in your compoment)we don’t check if aChoicesprop is, indeed, in the list of choiceswe don’t check the type at all, so for example if you want to pass a list for a string, it will work but with understandable strange things happening in yourrendermethod.But you may say that it’s in production that validation is important. Indeed. But of course your code is fully covered by tests, that you run in dev-mode, and so in production, you don’t need this validation! And note that it’s how React works, by the way, withNODE_ENV=production.How to change dev-mode? We don’t enforce any environment variable but we propose some functions. It’s up to you to call them:frommixtimportset_dev_mode,unset_dev_mode,override_dev_mode,in_dev_mode# by default, dev-mode is activeassertin_dev_mode()# you can unset the dev-modeunset_dev_mode()assertnotin_dev_mode()# and set it backset_dev_mode()assertin_dev_mode()# set_dev_mode can take a booleanset_dev_mode(False)assertnotin_dev_mode()set_dev_mode(True)assertin_dev_mode()# and we have a context manager to override for a blockwithoverride_dev_mode(False):assertnotin_dev_mode()withoverride_dev_mode(True):assertin_dev_mode()assertnotin_dev_mode()assertin_dev_mode()So let’s try this with thetypeprop. Remember, it looks like:type:DefaultChoices=['todo','thing']We try to pass another choice, first in dev-mode:withoverride_dev_mode(True):print(<TodoFormtype="stuff"/>)mixt.exceptions.InvalidPropChoiceError:<TodoForm>.type:`stuff`isnotavalidchoiceforthisprop(mustbein['todo','thing'])It fails as expected.And now by deactivating dev-mode:withoverride_dev_mode(False):print(<TodoFormtype="stuff"/>)<formmethod="post"action="/stuff/add"><label>New stuff:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form>It works, we have a todo type that was not in our choices that is used, and is in theactiontoo. It’s the work of your tests to ensure that you never pass invalid props, so you can be confident in production and deactivate dev-mode.Components cascadeNow we have our form. What other components do we need for our todo list app?Of course, we need a way to display a todo entry.But what is a todo entry? Let’s create a basicTodoObject:classTodoObject:def__init__(self,text):self.text=textIt’s a very simple class, but you can use what you want, of course. It could be Django models, etc…So we can create ourTodocomponent, making it accept a requiredTodoObjectas prop:classTodo(Element):classPropTypes:todo:Required[TodoObject]defrender(self,context):return<li>{self.todo.text}</li>And we can use it:todo=TodoObject("foo")print(<Todotodo={todo}/>)<li>foo</li>Now we want to have a list of todos. Let’s create aTodoListcomponent that will accept as props a list ofTodoObject.But what is different than our two other components, that only use html tags in theirrendermethod, it’s that now we will encapsulate a component into another. Let’s see how.classTodoList(Element):classPropTypes:todos:Required[List[TodoObject]]defrender(self,context):return<ul>{[<Todotodo={todo}/>fortodoinself.todos]}</ul>Yes, it’s as simple as that: you use<Todo...>for theTodocomponent as you would use an HTML tag. The only difference is that for html tags, you don’t need to import them directly (simple importhtmlfrommixt), and by convention we write them in lower-case. For normal components, you have to import them (you can still dofrom mylib import componentsand<components.MyComponent...>) and use the exact case.Notice how we required a list, and passed it into the<ul>via a list-comprehension in curly-braces.You can do things differently if you want.Like separating the list comprehension from the html:defrender(self,context):todos=[<Todotodo={todo}/>fortodoinself.todos]return<ul>{todos}</ul>Or in a dedicated method (that would be useful for testing):defrender_todos(self,todos):return[<Todotodo={todo}/>fortodointodos]defrender(self,context):return<ul>{self.render_todos(self.todos)}</ul>It’s up to you: at the end it’s just python.Let’s see what is rendered by this component:todos=[TodoObject("foo"),TodoObject("bar"),TodoObject("baz")]print(<TodoListtodos={todos}/>)<ul><li>foo</li><li>bar</li><li>baz</li></ul>And finally we have ourTodoAppcomponent that encapsulate the form and the list:classTodoApp(Element):classPropTypes:todos:Required[List[TodoObject]]type:DefaultChoices=['todo','thing']defrender(self,context):return\<div><h1>The"{self.type}"list</h1><TodoFormtype={self.type}/><TodoListtodos={self.todos}/></div>todos=[TodoObject("foo"),TodoObject("bar"),TodoObject("baz")]print(<TodoListtodos={todos}type="thing"/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>foo</li><li>bar</li><li>baz</li></ul></div>Let’s pass this HTML to an HTML beautifier:<div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ul><li>foo</li><li>bar</li><li>baz</li></ul></div>And that’s it, we have our todo-list app! To use it in a page, just create a component that will render the html base markup and integrate theTodoAppcomponent in it. You don’t even need a component:todos=[TodoObject("foo"),TodoObject("bar"),TodoObject("baz")]print(<html><body><TodoApptodos={todos}type="thing"/></body></html>)The beautified output would be:<html><body><div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ul><li>foo</li><li>bar</li><li>baz</li></ul></div></body></html>Overriding a componentWe have a generic todo-list, but following the available types of todo, we may want to have a “todo-list” and a “thing-list”.We already have the todo list because ourTodoApphas a type oftodoby default.So let’s create aThingApp.InheritanceThe first way of doing this is to inherit from ourTodoApp. But by inheriting we cannot remove props from the parent (it’s not really true, we’ll see this later), so we still have thetypeprop by default. But we don’t want to accept anything else than “thing”. So we can redefine thetypeprop like this:classThingApp(TodoApp):classPropTypes:type:DefaultChoices=['thing']Let’s use this component:print(<ThingApptodos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ul><li>foo</li></ul></div>If we try to pass “todo” for thetypeprops, it won’t work:print(<ThingApptodos={[TodoObject("foo")]}type="todo"/>)mixt.exceptions.InvalidPropChoiceError:<ThingApp>.type:`todo`isnotavalidchoiceforthisprop(mustbein['thing'])But still, it’s strange to be able to pass a type.Parent componentsLet’s try another way: A parent component. A component that does nothing else that doing things with its children and returning it. What we want here, is a component that will return aTodoAppwith thetypeprop forced to “thing”.Let’s do thisclassThingApp(Element):classPropTypes:todos:Required[List[TodoObject]]defrender(self,context):return<TodoApptodos={self.todos}type="thing"/>print(<ThingApptodos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"><label>New todo:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ul><li>foo</li></ul></div>It works, and this time, we cannot pass thetypeprop:print(<ThingApptodos={[TodoObject("foo")]}/>)mixt.exceptions.InvalidPropNameError:<ThingApp>.type:isnotanallowedpropPropTypes DRYnessNotice how we had to define the type for thetodosprops. Both inTodoAppandTodoThing.There are many ways to handle that.The first one would be to ignore the type inThingAppbecause it will be checked inTodoApp. So we’ll use the typeAny:fromtypingimportAny#...classThingApp(Element):classPropTypes:todos:Any#...Let’s try with a valid list of todos:print(<ThingApptodos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>foo</li></ul></div>But what if we pass something else?print(<ThingApptodos="foo, bar"/>)mixt.exceptions.InvalidPropValueError:<TodoApp>.todos:`foo,bar`isnotavalidvalueforthisprop(type:<class'str'>, expected: List[TodoObject])It works as expected but the error is reported at theTodoApplevel, which is perfectly normal.Another way would be to defined the type at a higher level:TodoObjects=Required[List[TodoObject]]classTodoApp(Element):classPropTypes:todos:TodoObjects# ...classThingApp(Element):classPropTypes:todos:TodoObjects# ...Now if we pass something else, we have the error reported at the correct level:print(<ThingApptodos="foo, bar"/>)mixt.exceptions.InvalidPropValueError:<TodoThing>.todos:`foo,bar`isnotavalidvalueforthisprop(type:<class'str'>, expected: List[TodoObject])But if you can’t or don’t want to do that, you can keep the type defined inTodoAppet use theprop_typeclass method of a component to get the type of a prop:classThingApp(Element):classPropTypes:todos:TodoApp.prop_type("todos")# ...But does it really matter to have the error raised forThingApporTodoApp? Because at the end, it’s reallyTodoAppthat have to do the check.So this should be a way to do this in a more generic way..FunctionWe saw earlier that a component can be a single function to render a component. It just have to return a component, an html tag. One difference with class components is that there is notPropTypesso no validation. But… it’s exactly what we need.We want ourThingAppto accept some props (thetodosprop), and return aTodoAppwith a specifictypeprop.So we could do:defThingApp(todos):return<TodoApptype="thing"todos={todos}/>Here we can see that we cannot passtypetoThingsApp, it is not a valid argument.Let’s try it:print(<ThingApptodos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>foo</li></ul></div>Here we have only one prop to pass so it’s easy. But imagine if we have many ones. We can use the{**props}syntax:defThingApp(**props):return<TodoApptype="thing"{**props}/>And you can do with even fewer characters (if it counts):ThingApp=lambda**props:<TodoApptype="thing"{**props}/>These two fonctions behave exactly the same.And you cannot pass atypeprop because it would be a python error, as it would be passed twice toTodoApp:print(<ThingApptodos={[TodoObject("foo")]}type="thing"/>)TypeError:BaseMetaclassobjectgotmultiplevaluesforkeywordargument'type'(yes it talks aboutBaseMetaclasswhich is the metaclass that creates our components classes)And any other wrong props would be validated byTodoApp:print(<ThingApptodos={[TodoObject("foo")]}foo="bar"/>)mixt.exceptions.InvalidPropNameError:<TodoApp>.foo:isnotanallowedpropWith this in mind, we could have created a generic fonction that force the type of any component accepting atypeprop:Thingify=lambdacomponent,**props:<componenttype="thing"{**props}/>print(<Thingifycomponent={TodoApp}todos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>foo</li></ul></div>The rendered component isTodoApp, thetypeprop is “thing” and the other props (here onlytodos) are correctly passed.Higher-order componentsNow extend this concept to a more generic case: “higher-order components”. InReact a “high order component”, is “a function that takes a component and returns a new component.”The idea is:EnhancedComponent=higherOrderComponent(WrappedComponent)A classic way of doing it is to return a new component class:defhigherOrderComponent(WrappedComponent):classHOC(Element):__display_name__=f"higherOrderComponent({WrappedComponent.__display_name__})"classPropTypes(WrappedComponent.PropTypes):passdefrender(self,context):return<WrappedComponent{**self.props}>{self.childre()}</WrappedComponent>returnHOCNotice how we set thePropTypesclass to inherit from the one of the wrapped component, and how we pass all the props to the wrapped component, along with the children. With the returned component will accept the same props, with the same types, as the wrapped one.And also notice the__display_name__. It will be used in exceptions to let you now the component that raised it. Here, without forcing it, it would have been set toHOC, which is not helpful. Instead, we indicate that it is a transformed version of the passed component.Here it is a function that does nothing useful.In our example we could have done this:defthingify(WrappedComponent):classHOC(Element):__display_name__=f"thingify({WrappedComponent.__display_name__})"classPropTypes(WrappedComponent.PropTypes):__exclude__={'type'}defrender(self,context):return<WrappedComponenttype="thing"{**self.props}>{self.children()}</WrappedComponent>returnHOCTwo important things here:notice how we use__exclude__ = {'type'}to remove thetypeprop from the ones we inherit fromWrappedComponent.PropTypes. So the returned component will expect the exact same props as the wrapped one, except fortype.we added{self.children()}in the rendered wrapped component, because even if we actually know that the component we’ll wrap,TodoApp, doesn’t take children (it could but it does nothing with them), we cannot say in advance that it will always be the case, and also that this higher-order component won’t be used to wrap another component thanTodoApp. So it’s better to always do this.And now we can create ourThingApp:ThingApp=thingify(TodoApp)And use it:print(<ThingApptodos={[TodoObject("foo")]}/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>foo</li></ul></div>If we try to pass the type:print(<ThingApptodos={[TodoObject("foo")]}type="thing"/>)mixt.exceptions.InvalidPropNameError:<thingify(TodoApp)>.type:isnotanallowedpropSo as planned, we cannot pass the type. And notice how the__display_name__is used.Let’s think about how powerful this is.Let say we want to keep ourTodoApptake a list ofTodoObject. But we want to get them from a “source”.We can even directly write it this new higher-order-component in a generic way:deffrom_data_source(WrappedComponent,prop_name,get_source):classHOC(Element):__display_name__=f"from_data_source({WrappedComponent.__display_name__})"classPropTypes(WrappedComponent.PropTypes):__exclude__={prop_name}defrender(self,context):props=self.props.copy()props[prop_name]=get_source(props,context)return<WrappedComponent{**props}>{self.children()}</WrappedComponent>returnHOCThis time, the functionfrom_data_sourcetakes two arguments in addition to theWrappedComponent:prop_name: it’s the name of the prop of the wrapped component to fill with some dataget_source: it’s a function that will be called to get the dataLook how we inherited thePropTypesfrom the wrapped component and how we excludedprop_name. So we don’t have (and can’t) pass the data to our new component.And then inrender, we set a prop to pass toWrappedComponentwith the result of a call toget_source.So let’s write a very simple function (this could be a complicated one with caching, filtering…) that take the props and the context, and returns some data:defget_todos(props,context):# here it could be a call to a databasereturn[TodoObject("fooooo"),TodoObject("baaaar"),]And we can compose our component:SourcedTodoApp=from_data_source(TodoApp,'todos',get_todos)ThingApp=thingify(SourcedTodoApp)And run it:print(<ThingApp/>)<div><h1>The "thing" list</h1><form>...</form><ul><li>fooooo</li><li>baaaar</li></ul></div>It works as expected, and the data is fetched only when the component needs to be rendered.ContextSo, we have a todo list, that can fetch data from an external source. But we may want the data to be different depending on the user.What we can do, it’s at the main level, get our user and passing it on every component to be sure that each component is able to get the current logged in user.Wouldn’t it be cumbersome?Solving this use case is the exact purpose of theContextconcept provided bymixt. It is, of course,inspired by the concept of context in React.And as they said:Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.Creating a context is as simple as creating a component, except that it will inherits fromBaseContextand doesn’t need arendermethod (it will render its children).And it takes aPropTypesclass, that define the types of data the context will accept and pass down the tree.So let’s create our context that will hold the id of the authenticated user.frommixtimportBaseContextclassUserContext(BaseContext):classPropTypes:authenticated_user_id:Required[int]Now, we want to update ourget_todosmethod to take theauthenticated_user_idinto account.Remember, we passed it the props and the context. The context will be useful here:defget_todos(props,context):return{1:[TodoObject("1-1"),TodoObject("1-2"),],2:[TodoObject("2-1"),TodoObject("2-2"),]}[context.authenticated_user_id]And now we can render our app with the context:print(<UserContextauthenticated_user_id=1><ThingApp/></UserContext>)<div><h1>The"thing"list</h1><form>...</form><ul><li>1-1</li><li>1-2</li></ul></div>We can see the todo entries for the user 1.Let’s try with the user 2:print(<UserContextauthenticated_user_id=2><ThingApp/></UserContext>)<div><h1>The"thing"list</h1><form>...</form><ul><li>2-1</li><li>2-2</li></ul></div>We can see the todo entries for the user 2.In this case of course we could have passed the user id as a prop. But imagine the todo app being deep in the components tree, it’s a lot easier to pass it this way.But as said in the React documentation:Don’t use context just to avoid passing props a few levels down. Stick to cases where the same data needs to be accessed in many components at multiple levels.When there is no context, thecontextargument of therendermethod is set toEmptyContextand not toNone. So you can directly use thehas_propmethod to check if a prop is available via the context.Let’s update theget_todosfunctions to return an empty list of todo objects if there is not authenticated user.defget_todos(props,context):ifnotcontext.has_prop('authenticated_user_id')ornotcontext.authenticated_user_id:return[]return{1:[TodoObject("1-1"),TodoObject("1-2"),],2:[TodoObject("2-1"),TodoObject("2-2"),]}[context.authenticated_user_id]Let’s try this:print(<ThingApp/>)<div><h1>The"thing"list</h1><form>...</form><ul></ul></div>And it still works with a user in the context:print(<UserContextauthenticated_user_id=1><ThingApp/></UserContext>)<div><h1>The"thing"list</h1><form>...</form><ul><li>1-1</li><li>1-2</li></ul></div>Important note about contexts: you can have many contexts! But defining the same prop in many contexts may lead to undefined behaviour.Style and JavascriptEverybody loves a beautiful design, and maybe some interaction.It is easily doable: we generate HTML and HTML can contains some CSS and JS.Let’s add some interaction first: when adding an item in theTodoForm, let’s add it to the list.First we add in ourTodoFormcomponent arender_javascriptmethod that will host our (bad, we could do better but it’s not the point) javascript:classTodoForm(Element):# ...defrender_javascript(self,context):returnhtml.Raw("""
function on_todo_add_submit(form) {
    var text = form.todo.value;
    alert(text);
}
        """)To start we only display the new todo text.Now update ourrendermethod to return this javascript (note that the use of arender_javascriptmethod is only to separate concerns, it could have been in therendermethod directly.classTodoForm(Element):# ...defrender(self,context):# ...return\<Fragment><script>{self.render_javascript(context)}</script><formmethod="post"action={add_url}onsubmit="return on_todo_add_submit(this);"><label>New{self.type}:</label><itextname="todo"/><buttontype="submit">Add</button></form></Fragment>Notice theFragmenttag. It’s a way to encapsulate many elements to be returned, like in React. It could have been a simple list but with comas at the end:return[<script>...</script>,<form>...</form>]Now we want to add an item to the list. It’s not the role of theTodoFormto do this, but to the list. So we’ll add some JS in theTodoListcomponent: a function that take some text and create a new entry.As forTodoForm, we add arender_javascriptmethod with (still bad) javascript:classTodoList(Element):# ...defrender_javascript(self,context):todo_placeholder=<Todotodo={TodoObject(text='placeholder')}/>returnhtml.Raw("""
TODO_TEMPLATE = "%s";
function add_todo(text) {
    var html = TODO_TEMPLATE.replace("placeholder", text);
    var ul = document.querySelector('#todo-items');
    ul.innerHTML = html + ul.innerHTML;
}
        """%(todo_placeholder))And we update ourrendermethod to add the<script>tag and anidto theultag, used in the javascript:classTodoList(Element):# ...defrender(self,context):return\<Fragment><script>{self.render_javascript(context)}</script><ulid="todo-items">{[<Todotodo={todo}/>fortodoinself.todos]}</ul></Fragment>And now we can update therender_javascriptmethod of theTodoFormcomponent to use our newadd_totojavascript function:classTodoForm(Element):# ...defrender_javascript(self,context):returnhtml.Raw("""
function on_todo_add_submit(form) {
    var text = form.todo.value;
    add_todo(text);
}
        """)And that’s all. Nothing special, in fact.But let’s take a look at the output of ouTodoApp:print(<UserContextauthenticated_user_id=1><ThingApp/></User>)The beautified output is:<div><h1>The "thing" list</h1><script>functionon_todo_add_submit(form){vartext=form.todo.value;add_todo(text);}</script><formmethod="post"action="/thing/add"onsubmit="return on_todo_add_submit(this);"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><script>TODO_TEMPLATE="<li>placeholder</li>";functionadd_todo(text){varhtml=TODO_TEMPLATE.replace("placeholder",text);varul=document.querySelector('#todo-items');ul.innerHTML=html+ul.innerHTML;}</script><ulid="todo-items"><li>1-1</li><li>1-2</li></ul></div>So we have manyscripttag. It could be great to have only one.Collectorsmixtcomes with a way to “collect” parts of what is rendered to put them somewhere else. We have at our disposal two simple collectors, to be used as components:JSCollectorandCSSCollector.These components collect parts of their children tree.Collector.CollectThe first way is by using the collectorCollecttag.First let’s change our main call:frommixtimportJSCollectorprint(<JSCollectorrender_position="after"><UserContextauthenticated_user_id=1><ThingApp/></User></JSCollector>)This will collect the content of all theJSCollector.Collecttag.Let’s update ourTodoFormand replace ourscripttag by aJSCollector.Collecttag:classTodoForm(Element):# ...defrender(self,context):ifcallable(self.add_url):add_url=self.add_url(self.type)else:add_url=self.add_urlreturn\<JSCollector.Collect>{self.render_javascript(context)}</JSCollector.Collect><formmethod="post"action={add_url}onsubmit="return on_todo_add_submit(this);"><label>New{self.type}:</label><itextname="todo"/><buttontype="submit">Add</button></form></Fragment>We can do the same with theTodoList:classTodoList(Element):# ...defrender(self,context):return\<Fragment><JSCollector.Collect>{self.render_javascript(context)}</JSCollector.Collect><ulid="todo-items">{[<Todotodo={todo}/>fortodoinself.todos]}</ul></Fragment>Now let’s run our updated code:print(<JSCollectorrender_position="after"><UserContextauthenticated_user_id=1><ThingApp/></User></JSCollector>)The beautified output is:<div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"onsubmit="return on_todo_add_submit(this);"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ulid="todo-items"><li>1-1</li><li>1-2</li></ul></div><scripttype="text/javascript">functionon_todo_add_submit(form){vartext=form.todo.value;add_todo(text);}TODO_TEMPLATE="<li>placeholder</li>";functionadd_todo(text){varhtml=TODO_TEMPLATE.replace("placeholder",text);varul=document.querySelector('#todo-items');ul.innerHTML=html+ul.innerHTML;}</script>As you can see, all the scripts are in a singlescripttag, at the end. More precisely, at the end of where theJSCollectortag was, because we usedrender_position="after". Another possibility isrender_position="before"to put this where theJSCollectortag started.All of this work exactly the same way for theCSSCollectortag, where content is put in a<styletype="text/css>tag.render_[js|css] methodsAs using JS/CSS is quite common in the HTML world, we added some sugar to make all of this even easier to do.If you have arender_jsmethod, theJSCollectorwill automatically collect the result of this method. Same forCSSSelectorand therender_cssmethod.With this, no need for aJSCollector.Collecttag.To make this work in our example, inTodoFormandTodoList:remove theJSCollector.Collecttagsremove the now unneededFragmenttagsrename therender_javascriptmethods torender_js.remove the call tohtml.Rawinrender_jsas it’s not needed when the collector callsrender_jsitself: if the output is a string, it is considered a “raw” oneThis way we have exactly the same result.render_[js|css]_global methodsIt works now because we only have one instance of a child with arender_jsmethod.But if we have many children, this method will be called for each child. If fact, it should only contains code that is very specific to this instance.To serve js/css only once for a Component class, we have to userender_js_globalorrender_css_global(expected to beclassmethod)It will be collected the first time, and only the first time, an instance is found, before collecting therender_jsmethod.So here, we can change ourrender_jstorender_js_global, decorate them with@classmethodand it will still work the same.referencesWe now are able to regroup javascript or style. But what if we want to put it elsewhere, like in theheadtag or at the end of thebodytag?It’s possible with references, aka “refs”. It’s the same context as in React, without the DOM part of course.You create a ref, pass it to a component, and you can use it anywhere.Let’s update our main code to do this.First we create a ref.frommixtimportRefjs_ref=Ref()This will create a new object that will hold a reference to a component. In a component, you don’t need to importRefand can usejs_ref = self.add_ref(), but we are not in a component here.To save a ref, we simply pass it to therefprop:<JSCollectorref={js_ref}>...</JSCollector>Notice that we removed therender_positionprop, because now we don’t want the JS to be put before or after the tag, but elsewhere.To access the component referenced by a ref, use thecurrentattribute:js_collector=js_ref.currentOf course this can be done only AFTER the rendering.How can we use this to add ascripttag in ourhead.First update our html to include the classichtml,headandbodytags:returnstr(<html><head></head><body><JSCollectorref={js_ref}><UserContextauthenticated_user_id=1><ThingApp/></UserContext></JSCollector></body></html>)At this point we don’t have anyscripttag in the output:<html><head></head><body><div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"onsubmit="return on_todo_add_submit(this);"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ulid="todo-items"><li>1-1</li><li>1-2</li></ul></div></body></html>First thing to know: a collector is able to render all the things it collected by calliing itsrender_collectedmethod.And remembering that it already includes thescripttag, we may want to do this:# ...<head>{js_ref.current.render_collected()}</head># ...but this doesn’t work:AttributeError:'NoneType'objecthasnoattribute'render_collected'It’s because we try to access the current value at render time. It must be done after.For this, we can use a feature ofmixt: if something added to the tree is a callable, it will be called after the rendering, when converting to string.So we can use for example a lambda:# ...<head>{lambda:js_ref.current.render_collected()}</head># ...And now it works:<html><head><scripttype="text/javascript">functionon_todo_add_submit(form){vartext=form.todo.value;add_todo(text);}TODO_TEMPLATE="<li>placeholder</li>";functionadd_todo(text){varhtml=TODO_TEMPLATE.replace("placeholder",text);varul=document.querySelector('#todo-items');ul.innerHTML=html+ul.innerHTML;}</script></head><body><div><h1>The "thing" list</h1><formmethod="post"action="/thing/add"onsubmit="return on_todo_add_submit(this);"><label>New thing:</label><inputtype="text"name="todo"/><buttontype="submit">Add</button></form><ulid="todo-items"><li>1-1</li><li>1-2</li></ul></div></body></html>User guide conclusionHurray we made it! All the main features ofmixtexplained. You can now usemixtin your own projects.APIAs a next step, you may want to readthe API documentation.