oe-python-template-example 0.0.10__py3-none-any.whl → 0.1.0__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.
@@ -34,7 +34,30 @@ def get_service() -> Generator[Service, None, None]:
34
34
  pass
35
35
 
36
36
 
37
- app = FastAPI(
37
+ api = FastAPI(
38
+ root_path="/api",
39
+ title="OE Python Template Example",
40
+ contact={
41
+ "name": "Helmut Hoffer von Ankershoffen",
42
+ "email": "helmuthva@gmail.com",
43
+ "url": "https://github.com/helmut-hoffer-von-ankershoffen",
44
+ },
45
+ terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
46
+ openapi_tags=[
47
+ {
48
+ "name": "v1",
49
+ "description": "API version 1, check link on the right",
50
+ "externalDocs": {"description": "sub-docs", "url": "/api/v1/docs"},
51
+ },
52
+ {
53
+ "name": "v2",
54
+ "description": "API version 2, check link on the right",
55
+ "externalDocs": {"description": "sub-docs", "url": "/api/v2/docs"},
56
+ },
57
+ ],
58
+ )
59
+
60
+ api_v1 = FastAPI(
38
61
  version="1.0.0",
39
62
  title="OE Python Template Example",
40
63
  contact={
@@ -45,6 +68,17 @@ app = FastAPI(
45
68
  terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
46
69
  )
47
70
 
71
+ api_v2 = FastAPI(
72
+ version="2.0.0",
73
+ title="OE Python Template Example",
74
+ contact={
75
+ "name": "Helmut Hoffer von Ankershoffen",
76
+ "email": "helmuthva@gmail.com",
77
+ "url": "https://github.com/helmut-hoffer-von-ankershoffen",
78
+ },
79
+ terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
80
+ )
81
+
48
82
 
49
83
  class _HealthStatus(StrEnum):
50
84
  """Health status enumeration.
@@ -78,8 +112,10 @@ class HealthResponse(BaseModel):
78
112
  )
79
113
 
80
114
 
81
- @app.get("/healthz", tags=["Observability"])
82
- @app.get("/health", tags=["Observability"])
115
+ @api_v1.get("/healthz", tags=["Observability"])
116
+ @api_v1.get("/health", tags=["Observability"])
117
+ @api_v2.get("/healthz", tags=["Observability"])
118
+ @api_v2.get("/health", tags=["Observability"])
83
119
  async def health(service: Annotated[Service, Depends(get_service)], response: Response) -> Health:
84
120
  """Check the health of the service.
85
121
 
@@ -118,7 +154,8 @@ class HelloWorldResponse(BaseModel):
118
154
  )
119
155
 
120
156
 
121
- @app.get("/hello-world", tags=["Basics"])
157
+ @api_v1.get("/hello-world", tags=["Basics"])
158
+ @api_v2.get("/hello-world", tags=["Basics"])
122
159
  async def hello_world() -> HelloWorldResponse:
123
160
  """
124
161
  Return a hello world message.
@@ -151,7 +188,7 @@ class EchoRequest(BaseModel):
151
188
  )
152
189
 
153
190
 
154
- @app.post("/echo", tags=["Basics"])
191
+ @api_v1.post("/echo", tags=["Basics"])
155
192
  async def echo(request: EchoRequest) -> EchoResponse:
156
193
  """
157
194
  Echo back the provided text.
@@ -166,3 +203,35 @@ async def echo(request: EchoRequest) -> EchoResponse:
166
203
  422 Unprocessable Entity: If text is not provided or empty.
167
204
  """
168
205
  return EchoResponse(message=request.text)
206
+
207
+
208
+ class Utterance(BaseModel):
209
+ """Request model for echo endpoint."""
210
+
211
+ utterance: str = Field(
212
+ ...,
213
+ min_length=1,
214
+ description="The utterance to echo back",
215
+ examples=[HELLO_WORLD_EXAMPLE],
216
+ )
217
+
218
+
219
+ @api_v2.post("/echo", tags=["Basics"])
220
+ async def echo_v2(request: Utterance) -> EchoResponse:
221
+ """
222
+ Echo back the provided utterance.
223
+
224
+ Args:
225
+ request (EchoRequestV2): The request containing the utterance to echo back.
226
+
227
+ Returns:
228
+ EchoResponse: A response containing the echoed utterance.
229
+
230
+ Raises:
231
+ 422 Unprocessable Entity: If utterance is not provided or empty.
232
+ """
233
+ return EchoResponse(message=request.utterance)
234
+
235
+
236
+ api.mount("/v1", api_v1)
237
+ api.mount("/v2", api_v2)
@@ -9,7 +9,7 @@ import yaml
9
9
  from rich.console import Console
10
10
 
11
11
  from oe_python_template_example import Service, __version__
12
- from oe_python_template_example.api import app as api
12
+ from oe_python_template_example.api import api_v1, api_v2
13
13
 
14
14
  console = Console()
15
15
 
@@ -50,13 +50,31 @@ def serve(
50
50
  """Start the API server."""
51
51
  console.print(f"Starting API server at http://{host}:{port}")
52
52
  uvicorn.run(
53
- "oe_python_template_example.api:app",
53
+ "oe_python_template_example.api:api",
54
54
  host=host,
55
55
  port=port,
56
56
  reload=watch,
57
57
  )
58
58
 
59
59
 
60
+ class APIVersion(StrEnum):
61
+ """
62
+ Enum representing the API versions.
63
+
64
+ This enum defines the supported API verions:
65
+ - V1: Output doc for v1 API
66
+ - V2: Output doc for v2 API
67
+
68
+ Usage:
69
+ version = APIVersion.V1
70
+ print(f"Using {version} version")
71
+
72
+ """
73
+
74
+ V1 = "v1"
75
+ V2 = "v2"
76
+
77
+
60
78
  class OutputFormat(StrEnum):
61
79
  """
62
80
  Enum representing the supported output formats.
@@ -76,12 +94,17 @@ class OutputFormat(StrEnum):
76
94
 
77
95
  @cli.command()
78
96
  def openapi(
97
+ api_version: Annotated[APIVersion, typer.Option(help="API Version", case_sensitive=False)] = APIVersion.V1,
79
98
  output_format: Annotated[
80
99
  OutputFormat, typer.Option(help="Output format", case_sensitive=False)
81
100
  ] = OutputFormat.YAML,
82
101
  ) -> None:
83
102
  """Dump the OpenAPI specification to stdout (YAML by default)."""
84
- schema = api.openapi()
103
+ match api_version:
104
+ case APIVersion.V1:
105
+ schema = api_v1.openapi()
106
+ case APIVersion.V2:
107
+ schema = api_v2.openapi()
85
108
  match output_format:
86
109
  case OutputFormat.JSON:
87
110
  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.0
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,38 @@ 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
173
175
  ```
174
176
 
177
+ When serving the 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 result.
178
+
179
+ The API is versioned and provides interactive documentation at [http://127.0.0.1:8000/api/v1/docs](http://127.0.0.1:8000/api/v1/docs) resp. [http://127.0.0.1:8000/api/v2/docs](http://127.0.0.1:8000/api/v2/docs)
180
+
181
+
182
+ ```shell
183
+
184
+ When running the webservice API, goto http://127.0.0.1:8000/api/v1/docs
185
+
175
186
  The CLI provides extensive help:
176
187
 
177
188
  ```shell
178
189
  uvx oe-python-template-example --help # all CLI commands
179
190
  uvx oe-python-template-example hello-world --help # help for specific command
191
+ uvx oe-python-template-example echo --help
192
+ uvx oe-python-template-example openapi --help
193
+ uvx oe-python-template-example serve --help
180
194
  ```
181
195
 
182
196
 
183
- ## Highlights
197
+ ## Operational Excellence
198
+
199
+ This project is designed with operational excellence in mind, using modern Python tooling and practices. It includes:
184
200
 
185
- * Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
186
- * Various Examples:
201
+ * Various examples demonstrating usage:
187
202
  - [Simple Python script](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/script.py)
188
203
  - [Streamlit web application](https://oe-python-template-example.streamlit.app/) deployed on [Streamlit Community Cloud](https://streamlit.io/cloud)
189
204
  - [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 +214,9 @@ uvx oe-python-template-example hello-world --help # help for specific command
199
214
 
200
215
  ## Usage Examples
201
216
 
217
+ The following examples run from source. Clone this repository first using
218
+ `git clone git@github.com:helmut-hoffer-von-ankershoffen/oe-python-template-example.git`.
219
+
202
220
  ### Minimal Python Script:
203
221
 
204
222
  ```python
@@ -240,7 +258,7 @@ uv run streamlit run examples/streamlit.py # Serve on localhost:8501, o
240
258
  ... or run within VSCode
241
259
 
242
260
  ```shell
243
- uv sync --all-extras # Install ipykernel dependency part of the examples extra, see pyproject.toml
261
+ uv sync --all-extras # Install dependencies required for examples such as Juypyter kernel, see pyproject.toml
244
262
  ```
245
263
  Install the [Jupyter extension for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter)
246
264
 
@@ -337,7 +355,10 @@ docker compose run oe-python-template-example echo "Lorem" --json
337
355
  docker compose run oe-python-template-example openapi
338
356
  docker compose run oe-python-template-example openapi --output-format=json
339
357
  docker compose up
340
- curl http://127.0.0.1 8000
358
+ curl http://127.0.0.1:8000/api/v1/hello-world
359
+ curl http://127.0.0.1:8000/api/v1/docs
360
+ curl http://127.0.0.1:8000/api/v2/hello-world
361
+ curl http://127.0.0.1:8000/api/v2/docs
341
362
  ```
342
363
 
343
364
  ## 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=Z5ZBrz7Ayjoi_h2UfD0Y_v3mlaHAkkXZYWonzB9L2hc,6356
3
+ oe_python_template_example/cli.py,sha256=PF4B8XygvY72obDFBu4idxNDntNvFbhSi-5Bl3vs4gU,3383
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.0.dist-info/METADATA,sha256=6tmPpUMuhnNOlQYfR1zHL0sACY0Hdw7EYR_5iyZ6ekw,21925
7
+ oe_python_template_example-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ oe_python_template_example-0.1.0.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
9
+ oe_python_template_example-0.1.0.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
10
+ oe_python_template_example-0.1.0.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,,