oe-python-template-example 0.0.10__py3-none-any.whl → 0.1.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.
@@ -8,6 +8,7 @@ This module provides a webservice API with several endpoints:
8
8
  The endpoints use Pydantic models for request and response validation.
9
9
  """
10
10
 
11
+ import os
11
12
  from collections.abc import Generator
12
13
  from enum import StrEnum
13
14
  from typing import Annotated
@@ -18,6 +19,8 @@ from pydantic import BaseModel, Field
18
19
  from oe_python_template_example import Service
19
20
 
20
21
  HELLO_WORLD_EXAMPLE = "Hello, world!"
22
+ UVICORN_HOST = os.environ.get("UVICORN_HOST", "127.0.0.1")
23
+ UVICORN_PORT = os.environ.get("UVICORN_PORT", "8000")
21
24
 
22
25
 
23
26
  def get_service() -> Generator[Service, None, None]:
@@ -34,7 +37,36 @@ def get_service() -> Generator[Service, None, None]:
34
37
  pass
35
38
 
36
39
 
37
- app = FastAPI(
40
+ api = FastAPI(
41
+ root_path="/api",
42
+ title="OE Python Template Example",
43
+ contact={
44
+ "name": "Helmut Hoffer von Ankershoffen",
45
+ "email": "helmuthva@gmail.com",
46
+ "url": "https://github.com/helmut-hoffer-von-ankershoffen",
47
+ },
48
+ terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
49
+ openapi_tags=[
50
+ {
51
+ "name": "v1",
52
+ "description": "API version 1, check link on the right",
53
+ "externalDocs": {
54
+ "description": "sub-docs",
55
+ "url": f"http://{UVICORN_HOST}:{UVICORN_PORT}/api/v1/docs",
56
+ },
57
+ },
58
+ {
59
+ "name": "v2",
60
+ "description": "API version 2, check link on the right",
61
+ "externalDocs": {
62
+ "description": "sub-docs",
63
+ "url": f"http://{UVICORN_HOST}:{UVICORN_PORT}/api/v2/docs",
64
+ },
65
+ },
66
+ ],
67
+ )
68
+
69
+ api_v1 = FastAPI(
38
70
  version="1.0.0",
39
71
  title="OE Python Template Example",
40
72
  contact={
@@ -45,6 +77,17 @@ app = FastAPI(
45
77
  terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
46
78
  )
47
79
 
80
+ api_v2 = FastAPI(
81
+ version="2.0.0",
82
+ title="OE Python Template Example",
83
+ contact={
84
+ "name": "Helmut Hoffer von Ankershoffen",
85
+ "email": "helmuthva@gmail.com",
86
+ "url": "https://github.com/helmut-hoffer-von-ankershoffen",
87
+ },
88
+ terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
89
+ )
90
+
48
91
 
49
92
  class _HealthStatus(StrEnum):
50
93
  """Health status enumeration.
@@ -78,8 +121,10 @@ class HealthResponse(BaseModel):
78
121
  )
79
122
 
80
123
 
81
- @app.get("/healthz", tags=["Observability"])
82
- @app.get("/health", tags=["Observability"])
124
+ @api_v1.get("/healthz", tags=["Observability"])
125
+ @api_v1.get("/health", tags=["Observability"])
126
+ @api_v2.get("/healthz", tags=["Observability"])
127
+ @api_v2.get("/health", tags=["Observability"])
83
128
  async def health(service: Annotated[Service, Depends(get_service)], response: Response) -> Health:
84
129
  """Check the health of the service.
85
130
 
@@ -118,7 +163,8 @@ class HelloWorldResponse(BaseModel):
118
163
  )
119
164
 
120
165
 
121
- @app.get("/hello-world", tags=["Basics"])
166
+ @api_v1.get("/hello-world", tags=["Basics"])
167
+ @api_v2.get("/hello-world", tags=["Basics"])
122
168
  async def hello_world() -> HelloWorldResponse:
123
169
  """
124
170
  Return a hello world message.
@@ -151,7 +197,7 @@ class EchoRequest(BaseModel):
151
197
  )
152
198
 
153
199
 
154
- @app.post("/echo", tags=["Basics"])
200
+ @api_v1.post("/echo", tags=["Basics"])
155
201
  async def echo(request: EchoRequest) -> EchoResponse:
156
202
  """
157
203
  Echo back the provided text.
@@ -166,3 +212,35 @@ async def echo(request: EchoRequest) -> EchoResponse:
166
212
  422 Unprocessable Entity: If text is not provided or empty.
167
213
  """
168
214
  return EchoResponse(message=request.text)
215
+
216
+
217
+ class Utterance(BaseModel):
218
+ """Request model for echo endpoint."""
219
+
220
+ utterance: str = Field(
221
+ ...,
222
+ min_length=1,
223
+ description="The utterance to echo back",
224
+ examples=[HELLO_WORLD_EXAMPLE],
225
+ )
226
+
227
+
228
+ @api_v2.post("/echo", tags=["Basics"])
229
+ async def echo_v2(request: Utterance) -> EchoResponse:
230
+ """
231
+ Echo back the provided utterance.
232
+
233
+ Args:
234
+ request (EchoRequestV2): The request containing the utterance to echo back.
235
+
236
+ Returns:
237
+ EchoResponse: A response containing the echoed utterance.
238
+
239
+ Raises:
240
+ 422 Unprocessable Entity: If utterance is not provided or empty.
241
+ """
242
+ return EchoResponse(message=request.utterance)
243
+
244
+
245
+ api.mount("/v1", api_v1)
246
+ api.mount("/v2", api_v2)
@@ -1,5 +1,6 @@
1
1
  """CLI (Command Line Interface) of OE Python Template Example."""
2
2
 
3
+ import os
3
4
  from enum import StrEnum
4
5
  from typing import Annotated
5
6
 
@@ -9,7 +10,7 @@ import yaml
9
10
  from rich.console import Console
10
11
 
11
12
  from oe_python_template_example import Service, __version__
12
- from oe_python_template_example.api import app as api
13
+ from oe_python_template_example.api import api_v1, api_v2
13
14
 
14
15
  console = Console()
15
16
 
@@ -49,14 +50,34 @@ def serve(
49
50
  ) -> None:
50
51
  """Start the API server."""
51
52
  console.print(f"Starting API server at http://{host}:{port}")
53
+ os.environ["UVICORN_HOST"] = host
54
+ os.environ["UVICORN_PORT"] = str(port)
52
55
  uvicorn.run(
53
- "oe_python_template_example.api:app",
56
+ "oe_python_template_example.api:api",
54
57
  host=host,
55
58
  port=port,
56
59
  reload=watch,
57
60
  )
58
61
 
59
62
 
63
+ class APIVersion(StrEnum):
64
+ """
65
+ Enum representing the API versions.
66
+
67
+ This enum defines the supported API verions:
68
+ - V1: Output doc for v1 API
69
+ - V2: Output doc for v2 API
70
+
71
+ Usage:
72
+ version = APIVersion.V1
73
+ print(f"Using {version} version")
74
+
75
+ """
76
+
77
+ V1 = "v1"
78
+ V2 = "v2"
79
+
80
+
60
81
  class OutputFormat(StrEnum):
61
82
  """
62
83
  Enum representing the supported output formats.
@@ -76,12 +97,17 @@ class OutputFormat(StrEnum):
76
97
 
77
98
  @cli.command()
78
99
  def openapi(
100
+ api_version: Annotated[APIVersion, typer.Option(help="API Version", case_sensitive=False)] = APIVersion.V1,
79
101
  output_format: Annotated[
80
102
  OutputFormat, typer.Option(help="Output format", case_sensitive=False)
81
103
  ] = OutputFormat.YAML,
82
104
  ) -> None:
83
105
  """Dump the OpenAPI specification to stdout (YAML by default)."""
84
- schema = api.openapi()
106
+ match api_version:
107
+ case APIVersion.V1:
108
+ schema = api_v1.openapi()
109
+ case APIVersion.V2:
110
+ schema = api_v2.openapi()
85
111
  match output_format:
86
112
  case OutputFormat.JSON:
87
113
  console.print_json(data=schema)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template-example
3
- Version: 0.0.10
3
+ Version: 0.1.1
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/
@@ -101,9 +101,9 @@ Use Cases:
101
101
  2) Consistent update of already scaffolded projects to benefit from new and improved features.
102
102
  3) Dummy CLI application and service demonstrating example usage of the generated directory structure and build pipeline
103
103
 
104
- ## Scaffolding Instructions
104
+ ## Scaffolding
105
105
 
106
- Step 1: Install uv package manager and copier
106
+ **Step 1**: Install uv package manager and copier
107
107
  ```shell
108
108
  if [[ "$OSTYPE" == "darwin"* ]]; then # Install dependencies for macOS X
109
109
  if ! command -v brew &> /dev/null; then ## Install Homebrew if not present
@@ -119,31 +119,32 @@ fi
119
119
  uv tool install copier # Install copier as global tool
120
120
  ```
121
121
 
122
- Step 2: Now create an empty repo on GitHub and clone it to your local machine in a directory of your choice. Change to that directory.
122
+ **Step 2**: Now create an empty repository on GitHubm, clone to your local machine, and change into it's directory.
123
123
 
124
- Step 3: Scaffold the project
124
+ **Step 3**: Scaffold the project
125
125
  ```shell
126
126
  copier copy gh:helmut-hoffer-von-ankershoffen/oe-python-template .
127
127
  ```
128
- Step 4: Setup the local environment
128
+ **Step 4**: Setup the local environment
129
129
 
130
130
  ```shell
131
131
  uv run nox -s setup_dev
132
132
  ```
133
133
 
134
- Step 5: Perform inital commit and push
134
+ **Step 5**: Perform initial commit and push
135
135
  ```shell
136
136
  git add .
137
137
  git commit -m "feat: Initial commit"
138
+ git push
138
139
  ```
139
140
 
140
141
  Visit your GitHub repository and check the Actions tab. The CI workflow should fail at the SonarQube step,
141
142
  as this external service is not yet configured for our new repository.
142
143
 
143
- Step 6: Follow the instructions in SERVICE_CONNECTIONS.md to setup the connections to external services
144
- such as Cloudcov, SonarQube Cloud, Read The Docs, Docker.io, GHCR.io and Streamlit Community Cloud.
144
+ **Step 6**: Follow the [SERVICE_INSTRUCTIONS.md](instructions) to wire up
145
+ external services such as Cloudcov, SonarQube Cloud, Read The Docs, Docker.io, GHCR.io and Streamlit Community Cloud.
145
146
 
146
- Step 7: Release the first versions
147
+ **Step 7**: Release the first versions
147
148
  ```shell
148
149
  ./bump
149
150
  ```
@@ -166,24 +167,36 @@ If you don't have uv installed follow [these instructions](https://docs.astral.s
166
167
  pip install oe-python-template-example # add dependency to your project
167
168
  ```
168
169
 
169
- Executing the command line interface (CLI) is just as easy:
170
+ Executing the command line interface (CLI) in an isolated Python environment is just as easy:
170
171
 
171
172
  ```shell
172
- uvx oe-python-template-example
173
+ uvx oe-python-template-example hello-world # prints "Hello, world! [..]"
174
+ uvx oe-python-template-example serve # serves webservice API
175
+ uvx oe-python-template-example serve --port=4711 # serves webservice API on port 4711
173
176
  ```
174
177
 
178
+ Notes:
179
+ * The API is versioned, mounted at ```/api/v1``` resp. ```/api/v2```
180
+ * While serving the webservice API go to [http://127.0.0.1:8000/api/v1/hello-world](http://127.0.0.1:8000/api/v1/hello-world) to see the respons of the ```hello-world``` operation.
181
+ * Interactive documentation is provided at [http://127.0.0.1:8000/api/docs](http://127.0.0.1:8000/api/docs)
182
+
183
+
175
184
  The CLI provides extensive help:
176
185
 
177
186
  ```shell
178
187
  uvx oe-python-template-example --help # all CLI commands
179
188
  uvx oe-python-template-example hello-world --help # help for specific command
189
+ uvx oe-python-template-example echo --help
190
+ uvx oe-python-template-example openapi --help
191
+ uvx oe-python-template-example serve --help
180
192
  ```
181
193
 
182
194
 
183
- ## Highlights
195
+ ## Operational Excellence
184
196
 
185
- * Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
186
- * Various Examples:
197
+ This project is designed with operational excellence in mind, using modern Python tooling and practices. It includes:
198
+
199
+ * Various examples demonstrating usage:
187
200
  - [Simple Python script](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/script.py)
188
201
  - [Streamlit web application](https://oe-python-template-example.streamlit.app/) deployed on [Streamlit Community Cloud](https://streamlit.io/cloud)
189
202
  - [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
@@ -199,6 +212,9 @@ uvx oe-python-template-example hello-world --help # help for specific command
199
212
 
200
213
  ## Usage Examples
201
214
 
215
+ The following examples run from source. Clone this repository first using
216
+ `git clone git@github.com:helmut-hoffer-von-ankershoffen/oe-python-template-example.git`.
217
+
202
218
  ### Minimal Python Script:
203
219
 
204
220
  ```python
@@ -240,7 +256,7 @@ uv run streamlit run examples/streamlit.py # Serve on localhost:8501, o
240
256
  ... or run within VSCode
241
257
 
242
258
  ```shell
243
- uv sync --all-extras # Install ipykernel dependency part of the examples extra, see pyproject.toml
259
+ uv sync --all-extras # Install dependencies required for examples such as Juypyter kernel, see pyproject.toml
244
260
  ```
245
261
  Install the [Jupyter extension for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter)
246
262
 
@@ -337,7 +353,10 @@ docker compose run oe-python-template-example echo "Lorem" --json
337
353
  docker compose run oe-python-template-example openapi
338
354
  docker compose run oe-python-template-example openapi --output-format=json
339
355
  docker compose up
340
- curl http://127.0.0.1 8000
356
+ curl http://127.0.0.1:8000/api/v1/hello-world
357
+ curl http://127.0.0.1:8000/api/v1/docs
358
+ curl http://127.0.0.1:8000/api/v2/hello-world
359
+ curl http://127.0.0.1:8000/api/v2/docs
341
360
  ```
342
361
 
343
362
  ## 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=-K3cLHgmtmnSfCIM3eyZpxL1R5KDkxFd9OvYv6Pdp4M,6647
3
+ oe_python_template_example/cli.py,sha256=BHuDCrzYkvJYDdvk06KwkK_sKQx7h7hhRiO0peQMM1s,3474
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.1.1.dist-info/METADATA,sha256=QM5M_qyG9DY9AH9wUrLQoHa_IoOSjdQSFsCBIxX3Rqo,21949
7
+ oe_python_template_example-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ oe_python_template_example-0.1.1.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
9
+ oe_python_template_example-0.1.1.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
10
+ oe_python_template_example-0.1.1.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=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,,