oe-python-template-example 0.0.9__py3-none-any.whl → 0.0.10__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.
@@ -1,13 +1,18 @@
1
- """FastAPI application with REST API endpoints.
1
+ """Webservice API of OE Python Template Example.
2
2
 
3
- This module provides a FastAPI application with several endpoints:
3
+ This module provides a webservice API with several endpoints:
4
+ - A health/healthz endpoint that returns the health status of the service
4
5
  - A hello-world endpoint that returns a greeting message
5
6
  - An echo endpoint that echoes back the provided text
6
7
 
7
8
  The endpoints use Pydantic models for request and response validation.
8
9
  """
9
10
 
10
- from fastapi import FastAPI
11
+ from collections.abc import Generator
12
+ from enum import StrEnum
13
+ from typing import Annotated
14
+
15
+ from fastapi import Depends, FastAPI, Response, status
11
16
  from pydantic import BaseModel, Field
12
17
 
13
18
  from oe_python_template_example import Service
@@ -15,6 +20,20 @@ from oe_python_template_example import Service
15
20
  HELLO_WORLD_EXAMPLE = "Hello, world!"
16
21
 
17
22
 
23
+ def get_service() -> Generator[Service, None, None]:
24
+ """Get the service instance.
25
+
26
+ Yields:
27
+ Service: The service instance.
28
+ """
29
+ service = Service()
30
+ try:
31
+ yield service
32
+ finally:
33
+ # Cleanup code if needed
34
+ pass
35
+
36
+
18
37
  app = FastAPI(
19
38
  version="1.0.0",
20
39
  title="OE Python Template Example",
@@ -27,6 +46,68 @@ app = FastAPI(
27
46
  )
28
47
 
29
48
 
49
+ class _HealthStatus(StrEnum):
50
+ """Health status enumeration.
51
+
52
+ Args:
53
+ StrEnum (_type_): _description_
54
+ """
55
+
56
+ UP = "UP"
57
+ DOWN = "DOWN"
58
+
59
+
60
+ class Health(BaseModel):
61
+ """Health status model.
62
+
63
+ Args:
64
+ BaseModel (_type_): _description_
65
+ """
66
+
67
+ status: _HealthStatus
68
+ reason: str | None = None
69
+
70
+
71
+ class HealthResponse(BaseModel):
72
+ """Response model for health endpoint."""
73
+
74
+ health: str = Field(
75
+ ...,
76
+ description="The hello world message",
77
+ examples=[HELLO_WORLD_EXAMPLE],
78
+ )
79
+
80
+
81
+ @app.get("/healthz", tags=["Observability"])
82
+ @app.get("/health", tags=["Observability"])
83
+ async def health(service: Annotated[Service, Depends(get_service)], response: Response) -> Health:
84
+ """Check the health of the service.
85
+
86
+ This endpoint returns the health status of the service.
87
+ The health status can be either UP or DOWN.
88
+ If the service is healthy, the status will be UP.
89
+ If the service is unhealthy, the status will be DOWN and a reason will be provided.
90
+ The response will have a 200 OK status code if the service is healthy,
91
+ and a 500 Internal Server Error status code if the service is unhealthy.
92
+
93
+ Args:
94
+ service (Annotated[Service, Depends): _description_
95
+ response (Response): _description_
96
+
97
+ Returns:
98
+ Health: The health status of the service.
99
+ """
100
+ if service.healthy():
101
+ health_result = Health(status=_HealthStatus.UP)
102
+ else:
103
+ health_result = Health(status=_HealthStatus.DOWN, reason="Service is unhealthy")
104
+
105
+ if health_result.status == _HealthStatus.DOWN:
106
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
107
+
108
+ return health_result
109
+
110
+
30
111
  class HelloWorldResponse(BaseModel):
31
112
  """Response model for hello-world endpoint."""
32
113
 
@@ -37,6 +118,17 @@ class HelloWorldResponse(BaseModel):
37
118
  )
38
119
 
39
120
 
121
+ @app.get("/hello-world", tags=["Basics"])
122
+ async def hello_world() -> HelloWorldResponse:
123
+ """
124
+ Return a hello world message.
125
+
126
+ Returns:
127
+ HelloWorldResponse: A response containing the hello world message.
128
+ """
129
+ return HelloWorldResponse(message=Service.get_hello_world())
130
+
131
+
40
132
  class EchoResponse(BaseModel):
41
133
  """Response model for echo endpoint."""
42
134
 
@@ -59,17 +151,6 @@ class EchoRequest(BaseModel):
59
151
  )
60
152
 
61
153
 
62
- @app.get("/hello-world", tags=["Basics"])
63
- async def hello_world() -> HelloWorldResponse:
64
- """
65
- Return a hello world message.
66
-
67
- Returns:
68
- HelloWorldResponse: A response containing the hello world message.
69
- """
70
- return HelloWorldResponse(message=Service.get_hello_world())
71
-
72
-
73
154
  @app.post("/echo", tags=["Basics"])
74
155
  async def echo(request: EchoRequest) -> EchoResponse:
75
156
  """
@@ -1,5 +1,6 @@
1
- """Command line interface of OE Python Template Example."""
1
+ """CLI (Command Line Interface) of OE Python Template Example."""
2
2
 
3
+ from enum import StrEnum
3
4
  from typing import Annotated
4
5
 
5
6
  import typer
@@ -44,7 +45,7 @@ def hello_world() -> None:
44
45
  def serve(
45
46
  host: Annotated[str, typer.Option(help="Host to bind the server to")] = "127.0.0.1",
46
47
  port: Annotated[int, typer.Option(help="Port to bind the server to")] = 8000,
47
- reload: Annotated[bool, typer.Option(help="Enable auto-reload")] = True,
48
+ watch: Annotated[bool, typer.Option(help="Enable auto-reload")] = True,
48
49
  ) -> None:
49
50
  """Start the API server."""
50
51
  console.print(f"Starting API server at http://{host}:{port}")
@@ -52,22 +53,40 @@ def serve(
52
53
  "oe_python_template_example.api:app",
53
54
  host=host,
54
55
  port=port,
55
- reload=reload,
56
+ reload=watch,
56
57
  )
57
58
 
58
59
 
60
+ class OutputFormat(StrEnum):
61
+ """
62
+ Enum representing the supported output formats.
63
+
64
+ This enum defines the possible formats for output data:
65
+ - YAML: Output data in YAML format
66
+ - JSON: Output data in JSON format
67
+
68
+ Usage:
69
+ format = OutputFormat.YAML
70
+ print(f"Using {format} format")
71
+ """
72
+
73
+ YAML = "yaml"
74
+ JSON = "json"
75
+
76
+
59
77
  @cli.command()
60
78
  def openapi(
61
79
  output_format: Annotated[
62
- str, typer.Option(help="Output format (yaml or json), defaults to yaml", case_sensitive=False)
63
- ] = "yaml",
80
+ OutputFormat, typer.Option(help="Output format", case_sensitive=False)
81
+ ] = OutputFormat.YAML,
64
82
  ) -> None:
65
83
  """Dump the OpenAPI specification to stdout (YAML by default)."""
66
84
  schema = api.openapi()
67
- if output_format.lower() == "json":
68
- console.print_json(data=schema)
69
- else:
70
- console.print(yaml.dump(schema, default_flow_style=False), end="")
85
+ match output_format:
86
+ case OutputFormat.JSON:
87
+ console.print_json(data=schema)
88
+ case OutputFormat.YAML:
89
+ console.print(yaml.dump(schema, default_flow_style=False), end="")
71
90
 
72
91
 
73
92
  def _apply_cli_settings(cli: typer.Typer, epilog: str) -> None:
@@ -13,6 +13,7 @@ class Service:
13
13
 
14
14
  def __init__(self) -> None:
15
15
  """Initialize service."""
16
+ self.is_healthy = True
16
17
 
17
18
  @staticmethod
18
19
  def get_hello_world() -> str:
@@ -23,3 +24,12 @@ class Service:
23
24
  str: Hello world message.
24
25
  """
25
26
  return f"Hello, world! The value of THE_VAR is {THE_VAR}"
27
+
28
+ def healthy(self) -> bool:
29
+ """
30
+ Check if the service is healthy.
31
+
32
+ Returns:
33
+ bool: True if the service is healthy, False otherwise.
34
+ """
35
+ return self.is_healthy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template-example
3
- Version: 0.0.9
3
+ Version: 0.0.10
4
4
  Summary: 🧠 Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
5
5
  Project-URL: Homepage, https://oe-python-template-example.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://oe-python-template-example.readthedocs.io/en/latest/
@@ -184,7 +184,7 @@ uvx oe-python-template-example hello-world --help # help for specific command
184
184
 
185
185
  * Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
186
186
  * Various Examples:
187
- - [Simple Python script]https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/script.py)
187
+ - [Simple Python script](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/script.py)
188
188
  - [Streamlit web application](https://oe-python-template-example.streamlit.app/) deployed on [Streamlit Community Cloud](https://streamlit.io/cloud)
189
189
  - [Jupyter](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/notebook.ipynb) and [Marimo](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/notebook.py) notebook
190
190
  * [Complete reference documenation](https://oe-python-template-example.readthedocs.io/en/latest/reference.html) on Read the Docs
@@ -284,8 +284,12 @@ Execute commands:
284
284
 
285
285
  ```shell
286
286
  uvx oe-python-template-example hello-world
287
- uvx oe-python-template-example hello-world --json
288
- uvx oe-python-template-example echo "Lorem Ipsum"
287
+ uvx oe-python-template-example echo --help
288
+ uvx oe-python-template-example echo "Lorem"
289
+ uvx oe-python-template-example echo "Lorem" --json
290
+ uvx oe-python-template-example openapi
291
+ uvx oe-python-template-example openapi --output-format=json
292
+ uvx oe-python-template-example serve
289
293
  ```
290
294
 
291
295
  ### Environment
@@ -306,8 +310,12 @@ You can as well run the CLI within Docker.
306
310
  ```shell
307
311
  docker run helmuthva/oe-python-template-example --help
308
312
  docker run helmuthva/oe-python-template-example hello-world
309
- docker run helmuthva/oe-python-template-example hello-world --json
313
+ docker run helmuthva/oe-python-template-example echo --help
310
314
  docker run helmuthva/oe-python-template-example echo "Lorem"
315
+ docker run helmuthva/oe-python-template-example echo "Lorem" --json
316
+ docker run helmuthva/oe-python-template-example openapi
317
+ docker run helmuthva/oe-python-template-example openapi --output-format=json
318
+ docker run helmuthva/oe-python-template-example serve
311
319
  ```
312
320
 
313
321
  Execute command:
@@ -321,8 +329,15 @@ Or use docker compose
321
329
  The .env is passed through from the host to the Docker container.
322
330
 
323
331
  ```shell
324
- docker compose up
325
332
  docker compose run oe-python-template-example --help
333
+ docker compose run oe-python-template-example hello-world
334
+ docker compose run oe-python-template-example echo --help
335
+ docker compose run oe-python-template-example echo "Lorem"
336
+ docker compose run oe-python-template-example echo "Lorem" --json
337
+ docker compose run oe-python-template-example openapi
338
+ docker compose run oe-python-template-example openapi --output-format=json
339
+ docker compose up
340
+ curl http://127.0.0.1 8000
326
341
  ```
327
342
 
328
343
  ## Extra: Lorem Ipsum
@@ -0,0 +1,10 @@
1
+ oe_python_template_example/__init__.py,sha256=-sCwS9lD6CvgWw88f7snBDF947PWIhEupJVebXL_w1M,314
2
+ oe_python_template_example/api.py,sha256=HMcssyqFSXXVxAxhXz8Mb-5TtVGxR09R-uS8-1c7mVw,4346
3
+ oe_python_template_example/cli.py,sha256=WL5Mn4U0jnORqjphfSxDlhFiPIAlz0RiSWPOLlB46jw,2824
4
+ oe_python_template_example/constants.py,sha256=6uQHr2CRgzWQWhUQCRRKiPuFhzKB2iblZk3dIRQ5dDc,358
5
+ oe_python_template_example/service.py,sha256=XlCrklSRy8_YaYvlVYiDFPUubHHm-J8BPx2f7_niGG4,760
6
+ oe_python_template_example-0.0.10.dist-info/METADATA,sha256=bYxQ6xq0kI3Qq3cUZ32esYtBhlTrDv9rWaSP1jBXXWg,20852
7
+ oe_python_template_example-0.0.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ oe_python_template_example-0.0.10.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
9
+ oe_python_template_example-0.0.10.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
10
+ oe_python_template_example-0.0.10.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- oe_python_template_example/__init__.py,sha256=-sCwS9lD6CvgWw88f7snBDF947PWIhEupJVebXL_w1M,314
2
- oe_python_template_example/api.py,sha256=3OmZFB_5bVdHS6wcGttfgiOmq86K5R6lXOzvTPXIQ94,2203
3
- oe_python_template_example/cli.py,sha256=ptgFKScjYQAj364TCBqs23rVZqIpXUxUXEoc_4f1YJE,2399
4
- oe_python_template_example/constants.py,sha256=6uQHr2CRgzWQWhUQCRRKiPuFhzKB2iblZk3dIRQ5dDc,358
5
- oe_python_template_example/service.py,sha256=ZpsZFnnJm_3EqoVqGomfAyIjLVmWJFuJ3G9qatWj5yI,516
6
- oe_python_template_example-0.0.9.dist-info/METADATA,sha256=XKogVxOI9bpkzWJfRstwyDquVsJRPfxZirfBVSq2p0k,20031
7
- oe_python_template_example-0.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- oe_python_template_example-0.0.9.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
9
- oe_python_template_example-0.0.9.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
10
- oe_python_template_example-0.0.9.dist-info/RECORD,,