djhtmx 1.1.1__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {djhtmx-1.1.1 → djhtmx-1.2.0}/CHANGELOG.md +32 -0
  2. {djhtmx-1.1.1 → djhtmx-1.2.0}/PKG-INFO +80 -1
  3. {djhtmx-1.1.1 → djhtmx-1.2.0}/README.md +79 -0
  4. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/__init__.py +1 -1
  5. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/apps.py +4 -2
  6. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/component.py +3 -3
  7. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/introspection.py +5 -2
  8. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/management/commands/htmx.py +52 -0
  9. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/utils.py +27 -0
  10. {djhtmx-1.1.1 → djhtmx-1.2.0}/.gitignore +0 -0
  11. {djhtmx-1.1.1 → djhtmx-1.2.0}/LICENSE +0 -0
  12. {djhtmx-1.1.1 → djhtmx-1.2.0}/MANIFEST.in +0 -0
  13. {djhtmx-1.1.1 → djhtmx-1.2.0}/pyproject.toml +0 -0
  14. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/command_queue.py +0 -0
  15. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/commands.py +0 -0
  16. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/consumer.py +0 -0
  17. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/context.py +0 -0
  18. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/exceptions.py +0 -0
  19. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/global_events.py +0 -0
  20. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/json.py +0 -0
  21. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/middleware.py +0 -0
  22. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/query.py +0 -0
  23. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/repo.py +0 -0
  24. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/settings.py +0 -0
  25. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/ext/ws.js +0 -0
  26. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.amd.js +0 -0
  27. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.cjs.js +0 -0
  28. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.esm.d.ts +0 -0
  29. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.esm.js +0 -0
  30. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.js +0 -0
  31. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/2.0.4/htmx.min.js +0 -0
  32. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/static/htmx/django.js +0 -0
  33. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/templates/htmx/headers.html +0 -0
  34. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/templates/htmx/lazy.html +0 -0
  35. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/templatetags/__init__.py +0 -0
  36. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/templatetags/htmx.py +0 -0
  37. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/testing.py +0 -0
  38. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/tracing.py +0 -0
  39. {djhtmx-1.1.1 → djhtmx-1.2.0}/src/djhtmx/urls.py +0 -0
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0] - 2025-09-29
11
+
12
+ ### Added
13
+ - **Enhanced HTMX Module Discovery**: HTMX components can now be organized in directory structures within Django apps. The autodiscovery system now recursively imports all Python modules under `htmx/` directories, in addition to the traditional single `htmx.py` files. This allows for better code organization in larger projects.
14
+ - **New Management Commands**:
15
+ - `python manage.py htmx check-unused`: Check for unused HTMX components in your project
16
+ - `python manage.py htmx check-unused-non-public`: Check for unused non-public HTMX components
17
+
18
+ ### Changed
19
+ - **BREAKING**: Template name validation now raises `ImproperlyConfigured` exceptions instead of logging warnings when HTMX component template names don't match the component class name. This provides better error visibility and prevents potential runtime issues.
20
+
21
+ ### Technical Details
22
+ - Modified `apps.py` to use the new autodiscovery function instead of Django's standard `autodiscover_modules("htmx")`
23
+ - Added `autodiscover_htmx_modules()` function in `utils.py` that recursively discovers and imports all Python modules in `htmx/` directories across Django apps
24
+ - Maintains full backward compatibility with existing single `htmx.py` files
25
+
26
+ ### Migration Guide
27
+ - **Template Name Validation**: If you have components with mismatched template names, you'll now get `ImproperlyConfigured` exceptions instead of warnings. Update your component template names to match the class names.
28
+ - **Module Organization**: You can now organize your HTMX components in directory structures under `htmx/` in your Django apps. No changes required for existing single `htmx.py` files.
29
+
30
+ ## [1.1.2] - 2025-08-27
31
+
32
+ ### Fixed
33
+ - **Python 3.13 Compatibility**: Added support for `defaultdict[..., ...]` type annotations in Query introspection
34
+ - Fixed type checking for generic aliases in collection annotations
35
+
36
+ ### Documentation
37
+ - **Redis Dependency**: Added clear documentation that Redis is required and must be installed separately
38
+ - **Framework Clarification**: Clarified that djhtmx is a framework, not a component library - no pre-built components are provided
39
+ - **Settings Documentation**: Added comprehensive documentation for all available Django settings
40
+ - **Installation Guide**: Added Redis installation instructions for different platforms
41
+
10
42
  ## [1.1.1] - 2025-08-23
11
43
 
12
44
  - Remove `get_model_subscriptions` `Action` annotation as string literals, as this does not reflect model relationships
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djhtmx
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Interactive UI Components for Django using HTMX
5
5
  Project-URL: Homepage, https://github.com/edelvalle/djhtmx
6
6
  Project-URL: Documentation, https://github.com/edelvalle/djhtmx#readme
@@ -66,6 +66,20 @@ pip install djhtmx
66
66
 
67
67
  # Configuration
68
68
 
69
+ ## Requirements
70
+
71
+ djhtmx requires **Redis** to be running for session storage and component state management.
72
+
73
+ **Important**: Redis is not included with djhtmx and must be installed separately on your system. Make sure Redis is installed and accessible before using djhtmx.
74
+
75
+ ### Installing Redis
76
+
77
+ - **macOS**: `brew install redis`
78
+ - **Ubuntu/Debian**: `sudo apt-get install redis-server`
79
+ - **CentOS/RHEL**: `sudo yum install redis` or `sudo dnf install redis`
80
+ - **Docker**: `docker run -d -p 6379:6379 redis:alpine`
81
+ - **Windows**: Download from [Redis for Windows](https://github.com/microsoftarchive/redis/releases)
82
+
69
83
  Add `djhtmx` to your `INSTALLED_APPS`.
70
84
 
71
85
  ```python
@@ -116,6 +130,40 @@ urlpatterns = [
116
130
  ]
117
131
  ```
118
132
 
133
+ ## Settings
134
+
135
+ djhtmx can be configured through Django settings:
136
+
137
+ ### Required Settings
138
+
139
+ - **`DJHTMX_REDIS_URL`** (default: `"redis://localhost/0"`): Redis connection URL for session storage and component state management.
140
+
141
+ ### Optional Settings
142
+
143
+ - **`DJHTMX_SESSION_TTL`** (default: `3600`): Session timeout in seconds. Can be an integer or a `datetime.timedelta` object.
144
+ - **`DJHTMX_DEFAULT_LAZY_TEMPLATE`** (default: `"htmx/lazy.html"`): Default template for lazy-loaded components.
145
+ - **`DJHTMX_ENABLE_SENTRY_TRACING`** (default: `True`): Enable Sentry tracing integration.
146
+ - **`DJHTMX_ENABLE_LOGFIRE_TRACING`** (default: `False`): Enable Logfire tracing integration.
147
+ - **`DJHTMX_STRICT_EVENT_HANDLER_CONSISTENCY_CHECK`** (default: `False`): Enable strict consistency checking for event handlers.
148
+ - **`DJHTMX_KEY_SIZE_ERROR_THRESHOLD`** (default: `0`): Threshold in bytes for session key size errors (0 = disabled).
149
+ - **`DJHTMX_KEY_SIZE_WARN_THRESHOLD`** (default: `51200`): Threshold in bytes for session key size warnings (50KB).
150
+ - **`DJHTMX_KEY_SIZE_SAMPLE_PROB`** (default: `0.1`): Probability for sampling session key size checks.
151
+
152
+ ### Example Configuration
153
+
154
+ ```python
155
+ # settings.py
156
+
157
+ # Redis connection (required)
158
+ DJHTMX_REDIS_URL = "redis://localhost:6379/0" # or redis://user:password@host:port/db
159
+
160
+ # Optional settings
161
+ DJHTMX_SESSION_TTL = 7200 # 2 hours
162
+ DJHTMX_DEFAULT_LAZY_TEMPLATE = "my_app/lazy_component.html"
163
+ DJHTMX_ENABLE_SENTRY_TRACING = True
164
+ DJHTMX_KEY_SIZE_WARN_THRESHOLD = 100 * 1024 # 100KB
165
+ ```
166
+
119
167
  In your base template you need to load the necessary scripts to make this work
120
168
 
121
169
  ```html
@@ -130,8 +178,39 @@ In your base template you need to load the necessary scripts to make this work
130
178
 
131
179
  ## Getting started
132
180
 
181
+ **Important**: djhtmx is a framework for building interactive components, not a component library. No pre-built components, templates, or behaviors are provided. You need to create your own components from scratch using the framework's base classes and conventions.
182
+
183
+ This library is opinionated about how to use HTMX with Django, but it is not opinionated about components, styling, or specific functionality. You have complete freedom to design and implement your components as needed for your application.
184
+
133
185
  This app will look for `htmx.py` files in your app and registers all components found there, but if you load any module where you have components manually when Django boots up, that also works.
134
186
 
187
+ ### Component Organization
188
+
189
+ As of version 1.2.0, djhtmx supports both single file and directory-based component organization:
190
+
191
+ **Single file (traditional):**
192
+ ```
193
+ myapp/
194
+ ├── htmx.py # All components in one file
195
+ └── ...
196
+ ```
197
+
198
+ **Directory structure (new in v1.2.0):**
199
+ ```
200
+ myapp/
201
+ ├── htmx/
202
+ │ ├── __init__.py
203
+ │ ├── components.py # Basic components
204
+ │ ├── forms.py # Form components
205
+ │ └── widgets/
206
+ │ ├── __init__.py
207
+ │ ├── calendar.py # Calendar widgets
208
+ │ └── charts.py # Chart widgets
209
+ └── ...
210
+ ```
211
+
212
+ The autodiscovery system will recursively find and import all Python modules under `htmx/` directories, allowing you to organize your components in a structured way that scales with your project size.
213
+
135
214
  ```python
136
215
  from djhtmx.component import HtmxComponent
137
216
 
@@ -19,6 +19,20 @@ pip install djhtmx
19
19
 
20
20
  # Configuration
21
21
 
22
+ ## Requirements
23
+
24
+ djhtmx requires **Redis** to be running for session storage and component state management.
25
+
26
+ **Important**: Redis is not included with djhtmx and must be installed separately on your system. Make sure Redis is installed and accessible before using djhtmx.
27
+
28
+ ### Installing Redis
29
+
30
+ - **macOS**: `brew install redis`
31
+ - **Ubuntu/Debian**: `sudo apt-get install redis-server`
32
+ - **CentOS/RHEL**: `sudo yum install redis` or `sudo dnf install redis`
33
+ - **Docker**: `docker run -d -p 6379:6379 redis:alpine`
34
+ - **Windows**: Download from [Redis for Windows](https://github.com/microsoftarchive/redis/releases)
35
+
22
36
  Add `djhtmx` to your `INSTALLED_APPS`.
23
37
 
24
38
  ```python
@@ -69,6 +83,40 @@ urlpatterns = [
69
83
  ]
70
84
  ```
71
85
 
86
+ ## Settings
87
+
88
+ djhtmx can be configured through Django settings:
89
+
90
+ ### Required Settings
91
+
92
+ - **`DJHTMX_REDIS_URL`** (default: `"redis://localhost/0"`): Redis connection URL for session storage and component state management.
93
+
94
+ ### Optional Settings
95
+
96
+ - **`DJHTMX_SESSION_TTL`** (default: `3600`): Session timeout in seconds. Can be an integer or a `datetime.timedelta` object.
97
+ - **`DJHTMX_DEFAULT_LAZY_TEMPLATE`** (default: `"htmx/lazy.html"`): Default template for lazy-loaded components.
98
+ - **`DJHTMX_ENABLE_SENTRY_TRACING`** (default: `True`): Enable Sentry tracing integration.
99
+ - **`DJHTMX_ENABLE_LOGFIRE_TRACING`** (default: `False`): Enable Logfire tracing integration.
100
+ - **`DJHTMX_STRICT_EVENT_HANDLER_CONSISTENCY_CHECK`** (default: `False`): Enable strict consistency checking for event handlers.
101
+ - **`DJHTMX_KEY_SIZE_ERROR_THRESHOLD`** (default: `0`): Threshold in bytes for session key size errors (0 = disabled).
102
+ - **`DJHTMX_KEY_SIZE_WARN_THRESHOLD`** (default: `51200`): Threshold in bytes for session key size warnings (50KB).
103
+ - **`DJHTMX_KEY_SIZE_SAMPLE_PROB`** (default: `0.1`): Probability for sampling session key size checks.
104
+
105
+ ### Example Configuration
106
+
107
+ ```python
108
+ # settings.py
109
+
110
+ # Redis connection (required)
111
+ DJHTMX_REDIS_URL = "redis://localhost:6379/0" # or redis://user:password@host:port/db
112
+
113
+ # Optional settings
114
+ DJHTMX_SESSION_TTL = 7200 # 2 hours
115
+ DJHTMX_DEFAULT_LAZY_TEMPLATE = "my_app/lazy_component.html"
116
+ DJHTMX_ENABLE_SENTRY_TRACING = True
117
+ DJHTMX_KEY_SIZE_WARN_THRESHOLD = 100 * 1024 # 100KB
118
+ ```
119
+
72
120
  In your base template you need to load the necessary scripts to make this work
73
121
 
74
122
  ```html
@@ -83,8 +131,39 @@ In your base template you need to load the necessary scripts to make this work
83
131
 
84
132
  ## Getting started
85
133
 
134
+ **Important**: djhtmx is a framework for building interactive components, not a component library. No pre-built components, templates, or behaviors are provided. You need to create your own components from scratch using the framework's base classes and conventions.
135
+
136
+ This library is opinionated about how to use HTMX with Django, but it is not opinionated about components, styling, or specific functionality. You have complete freedom to design and implement your components as needed for your application.
137
+
86
138
  This app will look for `htmx.py` files in your app and registers all components found there, but if you load any module where you have components manually when Django boots up, that also works.
87
139
 
140
+ ### Component Organization
141
+
142
+ As of version 1.2.0, djhtmx supports both single file and directory-based component organization:
143
+
144
+ **Single file (traditional):**
145
+ ```
146
+ myapp/
147
+ ├── htmx.py # All components in one file
148
+ └── ...
149
+ ```
150
+
151
+ **Directory structure (new in v1.2.0):**
152
+ ```
153
+ myapp/
154
+ ├── htmx/
155
+ │ ├── __init__.py
156
+ │ ├── components.py # Basic components
157
+ │ ├── forms.py # Form components
158
+ │ └── widgets/
159
+ │ ├── __init__.py
160
+ │ ├── calendar.py # Calendar widgets
161
+ │ └── charts.py # Chart widgets
162
+ └── ...
163
+ ```
164
+
165
+ The autodiscovery system will recursively find and import all Python modules under `htmx/` directories, allowing you to organize your components in a structured way that scales with your project size.
166
+
88
167
  ```python
89
168
  from djhtmx.component import HtmxComponent
90
169
 
@@ -1,4 +1,4 @@
1
1
  from .middleware import middleware
2
2
 
3
- __version__ = "1.1.1"
3
+ __version__ = "1.2.0"
4
4
  __all__ = ("middleware",)
@@ -1,11 +1,13 @@
1
1
  from django.apps import AppConfig
2
2
  from django.utils.module_loading import autodiscover_modules
3
3
 
4
+ from .utils import autodiscover_htmx_modules
5
+
4
6
 
5
7
  class App(AppConfig):
6
8
  name = "djhtmx"
7
9
  verbose_name = "Django HTMX"
8
10
 
9
11
  def ready(self):
10
- autodiscover_modules("live")
11
- autodiscover_modules("htmx")
12
+ autodiscover_modules("live") # legacy
13
+ autodiscover_htmx_modules()
@@ -20,6 +20,7 @@ from typing import (
20
20
  get_type_hints,
21
21
  )
22
22
 
23
+ from django.core.exceptions import ImproperlyConfigured
23
24
  from django.db import models
24
25
  from django.shortcuts import resolve_url
25
26
  from django.template import Context, loader
@@ -306,9 +307,8 @@ class HtmxComponent(BaseModel):
306
307
  basename(cls._template_name.default)
307
308
  not in (f"{klass.__name__}.html" for klass in cls.__mro__)
308
309
  ):
309
- logger.warning(
310
- "HTMX Component <%s> template name does not match the component name",
311
- FQN[cls],
310
+ raise ImproperlyConfigured(
311
+ f"HTMX Component <{FQN[cls]}> template name does not match the component name"
312
312
  )
313
313
 
314
314
  # We use 'get_type_hints' to resolve the forward refs if needed, but
@@ -419,9 +419,12 @@ def is_simple_annotation(ann):
419
419
 
420
420
 
421
421
  def is_collection_annotation(ann):
422
- return issubclass_safe(ann, _COLLECTION_TYPES)
422
+ if isinstance(ann, types.GenericAlias):
423
+ return issubclass_safe(ann.__origin__, _COLLECTION_TYPES)
424
+ else:
425
+ return issubclass_safe(ann, _COLLECTION_TYPES)
423
426
 
424
427
 
425
428
  Unset = object()
426
429
  _SIMPLE_TYPES = (int, str, float, UUID, types.NoneType, date, datetime, bool)
427
- _COLLECTION_TYPES = (dict, tuple, list, set)
430
+ _COLLECTION_TYPES = (dict, tuple, list, set, defaultdict)
@@ -46,6 +46,58 @@ def check_missing(fname):
46
46
  sys.exit(1)
47
47
 
48
48
 
49
+ @htmx.command("check-unused") # type: ignore
50
+ @click.argument("fname", type=click.File())
51
+ def check_unused(fname):
52
+ r"""Check if there are any unused HTMX components.
53
+
54
+ Expected usage:
55
+
56
+ find -type f -name '*.html' | while read f; do grep -P '{% htmx .(\\w+)' -o $f \
57
+ | awk '{print $3}' | cut -b2-; done | sort -u \
58
+ | python manage.py htmx check-unused -
59
+
60
+ """
61
+ names = {n.strip() for n in fname.readlines()}
62
+ known = set(REGISTRY)
63
+ unused = list(known - names)
64
+ if unused:
65
+ unused.sort()
66
+ for n in unused:
67
+ click.echo(
68
+ f"Unused component detected {bold(yellow(n))}",
69
+ file=sys.stderr,
70
+ )
71
+ sys.exit(1)
72
+
73
+
74
+ @htmx.command("check-unused-non-public") # type: ignore
75
+ def check_unused_non_public():
76
+ """Check if there are any unused non-public HTMX components.
77
+
78
+ Non-public components that are final subclasses (have no subclasses themselves)
79
+ are considered unused since they can't be instantiated from templates and serve
80
+ no purpose as base classes.
81
+ """
82
+ final_subclasses = set(
83
+ get_final_subclasses(
84
+ HtmxComponent, # type: ignore
85
+ without_duplicates=True,
86
+ )
87
+ )
88
+ registered = set(REGISTRY.values())
89
+ unused_non_public = list(final_subclasses - registered)
90
+
91
+ if unused_non_public:
92
+ unused_non_public.sort(key=lambda cls: cls.__name__)
93
+ for cls in unused_non_public:
94
+ click.echo(
95
+ f"Unused non-public component detected {bold(yellow(cls.__name__))}",
96
+ file=sys.stderr,
97
+ )
98
+ sys.exit(1)
99
+
100
+
49
101
  @htmx.command("check-shadowing") # type: ignore
50
102
  def check_shadowing():
51
103
  "Checks if there are components that might shadow one another."
@@ -1,8 +1,12 @@
1
+ import contextlib
2
+ import importlib
3
+ import pkgutil
1
4
  import typing as t
2
5
  from urllib.parse import urlparse
3
6
 
4
7
  import mmh3
5
8
  from channels.db import database_sync_to_async as db # type: ignore
9
+ from django.apps import apps
6
10
  from django.db import models
7
11
  from django.http.request import HttpRequest, QueryDict
8
12
  from uuid6 import uuid7
@@ -115,3 +119,26 @@ def compact_hash(value: str) -> str:
115
119
  # The symbols are chosen to avoid extra encoding in the URL and HTML, and
116
120
  # allowed in plain CSS selectors.
117
121
  _BASE = "ZmBeUHhTgusXNW_Y1b05KPiFcQJD86joqnIRE7Lfkrdp3AOMCvltSwzVG9yxa42"
122
+
123
+
124
+ def autodiscover_htmx_modules():
125
+ """
126
+ Auto-discover HTMX modules in Django apps.
127
+
128
+ This discovers both:
129
+ - htmx.py files (like standard autodiscover_modules("htmx"))
130
+ - All Python files under htmx/ directories in apps (recursively)
131
+ """
132
+ def _import_modules_recursively(module_name):
133
+ """Recursively import a module and all its submodules."""
134
+ with contextlib.suppress(ImportError):
135
+ module = importlib.import_module(module_name)
136
+
137
+ # If this is a package, recursively import all modules in it
138
+ if hasattr(module, "__path__"):
139
+ for _finder, submodule_name, _is_pkg in pkgutil.iter_modules(module.__path__):
140
+ _import_modules_recursively(f"{module_name}.{submodule_name}")
141
+
142
+ for app_config in apps.get_app_configs():
143
+ # Import htmx module and all its submodules recursively
144
+ _import_modules_recursively(f"{app_config.name}.htmx")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes