Thursday, 16 July 2026

Optimizing Django-hypergen

Untitled Document.md

Making hypergen’s render path fast

Hypergen renders HTML by walking a tree of Python objects, backed by a
pyrsistent (immutable) context. That’s convenient but slow. Three commits,
in order.

1. Cache the pyrsistent lookups (~4.2x)

contextlist.data re-derived the active target_id from the pmap on
every append/extend/len/getitem, and base_element.__init__ re-read
c.hypergen from scratch 6+ times per element. Neither needed to —
nothing changes context mid-call.

# before
@property
def data(self):
    return self.contexts[self._get_context_value()]
# after — cache keyed by context-snapshot identity (O(1) check)
@property
def data(self):
    token = context.ctx
    if token is not self._cache_ctx_token:
        self._cache_ctx_token = token
        self._cache_list = self.contexts[self._get_context_value()]
    return self._cache_list
# before — c.hypergen re-walked on every access
def __init__(self, *children, **attrs):
    with ExitStack() as stack:
        ...
        for plugin in c.hypergen.plugins: ...
        c.hypergen["ids"].add(id_)
        self.i = len(c.hypergen.into)
        c.hypergen.into.extend(self.start())
# after — fetch once, reuse
def __init__(self, *children, **attrs):
    hg = c.hypergen
    with ExitStack() as stack:
        ...
        for plugin in hg.plugins: ...
        hg["ids"].add(id_)
        into = hg.into
        self.i = len(into)
        into.extend(self.start())

2000 rows / 100 cards: 925ms → 221ms.

2. Trim per-element dispatch (~1.1x more)

A dead assert, and two hot functions checked the rare case before the
common one.

# before — assert re-proves what the fetch above already proved
hg = c.hypergen
...
assert "hypergen" in c, "Element called outside hypergen context."
# after — dropped; a successful c.hypergen fetch already guarantees it
hg = c.hypergen
...
# before — str is the common case, checked last
def fmt(html):
    for item in html:
        if issubclass(type(item), base_element):
            yield item.as_string()
        elif callable(item):
            yield item()
# after — str checked first
def fmt(html):
    for item in html:
        item_type = type(item)
        if item_type is str:
            yield item
        elif issubclass(item_type, base_element):
            yield item.as_string()
        elif callable(item):
            yield item()
# before
def make_string(s):
    if s is None:
        return ""
    else:
        return force_str(s)
# after — skip the cross-module call for the common case
def make_string(s):
    if type(s) is str:
        return s
    elif s is None:
        return ""
    else:
        return force_str(s)

Tried replacing html.escape()'s chained .replace() calls with a single
.translate() pass. Measured it as a regression: translate() always
copies; replace() on a no-op returns the input unchanged. Reverted.

221ms → 194ms (4.8x cumulative).

3. Fix UserList iteration (~19%)

UserList is a class from Python’s standard library (collections.UserList) — a wrapper around a plain list that’s designed to be subclassed. Instead of subclassing list directly, you subclass UserList, and it stores the actual data in a .data attribute (a real list) while UserList itself provides all the list-like methods (append, __getitem__, __len__, etc.) by delegating to self.data.

Why use it instead of subclassing list directly? Because UserList makes it trivial to override behavior — e.g. hypergen’s contextlist (in src/hypergen/context.py) overrides .data itself to be a property rather than a plain attribute, so that “the list” resolves to a different underlying list depending on the current pyrsistent context (the active target_id). You can’t do that cleanly with a raw list subclass, since list’s storage isn’t a separate overridable attribute.
UserList has no __iter__, so it fell through to collections.abc.Sequence’s mixin, which iterates via repeated __getitem__ — re-entering the .data
property (and its identity check) once per item.

# before — no __iter__ defined, falls back to Sequence mixin's
# per-index __getitem__ loop, re-entering .data every time

# after
def __iter__(self):
    return iter(self.data)

Free 19% on typical page sizes.

Takeaway

Cache before you rewrite. All three steps got 4.8x+ cumulative out of the
existing architecture with no behavior change and no new dependencies —
verified with the existing test suite plus a byte-for-byte diff of
rendered output before/after.

Monday, 5 January 2026

How to setup pyright with sublime when it's installed with homebrew

Just open file /Users/myuser/Library/Application Support/Sublime Text/Packages/User/LSP-pyright.sublime-settings
and add new path for nodejs and pyright

{

"command": ["/opt/homebrew/bin/node", "/opt/homebrew/lib/node_modules/pyright/langserver.index.js", "--stdio"]

}

Tuesday, 9 September 2025

Zuban lsp with sublime text using uv virtual env

install zuban as said in docs

create starter shell script to activate venv

```bash

#!/bin/bash

source "$(uv python find | sed 's/python3$/activate/')"

/opt/homebrew/bin/zuban server

```

 in LSP.sublime-settings add this

{
"log_debug": true,
"clients": {
    "zuban": {
      // enable this configuration
      "enabled": true,
      // the startup command -- what you would type in a terminal
      "command": ["/bin/bash", "/opt/homebrew/bin/zubanvenv"],
      "selector": "source.python"
    }
}
}

Wednesday, 12 June 2024

Pydantic (or Django Ninja) + Timeflake

 So if you want to publish some model to your rest api, and model is declared using Pydantic or Django Ninja (which is using Pydantic under the hood), here is the solution I came with:

from typing import Any, Annotated

from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler, TypeAdapter
from pydantic.types import AnyType
from pydantic_core import CoreSchema, core_schema
from pydantic.json_schema import JsonSchemaValue


def vld(x: Any) -> Any:
return x

class TimeflakeType:

@classmethod
def __get_pydantic_core_schema__(
cls, source: type[Any], handler: GetCoreSchemaHandler
) -> core_schema.CoreSchema:
serializer = core_schema.plain_serializer_function_ser_schema(cls._serialize, when_used='json')
if cls is source:
return core_schema.no_info_plain_validator_function(
function=vld, serialization=serializer
)
else:
return core_schema.no_info_before_validator_function(
function=vld, schema=handler(source), serialization=serializer
)
@classmethod
def __get_pydantic_json_schema__(cls, cs: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue:
return handler(core_schema.str_schema())

@staticmethod
def _serialize(v: Any) -> str:
return str(v)

def __repr__(self) -> str:
return 'Timeflake'

This code is based on ImportString type declaration in standard Pydantic repo. 

Sunday, 7 January 2018

Getting started with N2O Erlang web framework

Synrc.com guys did a great job with creating N2O framework. Let's try to make some app with it.
But before we start we need to install MAD. Mad is a deps management & build kind of tool.
Let's download it. Open this link and press Download button.  What we've just got is a file with compiled erlang bytecode inside and it's self executable.
Place it somewere in your $PATH (for example "/usr/local/bin") just to be able to execute it anywhere in your console.
Then you can type "mad app helloworld" which is going to create a new directory called helloworld with a bunch of files in it.
helloworld/rebar.config will have something like this in it:
{sub_dirs,["apps"]}.
{deps_dir,"deps"}.
{deps, [
    {erlydtl,".*", {git, "git://github.com/evanmiller/erlydtl", {tag, "0.8.0"}  }},
    {cowboy, ".*", {git, "git://github.com/extend/cowboy",      {tag, "1.0.1"}  }},
    {gproc,  ".*", {git, "git://github.com/uwiger/gproc.git",   {tag, "0.3"}    }},
    {fs,     ".*", {git, "git://github.com/synrc/fs",           {tag, "1.9"}    }},
    {sh,     ".*", {git, "git://github.com/synrc/sh",           {tag, "1.9"}    }},
    {mad,    ".*", {git, "git://github.com/synrc/mad",          {tag, "1.9"} }},
    {active, ".*", {git, "git://github.com/synrc/active",       {tag, "1.9"} }},
    {nitro,  ".*", {git, "git://github.com/synrc/nitro",        {tag, "0.9"} }},
    {n2o,    ".*", {git, "git://github.com/synrc/n2o",          {tag, "4.4"} }},
]}.
These are dependencies, which MAD is going to download and place in a deps_dir, which happens to be "deps" as it says on line 2.   As you can see n2o is in the list already.

So now we can just try to run the template app. Type this commands:
cd helloworld
mad dep com pla rep
If everythings runs smoothly you are going to see some console output. And then you can press Enter and there's "1> " line at the bottom, which means Erlang virtual machine started successfully and opened REPL for you.
First argument of  "mad dep com pla rep" command is "dep" which is "fetch all my dependencies from rebar.config". After that comes "com" which is "compile all the files in sub_dirs, and deps_dir".  And then comes "pla" which is "search over the project files and build the plan of applications structure so I can be sure of right start order of my apps"
And finally there's "rep" which is "open the repl".

And now if you look into sys.config you're going to see something like this
[{n2o, [{port,8001},{route,routes},{log_modules,sample}]}].
Aha, the auto-generated template says to start n2o on port 8001. Since we already started our project with MAD let's open "localhost:8001" in the browser.




And here it is, our first app on N2O.

Sunday, 26 February 2017

Quick Tip: find all Django tags in Sublime Text

Here's the quick tip:
1. Open find panel at the bottom: Ctrl (or Cmd on MacOS) + F
2. Toggle Regular Expression button. It has .* on it.
3. And here is the trickiest part: {% block \w+ %}(?s).*?{% endblock \w+ %}
It's the regex for finding all django tags

Monday, 6 April 2015

Mithril.js cram.js curl.js

При использовании библиотеки mithril если использовать только curl, т.е. не собирать js файлы в bundle, то все работает без ошибок. Но если собирать с помощью cram, то mithril.js попадет в bundle, и curl его еще подгрузит дополнительно, т.е. почему-то он не знает, что файл уже в сборке. Это приводит к ошибке "Multiple anonymous defines encountered"
Это лечится следующим конфигом в настройках curl.path:

"mithril": {
            location: "mithril.min.js",
            config: {
                loader: "curl/loader/legacy",
                exports: "m"
            }
        }
 Обратите внимание, что используется legacy loader, несмотря на то, что в mithril.js задан amd конфиг:
if (typeof module != "undefined" && module !== null && module.exports) module.exports = m; else if (typeof define === "function" && define.amd) define(function() {return m});