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/__init__.py
ADDED
joop/abstract/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides utilities for abstract behavior.
|
|
3
|
+
|
|
4
|
+
Classes:
|
|
5
|
+
AbstractMethod: A descriptor to define abstract methods in classes, particularly useful with ABCMeta and dataclasses.
|
|
6
|
+
|
|
7
|
+
Functions:
|
|
8
|
+
exclude_fields_from_my_init(cls, tgt_fields):
|
|
9
|
+
Modifies a dataclass to exclude specified fields from its `__init__` method.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import List, Any
|
|
13
|
+
from dataclasses import field
|
|
14
|
+
|
|
15
|
+
class AbstractMethod:
|
|
16
|
+
"""
|
|
17
|
+
A descriptor for defining abstract methods in classes.
|
|
18
|
+
|
|
19
|
+
This class is particularly useful when working with ABCMeta classes and/or dataclasses.
|
|
20
|
+
It allows marking methods as abstract, ensuring that they must be overridden in subclasses.
|
|
21
|
+
|
|
22
|
+
Example usage:
|
|
23
|
+
@classmethod
|
|
24
|
+
def _my_abstract_class_method(cls, something: Any):
|
|
25
|
+
raise NotImplementedError("Abstract; not implemented")
|
|
26
|
+
|
|
27
|
+
_my_abstract_class_method = AbstractMethod(_my_abstract_class_method)
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
func (callable): The function to be marked as abstract.
|
|
31
|
+
_isabstract (bool): A flag indicating whether the method is abstract.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, func):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the AbstractMethod descriptor.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
func (callable): The function to be marked as abstract.
|
|
40
|
+
"""
|
|
41
|
+
self.func = func
|
|
42
|
+
self._isabstract = True # Custom flag to mark as abstract
|
|
43
|
+
|
|
44
|
+
def __get__(self, instance, owner):
|
|
45
|
+
"""
|
|
46
|
+
Bind the method to the class or instance.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
NotImplementedError: If the method is abstract and not overridden.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
callable: The bound method.
|
|
53
|
+
"""
|
|
54
|
+
if self._isabstract:
|
|
55
|
+
raise NotImplementedError(f"The method {self.func.__name__} is abstract and must be overridden.")
|
|
56
|
+
return self.func.__get__(instance, owner)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def __isabstractmethod__(self):
|
|
60
|
+
"""
|
|
61
|
+
Check if the method is abstract.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
bool: True if the method is abstract, False otherwise.
|
|
65
|
+
"""
|
|
66
|
+
return self._isabstract
|
|
67
|
+
|
|
68
|
+
@__isabstractmethod__.setter
|
|
69
|
+
def __isabstractmethod__(self, value):
|
|
70
|
+
"""
|
|
71
|
+
Set the abstract status of the method.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
value (bool): The new abstract status.
|
|
75
|
+
"""
|
|
76
|
+
self._isabstract = value
|
|
77
|
+
|
|
78
|
+
def exclude_fields_from_my_init(cls, tgt_fields: List[str]):
|
|
79
|
+
"""
|
|
80
|
+
Exclude specified fields from the `__init__` method of a dataclass.
|
|
81
|
+
|
|
82
|
+
This function is intended to be called in the `__init_subclass__` class method
|
|
83
|
+
of a dataclass. It ensures that the fields with names in the provided list
|
|
84
|
+
are marked as non-init fields.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
cls (type): The dataclass to modify.
|
|
88
|
+
tgt_fields (List[str]): A list of field names to exclude from the `__init__` method.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
for _field_name in tgt_fields:
|
|
92
|
+
current_value = getattr(cls, _field_name, None)
|
|
93
|
+
if _field_name not in cls.__annotations__:
|
|
94
|
+
cls.__annotations__[_field_name] = Any
|
|
95
|
+
setattr(cls, _field_name, field(init=False, default=current_value))
|
joop/cli/README.md
ADDED
joop/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from joop.cli.__main__ import main
|
joop/cli/__main__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Console script for joop development and testing purposes.
|
|
2
|
+
|
|
3
|
+
Includes:
|
|
4
|
+
|
|
5
|
+
- A hello world for the CLI.
|
|
6
|
+
- Start a Flask webserver for testing purposes using the `--flask-server` option.
|
|
7
|
+
- Display a help menu with usage instructions using the `--help` or `-h` options.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
- Run the CLI normally: `python -m joop.cli`
|
|
11
|
+
- Start the Flask webserver: `python -m joop.cli --flask-server`
|
|
12
|
+
- Display the help menu: `python -m joop.cli --help`
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
import sys
|
|
16
|
+
import click
|
|
17
|
+
from joop.cli.test_flask import start_test_flask
|
|
18
|
+
|
|
19
|
+
from joop import hello_world
|
|
20
|
+
|
|
21
|
+
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
|
|
22
|
+
@click.option('--flask-server', is_flag=True, help="Spin up a Flask webserver for testing.")
|
|
23
|
+
def main(flask_server, args=None):
|
|
24
|
+
"""Console script for joop.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
- Run the CLI normally: `python -m joop.cli`
|
|
28
|
+
- Start the Flask webserver: `python -m joop.cli --flask-server`
|
|
29
|
+
- Display the help menu: `python -m joop.cli --help`
|
|
30
|
+
"""
|
|
31
|
+
if flask_server:
|
|
32
|
+
click.echo("Starting Flask webserver...")
|
|
33
|
+
start_test_flask()
|
|
34
|
+
else:
|
|
35
|
+
click.echo(hello_world())
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
joop/cli/test_flask.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Flask testing module for joop.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to start a Flask webserver for testing and development purposes.
|
|
4
|
+
|
|
5
|
+
Important: The test joop env is set as the jinja env for the Flask server. This is specific and desirable
|
|
6
|
+
though your project may implement the environment otherwise.
|
|
7
|
+
|
|
8
|
+
Functions:
|
|
9
|
+
start_test_flask():
|
|
10
|
+
Starts the Flask webserver in debug mode using the `app` instance from `joop.flask`.
|
|
11
|
+
|
|
12
|
+
Dependencies:
|
|
13
|
+
- Flask: Ensure Flask is installed. If not, install it using `pip install joop[flask]`.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
- Import the `start_test_flask` function and call it to start the Flask webserver.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from joop.tests.test_templater import environment
|
|
21
|
+
try:
|
|
22
|
+
from joop.flask import app
|
|
23
|
+
|
|
24
|
+
joop_env = environment
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def start_test_flask():
|
|
28
|
+
global app
|
|
29
|
+
|
|
30
|
+
app.run(debug=True)
|
|
31
|
+
|
|
32
|
+
except ImportError as e:
|
|
33
|
+
raise ImportError(
|
|
34
|
+
"The 'flask' module requires additional dependencies. "
|
|
35
|
+
"Install them using 'pip install joop[flask]'."
|
|
36
|
+
)
|
joop/dao/__init__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Data Access Object (DAO) module for joop.
|
|
2
|
+
|
|
3
|
+
This module provides abstract classes for creating Data Access Objects (DAOs) to manage
|
|
4
|
+
Pydantic and SQLModel-based models. DAOs provide a layer of abstraction between the
|
|
5
|
+
representation of the object (especially at the storage layer) and the business logic
|
|
6
|
+
code that uses or manipulates the object.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
DAO:
|
|
10
|
+
An abstract wrapper for a Pydantic model or any class derived from it.
|
|
11
|
+
|
|
12
|
+
SQLDAO:
|
|
13
|
+
An abstract class for SQL models, extending the DAO class.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import List, Type, Optional
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
import pydantic
|
|
20
|
+
import sqlmodel
|
|
21
|
+
|
|
22
|
+
class DAO():
|
|
23
|
+
"""
|
|
24
|
+
An abstract wrapper for a Pydantic model or any class derived from it.
|
|
25
|
+
|
|
26
|
+
The DAO class provides methods to interact with the underlying model, including
|
|
27
|
+
converting it to a dictionary and retrieving its fields. It is typically constructed
|
|
28
|
+
around an existing model using the `from_model` method.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
_modeltype (Type): The type of the model, defaulting to `pydantic.BaseModel`.
|
|
32
|
+
|
|
33
|
+
Properties:
|
|
34
|
+
model (pydantic.BaseModel): The underlying Pydantic model instance.
|
|
35
|
+
|
|
36
|
+
Methods:
|
|
37
|
+
from_model(model: pydantic.BaseModel) -> 'DAO':
|
|
38
|
+
Creates a DAO instance from a given Pydantic model.
|
|
39
|
+
|
|
40
|
+
to_dict() -> dict:
|
|
41
|
+
Converts the underlying model to a dictionary using aliases for field names.
|
|
42
|
+
|
|
43
|
+
get_model_fields() -> List[str]:
|
|
44
|
+
Retrieves the names of all fields defined in the `_modeltype`.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
_modeltype : Type = pydantic.BaseModel
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def model(self) -> pydantic.BaseModel:
|
|
51
|
+
return self._model
|
|
52
|
+
|
|
53
|
+
@model.setter
|
|
54
|
+
def model(self, value : pydantic.BaseModel):
|
|
55
|
+
self._model = value
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_model(cls, model : pydantic.BaseModel) -> 'DAO':
|
|
59
|
+
if model is None:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
if isinstance(type(model), cls._modeltype):
|
|
63
|
+
raise ("Invalid Model Supplied")
|
|
64
|
+
|
|
65
|
+
res = cls()
|
|
66
|
+
res._model = model
|
|
67
|
+
return res
|
|
68
|
+
|
|
69
|
+
def to_dict(self) -> dict:
|
|
70
|
+
"""
|
|
71
|
+
Convert the underlying model to a dictionary, using aliases for field names.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
dict: A dictionary representation of the model with aliases as keys.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If no model is set for this DAO instance.
|
|
78
|
+
TypeError: If the model does not have Pydantic fields.
|
|
79
|
+
"""
|
|
80
|
+
if not hasattr(self, '_model') or self._model is None:
|
|
81
|
+
raise ValueError("No model is set for this DAO instance.")
|
|
82
|
+
|
|
83
|
+
# Ensure the model has the necessary fields and aliases
|
|
84
|
+
if not hasattr(self._model, '__fields__'):
|
|
85
|
+
raise TypeError("The model does not have Pydantic fields.")
|
|
86
|
+
|
|
87
|
+
# Use SQLModel's/Pydantic basemodel's dict() method to get the base dictionary
|
|
88
|
+
base_dict = self._model.dict()
|
|
89
|
+
|
|
90
|
+
# Build the dictionary using aliases
|
|
91
|
+
result = {}
|
|
92
|
+
for field_name, field in self._model.__fields__.items():
|
|
93
|
+
alias = field.alias or field_name # Use alias if defined, otherwise fallback to field name
|
|
94
|
+
result[alias] = base_dict[field_name]
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def get_model_fields(cls) -> List[str]:
|
|
100
|
+
"""
|
|
101
|
+
Get the names of all fields defined in the `_modeltype`.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List[str]: A list of field names.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
TypeError: If `_modeltype` is not a subclass of `pydantic.BaseModel`.
|
|
108
|
+
"""
|
|
109
|
+
if not issubclass(cls._modeltype, pydantic.BaseModel):
|
|
110
|
+
raise TypeError("_modeltype must be a subclass of pydantic.BaseModel")
|
|
111
|
+
|
|
112
|
+
res = [field.alias if field.alias else name for name, field in cls._modeltype.__fields__.items()]
|
|
113
|
+
return res
|
|
114
|
+
|
|
115
|
+
class SQLDAO(DAO):
|
|
116
|
+
"""
|
|
117
|
+
An abstract class for SQL models, extending the DAO class.
|
|
118
|
+
|
|
119
|
+
The SQLDAO class provides additional methods for interacting with SQLModel-based
|
|
120
|
+
models, such as retrieving all records from the database.
|
|
121
|
+
|
|
122
|
+
Attributes:
|
|
123
|
+
_modeltype (Type): The type of the model, defaulting to `sqlmodel.SQLModel`.
|
|
124
|
+
|
|
125
|
+
Methods:
|
|
126
|
+
get_all(session: sqlmodel.Session) -> List['SQLDAO']:
|
|
127
|
+
Retrieves all records from the database for the given model type and returns
|
|
128
|
+
them as a list of SQLDAO instances.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
_modeltype : Type = sqlmodel.SQLModel
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def get_all(cls, session: sqlmodel.Session) -> List['SQLDAO']:
|
|
135
|
+
"""
|
|
136
|
+
Retrieve all records from the database for the given model type and return them as a list of SQLDAO instances.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
session (sqlmodel.Session): The database session to use for the query.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List[SQLDAO]: A list of SQLDAO instances for the model type.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
TypeError: If `_modeltype` is not a subclass of `sqlmodel.SQLModel`.
|
|
146
|
+
"""
|
|
147
|
+
if not issubclass(cls._modeltype, sqlmodel.SQLModel):
|
|
148
|
+
raise TypeError("_modeltype must be a subclass of sqlmodel.SQLModel")
|
|
149
|
+
|
|
150
|
+
db_results = session.query(cls._modeltype).all()
|
|
151
|
+
return [cls.from_model(result) for result in db_results]
|
joop/flask/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Flask application initialization module for joop.
|
|
2
|
+
|
|
3
|
+
This is used for example, development, and testing purposes.
|
|
4
|
+
This module initializes a Flask application and configures it with the joop Jinja2 environment.
|
|
5
|
+
That is important. ^^^ We are not using the default Jinja2 env, and that is intentional.
|
|
6
|
+
This is the place to register example Flask views to the application.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
app (Flask): The Flask application instance.
|
|
10
|
+
|
|
11
|
+
Modules:
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import absolute_import
|
|
15
|
+
from flask import Flask
|
|
16
|
+
from joop.web.j_env import joop_env
|
|
17
|
+
from joop.flask.example import (
|
|
18
|
+
FlaskHello, FlaskName, FlaskTablePage
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app = Flask(__name__)
|
|
22
|
+
|
|
23
|
+
# Configure the Jinja2 environment for the Flask application
|
|
24
|
+
# Important to do this and not use the default.
|
|
25
|
+
app.jinja_env = joop_env
|
|
26
|
+
|
|
27
|
+
# Register example views to the Flask application
|
|
28
|
+
# ex. MyView.add_to_app(app)
|
|
29
|
+
FlaskHello.add_to_app(app)
|
|
30
|
+
FlaskName.add_to_app(app)
|
|
31
|
+
FlaskTablePage.add_to_app(app)
|
joop/flask/example.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
This is the default place to turn example or other View definitions into
|
|
4
|
+
flask-specific views.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from joop.web.examples.view import (
|
|
8
|
+
NameView, HelloView
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from joop.web.examples.table import MyTableWholePage
|
|
12
|
+
|
|
13
|
+
from joop.flask.flask_view import FlaskView
|
|
14
|
+
|
|
15
|
+
class FlaskHello(HelloView, FlaskView): pass
|
|
16
|
+
|
|
17
|
+
class FlaskName(NameView, FlaskView): pass
|
|
18
|
+
|
|
19
|
+
class FlaskTablePage(MyTableWholePage, FlaskView): pass
|
joop/flask/flask_view.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Flask view integration for joop.
|
|
2
|
+
|
|
3
|
+
This module provides a base class for integrating joop views with Flask applications.
|
|
4
|
+
|
|
5
|
+
Any regular joop View can do multiple inheritance with a FlaskView to result
|
|
6
|
+
in a class that can be used with Flask.
|
|
7
|
+
|
|
8
|
+
Functionality include jinja env proxying.
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
FlaskView:
|
|
12
|
+
A base class for creating Flask-compatible views from joop View classes.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
from flask import Flask, current_app
|
|
17
|
+
|
|
18
|
+
from joop.web.view import View, Component
|
|
19
|
+
|
|
20
|
+
class FlaskView(View):
|
|
21
|
+
"""
|
|
22
|
+
A base class for creating Flask-compatible views from joop View classes.
|
|
23
|
+
|
|
24
|
+
This class provides methods to integrate joop views with Flask applications by
|
|
25
|
+
adding URL rules and accessing the Flask Jinja2 environment.
|
|
26
|
+
|
|
27
|
+
Use multiple inheritance to make your View a FlaskView ex.
|
|
28
|
+
|
|
29
|
+
`class FlaskHello(HelloView, FlaskView):`
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Methods:
|
|
33
|
+
_add_to_app(app: Flask, view_func: callable):
|
|
34
|
+
Adds the view to a Flask application with the specified view function.
|
|
35
|
+
|
|
36
|
+
_get_jinja_env():
|
|
37
|
+
Retrieves the Jinja2 environment from the current Flask application context.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _add_to_app(cls, app: Flask, view_func: callable):
|
|
42
|
+
"""
|
|
43
|
+
Add the view to a Flask application with the specified view function.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
app (Flask): The Flask application instance.
|
|
47
|
+
view_func (callable): The view function to associate with the URL rule.
|
|
48
|
+
|
|
49
|
+
This method registers a URL rule with the Flask application, associating it
|
|
50
|
+
with the specified view function and HTTP methods defined in the Endpoint.
|
|
51
|
+
"""
|
|
52
|
+
app.add_url_rule(
|
|
53
|
+
rule=cls.Endpoint._url, # URL rule as a string
|
|
54
|
+
endpoint=cls.Endpoint._name, # Endpoint name
|
|
55
|
+
view_func=view_func, # The view function to call
|
|
56
|
+
methods=cls.Endpoint._methods # List of HTTP methods allowed
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def _get_jinja_env(cls):
|
|
61
|
+
"""
|
|
62
|
+
Retrieve the Jinja2 environment from the current Flask application context.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
jinja2.Environment: The Jinja2 environment associated with the current
|
|
66
|
+
Flask application context.
|
|
67
|
+
|
|
68
|
+
This method allows joop views to access the Jinja2 environment configured
|
|
69
|
+
for the Flask application.
|
|
70
|
+
"""
|
|
71
|
+
return current_app.jinja_env
|
joop/http/methods.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""HTTP Methods Enumeration Module.
|
|
2
|
+
|
|
3
|
+
joop uses its own HTTP method enum implementation for compatibility purposes with
|
|
4
|
+
older versions of python, to provide a standardized way to
|
|
5
|
+
represent and describe HTTP methods.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
HttpMethod:
|
|
9
|
+
An enumeration of HTTP methods, each with a corresponding description.
|
|
10
|
+
|
|
11
|
+
Methods:
|
|
12
|
+
description() -> str:
|
|
13
|
+
Returns a description of the HTTP method.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
- Use `HttpMethod` to refer to HTTP methods in a type-safe manner.
|
|
17
|
+
- Call the `description` method on an `HttpMethod` instance to get its description.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from enum import Enum
|
|
22
|
+
|
|
23
|
+
class HttpMethod(str, Enum): # Changed from StrEnum to str + Enum for Python 3.10 compatibility
|
|
24
|
+
"""Enumeration of HTTP methods with their descriptions."""
|
|
25
|
+
|
|
26
|
+
GET = "GET"
|
|
27
|
+
POST = "POST"
|
|
28
|
+
PUT = "PUT"
|
|
29
|
+
DELETE = "DELETE"
|
|
30
|
+
PATCH = "PATCH"
|
|
31
|
+
OPTIONS = "OPTIONS"
|
|
32
|
+
HEAD = "HEAD"
|
|
33
|
+
TRACE = "TRACE"
|
|
34
|
+
CONNECT = "CONNECT"
|
|
35
|
+
|
|
36
|
+
def description(self) -> str:
|
|
37
|
+
"""Return a description of the HTTP method."""
|
|
38
|
+
descriptions = {
|
|
39
|
+
"GET": "Retrieve data from the server.",
|
|
40
|
+
"POST": "Submit data to the server.",
|
|
41
|
+
"PUT": "Update or create a resource on the server.",
|
|
42
|
+
"DELETE": "Delete a resource on the server.",
|
|
43
|
+
"PATCH": "Apply partial modifications to a resource.",
|
|
44
|
+
"OPTIONS": "Describe the communication options for the target resource.",
|
|
45
|
+
"HEAD": "Retrieve metadata about the resource without the body.",
|
|
46
|
+
"TRACE": "Perform a message loop-back test along the path to the target resource.",
|
|
47
|
+
"CONNECT": "Establish a tunnel to the server identified by the target resource."
|
|
48
|
+
}
|
|
49
|
+
return descriptions[self.value]
|
joop/tests/__init__.py
ADDED
joop/tests/test_joop.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""High-level test suite. Currently, verifies that the CLI works.."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
from click.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from joop.cli import main
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestJoop(unittest.TestCase):
|
|
13
|
+
"""Tests for `joop` package."""
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
"""Set up test fixtures, if any."""
|
|
17
|
+
|
|
18
|
+
def tearDown(self):
|
|
19
|
+
"""Tear down test fixtures, if any."""
|
|
20
|
+
|
|
21
|
+
def test_001_command_line_interface(self):
|
|
22
|
+
"""Test the CLI."""
|
|
23
|
+
runner = CliRunner()
|
|
24
|
+
result = runner.invoke(main)
|
|
25
|
+
assert result.exit_code == 0
|
|
26
|
+
assert 'Hello World' in result.output
|
|
27
|
+
help_result = runner.invoke(main, ['--help'])
|
|
28
|
+
assert help_result.exit_code == 0
|
|
29
|
+
assert '--help Show this message and exit.' in help_result.output
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Templater testing module for joop.
|
|
2
|
+
|
|
3
|
+
This module instantiates a jinja templating engine for development and testing purposes.
|
|
4
|
+
It's not just restricted to automated testing.
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
BaseTestHTMLComponent:
|
|
8
|
+
A base class for testing HTML components.
|
|
9
|
+
|
|
10
|
+
Variables:
|
|
11
|
+
environment:
|
|
12
|
+
A placeholder variable for the templating environment.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
- Utilize the `environment` variable for templating-related operations.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from joop.web.templater import EnvironmentFactory
|
|
20
|
+
from jinja2 import FileSystemLoader
|
|
21
|
+
from joop.web.j_env import set_joop_env
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
WEB_TEMPLATES_ROOT = Path("../templates/examples/")
|
|
26
|
+
|
|
27
|
+
environment = EnvironmentFactory.create_environment(
|
|
28
|
+
loader=FileSystemLoader(WEB_TEMPLATES_ROOT)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
set_joop_env(environment)
|
joop/tests/test_view.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Unit tests for joop Flask integration.
|
|
2
|
+
|
|
3
|
+
This module contains unit tests for the Flask views in the joop project. It uses the
|
|
4
|
+
unittest framework and Flask's test_client to test the functionality of the views.
|
|
5
|
+
|
|
6
|
+
It borders on being an integration test, insofar as we are testing:
|
|
7
|
+
* joop's views
|
|
8
|
+
* joop's templater
|
|
9
|
+
* joop's flask integration
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
TestView:
|
|
13
|
+
Test cases for the Flask views in the joop application.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from joop.flask import app
|
|
21
|
+
APP_AVAILABLE = app is not None
|
|
22
|
+
except ImportError:
|
|
23
|
+
APP_AVAILABLE = False
|
|
24
|
+
|
|
25
|
+
@unittest.skipIf(not APP_AVAILABLE, "joop.flask or app is not available")
|
|
26
|
+
class TestView(unittest.TestCase):
|
|
27
|
+
"""
|
|
28
|
+
Test cases for the Flask views in the joop application.
|
|
29
|
+
|
|
30
|
+
This test class uses the Flask test client to send requests to the application
|
|
31
|
+
and verify the responses.
|
|
32
|
+
|
|
33
|
+
Methods:
|
|
34
|
+
setUp():
|
|
35
|
+
Sets up the test client for the Flask application.
|
|
36
|
+
|
|
37
|
+
test_000_hello():
|
|
38
|
+
Tests the '/hello' endpoint to ensure it returns a 200 status code.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def setUp(self):
|
|
42
|
+
self.client = app.test_client()
|
|
43
|
+
|
|
44
|
+
def test_000_hello(self):
|
|
45
|
+
response = self.client.get('/hello')
|
|
46
|
+
self.assertEqual(response.status_code, 200)
|