joop 0.0.1__py3-none-any.whl
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.
- joop/__init__.py +9 -0
- joop/abstract/README.md +3 -0
- joop/abstract/__init__.py +95 -0
- joop/cli/README.md +6 -0
- joop/cli/__init__.py +1 -0
- joop/cli/__main__.py +39 -0
- joop/cli/test_flask.py +36 -0
- joop/dao/__init__.py +151 -0
- joop/flask/__init__.py +31 -0
- joop/flask/example.py +19 -0
- joop/flask/flask_view.py +71 -0
- joop/http/methods.py +49 -0
- joop/tests/__init__.py +5 -0
- joop/tests/test_joop.py +29 -0
- joop/tests/test_templater.py +31 -0
- joop/tests/test_view.py +46 -0
- joop/tests/test_web.py +75 -0
- joop/web/__init__.py +14 -0
- joop/web/component.py +163 -0
- joop/web/components.py +116 -0
- joop/web/examples/hello.py +148 -0
- joop/web/examples/table.py +148 -0
- joop/web/examples/view.py +30 -0
- joop/web/html.py +212 -0
- joop/web/j_env.py +39 -0
- joop/web/templater.py +139 -0
- joop/web/view.py +214 -0
- joop-0.0.1.dist-info/METADATA +133 -0
- joop-0.0.1.dist-info/RECORD +31 -0
- joop-0.0.1.dist-info/WHEEL +4 -0
- joop-0.0.1.dist-info/entry_points.txt +3 -0
joop/tests/test_web.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Unit tests for joop web components.
|
|
2
|
+
|
|
3
|
+
We're testing both components, their templates, and the rendering here.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from joop.web import HTMLComponent
|
|
7
|
+
import unittest
|
|
8
|
+
from dataclasses import is_dataclass, dataclass, asdict
|
|
9
|
+
from joop.tests.test_templater import environment
|
|
10
|
+
from joop.web.examples.hello import (
|
|
11
|
+
HelloWorld, HelloName, HelloSuperComponent
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Create our classes for the test and
|
|
15
|
+
# specify the environment via multiple inheritance:
|
|
16
|
+
|
|
17
|
+
class BaseTestHTMLComponent(HTMLComponent):
|
|
18
|
+
_jinja_env = environment
|
|
19
|
+
|
|
20
|
+
class MyHello(HelloWorld,
|
|
21
|
+
BaseTestHTMLComponent): pass
|
|
22
|
+
|
|
23
|
+
class MyHelloName(HelloName,
|
|
24
|
+
BaseTestHTMLComponent): pass
|
|
25
|
+
|
|
26
|
+
class MyHelloSuper(HelloSuperComponent,
|
|
27
|
+
BaseTestHTMLComponent): pass
|
|
28
|
+
|
|
29
|
+
class TestHTMLComponent(unittest.TestCase):
|
|
30
|
+
|
|
31
|
+
def _setup_hello(self):
|
|
32
|
+
self.hello = MyHello()
|
|
33
|
+
self.hello.inputs = MyHello.Inputs()
|
|
34
|
+
self.hello.subs = MyHello.SubComponents()
|
|
35
|
+
|
|
36
|
+
def _setup_name(self):
|
|
37
|
+
self.hello_name = MyHelloName()
|
|
38
|
+
self.hello_name.inputs = MyHelloName.Inputs(first_name = "Justin",
|
|
39
|
+
last_name = "Rushin")
|
|
40
|
+
self.hello_name.subs = MyHelloName.SubComponents()
|
|
41
|
+
|
|
42
|
+
def _setup_super(self):
|
|
43
|
+
self.hello_super = MyHelloSuper()
|
|
44
|
+
self.hello_super.inputs = MyHelloSuper.Inputs()
|
|
45
|
+
|
|
46
|
+
def setUp(self):
|
|
47
|
+
# Configure the Jinja2 environment with a FileSystemLoader
|
|
48
|
+
self._setup_hello()
|
|
49
|
+
self._setup_name()
|
|
50
|
+
self._setup_super()
|
|
51
|
+
|
|
52
|
+
def test_000_type_check(self):
|
|
53
|
+
# here, we are verifying that our custom abstract code works
|
|
54
|
+
assert is_dataclass(self.hello.inputs)
|
|
55
|
+
|
|
56
|
+
def test_001_hello_world(self):
|
|
57
|
+
hello_html = self.hello.render()
|
|
58
|
+
assert hello_html == "<p>Hello, World!</p>"
|
|
59
|
+
|
|
60
|
+
def test_002_hello_name(self):
|
|
61
|
+
hello_name_html = self.hello_name.render()
|
|
62
|
+
_tgt_html = """<p>Hello, Justin Rushin!</p>"""
|
|
63
|
+
assert hello_name_html == _tgt_html
|
|
64
|
+
|
|
65
|
+
def test_003_hello_subcomponent(self):
|
|
66
|
+
_hello = MyHello(parent=self.hello_super)
|
|
67
|
+
_hello.inputs = MyHello.Inputs()
|
|
68
|
+
_hello.subs = MyHello.SubComponents()
|
|
69
|
+
self.hello_super.subs = MyHelloSuper.SubComponents(
|
|
70
|
+
my_hello = _hello
|
|
71
|
+
)
|
|
72
|
+
assert is_dataclass(self.hello_super.subs)
|
|
73
|
+
hello_super_html = self.hello_super.render()
|
|
74
|
+
_tgt_html = """<p>I'm a supercomponent! And I say:</p>\n<p>Hello, World!</p>"""
|
|
75
|
+
assert hello_super_html == _tgt_html
|
joop/web/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Web module.
|
|
2
|
+
|
|
3
|
+
All functionality related to web-backends, HTML templating, etc. lives here.
|
|
4
|
+
|
|
5
|
+
Modules:
|
|
6
|
+
component: Define web UI or API components.
|
|
7
|
+
html: Where components get rendered to HTML.
|
|
8
|
+
view: Register components to webservers, set up views routes, etc.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from joop.web.component import Component #, JSONComponent
|
|
13
|
+
from joop.web.html import HTML, HTMLComponent
|
|
14
|
+
from joop.web.view import View
|
joop/web/component.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Components are programmatic representations of data or UI elements, designed to be rendered dynamically,
|
|
4
|
+
in the context of a web server.
|
|
5
|
+
A highly abstract class, the purpose of it is to provide a standard way to define inputs, outputs,
|
|
6
|
+
and the transformations needed to render them. Components are the backbone of joop.web.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
Component:
|
|
10
|
+
The base class for all components, providing a structure for inputs, data, and subcomponents.
|
|
11
|
+
|
|
12
|
+
JSONComponent:
|
|
13
|
+
A specialized component for handling JSON data. Implementation pending.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Optional
|
|
18
|
+
from dataclasses import dataclass, fields
|
|
19
|
+
from abc import ABCMeta
|
|
20
|
+
|
|
21
|
+
from joop.abstract import AbstractMethod
|
|
22
|
+
|
|
23
|
+
class Component(metaclass=ABCMeta):
|
|
24
|
+
'''
|
|
25
|
+
Base class for programmatically rendering data or UI elements.
|
|
26
|
+
|
|
27
|
+
This class provides a structure for defining inputs, data, and subcomponents,
|
|
28
|
+
and includes methods for processing inputs and rendering components.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
inputs (Inputs): The input data for the component.
|
|
32
|
+
data (Data): The processed data for the component.
|
|
33
|
+
subs (SubComponents): The subcomponents of the component.
|
|
34
|
+
|
|
35
|
+
Methods:
|
|
36
|
+
_process_inputs():
|
|
37
|
+
Processes the inputs to generate the component's data.
|
|
38
|
+
|
|
39
|
+
render() -> str:
|
|
40
|
+
Abstract method to render the component as a string.
|
|
41
|
+
'''
|
|
42
|
+
|
|
43
|
+
class Inputs(metaclass=ABCMeta):
|
|
44
|
+
"""Abstract base class for defining the input data structure of a component."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
Inputs = dataclass(Inputs)
|
|
48
|
+
|
|
49
|
+
class _Data(metaclass=ABCMeta):
|
|
50
|
+
"""Abstract base class for defining the internal data structure of a component."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
_Data = dataclass(_Data)
|
|
54
|
+
|
|
55
|
+
class Data(_Data):
|
|
56
|
+
"""
|
|
57
|
+
Class for defining the processed data of a component.
|
|
58
|
+
|
|
59
|
+
Methods:
|
|
60
|
+
_from_inputs(inputs: Component.Inputs) -> Component.Data:
|
|
61
|
+
Creates a Data instance from the given Inputs.
|
|
62
|
+
|
|
63
|
+
from_inputs(inputs: Component.Inputs) -> Component.Data:
|
|
64
|
+
Abstract method to create a Data instance from the given Inputs.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def _from_inputs(cls, inputs: 'Component.Inputs') -> 'Component.Data':
|
|
69
|
+
"""
|
|
70
|
+
Create a Data instance from the given Inputs.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
inputs (Component.Inputs): The input data for the component.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Component.Data: A new Data instance.
|
|
77
|
+
"""
|
|
78
|
+
return cls()
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_inputs(cls, inputs: 'Component.Inputs') -> 'Component.Data':
|
|
82
|
+
"""
|
|
83
|
+
Abstract method to create a Data instance from the given Inputs.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
inputs (Component.Inputs): The input data for the component.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Component.Data: A new Data instance.
|
|
90
|
+
"""
|
|
91
|
+
return Component.Data() # pragma: no cover
|
|
92
|
+
|
|
93
|
+
from_inputs = AbstractMethod(from_inputs)
|
|
94
|
+
|
|
95
|
+
class SubComponents(metaclass=ABCMeta):
|
|
96
|
+
"""Abstract base class for defining the subcomponents of a component."""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
SubComponents = dataclass(SubComponents)
|
|
100
|
+
|
|
101
|
+
inputs: Inputs
|
|
102
|
+
data: Data
|
|
103
|
+
subs: SubComponents
|
|
104
|
+
|
|
105
|
+
def __init__(self, parent: Optional['Component'] = None, *args, **kwargs):
|
|
106
|
+
"""
|
|
107
|
+
Initialize a Component instance.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
parent (Optional[Component]): The parent component, if any.
|
|
111
|
+
*args: Additional positional arguments.
|
|
112
|
+
**kwargs: Additional keyword arguments.
|
|
113
|
+
"""
|
|
114
|
+
super().__init__(*args, **kwargs) # Pass additional arguments to the next class in the MRO
|
|
115
|
+
if parent is not None:
|
|
116
|
+
self._parent = parent
|
|
117
|
+
|
|
118
|
+
def _process_inputs(self):
|
|
119
|
+
"""
|
|
120
|
+
Process the inputs to generate the component's data.
|
|
121
|
+
|
|
122
|
+
This method uses the `from_inputs` method of the Data class to create a
|
|
123
|
+
Data instance from the component's inputs.
|
|
124
|
+
"""
|
|
125
|
+
self.data = self.Data.from_inputs(self.inputs)
|
|
126
|
+
|
|
127
|
+
def render(self) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Abstract method to render the component as a string.
|
|
130
|
+
|
|
131
|
+
This method processes the inputs and generates the component's data before
|
|
132
|
+
rendering it as a string. Subclasses must implement this method.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
str: The rendered component as a string.
|
|
136
|
+
"""
|
|
137
|
+
self._process_inputs()
|
|
138
|
+
|
|
139
|
+
render.__isabstractmethod__ = True
|
|
140
|
+
|
|
141
|
+
def __init_subclass__(cls, **kwargs):
|
|
142
|
+
"""
|
|
143
|
+
Initialize a subclass of Component.
|
|
144
|
+
|
|
145
|
+
This method ensures that the Data, Inputs, and SubComponents classes of the
|
|
146
|
+
subclass are decorated as dataclasses.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
**kwargs: Additional keyword arguments.
|
|
150
|
+
"""
|
|
151
|
+
super().__init_subclass__(**kwargs)
|
|
152
|
+
cls.Data = dataclass(cls.Data)
|
|
153
|
+
cls.Inputs = dataclass(cls.Inputs)
|
|
154
|
+
cls.SubComponents = dataclass(cls.SubComponents)
|
|
155
|
+
|
|
156
|
+
class JSONComponent(Component):
|
|
157
|
+
"""
|
|
158
|
+
A specialized component for handling JSON data.
|
|
159
|
+
|
|
160
|
+
Inherits:
|
|
161
|
+
Component: The base Component class.
|
|
162
|
+
"""
|
|
163
|
+
pass
|
joop/web/components.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Useful components for web development.
|
|
3
|
+
It includes a base class `AlpineTableComponent` for generating AlpineJS-powered
|
|
4
|
+
tables based on Pydantic or SQLModel models.
|
|
5
|
+
The module also defines data access object (DAO) classes for handling row data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from joop.web.html import HTMLComponent
|
|
9
|
+
from joop.dao import DAO
|
|
10
|
+
import typing
|
|
11
|
+
|
|
12
|
+
class MetaRowDAO(DAO):
|
|
13
|
+
"""
|
|
14
|
+
Implementation pending.
|
|
15
|
+
A base Data Access Object (DAO) class for handling row data.
|
|
16
|
+
This class can be extended to implement specific data access logic.
|
|
17
|
+
"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class RowDAO(MetaRowDAO):
|
|
21
|
+
"""
|
|
22
|
+
Implementation pending.
|
|
23
|
+
A concrete implementation of `MetaRowDAO` for handling generic row data.
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class SQLRowDAO(MetaRowDAO):
|
|
28
|
+
"""
|
|
29
|
+
Implementation pending.
|
|
30
|
+
A concrete implementation of `MetaRowDAO` for handling SQL-based row data.
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
'''
|
|
35
|
+
A common base component to allow for ready-made,
|
|
36
|
+
AlpineJS powered
|
|
37
|
+
tables based on Pydantic or SQLModel models.
|
|
38
|
+
'''
|
|
39
|
+
class AlpineTableComponent(HTMLComponent):
|
|
40
|
+
"""
|
|
41
|
+
A reusable component for rendering tables using Alpine.js and HTML templates.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
_template_location (str): Path to the HTML template for the table.
|
|
45
|
+
_use_prefix_template (bool): Determines whether to use a prefixed template directory.
|
|
46
|
+
_row_type (typing.Type[MetaRowDAO]): Specifies the type of row data to be used in the table.
|
|
47
|
+
"""
|
|
48
|
+
_template_location = "table/alp_table.html"
|
|
49
|
+
_use_prefix_template = False
|
|
50
|
+
_row_type: typing.Type[MetaRowDAO]
|
|
51
|
+
|
|
52
|
+
class Inputs(HTMLComponent.Inputs):
|
|
53
|
+
"""
|
|
54
|
+
Represents the input data structure for the `AlpineTableComponent`.
|
|
55
|
+
Extend this class to define specific input fields for the table.
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
class Data(HTMLComponent.Data):
|
|
60
|
+
"""
|
|
61
|
+
Represents the data structure for the `AlpineTableComponent`.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
rows (typing.Iterable[MetaRowDAO]): The rows of data to be displayed in the table.
|
|
65
|
+
table_headers (typing.Any): The headers of the table, derived from the row type.
|
|
66
|
+
_row_type: The type of row data used in the table.
|
|
67
|
+
"""
|
|
68
|
+
rows : typing.Iterable[MetaRowDAO]
|
|
69
|
+
table_headers: typing.Any
|
|
70
|
+
_row_type = None
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def _get_table_headers(cls):
|
|
74
|
+
"""
|
|
75
|
+
Retrieve the table headers based on the fields of the row type's model.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
list: A list of field names for the table headers.
|
|
79
|
+
"""
|
|
80
|
+
return cls._row_type.get_model_fields()
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_inputs(cls,
|
|
84
|
+
inputs : 'AlpineTableComponent.Inputs',
|
|
85
|
+
) -> 'AlpineTableComponent.Data':
|
|
86
|
+
"""
|
|
87
|
+
Create a `Data` instance from the provided inputs.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
inputs (AlpineTableComponent.Inputs): The input data for the table.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
AlpineTableComponent.Data: An instance of the Data class with initialized values.
|
|
94
|
+
"""
|
|
95
|
+
return cls(rows = [],
|
|
96
|
+
table_headers = cls._get_table_headers())
|
|
97
|
+
|
|
98
|
+
def _process_inputs(self, **kwargs):
|
|
99
|
+
"""
|
|
100
|
+
Process the input data and set the row type for the table.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
**kwargs: Additional keyword arguments for processing inputs.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Any: The processed input data.
|
|
107
|
+
"""
|
|
108
|
+
self.Data._row_type = self._row_type
|
|
109
|
+
return super()._process_inputs(**kwargs)
|
|
110
|
+
|
|
111
|
+
class SubComponents(HTMLComponent.SubComponents):
|
|
112
|
+
"""
|
|
113
|
+
Represents subcomponents of the `AlpineTableComponent`.
|
|
114
|
+
Extend this class to define specific subcomponents.
|
|
115
|
+
"""
|
|
116
|
+
pass
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
This file contains examples for learning, development, and testing of joop.web components.
|
|
4
|
+
Included is examples of how to define and implement web components of the most basic kind.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from joop.web import HTMLComponent
|
|
9
|
+
|
|
10
|
+
class HelloWorld(HTMLComponent):
|
|
11
|
+
|
|
12
|
+
# Every HTMLComponent has a template location.
|
|
13
|
+
_template_location = "hello.html"
|
|
14
|
+
|
|
15
|
+
# A definition of an inner input class is required.
|
|
16
|
+
class Inputs(HTMLComponent.Inputs):
|
|
17
|
+
# Even if it's empty.
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
# For the inner classes, inherit from the parent class:
|
|
21
|
+
class Data(HTMLComponent.Data):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
# `from_inputs` must be defined as well.
|
|
25
|
+
@classmethod # Yes, you still have to specify classmethod.
|
|
26
|
+
# Also, it is good to be specific with type hints:
|
|
27
|
+
def from_inputs(cls, inputs : 'HelloWorld.Inputs') -> 'HelloWorld.Data':
|
|
28
|
+
# If you've got a no-op class like this, use:
|
|
29
|
+
return super()._from_inputs(inputs)
|
|
30
|
+
|
|
31
|
+
# Copy-pasting the above ^^^ to a new component is safe
|
|
32
|
+
|
|
33
|
+
# Same as inputs, just make an empty class if there are no
|
|
34
|
+
# child/sub components.
|
|
35
|
+
class SubComponents(HTMLComponent.SubComponents):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
'''
|
|
39
|
+
To then render the hello world component:
|
|
40
|
+
```
|
|
41
|
+
component = HelloWorld()
|
|
42
|
+
component.inputs = component.Inputs()
|
|
43
|
+
# You don't need to do data because of the `from_inputs`.
|
|
44
|
+
component.subs = component.SubComponents()
|
|
45
|
+
return component.render()
|
|
46
|
+
```
|
|
47
|
+
That's it. But why complicate a "hello world" by
|
|
48
|
+
requiring empty classes, functions etc.?
|
|
49
|
+
A few reasons:
|
|
50
|
+
1. We don't optimize for "hello world" because real
|
|
51
|
+
components are unlikely to be this simple, but
|
|
52
|
+
compromise on succintness for the sake of simplicity
|
|
53
|
+
and consistency.
|
|
54
|
+
2. joop is a declarative paradigm, and accordingly,
|
|
55
|
+
explicitly declared symbols show the class inheritance.
|
|
56
|
+
3. Explicitly declared stubs show you that nothing is there.
|
|
57
|
+
4. It facilitates the derivation of more complex components
|
|
58
|
+
from more simple ones (and possibly, vice-versa).
|
|
59
|
+
5. A place for everything, and everything in its place.
|
|
60
|
+
'''
|
|
61
|
+
|
|
62
|
+
# Now for a component with dynamic data rendering.
|
|
63
|
+
|
|
64
|
+
class HelloName(HTMLComponent):
|
|
65
|
+
_template_location = "hello_name.html" # Adjusted for simplicity
|
|
66
|
+
|
|
67
|
+
# Inputs and data are dataclasses via inheritance (there's a bit of trickery).
|
|
68
|
+
class Inputs(HTMLComponent.Inputs):
|
|
69
|
+
first_name: str
|
|
70
|
+
last_name : str
|
|
71
|
+
|
|
72
|
+
# Inputs is what it sounds like.
|
|
73
|
+
# Data goes to the template to be rendered.
|
|
74
|
+
class Data(HTMLComponent.Data):
|
|
75
|
+
# The name of the data fields will be used in the template.
|
|
76
|
+
full_name : str
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_inputs(cls, inputs: "HelloName.Inputs") -> "HelloName.Data":
|
|
80
|
+
res = f"{inputs.first_name} {inputs.last_name}"
|
|
81
|
+
return cls(full_name = res)
|
|
82
|
+
|
|
83
|
+
class SubComponents(HTMLComponent.SubComponents):
|
|
84
|
+
pass
|
|
85
|
+
'''
|
|
86
|
+
To then render the hello name component:
|
|
87
|
+
```
|
|
88
|
+
component = HelloName()
|
|
89
|
+
component.inputs = component.Inputs(
|
|
90
|
+
first_name = "Justin",
|
|
91
|
+
last_name = "Rushin")
|
|
92
|
+
component.subs = res.SubComponents()
|
|
93
|
+
return component.render()
|
|
94
|
+
```
|
|
95
|
+
'''
|
|
96
|
+
|
|
97
|
+
# Now for an example with subcomponents aka:
|
|
98
|
+
# child components, or nested components.
|
|
99
|
+
|
|
100
|
+
class HelloSuperComponent(HTMLComponent):
|
|
101
|
+
# Of course, this is our/outer's template. Inner
|
|
102
|
+
# template isn't specified. It's in the subcomponent.
|
|
103
|
+
_template_location = "hello_supercomponent.html"
|
|
104
|
+
|
|
105
|
+
# Empty inputs and data classes follow:
|
|
106
|
+
class Inputs(HTMLComponent.Inputs):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
class Data(HTMLComponent.Data):
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_inputs(cls, inputs):
|
|
113
|
+
return super()._from_inputs(inputs)
|
|
114
|
+
|
|
115
|
+
class SubComponents(HTMLComponent.SubComponents):
|
|
116
|
+
# No need to specify a default factory.
|
|
117
|
+
# We use some trickery. Just specify the class
|
|
118
|
+
# of the child component.
|
|
119
|
+
my_hello: HelloWorld
|
|
120
|
+
|
|
121
|
+
def render(self) -> str:
|
|
122
|
+
return super().render()
|
|
123
|
+
|
|
124
|
+
'''
|
|
125
|
+
To render this component with nesting:
|
|
126
|
+
```
|
|
127
|
+
component = HelloSuperComponent()
|
|
128
|
+
component.inputs = component.Inputs() # empty inputs
|
|
129
|
+
subcomponent = HelloWorld(parent = component)
|
|
130
|
+
subcomponent.inputs = subcomponent.Inputs()
|
|
131
|
+
component.subs = component.SubComponents(
|
|
132
|
+
my_hello = subcomponent
|
|
133
|
+
)
|
|
134
|
+
component.render()
|
|
135
|
+
```
|
|
136
|
+
`render` will include subcomponents recursively.
|
|
137
|
+
'''
|
|
138
|
+
|
|
139
|
+
'''
|
|
140
|
+
To summarize, a component is made by:
|
|
141
|
+
1. Deriving the outer class from the correct class.
|
|
142
|
+
2. Defining the fields/properties of the innner classes.
|
|
143
|
+
3. Implementing the `from_inputs` function to transform
|
|
144
|
+
input to output.
|
|
145
|
+
4. Carrying out any other special implementation necessary.
|
|
146
|
+
|
|
147
|
+
Of course, this ignores what's done on the template side.
|
|
148
|
+
'''
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides an example implementation of a table component and its integration into a web page.
|
|
3
|
+
It demonstrates the usage of the `AlpineTableComponent` for rendering tables and the `View` class for creating web pages.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from joop.web.components import AlpineTableComponent
|
|
7
|
+
from joop.web.view import View
|
|
8
|
+
from joop.web.html import HTMLComponent
|
|
9
|
+
from joop.http.methods import HttpMethod
|
|
10
|
+
from joop.web.examples.view import HELLO_DESIG, HELLO_ROOT
|
|
11
|
+
from joop.dao import DAO
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
class Hello_DAO(DAO):
|
|
15
|
+
"""
|
|
16
|
+
A Data Access Object (DAO) for handling data related to the `Hello_Model`.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
Hello_Model (BaseModel): A Pydantic model representing the data structure.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
class Hello_Model(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
A Pydantic model representing a simple data structure with a single field `Desig`.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
Desig (str): A string field representing a designation.
|
|
28
|
+
"""
|
|
29
|
+
Desig : str
|
|
30
|
+
|
|
31
|
+
_modeltype = Hello_Model
|
|
32
|
+
|
|
33
|
+
class MyTableComponent(AlpineTableComponent):
|
|
34
|
+
"""
|
|
35
|
+
A custom table component that extends the `AlpineTableComponent`.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
_row_type (type): Specifies the type of row data to be used in the table.
|
|
39
|
+
"""
|
|
40
|
+
_row_type = Hello_DAO
|
|
41
|
+
|
|
42
|
+
class Inputs(AlpineTableComponent.Inputs):
|
|
43
|
+
"""
|
|
44
|
+
Represents the input data structure for the `MyTableComponent`.
|
|
45
|
+
Extend this class to define specific input fields for the table.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
class Data(AlpineTableComponent.Data):
|
|
50
|
+
"""
|
|
51
|
+
Represents the data structure for the `MyTableComponent`.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
definition_name (str): The name of the table definition.
|
|
55
|
+
"""
|
|
56
|
+
definition_name : str = "myTable"
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_inputs(cls, inputs : 'MyTableComponent.Inputs') -> AlpineTableComponent.Data:
|
|
60
|
+
"""
|
|
61
|
+
Create a `Data` instance from the provided inputs.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
inputs (MyTableComponent.Inputs): The input data for the table.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
AlpineTableComponent.Data: An instance of the Data class with initialized values.
|
|
68
|
+
"""
|
|
69
|
+
return cls(
|
|
70
|
+
rows = [
|
|
71
|
+
Hello_DAO.from_model(Hello_DAO.Hello_Model(Desig = "Hello")),
|
|
72
|
+
Hello_DAO.from_model(Hello_DAO.Hello_Model(Desig = "World"))
|
|
73
|
+
],
|
|
74
|
+
table_headers = cls._get_table_headers()
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
class SubComponents(AlpineTableComponent.SubComponents):
|
|
78
|
+
"""
|
|
79
|
+
Represents subcomponents of the `MyTableComponent`.
|
|
80
|
+
Extend this class to define specific subcomponents.
|
|
81
|
+
"""
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
class MyTablePage(HTMLComponent):
|
|
85
|
+
"""
|
|
86
|
+
A web page component that includes the `MyTableComponent`.
|
|
87
|
+
|
|
88
|
+
Attributes:
|
|
89
|
+
_template_location (str): Path to the HTML template for the page.
|
|
90
|
+
"""
|
|
91
|
+
_template_location = "table/page.html"
|
|
92
|
+
|
|
93
|
+
class Inputs(HTMLComponent.Inputs):
|
|
94
|
+
"""
|
|
95
|
+
Represents the input data structure for the `MyTablePage`.
|
|
96
|
+
Extend this class to define specific input fields for the page.
|
|
97
|
+
"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
class Data(HTMLComponent.Data):
|
|
101
|
+
"""
|
|
102
|
+
Represents the data structure for the `MyTablePage`.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_inputs(cls, inputs : 'MyTablePage.Inputs'):
|
|
107
|
+
"""
|
|
108
|
+
Create a `Data` instance from the provided inputs.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
inputs (MyTablePage.Inputs): The input data for the page.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
MyTablePage.Data: An instance of the Data class with initialized values.
|
|
115
|
+
"""
|
|
116
|
+
return cls()
|
|
117
|
+
|
|
118
|
+
class SubComponents(HTMLComponent.SubComponents):
|
|
119
|
+
"""
|
|
120
|
+
Represents subcomponents of the `MyTablePage`.
|
|
121
|
+
|
|
122
|
+
Attributes:
|
|
123
|
+
my_t (MyTableComponent): An instance of the `MyTableComponent`.
|
|
124
|
+
"""
|
|
125
|
+
my_t = MyTableComponent
|
|
126
|
+
table : my_t = my_t()
|
|
127
|
+
|
|
128
|
+
class MyTableWholePage(View):
|
|
129
|
+
"""
|
|
130
|
+
A web view that renders the `MyTablePage` component.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
_component_type (type): Specifies the type of the main component for the view.
|
|
134
|
+
"""
|
|
135
|
+
_component_type = MyTablePage
|
|
136
|
+
|
|
137
|
+
class Endpoint():
|
|
138
|
+
"""
|
|
139
|
+
Defines the endpoint for the `MyTableWholePage` view.
|
|
140
|
+
|
|
141
|
+
Attributes:
|
|
142
|
+
_url (str): The URL path for the endpoint.
|
|
143
|
+
_name (str): The name of the endpoint.
|
|
144
|
+
_methods (list): The HTTP methods supported by the endpoint.
|
|
145
|
+
"""
|
|
146
|
+
_url = HELLO_ROOT + "/table"
|
|
147
|
+
_name = HELLO_DESIG + "_table"
|
|
148
|
+
_methods = [HttpMethod.GET.value]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
This is an example of how to define Views from components.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from joop.http.methods import HttpMethod
|
|
8
|
+
from joop.web.examples.hello import HelloWorld, HelloName
|
|
9
|
+
from joop.web.view import View
|
|
10
|
+
|
|
11
|
+
HELLO_ROOT = "/hello"
|
|
12
|
+
HELLO_DESIG = "hello"
|
|
13
|
+
|
|
14
|
+
class HelloView(View):
|
|
15
|
+
|
|
16
|
+
_component_type = HelloWorld
|
|
17
|
+
|
|
18
|
+
class Endpoint():
|
|
19
|
+
_url = HELLO_ROOT
|
|
20
|
+
_name = HELLO_DESIG
|
|
21
|
+
_methods = [HttpMethod.GET.value]
|
|
22
|
+
|
|
23
|
+
class NameView(View):
|
|
24
|
+
|
|
25
|
+
_component_type = HelloName
|
|
26
|
+
|
|
27
|
+
class Endpoint():
|
|
28
|
+
_url = HELLO_ROOT + "/<string:first_name>/<string:last_name>"
|
|
29
|
+
_name = HELLO_DESIG + "_name"
|
|
30
|
+
_methods = [HttpMethod.GET.value]
|