oe-python-template-example 0.0.9__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.
@@ -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,7 +20,44 @@ from oe_python_template_example import Service
15
20
  HELLO_WORLD_EXAMPLE = "Hello, world!"
16
21
 
17
22
 
18
- app = FastAPI(
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
+
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(
19
61
  version="1.0.0",
20
62
  title="OE Python Template Example",
21
63
  contact={
@@ -26,6 +68,81 @@ app = FastAPI(
26
68
  terms_of_service="https://oe-python-template-example.readthedocs.io/en/latest/",
27
69
  )
28
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
+
82
+
83
+ class _HealthStatus(StrEnum):
84
+ """Health status enumeration.
85
+
86
+ Args:
87
+ StrEnum (_type_): _description_
88
+ """
89
+
90
+ UP = "UP"
91
+ DOWN = "DOWN"
92
+
93
+
94
+ class Health(BaseModel):
95
+ """Health status model.
96
+
97
+ Args:
98
+ BaseModel (_type_): _description_
99
+ """
100
+
101
+ status: _HealthStatus
102
+ reason: str | None = None
103
+
104
+
105
+ class HealthResponse(BaseModel):
106
+ """Response model for health endpoint."""
107
+
108
+ health: str = Field(
109
+ ...,
110
+ description="The hello world message",
111
+ examples=[HELLO_WORLD_EXAMPLE],
112
+ )
113
+
114
+
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"])
119
+ async def health(service: Annotated[Service, Depends(get_service)], response: Response) -> Health:
120
+ """Check the health of the service.
121
+
122
+ This endpoint returns the health status of the service.
123
+ The health status can be either UP or DOWN.
124
+ If the service is healthy, the status will be UP.
125
+ If the service is unhealthy, the status will be DOWN and a reason will be provided.
126
+ The response will have a 200 OK status code if the service is healthy,
127
+ and a 500 Internal Server Error status code if the service is unhealthy.
128
+
129
+ Args:
130
+ service (Annotated[Service, Depends): _description_
131
+ response (Response): _description_
132
+
133
+ Returns:
134
+ Health: The health status of the service.
135
+ """
136
+ if service.healthy():
137
+ health_result = Health(status=_HealthStatus.UP)
138
+ else:
139
+ health_result = Health(status=_HealthStatus.DOWN, reason="Service is unhealthy")
140
+
141
+ if health_result.status == _HealthStatus.DOWN:
142
+ response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
143
+
144
+ return health_result
145
+
29
146
 
30
147
  class HelloWorldResponse(BaseModel):
31
148
  """Response model for hello-world endpoint."""
@@ -37,6 +154,18 @@ class HelloWorldResponse(BaseModel):
37
154
  )
38
155
 
39
156
 
157
+ @api_v1.get("/hello-world", tags=["Basics"])
158
+ @api_v2.get("/hello-world", tags=["Basics"])
159
+ async def hello_world() -> HelloWorldResponse:
160
+ """
161
+ Return a hello world message.
162
+
163
+ Returns:
164
+ HelloWorldResponse: A response containing the hello world message.
165
+ """
166
+ return HelloWorldResponse(message=Service.get_hello_world())
167
+
168
+
40
169
  class EchoResponse(BaseModel):
41
170
  """Response model for echo endpoint."""
42
171
 
@@ -59,18 +188,7 @@ class EchoRequest(BaseModel):
59
188
  )
60
189
 
61
190
 
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
- @app.post("/echo", tags=["Basics"])
191
+ @api_v1.post("/echo", tags=["Basics"])
74
192
  async def echo(request: EchoRequest) -> EchoResponse:
75
193
  """
76
194
  Echo back the provided text.
@@ -85,3 +203,35 @@ async def echo(request: EchoRequest) -> EchoResponse:
85
203
  422 Unprocessable Entity: If text is not provided or empty.
86
204
  """
87
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)
@@ -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
@@ -8,7 +9,7 @@ import yaml
8
9
  from rich.console import Console
9
10
 
10
11
  from oe_python_template_example import Service, __version__
11
- from oe_python_template_example.api import app as api
12
+ from oe_python_template_example.api import api_v1, api_v2
12
13
 
13
14
  console = Console()
14
15
 
@@ -44,30 +45,71 @@ 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}")
51
52
  uvicorn.run(
52
- "oe_python_template_example.api:app",
53
+ "oe_python_template_example.api:api",
53
54
  host=host,
54
55
  port=port,
55
- reload=reload,
56
+ reload=watch,
56
57
  )
57
58
 
58
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
+
78
+ class OutputFormat(StrEnum):
79
+ """
80
+ Enum representing the supported output formats.
81
+
82
+ This enum defines the possible formats for output data:
83
+ - YAML: Output data in YAML format
84
+ - JSON: Output data in JSON format
85
+
86
+ Usage:
87
+ format = OutputFormat.YAML
88
+ print(f"Using {format} format")
89
+ """
90
+
91
+ YAML = "yaml"
92
+ JSON = "json"
93
+
94
+
59
95
  @cli.command()
60
96
  def openapi(
97
+ api_version: Annotated[APIVersion, typer.Option(help="API Version", case_sensitive=False)] = APIVersion.V1,
61
98
  output_format: Annotated[
62
- str, typer.Option(help="Output format (yaml or json), defaults to yaml", case_sensitive=False)
63
- ] = "yaml",
99
+ OutputFormat, typer.Option(help="Output format", case_sensitive=False)
100
+ ] = OutputFormat.YAML,
64
101
  ) -> None:
65
102
  """Dump the OpenAPI specification to stdout (YAML by default)."""
66
- 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="")
103
+ match api_version:
104
+ case APIVersion.V1:
105
+ schema = api_v1.openapi()
106
+ case APIVersion.V2:
107
+ schema = api_v2.openapi()
108
+ match output_format:
109
+ case OutputFormat.JSON:
110
+ console.print_json(data=schema)
111
+ case OutputFormat.YAML:
112
+ console.print(yaml.dump(schema, default_flow_style=False), end="")
71
113
 
72
114
 
73
115
  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.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,25 +167,39 @@ 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
184
198
 
185
- * Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
186
- * Various Examples:
187
- - [Simple Python script]https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/blob/main/examples/script.py)
199
+ This project is designed with operational excellence in mind, using modern Python tooling and practices. It includes:
200
+
201
+ * Various examples demonstrating usage:
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
190
205
  * [Complete reference documenation](https://oe-python-template-example.readthedocs.io/en/latest/reference.html) on Read the Docs
@@ -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
 
@@ -284,8 +302,12 @@ Execute commands:
284
302
 
285
303
  ```shell
286
304
  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"
305
+ uvx oe-python-template-example echo --help
306
+ uvx oe-python-template-example echo "Lorem"
307
+ uvx oe-python-template-example echo "Lorem" --json
308
+ uvx oe-python-template-example openapi
309
+ uvx oe-python-template-example openapi --output-format=json
310
+ uvx oe-python-template-example serve
289
311
  ```
290
312
 
291
313
  ### Environment
@@ -306,8 +328,12 @@ You can as well run the CLI within Docker.
306
328
  ```shell
307
329
  docker run helmuthva/oe-python-template-example --help
308
330
  docker run helmuthva/oe-python-template-example hello-world
309
- docker run helmuthva/oe-python-template-example hello-world --json
331
+ docker run helmuthva/oe-python-template-example echo --help
310
332
  docker run helmuthva/oe-python-template-example echo "Lorem"
333
+ docker run helmuthva/oe-python-template-example echo "Lorem" --json
334
+ docker run helmuthva/oe-python-template-example openapi
335
+ docker run helmuthva/oe-python-template-example openapi --output-format=json
336
+ docker run helmuthva/oe-python-template-example serve
311
337
  ```
312
338
 
313
339
  Execute command:
@@ -321,8 +347,18 @@ Or use docker compose
321
347
  The .env is passed through from the host to the Docker container.
322
348
 
323
349
  ```shell
324
- docker compose up
325
350
  docker compose run oe-python-template-example --help
351
+ docker compose run oe-python-template-example hello-world
352
+ docker compose run oe-python-template-example echo --help
353
+ docker compose run oe-python-template-example echo "Lorem"
354
+ docker compose run oe-python-template-example echo "Lorem" --json
355
+ docker compose run oe-python-template-example openapi
356
+ docker compose run oe-python-template-example openapi --output-format=json
357
+ docker compose up
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
326
362
  ```
327
363
 
328
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=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,,