Skip to content

Commit 7b2fdf6

Browse files
authored
Merge pull request #36 from br3ndonland/python-3.10
Add Python 3.10 support
2 parents 97e35b7 + f3fd95d commit 7b2fdf6

12 files changed

+43
-33
lines changed

.github/workflows/builds.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
strategy:
1919
matrix:
20-
python-version: [3.8, 3.9]
20+
python-version: ["3.8", "3.9", "3.10"]
2121
steps:
2222
- uses: actions/checkout@v2
2323
- uses: actions/setup-python@v2
@@ -67,12 +67,13 @@ jobs:
6767
- name: Run unit tests
6868
run: poetry run pytest --cov-report=xml
6969
- name: Upload test coverage report to Codecov
70+
if: matrix.python-version == '3.10'
7071
uses: codecov/codecov-action@v2
7172
with:
7273
fail_ci_if_error: true
7374
flags: unit
7475
- name: Build Python package with latest Python version and publish to PyPI
75-
if: startsWith(github.ref, 'refs/tags/') && matrix.python-version == 3.9
76+
if: startsWith(github.ref, 'refs/tags/') && matrix.python-version == '3.10'
7677
run: |
7778
PACKAGE_VERSION=$(poetry version -s)
7879
GIT_TAG_VERSION=$(echo ${{ github.ref }} | cut -d / -f 3)
@@ -91,7 +92,7 @@ jobs:
9192
fail-fast: false
9293
matrix:
9394
linux-version: ["", "alpine", "slim"]
94-
python-version: [3.8, 3.9]
95+
python-version: ["3.8", "3.9", "3.10"]
9596
steps:
9697
- uses: actions/checkout@v2
9798
- uses: actions/setup-python@v2
@@ -231,7 +232,7 @@ jobs:
231232
-u ${{ github.actor }} --password-stdin
232233
- name: Tag and push Docker images with latest tags
233234
if: >
234-
matrix.python-version == 3.9 &&
235+
matrix.python-version == '3.10' &&
235236
(
236237
startsWith(github.ref, 'refs/tags/') ||
237238
github.ref == 'refs/heads/develop' ||

.github/workflows/codeql.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- uses: actions/checkout@v2
2525
- uses: actions/setup-python@v2
2626
with:
27-
python-version: "3.x"
27+
python-version: "3.10"
2828
- name: Set up Poetry cache for Python dependencies
2929
uses: actions/cache@v2
3030
with:

.github/workflows/hooks.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: [3.8, 3.9]
18+
python-version: ["3.8", "3.9", "3.10"]
1919
steps:
2020
- uses: actions/checkout@v2
2121
- uses: actions/setup-python@v2

.github/workflows/tests.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
runs-on: ubuntu-latest
2020
strategy:
2121
matrix:
22-
python-version: [3.8, 3.9]
22+
python-version: ["3.8", "3.9", "3.10"]
2323
steps:
2424
- uses: actions/checkout@v2
2525
- uses: actions/setup-python@v2
@@ -62,6 +62,7 @@ jobs:
6262
- name: Run unit tests
6363
run: poetry run pytest --cov-report=xml
6464
- name: Upload test coverage report to Codecov
65+
if: matrix.python-version == '3.10'
6566
uses: codecov/codecov-action@v2
6667
with:
6768
fail_ci_if_error: true

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG PYTHON_VERSION=3.9 LINUX_VERSION=
1+
ARG PYTHON_VERSION=3.10 LINUX_VERSION=
22
FROM python:${PYTHON_VERSION}${LINUX_VERSION:+-$LINUX_VERSION} AS base
33
LABEL org.opencontainers.image.authors="Brendon Smith <[email protected]>"
44
LABEL org.opencontainers.image.description="Docker images and utilities to power your Python APIs and help you ship faster."

inboard/app/main_base.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from __future__ import annotations
2+
13
import os
24
import sys
3-
from typing import Awaitable, Callable, Dict
5+
from typing import Awaitable, Callable
46

57

68
class App:
@@ -9,13 +11,13 @@ class App:
911
https://www.uvicorn.org/
1012
"""
1113

12-
def __init__(self, scope: Dict) -> None:
14+
def __init__(self, scope: dict) -> None:
1315
assert scope["type"] == "http"
1416
self.scope = scope
1517

1618
async def __call__(
17-
self, receive: Dict, send: Callable[[Dict], Awaitable]
18-
) -> Dict[str, str]:
19+
self, receive: dict, send: Callable[[dict], Awaitable]
20+
) -> dict[str, str]:
1921
await send(
2022
{
2123
"type": "http.response.start",
@@ -32,7 +34,7 @@ async def __call__(
3234
raise NameError("Process manager needs to be either uvicorn or gunicorn.")
3335
server = "Uvicorn" if process_manager == "uvicorn" else "Uvicorn, Gunicorn,"
3436
message = f"Hello World, from {server} and Python {version}!"
35-
response: Dict = {"type": "http.response.body", "body": message.encode("utf-8")}
37+
response: dict = {"type": "http.response.body", "body": message.encode("utf-8")}
3638
await send(response)
3739
return response
3840

inboard/app/utilities_starlette.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from __future__ import annotations
2+
13
import base64
24
import os
35
import secrets
4-
from typing import Optional, Tuple
56

67
from starlette.authentication import (
78
AuthCredentials,
@@ -17,7 +18,7 @@ class BasicAuth(AuthenticationBackend):
1718

1819
async def authenticate(
1920
self, request: HTTPConnection
20-
) -> Optional[Tuple[AuthCredentials, SimpleUser]]:
21+
) -> tuple[AuthCredentials, SimpleUser] | None:
2122
"""Authenticate a Starlette request with HTTP Basic auth."""
2223
if "Authorization" not in request.headers:
2324
return None

inboard/gunicorn_conf.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
from __future__ import annotations
2+
13
import multiprocessing
24
import os
3-
from typing import Optional
45

56
from inboard.logging_conf import configure_logging
67

78

89
def calculate_workers(
9-
max_workers: Optional[str] = None,
10-
total_workers: Optional[str] = None,
10+
max_workers: str | None = None,
11+
total_workers: str | None = None,
1112
workers_per_core: str = "1",
1213
) -> int:
1314
"""Calculate the number of Gunicorn worker processes."""

inboard/logging_conf.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from __future__ import annotations
2+
13
import importlib.util
24
import logging
35
import logging.config
46
import os
57
import sys
68
from pathlib import Path
7-
from typing import Optional, Set
89

910

1011
def find_and_load_logging_conf(logging_conf: str) -> dict:
@@ -30,7 +31,7 @@ def find_and_load_logging_conf(logging_conf: str) -> dict:
3031

3132
def configure_logging(
3233
logger: logging.Logger = logging.getLogger(),
33-
logging_conf: Optional[str] = os.getenv("LOGGING_CONF"),
34+
logging_conf: str | None = os.getenv("LOGGING_CONF"),
3435
) -> dict:
3536
"""Configure Python logging given the name of a logging module or file."""
3637
try:
@@ -67,7 +68,7 @@ class LogFilter(logging.Filter):
6768
def __init__(
6869
self,
6970
name: str = "",
70-
filters: Optional[Set[str]] = None,
71+
filters: set[str] | None = None,
7172
) -> None:
7273
"""Initialize a filter."""
7374
self.name = name
@@ -85,7 +86,7 @@ def filter(self, record: logging.LogRecord) -> bool:
8586
return all(match not in message for match in self.filters)
8687

8788
@staticmethod
88-
def set_filters(input_filters: Optional[str] = None) -> Optional[Set[str]]:
89+
def set_filters(input_filters: str | None = None) -> set[str] | None:
8990
"""Set log message filters.
9091
9192
Filters identify log messages to filter out, so that the logger does not

inboard/start.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
24
import importlib.util
35
import json
46
import logging
57
import os
68
import subprocess
79
from pathlib import Path
8-
from typing import Optional
910

1011
import uvicorn # type: ignore
1112

@@ -52,7 +53,7 @@ def set_gunicorn_options(app_module: str) -> list:
5253
return ["gunicorn", "-k", worker_class, "-c", gunicorn_conf_path, app_module]
5354

5455

55-
def _split_uvicorn_option(option: str) -> Optional[list]:
56+
def _split_uvicorn_option(option: str) -> list | None:
5657
return (
5758
[option_item.strip() for option_item in str(option_value).split(sep=",")]
5859
if (option_value := os.getenv(option.upper()))
@@ -77,7 +78,7 @@ def _update_uvicorn_config_options(uvicorn_config_options: dict) -> dict:
7778
return uvicorn_config_options
7879

7980

80-
def set_uvicorn_options(log_config: Optional[dict] = None) -> dict:
81+
def set_uvicorn_options(log_config: dict | None = None) -> dict:
8182
"""Set options for running the Uvicorn server."""
8283
host = os.getenv("HOST", "0.0.0.0")
8384
port = int(os.getenv("PORT", "80"))
@@ -99,7 +100,7 @@ def start_server(
99100
process_manager: str,
100101
app_module: str,
101102
logger: logging.Logger = logging.getLogger(),
102-
logging_conf_dict: Optional[dict] = None,
103+
logging_conf_dict: dict | None = None,
103104
) -> None:
104105
"""Start the Uvicorn or Gunicorn server."""
105106
try:

tests/app/test_main.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from __future__ import annotations
2+
13
import sys
2-
from typing import Dict, List
34

45
import pytest
56
from fastapi import FastAPI
@@ -14,7 +15,7 @@ class TestCors:
1415
[Starlette CORS docs](https://www.starlette.io/middleware/#corsmiddleware).
1516
"""
1617

17-
origins: Dict[str, List[str]] = {
18+
origins: dict[str, list[str]] = {
1819
"allowed": [
1920
"http://br3ndon.land",
2021
"https://br3ndon.land",
@@ -38,7 +39,7 @@ def test_cors_preflight_response_allowed(
3839
self, allowed_origin: str, client: TestClient
3940
) -> None:
4041
"""Test pre-flight response to cross-origin request from allowed origin."""
41-
headers: Dict[str, str] = {
42+
headers: dict[str, str] = {
4243
"Origin": allowed_origin,
4344
"Access-Control-Request-Method": "GET",
4445
"Access-Control-Request-Headers": "X-Example",
@@ -54,7 +55,7 @@ def test_cors_preflight_response_disallowed(
5455
self, disallowed_origin: str, client: TestClient
5556
) -> None:
5657
"""Test pre-flight response to cross-origin request from disallowed origin."""
57-
headers: Dict[str, str] = {
58+
headers: dict[str, str] = {
5859
"Origin": disallowed_origin,
5960
"Access-Control-Request-Method": "GET",
6061
"Access-Control-Request-Headers": "X-Example",

tests/test_gunicorn_conf.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from __future__ import annotations
2+
13
import multiprocessing
24
import subprocess
35
from pathlib import Path
4-
from typing import Optional
56

67
import pytest
78

@@ -20,7 +21,7 @@ def test_calculate_workers_default(self) -> None:
2021
assert gunicorn_conf.workers == max(cores, 2)
2122

2223
@pytest.mark.parametrize("max_workers", (None, "1", "2", "5", "10"))
23-
def test_calculate_workers_max(self, max_workers: Optional[str]) -> None:
24+
def test_calculate_workers_max(self, max_workers: str | None) -> None:
2425
"""Test Gunicorn worker process calculation with custom maximum."""
2526
cores = multiprocessing.cpu_count()
2627
default = max(cores, 2)
@@ -31,7 +32,7 @@ def test_calculate_workers_max(self, max_workers: Optional[str]) -> None:
3132
assert result == default
3233

3334
@pytest.mark.parametrize("total_workers", (None, "1", "2", "5", "10"))
34-
def test_calculate_workers_total(self, total_workers: Optional[str]) -> None:
35+
def test_calculate_workers_total(self, total_workers: str | None) -> None:
3536
"""Test Gunicorn worker process calculation with custom total."""
3637
cores = multiprocessing.cpu_count()
3738
result = gunicorn_conf.calculate_workers(None, total_workers)

0 commit comments

Comments
 (0)