mirror of
				https://github.com/django-components/django-components.git
				synced 2025-11-04 00:43:36 +00:00 
			
		
		
		
	feat: allow to set comp defaults on Kwargs class (#1467)
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				Docs - build & deploy / docs (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.10) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.11) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.12) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.13) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.8) (push) Waiting to run
				
			
		
			
				
	
				Run tests / build (windows-latest, 3.9) (push) Waiting to run
				
			
		
			
				
	
				Run tests / test_docs (3.13) (push) Waiting to run
				
			
		
			
				
	
				Run tests / test_sampleproject (3.13) (push) Waiting to run
				
			
		
		
	
	
				
					
				
			
		
			Some checks are pending
		
		
	
	Docs - build & deploy / docs (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.10) (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.11) (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.12) (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.13) (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.8) (push) Waiting to run
				
			Run tests / build (ubuntu-latest, 3.9) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.10) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.11) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.12) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.13) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.8) (push) Waiting to run
				
			Run tests / build (windows-latest, 3.9) (push) Waiting to run
				
			Run tests / test_docs (3.13) (push) Waiting to run
				
			Run tests / test_sampleproject (3.13) (push) Waiting to run
				
			This commit is contained in:
		
							parent
							
								
									c37628dea0
								
							
						
					
					
						commit
						28ff1d072a
					
				
					 11 changed files with 561 additions and 119 deletions
				
			
		
							
								
								
									
										62
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										62
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,10 +1,64 @@
 | 
			
		|||
# Release notes
 | 
			
		||||
 | 
			
		||||
## v0.142.4
 | 
			
		||||
## v0.143.0
 | 
			
		||||
 | 
			
		||||
#### Refactor
 | 
			
		||||
#### Feat
 | 
			
		||||
 | 
			
		||||
- Simpler syntax for defining component inputs.
 | 
			
		||||
- You can now define component input defaults directly on `Component.Kwargs`.
 | 
			
		||||
 | 
			
		||||
    Before, the defaults had to be defined on a separate `Component.Defaults` class:
 | 
			
		||||
 | 
			
		||||
    ```python
 | 
			
		||||
    class ProfileCard(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            user_id: int
 | 
			
		||||
            show_details: bool
 | 
			
		||||
 | 
			
		||||
        class Defaults:
 | 
			
		||||
            show_details = True
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    Now, django-components can detect the defaults from `Component.Kwargs` and apply
 | 
			
		||||
    them. So you can merge `Component.Kwargs` with `Component.Defaults`:
 | 
			
		||||
    
 | 
			
		||||
    ```python
 | 
			
		||||
    class ProfileCard(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            user_id: int
 | 
			
		||||
            show_details: bool = True
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    NOTE: This applies only when `Component.Kwargs` is a NamedTuple or dataclass.
 | 
			
		||||
 | 
			
		||||
- New helper `get_component_defaults()`:
 | 
			
		||||
 | 
			
		||||
    Now, the defaults may be defined on either `Component.Defaults` and `Component.Kwargs` classes.
 | 
			
		||||
 | 
			
		||||
    To get a final, merged dictionary of all the component's defaults, use `get_component_defaults()`:
 | 
			
		||||
 | 
			
		||||
    ```py
 | 
			
		||||
    from django_components import Component, Default, get_component_defaults
 | 
			
		||||
 | 
			
		||||
    class MyTable(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            position: str
 | 
			
		||||
            order: int
 | 
			
		||||
            items: list[int]
 | 
			
		||||
            variable: str = "from_kwargs"
 | 
			
		||||
 | 
			
		||||
        class Defaults:
 | 
			
		||||
            position: str = "left"
 | 
			
		||||
            items = Default(lambda: [1, 2, 3])
 | 
			
		||||
 | 
			
		||||
    defaults = get_component_defaults(MyTable)
 | 
			
		||||
    # {
 | 
			
		||||
    #     "position": "left",
 | 
			
		||||
    #     "items": [1, 2, 3],
 | 
			
		||||
    #     "variable": "from_kwargs",
 | 
			
		||||
    # }
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
- Simpler syntax for defining component inputs:
 | 
			
		||||
 | 
			
		||||
    When defining `Args`, `Kwargs`, `Slots`, `JsData`, `CssData`, `TemplateData`, these data classes now don't have to subclass any other class.
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +105,8 @@
 | 
			
		|||
            ...
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
#### Refactor
 | 
			
		||||
 | 
			
		||||
- Extension authors: The `ExtensionComponentConfig` can be instantiated with `None` instead of a component instance.
 | 
			
		||||
 | 
			
		||||
  This allows to call component-level extension methods outside of the normal rendering lifecycle.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,25 +65,33 @@ and so `selected_items` will be set to `[1, 2, 3]`.
 | 
			
		|||
 | 
			
		||||
    The defaults are aplied only to keyword arguments. They are NOT applied to positional arguments!
 | 
			
		||||
 | 
			
		||||
### Defaults from `Kwargs`
 | 
			
		||||
 | 
			
		||||
If you are using [`Component.Kwargs`](../fundamentals/typing_and_validation.md#typing-inputs) to specify the component input,
 | 
			
		||||
you can set the defaults directly on `Kwargs`:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
class ProfileCard(Component):
 | 
			
		||||
    class Kwargs:
 | 
			
		||||
        user_id: int
 | 
			
		||||
        show_details: bool = True
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Which is the same as:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
class ProfileCard(Component):
 | 
			
		||||
    class Kwargs:
 | 
			
		||||
        user_id: int
 | 
			
		||||
        show_details: bool
 | 
			
		||||
 | 
			
		||||
    class Defaults:
 | 
			
		||||
        show_details = True
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
!!! warning
 | 
			
		||||
 | 
			
		||||
    When [typing](../fundamentals/typing_and_validation.md) your components with [`Args`](../../../reference/api/#django_components.Component.Args),
 | 
			
		||||
    [`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
 | 
			
		||||
    or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
 | 
			
		||||
    you may be inclined to define the defaults in the classes.
 | 
			
		||||
 | 
			
		||||
    ```py
 | 
			
		||||
    class ProfileCard(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            show_details: bool = True
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    This is **NOT recommended**, because:
 | 
			
		||||
 | 
			
		||||
    - The defaults will NOT be applied to inputs when using [`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs) property.
 | 
			
		||||
    - The defaults will NOT be applied when a field is given but set to `None`.
 | 
			
		||||
 | 
			
		||||
    Instead, define the defaults in the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class.
 | 
			
		||||
    This works only when `Component.Kwargs` is a plain class, NamedTuple or dataclass.
 | 
			
		||||
 | 
			
		||||
### Default factories
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,30 +132,28 @@ class MyTable(Component):
 | 
			
		|||
 | 
			
		||||
### Accessing defaults
 | 
			
		||||
 | 
			
		||||
Since the defaults are defined on the component class, you can access the defaults for a component with the [`Component.Defaults`](../../../reference/api#django_components.Component.Defaults) property.
 | 
			
		||||
The defaults may be defined on both [`Component.Defaults`](../../../reference/api#django_components.Component.Defaults) and [`Component.Kwargs`](../../../reference/api#django_components.Component.Kwargs) classes.
 | 
			
		||||
 | 
			
		||||
So if we have a component like this:
 | 
			
		||||
To get a final, merged dictionary of all the component's defaults, use [`get_component_defaults()`](../../../reference/api#django_components.get_component_defaults):
 | 
			
		||||
 | 
			
		||||
```py
 | 
			
		||||
from django_components import Component, Default, register
 | 
			
		||||
from django_components import Component, Default, get_component_defaults
 | 
			
		||||
 | 
			
		||||
@register("my_table")
 | 
			
		||||
class MyTable(Component):
 | 
			
		||||
    class Kwargs:
 | 
			
		||||
        position: str
 | 
			
		||||
        order: int
 | 
			
		||||
        items: list[int]
 | 
			
		||||
        variable: str = "from_kwargs"
 | 
			
		||||
 | 
			
		||||
    class Defaults:
 | 
			
		||||
        position = "left"
 | 
			
		||||
        selected_items = Default(lambda: [1, 2, 3])
 | 
			
		||||
        position: str = "left"
 | 
			
		||||
        items = Default(lambda: [1, 2, 3])
 | 
			
		||||
 | 
			
		||||
    def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
        return {
 | 
			
		||||
            "position": kwargs["position"],
 | 
			
		||||
            "selected_items": kwargs["selected_items"],
 | 
			
		||||
        }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We can access individual defaults like this:
 | 
			
		||||
 | 
			
		||||
```py
 | 
			
		||||
print(MyTable.Defaults.position)
 | 
			
		||||
print(MyTable.Defaults.selected_items)
 | 
			
		||||
defaults = get_component_defaults(MyTable)
 | 
			
		||||
# {
 | 
			
		||||
#     "position": "left",
 | 
			
		||||
#     "items": [1, 2, 3],
 | 
			
		||||
#     "variable": "from_kwargs",
 | 
			
		||||
# }
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,7 @@ Each method handles the data independently - you can define different data for t
 | 
			
		|||
class ProfileCard(Component):
 | 
			
		||||
    class Kwargs:
 | 
			
		||||
        user_id: int
 | 
			
		||||
        show_details: bool
 | 
			
		||||
 | 
			
		||||
    class Defaults:
 | 
			
		||||
        show_details = True
 | 
			
		||||
        show_details: bool = True
 | 
			
		||||
 | 
			
		||||
    def get_template_data(self, args, kwargs: Kwargs, slots, context):
 | 
			
		||||
        user = User.objects.get(id=kwargs.user_id)
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +301,7 @@ class ProfileCard(Component):
 | 
			
		|||
 | 
			
		||||
## Default values
 | 
			
		||||
 | 
			
		||||
You can use [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class to provide default values for your inputs.
 | 
			
		||||
You can use the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) and [`Kwargs`](../../../reference/api/#django_components.Component.Kwargs) classes to provide default values for your inputs.
 | 
			
		||||
 | 
			
		||||
These defaults will be applied either when:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -321,12 +318,9 @@ from django_components import Component, Default, register
 | 
			
		|||
@register("profile_card")
 | 
			
		||||
class ProfileCard(Component):
 | 
			
		||||
    class Kwargs:
 | 
			
		||||
        show_details: bool
 | 
			
		||||
        # Will be set to True if `None` or missing
 | 
			
		||||
        show_details: bool = True
 | 
			
		||||
 | 
			
		||||
    class Defaults:
 | 
			
		||||
        show_details = True
 | 
			
		||||
 | 
			
		||||
    # show_details will be set to True if `None` or missing
 | 
			
		||||
    def get_template_data(self, args, kwargs: Kwargs, slots, context):
 | 
			
		||||
        return {
 | 
			
		||||
            "show_details": kwargs.show_details,
 | 
			
		||||
| 
						 | 
				
			
			@ -335,26 +329,6 @@ class ProfileCard(Component):
 | 
			
		|||
    ...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
!!! warning
 | 
			
		||||
 | 
			
		||||
    When typing your components with [`Args`](../../../reference/api/#django_components.Component.Args),
 | 
			
		||||
    [`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
 | 
			
		||||
    or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
 | 
			
		||||
    you may be inclined to define the defaults in the classes.
 | 
			
		||||
 | 
			
		||||
    ```py
 | 
			
		||||
    class ProfileCard(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            show_details: bool = True
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    This is **NOT recommended**, because:
 | 
			
		||||
 | 
			
		||||
    - The defaults will NOT be applied to inputs when using [`self.raw_kwargs`](../../../reference/api/#django_components.Component.raw_kwargs) property.
 | 
			
		||||
    - The defaults will NOT be applied when a field is given but set to `None`.
 | 
			
		||||
 | 
			
		||||
    Instead, define the defaults in the [`Defaults`](../../../reference/api/#django_components.Component.Defaults) class.
 | 
			
		||||
 | 
			
		||||
## Accessing Render API
 | 
			
		||||
 | 
			
		||||
All three data methods have access to the Component's [Render API](../render_api), which includes:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -233,7 +233,7 @@ or [`get_css_data()`](../../reference/api#django_components.Component.get_css_da
 | 
			
		|||
To make things easier, Components can specify their defaults. Defaults are used when
 | 
			
		||||
no value is provided, or when the value is set to `None` for a particular input.
 | 
			
		||||
 | 
			
		||||
To define defaults for a component, you create a nested `Defaults` class within your
 | 
			
		||||
To define defaults for a component, you create a nested [`Defaults`](../../reference/api#django_components.Component.Defaults) class within your
 | 
			
		||||
[`Component`](../../reference/api#django_components.Component) class.
 | 
			
		||||
Each attribute in the `Defaults` class represents a default value for a corresponding input.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -255,6 +255,57 @@ class Calendar(Component):
 | 
			
		|||
        }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 6. Add input validation
 | 
			
		||||
 | 
			
		||||
Right now our `Calendar` component accepts any number of args and kwargs,
 | 
			
		||||
and we can't see which ones are being used.
 | 
			
		||||
 | 
			
		||||
*This is a maintenance nightmare!*
 | 
			
		||||
 | 
			
		||||
Let's be good colleagues and document the component inputs.
 | 
			
		||||
As a bonus, we will also get runtime validation of these inputs.
 | 
			
		||||
 | 
			
		||||
For defining component inputs, there's 3 options:
 | 
			
		||||
 | 
			
		||||
- [`Args`](../../reference/api#django_components.Component.Args) - For defining positional args passed to the component
 | 
			
		||||
- [`Kwargs`](../../reference/api#django_components.Component.Kwargs) - For keyword args
 | 
			
		||||
- [`Slots`](../../reference/api#django_components.Component.Slots) - For slots
 | 
			
		||||
 | 
			
		||||
Our calendar component is using only kwargs, so we can ignore `Args` and `Slots`.
 | 
			
		||||
The new `Kwargs` class defines fields that this component accepts:
 | 
			
		||||
 | 
			
		||||
```py
 | 
			
		||||
from django_components import Component, Default, register
 | 
			
		||||
 | 
			
		||||
@register("calendar")
 | 
			
		||||
class Calendar(Component):
 | 
			
		||||
    template_file = "calendar.html"
 | 
			
		||||
 | 
			
		||||
    class Kwargs:  # <--- changed (replaced Defaults)
 | 
			
		||||
        date: Date
 | 
			
		||||
        extra_class: str = "text-blue"
 | 
			
		||||
 | 
			
		||||
    def get_template_data(self, args, kwargs: Kwargs, slots, context):  # <--- changed
 | 
			
		||||
        workweek_date = to_workweek_date(kwargs.date)  # <--- changed
 | 
			
		||||
        return {
 | 
			
		||||
            "date": workweek_date,
 | 
			
		||||
            "extra_class": kwargs.extra_class,  # <--- changed
 | 
			
		||||
        }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Notice that:
 | 
			
		||||
 | 
			
		||||
- When we defined `Kwargs` class, the `kwargs` parameter to `get_template_data`
 | 
			
		||||
  changed to an instance of `Kwargs`. Fields are now accessed as attributes.
 | 
			
		||||
- Since `kwargs` is of class `Kwargs`, we've added annotation to the `kwargs` parameter.
 | 
			
		||||
- `Kwargs` replaced `Defaults`, because defaults can be defined also on `Kwargs` class.
 | 
			
		||||
 | 
			
		||||
And that's it! Now you can sleep safe knowing you won't break anything when
 | 
			
		||||
adding or removing component inputs.
 | 
			
		||||
 | 
			
		||||
Read more about [Component defaults](../concepts/fundamentals/component_defaults.md)
 | 
			
		||||
and [Typing and validation](../concepts/fundamentals/typing_and_validation.md).
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Next, you will learn [how to use slots give your components even more flexibility ➡️](./adding_slots.md)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,6 +171,10 @@
 | 
			
		|||
    options:
 | 
			
		||||
      show_if_no_docstring: true
 | 
			
		||||
 | 
			
		||||
::: django_components.get_component_defaults
 | 
			
		||||
    options:
 | 
			
		||||
      show_if_no_docstring: true
 | 
			
		||||
 | 
			
		||||
::: django_components.get_component_dirs
 | 
			
		||||
    options:
 | 
			
		||||
      show_if_no_docstring: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,7 @@ from django_components.extension import (
 | 
			
		|||
    OnTemplateLoadedContext,
 | 
			
		||||
)
 | 
			
		||||
from django_components.extensions.cache import ComponentCache
 | 
			
		||||
from django_components.extensions.defaults import ComponentDefaults, Default
 | 
			
		||||
from django_components.extensions.defaults import ComponentDefaults, Default, get_component_defaults
 | 
			
		||||
from django_components.extensions.debug_highlight import ComponentDebugHighlight
 | 
			
		||||
from django_components.extensions.view import ComponentView, get_component_url
 | 
			
		||||
from django_components.library import TagProtectedError
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +162,7 @@ __all__ = [
 | 
			
		|||
    "component_shorthand_formatter",
 | 
			
		||||
    "format_attributes",
 | 
			
		||||
    "get_component_by_class_id",
 | 
			
		||||
    "get_component_defaults",
 | 
			
		||||
    "get_component_dirs",
 | 
			
		||||
    "get_component_files",
 | 
			
		||||
    "get_component_url",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -208,10 +208,7 @@ class CreateCommand(ComponentCommand):
 | 
			
		|||
                        css_file = "{css_filename}"
 | 
			
		||||
 | 
			
		||||
                        class Kwargs:
 | 
			
		||||
                            param: str
 | 
			
		||||
 | 
			
		||||
                        class Defaults:
 | 
			
		||||
                            param = "sample value"
 | 
			
		||||
                            param: str = "sample value"
 | 
			
		||||
 | 
			
		||||
                        def get_template_data(self, args, kwargs: Kwargs, slots, context):
 | 
			
		||||
                            return {{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -527,6 +527,10 @@ class ComponentMeta(ComponentMediaMeta):
 | 
			
		|||
        #     class Kwargs:
 | 
			
		||||
        #         ...
 | 
			
		||||
        # ```
 | 
			
		||||
        # NOTE: Using dataclasses with `slots=True` could be faster than using NamedTuple,
 | 
			
		||||
        #       but in real world web pages that may load 1-2s, data access and instantiation
 | 
			
		||||
        #       is only on the order of milliseconds, or about 0.1% of the overall time.
 | 
			
		||||
        #       See https://github.com/django-components/django-components/pull/1467#discussion_r2449009201
 | 
			
		||||
        for data_class_name in ["Args", "Kwargs", "Slots", "TemplateData", "JsData", "CssData"]:
 | 
			
		||||
            data_class = attrs.get(data_class_name)
 | 
			
		||||
            # Not a class
 | 
			
		||||
| 
						 | 
				
			
			@ -698,7 +702,7 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
    class Table(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            color: str
 | 
			
		||||
            size: int
 | 
			
		||||
            size: int = 10
 | 
			
		||||
 | 
			
		||||
        def get_template_data(self, args, kwargs: Kwargs, slots, context):
 | 
			
		||||
            assert isinstance(kwargs, Table.Kwargs)
 | 
			
		||||
| 
						 | 
				
			
			@ -714,6 +718,7 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
    - Validate the input at runtime.
 | 
			
		||||
    - Set type hints for the keyword arguments for data methods like
 | 
			
		||||
      [`get_template_data()`](../api#django_components.Component.get_template_data).
 | 
			
		||||
    - Set defaults for individual fields
 | 
			
		||||
    - Document the component inputs.
 | 
			
		||||
 | 
			
		||||
    You can also use `Kwargs` to validate the keyword arguments for
 | 
			
		||||
| 
						 | 
				
			
			@ -725,6 +730,10 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
    )
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    The defaults set on `Kwargs` will be merged with defaults from
 | 
			
		||||
    [`Component.Defaults`](../api/#django_components.Component.Defaults) class.
 | 
			
		||||
    `Kwargs` takes precendence. Read more about [Component defaults](../../concepts/fundamentals/component_defaults).
 | 
			
		||||
 | 
			
		||||
    If you do not specify any bases, the `Kwargs` class will be automatically
 | 
			
		||||
    converted to a `NamedTuple`:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2265,6 +2274,8 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
    """
 | 
			
		||||
    The fields of this class are used to set default values for the component's kwargs.
 | 
			
		||||
 | 
			
		||||
    These defaults will be merged with defaults on [`Component.Kwargs`](../api/#django_components.Component.Kwargs).
 | 
			
		||||
 | 
			
		||||
    Read more about [Component defaults](../../concepts/fundamentals/component_defaults).
 | 
			
		||||
 | 
			
		||||
    **Example:**
 | 
			
		||||
| 
						 | 
				
			
			@ -2669,6 +2680,9 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
        then the `kwargs` property will return an instance of that `Kwargs` class.
 | 
			
		||||
    - Otherwise, `kwargs` will be a plain dict.
 | 
			
		||||
 | 
			
		||||
    Kwargs have the defaults applied to them.
 | 
			
		||||
    Read more about [Component defaults](../../concepts/fundamentals/component_defaults).
 | 
			
		||||
 | 
			
		||||
    **Example:**
 | 
			
		||||
 | 
			
		||||
    With `Kwargs` class:
 | 
			
		||||
| 
						 | 
				
			
			@ -2715,6 +2729,9 @@ class Component(metaclass=ComponentMeta):
 | 
			
		|||
    is not typed and will remain as plain dict even if you define the
 | 
			
		||||
    [`Component.Kwargs`](../api/#django_components.Component.Kwargs) class.
 | 
			
		||||
 | 
			
		||||
    `raw_kwargs` have the defaults applied to them.
 | 
			
		||||
    Read more about [Component defaults](../../concepts/fundamentals/component_defaults).
 | 
			
		||||
 | 
			
		||||
    **Example:**
 | 
			
		||||
 | 
			
		||||
    ```python
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
import dataclasses
 | 
			
		||||
import sys
 | 
			
		||||
from dataclasses import MISSING, Field, dataclass
 | 
			
		||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Optional, Type
 | 
			
		||||
from inspect import isclass
 | 
			
		||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple, Optional, Type, Union
 | 
			
		||||
from weakref import WeakKeyDictionary
 | 
			
		||||
 | 
			
		||||
from django_components.extension import (
 | 
			
		||||
| 
						 | 
				
			
			@ -57,55 +59,137 @@ class ComponentDefaultField(NamedTuple):
 | 
			
		|||
    is_factory: bool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_component_defaults(component: Union[Type["Component"], "Component"]) -> Dict[str, Any]:
 | 
			
		||||
    """
 | 
			
		||||
    Generate a defaults dictionary for a [`Component`](../api#django_components.Component).
 | 
			
		||||
 | 
			
		||||
    The defaults dictionary is generated from the [`Component.Defaults`](../api#django_components.Component.Defaults)
 | 
			
		||||
    and [`Component.Kwargs`](../api#django_components.Component.Kwargs) classes.
 | 
			
		||||
    `Kwargs` take precedence over `Defaults`.
 | 
			
		||||
 | 
			
		||||
    Read more about [Component defaults](../../concepts/fundamentals/component_defaults).
 | 
			
		||||
 | 
			
		||||
    **Example:**
 | 
			
		||||
 | 
			
		||||
    ```py
 | 
			
		||||
    from django_components import Component, Default, get_component_defaults
 | 
			
		||||
 | 
			
		||||
    class MyTable(Component):
 | 
			
		||||
        class Kwargs:
 | 
			
		||||
            position: str
 | 
			
		||||
            order: int
 | 
			
		||||
            items: list[int]
 | 
			
		||||
            variable: str = "from_kwargs"
 | 
			
		||||
 | 
			
		||||
        class Defaults:
 | 
			
		||||
            position: str = "left"
 | 
			
		||||
            items = Default(lambda: [1, 2, 3])
 | 
			
		||||
 | 
			
		||||
    # Get the defaults dictionary
 | 
			
		||||
    defaults = get_component_defaults(MyTable)
 | 
			
		||||
    # {
 | 
			
		||||
    #     "position": "left",
 | 
			
		||||
    #     "items": [1, 2, 3],
 | 
			
		||||
    #     "variable": "from_kwargs",
 | 
			
		||||
    # }
 | 
			
		||||
    ```
 | 
			
		||||
    """
 | 
			
		||||
    component_cls = component if isclass(component) else component.__class__
 | 
			
		||||
    defaults_fields = defaults_by_component[component_cls]  # type: ignore[index]
 | 
			
		||||
    defaults: dict[str, Any] = {}
 | 
			
		||||
    _apply_defaults(defaults, defaults_fields)
 | 
			
		||||
    return defaults
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Figure out which defaults are factories and which are not, at class creation,
 | 
			
		||||
# so that the actual creation of the defaults dictionary is simple.
 | 
			
		||||
def _extract_defaults(defaults: Optional[Type]) -> List[ComponentDefaultField]:
 | 
			
		||||
    defaults_fields: List[ComponentDefaultField] = []
 | 
			
		||||
    if defaults is None:
 | 
			
		||||
        return defaults_fields
 | 
			
		||||
def _extract_defaults(defaults: Optional[Type], kwargs: Optional[Type]) -> List[ComponentDefaultField]:
 | 
			
		||||
    """
 | 
			
		||||
    Given the `Defaults` and `Kwargs` classes from a component, this function extracts
 | 
			
		||||
    the default values from them.
 | 
			
		||||
    """
 | 
			
		||||
    # First, extract defaults from the `Defaults` class
 | 
			
		||||
    defaults_fields_map: Dict[str, ComponentDefaultField] = {}
 | 
			
		||||
    if defaults is not None:
 | 
			
		||||
        for default_field_key in dir(defaults):
 | 
			
		||||
            # Iterate only over fields set by the user (so non-dunder fields).
 | 
			
		||||
            # Plus ignore `component_class` because that was set by the extension system.
 | 
			
		||||
            # TODO_V1 - Remove `component_class`
 | 
			
		||||
            if default_field_key.startswith("__") or default_field_key in {"component_class", "component_cls"}:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
    for default_field_key in dir(defaults):
 | 
			
		||||
        # Iterate only over fields set by the user (so non-dunder fields).
 | 
			
		||||
        # Plus ignore `component_class` because that was set by the extension system.
 | 
			
		||||
        # TODO_V1 - Remove `component_class`
 | 
			
		||||
        if default_field_key.startswith("__") or default_field_key in {"component_class", "component_cls"}:
 | 
			
		||||
            continue
 | 
			
		||||
            default_field = getattr(defaults, default_field_key)
 | 
			
		||||
 | 
			
		||||
        default_field = getattr(defaults, default_field_key)
 | 
			
		||||
            if isinstance(default_field, property):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        if isinstance(default_field, property):
 | 
			
		||||
            continue
 | 
			
		||||
            # If the field was defined with dataclass.field(), take the default / factory from there.
 | 
			
		||||
            if isinstance(default_field, Field):
 | 
			
		||||
                if default_field.default is not MISSING:
 | 
			
		||||
                    field_value = default_field.default
 | 
			
		||||
                    is_factory = False
 | 
			
		||||
                elif default_field.default_factory is not MISSING:
 | 
			
		||||
                    field_value = default_field.default_factory
 | 
			
		||||
                    is_factory = True
 | 
			
		||||
                else:
 | 
			
		||||
                    field_value = None
 | 
			
		||||
                    is_factory = False
 | 
			
		||||
 | 
			
		||||
        # If the field was defined with dataclass.field(), take the default / factory from there.
 | 
			
		||||
        if isinstance(default_field, Field):
 | 
			
		||||
            if default_field.default is not MISSING:
 | 
			
		||||
                field_value = default_field.default
 | 
			
		||||
                is_factory = False
 | 
			
		||||
            elif default_field.default_factory is not MISSING:
 | 
			
		||||
                field_value = default_field.default_factory
 | 
			
		||||
            # If the field was defined with our `Default` class, it defined a factory
 | 
			
		||||
            elif isinstance(default_field, Default):
 | 
			
		||||
                field_value = default_field.value
 | 
			
		||||
                is_factory = True
 | 
			
		||||
 | 
			
		||||
            # If the field was defined with a simple assignment, assume it's NOT a factory.
 | 
			
		||||
            else:
 | 
			
		||||
                field_value = None
 | 
			
		||||
                field_value = default_field
 | 
			
		||||
                is_factory = False
 | 
			
		||||
 | 
			
		||||
        # If the field was defined with our `Default` class, it defined a factory
 | 
			
		||||
        elif isinstance(default_field, Default):
 | 
			
		||||
            field_value = default_field.value
 | 
			
		||||
            is_factory = True
 | 
			
		||||
            field_data = ComponentDefaultField(
 | 
			
		||||
                key=default_field_key,
 | 
			
		||||
                value=field_value,
 | 
			
		||||
                is_factory=is_factory,
 | 
			
		||||
            )
 | 
			
		||||
            defaults_fields_map[default_field_key] = field_data
 | 
			
		||||
 | 
			
		||||
        # If the field was defined with a simple assignment, assume it's NOT a factory.
 | 
			
		||||
        else:
 | 
			
		||||
            field_value = default_field
 | 
			
		||||
            is_factory = False
 | 
			
		||||
    # Next, extract defaults from the `Kwargs` class.
 | 
			
		||||
    # We check for dataclasses and NamedTuple, as those are the supported ways to define defaults.
 | 
			
		||||
    # Support for other types of `Kwargs` classes, like Pydantic models, is left to extensions.
 | 
			
		||||
    kwargs_fields_map: Dict[str, ComponentDefaultField] = {}
 | 
			
		||||
    if kwargs is not None:
 | 
			
		||||
        if dataclasses.is_dataclass(kwargs):
 | 
			
		||||
            for field in dataclasses.fields(kwargs):
 | 
			
		||||
                if field.default is not dataclasses.MISSING:
 | 
			
		||||
                    field_value = field.default
 | 
			
		||||
                    is_factory = False
 | 
			
		||||
                elif field.default_factory is not dataclasses.MISSING:
 | 
			
		||||
                    field_value = field.default_factory
 | 
			
		||||
                    is_factory = True
 | 
			
		||||
                else:
 | 
			
		||||
                    continue  # No default value
 | 
			
		||||
 | 
			
		||||
        field_data = ComponentDefaultField(
 | 
			
		||||
            key=default_field_key,
 | 
			
		||||
            value=field_value,
 | 
			
		||||
            is_factory=is_factory,
 | 
			
		||||
        )
 | 
			
		||||
        defaults_fields.append(field_data)
 | 
			
		||||
                field_data = ComponentDefaultField(
 | 
			
		||||
                    key=field.name,
 | 
			
		||||
                    value=field_value,
 | 
			
		||||
                    is_factory=is_factory,
 | 
			
		||||
                )
 | 
			
		||||
                kwargs_fields_map[field.name] = field_data
 | 
			
		||||
 | 
			
		||||
    return defaults_fields
 | 
			
		||||
        # Check for NamedTuple.
 | 
			
		||||
        # Note that we check for `_fields` to avoid accidentally matching `tuple` subclasses.
 | 
			
		||||
        elif issubclass(kwargs, tuple) and hasattr(kwargs, "_fields"):
 | 
			
		||||
            # `_field_defaults` is a dict of {field_name: default_value}
 | 
			
		||||
            for field_name, default_value in getattr(kwargs, "_field_defaults", {}).items():
 | 
			
		||||
                field_data = ComponentDefaultField(
 | 
			
		||||
                    key=field_name,
 | 
			
		||||
                    value=default_value,
 | 
			
		||||
                    is_factory=False,
 | 
			
		||||
                )
 | 
			
		||||
                kwargs_fields_map[field_name] = field_data
 | 
			
		||||
 | 
			
		||||
    # Merge the two, with `kwargs` overwriting `defaults`.
 | 
			
		||||
    merged_fields_map = {**defaults_fields_map, **kwargs_fields_map}
 | 
			
		||||
    return list(merged_fields_map.values())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _apply_defaults(kwargs: Dict, defaults: List[ComponentDefaultField]) -> None:
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +261,9 @@ class DefaultsExtension(ComponentExtension):
 | 
			
		|||
    # each time a component is rendered.
 | 
			
		||||
    def on_component_class_created(self, ctx: OnComponentClassCreatedContext) -> None:
 | 
			
		||||
        defaults_cls = getattr(ctx.component_cls, "Defaults", None)
 | 
			
		||||
        defaults_by_component[ctx.component_cls] = _extract_defaults(defaults_cls)
 | 
			
		||||
        # Allow to simply define `Component.Kwargs` with defaults instead of 2 separate classes
 | 
			
		||||
        kwargs_cls = getattr(ctx.component_cls, "Kwargs", None)
 | 
			
		||||
        defaults_by_component[ctx.component_cls] = _extract_defaults(defaults_cls, kwargs_cls)
 | 
			
		||||
 | 
			
		||||
    # Apply defaults to missing or `None` values in `kwargs`
 | 
			
		||||
    def on_component_input(self, ctx: OnComponentInputContext) -> None:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
from dataclasses import field
 | 
			
		||||
from dataclasses import dataclass, field
 | 
			
		||||
from typing import NamedTuple
 | 
			
		||||
 | 
			
		||||
from django.template import Context
 | 
			
		||||
 | 
			
		||||
from django_components import Component, Default
 | 
			
		||||
from django_components import Component, Default, get_component_defaults
 | 
			
		||||
from django_components.testing import djc_test
 | 
			
		||||
 | 
			
		||||
from .testutils import setup_test_config
 | 
			
		||||
| 
						 | 
				
			
			@ -169,3 +170,255 @@ class TestComponentDefaults:
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_kwargs_namedtuple(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Kwargs(NamedTuple):
 | 
			
		||||
                another: int
 | 
			
		||||
                variable: str = "default_from_kwargs"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "variable": "default_from_kwargs",
 | 
			
		||||
                    "another": 123,
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(
 | 
			
		||||
            kwargs={"another": 123},
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_kwargs_dataclass(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            @dataclass
 | 
			
		||||
            class Kwargs:
 | 
			
		||||
                another: int
 | 
			
		||||
                variable: str = "default_from_kwargs"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "variable": "default_from_kwargs",
 | 
			
		||||
                    "another": 123,
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(
 | 
			
		||||
            kwargs={"another": 123},
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_kwargs_other_class(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class CustomKwargs:
 | 
			
		||||
            def __init__(self, **kwargs):
 | 
			
		||||
                for key, value in kwargs.items():
 | 
			
		||||
                    setattr(self, key, value)
 | 
			
		||||
                self._kwargs = kwargs
 | 
			
		||||
 | 
			
		||||
            def _asdict(self):
 | 
			
		||||
                return self._kwargs
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Kwargs(CustomKwargs):
 | 
			
		||||
                another: int
 | 
			
		||||
                variable: str = "default_from_kwargs"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                # No defaults should be applied from a plain class
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "another": 123,
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(
 | 
			
		||||
            kwargs={"another": 123},
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_defaults_and_kwargs_namedtuple(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Kwargs(NamedTuple):
 | 
			
		||||
                from_defaults_only: str
 | 
			
		||||
                variable: str = "from_kwargs"
 | 
			
		||||
                from_kwargs_only: str = "kwargs_default"
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                variable = "from_defaults"
 | 
			
		||||
                from_defaults_only = "defaults_default"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "variable": "from_kwargs",  # Overridden by Kwargs
 | 
			
		||||
                    "from_defaults_only": "defaults_default",
 | 
			
		||||
                    "from_kwargs_only": "kwargs_default",
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(kwargs={})
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_defaults_and_kwargs_dataclass(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            @dataclass
 | 
			
		||||
            class Kwargs:
 | 
			
		||||
                from_defaults_only: str
 | 
			
		||||
                variable: str = "from_kwargs"
 | 
			
		||||
                from_kwargs_only: str = "kwargs_default"
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                variable = "from_defaults"
 | 
			
		||||
                from_defaults_only = "defaults_default"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "variable": "from_kwargs",  # Overridden by Kwargs
 | 
			
		||||
                    "from_defaults_only": "defaults_default",
 | 
			
		||||
                    "from_kwargs_only": "kwargs_default",
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(kwargs={})
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
    def test_defaults_from_defaults_and_kwargs_other_class(self):
 | 
			
		||||
        did_call_context = False
 | 
			
		||||
 | 
			
		||||
        class CustomKwargs:
 | 
			
		||||
            def __init__(self, **kwargs):
 | 
			
		||||
                for key, value in kwargs.items():
 | 
			
		||||
                    setattr(self, key, value)
 | 
			
		||||
                self._kwargs = kwargs
 | 
			
		||||
 | 
			
		||||
            def _asdict(self):
 | 
			
		||||
                return self._kwargs
 | 
			
		||||
 | 
			
		||||
        class TestComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Kwargs(CustomKwargs):
 | 
			
		||||
                variable: str = "from_kwargs"
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                variable = "from_defaults"
 | 
			
		||||
                from_defaults_only = "defaults_default"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs, slots, context):
 | 
			
		||||
                nonlocal did_call_context
 | 
			
		||||
                did_call_context = True
 | 
			
		||||
 | 
			
		||||
                assert self.raw_kwargs == {
 | 
			
		||||
                    "variable": "from_defaults",  # No override
 | 
			
		||||
                    "from_defaults_only": "defaults_default",
 | 
			
		||||
                }
 | 
			
		||||
                return {}
 | 
			
		||||
 | 
			
		||||
        TestComponent.render(kwargs={})
 | 
			
		||||
        assert did_call_context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@djc_test
 | 
			
		||||
class TestGetComponentDefaults:
 | 
			
		||||
    def test_defaults_with_factory(self):
 | 
			
		||||
        class MyComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                val = "static"
 | 
			
		||||
                factory_val = Default(lambda: "from_factory")
 | 
			
		||||
 | 
			
		||||
        defaults = get_component_defaults(MyComponent)
 | 
			
		||||
        assert defaults == {
 | 
			
		||||
            "val": "static",
 | 
			
		||||
            "factory_val": "from_factory",
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_kwargs_dataclass_with_factory(self):
 | 
			
		||||
        class MyComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            @dataclass
 | 
			
		||||
            class Kwargs:
 | 
			
		||||
                val: str = "static"
 | 
			
		||||
                factory_val: str = field(default_factory=lambda: "from_factory")
 | 
			
		||||
 | 
			
		||||
        defaults = get_component_defaults(MyComponent)
 | 
			
		||||
        assert defaults == {
 | 
			
		||||
            "val": "static",
 | 
			
		||||
            "factory_val": "from_factory",
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_defaults_and_kwargs_overrides_with_factories(self):
 | 
			
		||||
        class MyComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            @dataclass
 | 
			
		||||
            class Kwargs:
 | 
			
		||||
                val_both: str = field(default_factory=lambda: "from_kwargs_factory")
 | 
			
		||||
                val_kwargs: str = field(default_factory=lambda: "kwargs_only")
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                val_both = Default(lambda: "from_defaults_factory")
 | 
			
		||||
                val_defaults = Default(lambda: "defaults_only")
 | 
			
		||||
 | 
			
		||||
        defaults = get_component_defaults(MyComponent)
 | 
			
		||||
        assert defaults == {
 | 
			
		||||
            "val_both": "from_kwargs_factory",
 | 
			
		||||
            "val_kwargs": "kwargs_only",
 | 
			
		||||
            "val_defaults": "defaults_only",
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_kwargs_namedtuple_with_defaults(self):
 | 
			
		||||
        class MyComponent(Component):
 | 
			
		||||
            template = ""
 | 
			
		||||
 | 
			
		||||
            class Kwargs(NamedTuple):
 | 
			
		||||
                val_no_default: int
 | 
			
		||||
                val_defaults: str
 | 
			
		||||
                val_kwargs: str = "kwargs_default"
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                val_defaults = "defaults_default"
 | 
			
		||||
 | 
			
		||||
        defaults = get_component_defaults(component=MyComponent)
 | 
			
		||||
        assert defaults == {
 | 
			
		||||
            "val_kwargs": "kwargs_default",
 | 
			
		||||
            "val_defaults": "defaults_default",
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,10 +22,7 @@ class TestDynamicComponent:
 | 
			
		|||
 | 
			
		||||
            class Kwargs:
 | 
			
		||||
                variable: str
 | 
			
		||||
                variable2: str
 | 
			
		||||
 | 
			
		||||
            class Defaults:
 | 
			
		||||
                variable2 = "default"
 | 
			
		||||
                variable2: str = "default"
 | 
			
		||||
 | 
			
		||||
            def get_template_data(self, args, kwargs: Kwargs, slots, context):
 | 
			
		||||
                return {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue