Skip to content

Commit

Permalink
refactor: project re-struct phase1, use hatch as build tool (#310)
Browse files Browse the repository at this point in the history
This is the phase1 project re-struct, including the following changes:

1. use hatch instead of setuptools for CI & package build,
2. use /src/ project layout.
3. split ota_proxy out of otaclient package.

Other changes are related to the above changes, including:
1. fix up import paths accordingly.
  • Loading branch information
Bodong-Yang authored May 31, 2024
1 parent c789d7b commit 96ec607
Show file tree
Hide file tree
Showing 78 changed files with 90 additions and 105 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ jobs:

- name: install build deps
run: |
python -m pip install -U pip build
python -m pip install -U pip
python -m pip install -U hatch
python -m pip install -r proto/requirements.txt
- name: build otaclient package
run: |
python -m build --wheel --outdir dist/ .
hatch build -t wheel
- name: build a proto binary
run: |
Expand Down
6 changes: 1 addition & 5 deletions docker/docker-compose_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ services:
container_name: ota-test
environment:
OUTPUT_DIR: /test_result
OTACLIENT_DIR: /ota-client
CERTS_DIR: /certs
volumes:
- ../pyproject.toml:/ota-client/pyproject.toml:ro
- ../.flake8:/ota-client/.flake8:ro
- ../otaclient:/ota-client/otaclient:ro
- ../tests:/ota-client/tests:ro
- ..:/otaclient_src:ro
- ../test_result:/test_result:rw
14 changes: 2 additions & 12 deletions docker/test_base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ ENV DEBIAN_FRONTEND=noninteractive
ARG KERNEL_VERSION="5.8.0-53-generic"
ARG BASE_IMG_URL="http://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ubuntu-base-20.04.1-base-amd64.tar.gz"
ARG OTA_METADATA_REPO="https://github.com/tier4/ota-metadata"
ENV OTA_CLIENT_DIR="/ota-client"
ENV OTA_IMAGE_SERVER_ROOT="/ota-image"
ENV OTA_IMAGE_DIR="${OTA_IMAGE_SERVER_ROOT}/data"
ENV CERTS_DIR="/certs"
ENV VENV="${OTA_CLIENT_DIR}/.venv"
ENV SPECIAL_FILE="path;adf.ae?qu.er\y=str#fragファイルement"


Expand Down Expand Up @@ -43,17 +41,10 @@ RUN set -eu; \
# NOTE: include special identifiers #?; into the pathname
RUN echo -n "${SPECIAL_FILE}" > "${OTA_IMAGE_DIR}/${SPECIAL_FILE}"

# install otaclient dependencies
COPY ./otaclient/requirements.txt /tmp/requirements.txt
COPY ./tests/requirements.txt /tmp/requirements-dev.txt
# install hatch
RUN set -eu; \
python3 -m venv ${VENV}; \
source ${VENV}/bin/activate; \
python3 -m pip install --no-cache-dir -q -U pip; \
python3 -m pip install \
--no-cache-dir -q \
-r /tmp/requirements.txt \
-r /tmp/requirements-dev.txt
python3 -m pip install --no-cache-dir -U hatch

# generate test certs and sign key
COPY --chmod=755 ./tests/keys/gen_certs.sh /tmp/certs/
Expand Down Expand Up @@ -98,5 +89,4 @@ RUN set -eu; \
COPY ./docker/test_base/entry_point.sh /entry_point.sh
RUN chmod +x /entry_point.sh

WORKDIR ${OTA_CLIENT_DIR}
ENTRYPOINT [ "/entry_point.sh" ]
28 changes: 10 additions & 18 deletions docker/test_base/entry_point.sh
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
#!/bin/bash
set -eu

OTA_CLIENT_DIR="${OTACLIENT_DIR:-/ota-client}"
TEST_ROOT=/test_root
OTACLIENT_SRC=/otaclient_src
OUTPUT_DIR="${OUTPUT_DIR:-/test_result}"
CERTS_DIR="${CERTS_DIR:-/certs}"
VENV="${OTA_CLIENT_DIR}/.venv"

# copy the source code as source is read-only
cp -R "${OTACLIENT_SRC}" "${TEST_ROOT}"

# copy the certs generated in the docker image to otaclient dir
echo "setup certificates for testing..."
cd ${OTA_CLIENT_DIR}
cd ${TEST_ROOT}
mkdir -p ./certs
cp -av ${CERTS_DIR}/root.pem ./certs/1.root.pem
cp -av ${CERTS_DIR}/interm.pem ./certs/1.interm.pem

source ${VENV}/bin/activate

# setup dependencies unconditionally
# NOTE: if we only do minor modified to the requirements.txt,
# we don't have to rebuild the image, just call pip here
APP_DEPENDENCIES="${OTA_CLIENT_DIR}/otaclient/requirements.txt"
[ -f "$APP_DEPENDENCIES" ] && echo "setup app dependencies..." && \
python3 -m pip install --no-cache-dir -q -r $APP_DEPENDENCIES
TESTS_DEPENDENCIES="${OTA_CLIENT_DIR}/tests/requirements.txt"
[ -f "$TESTS_DEPENDENCIES" ] && echo "setup tests dependencies..." && \
python3 -m pip install --no-cache-dir -q -r $TESTS_DEPENDENCIES

# exec the input params
echo "execute test with coverage"
cd ${OTA_CLIENT_DIR}
coverage run -m pytest --junit-xml=${OUTPUT_DIR}/pytest.xml ${@:-}
coverage xml -o ${OUTPUT_DIR}/coverage.xml
cd ${TEST_ROOT}
hatch env create dev
hatch run dev:coverage run -m pytest --junit-xml=${OUTPUT_DIR}/pytest.xml ${@:-}
hatch run dev:coverage xml -o ${OUTPUT_DIR}/coverage.xml
14 changes: 0 additions & 14 deletions otaclient/requirements.txt

This file was deleted.

59 changes: 43 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[build-system]
build-backend = "setuptools.build_meta"
build-backend = "hatchling.build"
requires = [
"setuptools>=61",
"setuptools_scm[toml]>=6.4",
"hatch-vcs",
"hatchling>=1.20",
]

[project]
Expand All @@ -21,27 +21,54 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dynamic = [
"dependencies",
"optional-dependencies",
"version",
]
dependencies = [
"aiofiles==22.1",
"aiohttp<3.10.0,>=3.9.5",
"cryptography<43.0.0,>=42.0.4",
"grpcio<1.54.0,>=1.53.2",
"protobuf<4.22.0,>=4.21.12",
"pydantic==2.7",
"pydantic-settings==2.2.1",
"pyOpenSSL==24.1",
"PyYAML>=3.12",
"requests<2.32.0,>=2.31",
'typing_extensions>=4.6.3; python_version < "3.11"',
"urllib3<2.0.0,>=1.26.8",
"uvicorn[standard]==0.20",
"zstandard==0.18",
]
[project.optional-dependencies]
dev = [
"black",
"coverage",
"flake8",
"isort",
"pytest==7.1.2",
"pytest-asyncio==0.21",
"pytest-mock==3.8.2",
"requests-mock",
]
[project.urls]
Source = "https://github.com/tier4/ota-client"

[tool.setuptools.dynamic]
dependencies = { file = ["otaclient/requirements.txt"] }
[tool.hatch.version]
source = "vcs"

[tool.hatch.build.hooks.vcs]
version-file = "src/_otaclient_version.py"

[tool.setuptools.dynamic.optional-dependencies]
test = { file = ["tests/requirements.txt"] }
[tool.hatch.build.targets.sdist]
exclude = ["/tools"]

[tool.setuptools.packages.find]
include = ["otaclient*"]
namespaces = false
[tool.hatch.build.targets.wheel]
only-include = ["src"]
sources = ["src"]

[tool.setuptools_scm]
write_to = "otaclient/_version.py"
version_scheme = "post-release"
local_scheme = "no-local-version"
[tool.hatch.envs.dev]
type = "virtual"
features = ["dev"]

[tool.black]
line-length = 88
Expand Down
2 changes: 1 addition & 1 deletion otaclient/.gitignore → src/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# generated version file by build
_version.py
_otaclient_version.py
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion otaclient/__init__.py → src/otaclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

try:
from otaclient._version import __version__, version
from _otaclient_version import __version__, version
except ImportError:
# unknown version
version = __version__ = "0.0.0"
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import Any, Callable, TypeVar, Union

from pydantic import Field
from typing_extensions import Annotated, ParamSpec

P = ParamSpec("P")
T = TypeVar("T")
T = TypeVar("T", bound=Enum)
RT = TypeVar("RT")

StrOrPath = Union[str, Path]
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
from urllib3.response import HTTPResponse
from urllib3.util.retry import Retry

from ota_proxy import OTAFileCacheControl
from otaclient._utils import copy_callable_typehint
from otaclient.ota_proxy import OTAFileCacheControl

from .common import wait_with_backoff
from .configs import config as cfg
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,5 @@ def configure_logging():
# NOTE: "otaclient" logger will be the root logger for all loggers name
# starts with "otaclient.", and the settings will affect its child loggers.
# For example, settings for "otaclient" logger will also be effective to
# "otaclient.app.*" logger and "otaclient.ota_proxy.*" logger.
# "otaclient.app.*" logger and "ota_proxy.*" logger.
_otaclient_logger.addHandler(ch)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@

from typing_extensions import Self

from ota_proxy import OTAProxyContextProto
from ota_proxy import config as local_otaproxy_cfg
from ota_proxy import subprocess_otaproxy_launcher
from otaclient.configs.ecu_info import ECUContact
from otaclient.ota_proxy import OTAProxyContextProto
from otaclient.ota_proxy import config as local_otaproxy_cfg
from otaclient.ota_proxy import subprocess_otaproxy_launcher

from . import log_setting
from .boot_control._common import CMDHelperFuncs
Expand Down Expand Up @@ -65,7 +65,7 @@ def __init__(
self._external_cache_dev_mp = external_cache_dev_mp
self._external_cache_data_dir = external_cache_path

self.logger = logging.getLogger("otaclient.ota_proxy")
self.logger = logging.getLogger("ota_proxy")

@property
def extra_kwargs(self) -> Dict[str, Any]:
Expand All @@ -85,9 +85,9 @@ def _subprocess_init(self):
# configure logging for otaproxy subprocess
# NOTE: on otaproxy subprocess, we first set log level of the root logger
# to CRITICAL to filter out third_party libs' logging(requests, urllib3, etc.),
# and then set the otaclient.ota_proxy logger to DEFAULT_LOG_LEVEL
# and then set the ota_proxy logger to DEFAULT_LOG_LEVEL
log_setting.configure_logging()
otaproxy_logger = logging.getLogger("otaclient.ota_proxy")
otaproxy_logger = logging.getLogger("ota_proxy")
otaproxy_logger.setLevel(cfg.DEFAULT_LOG_LEVEL)
self.logger = otaproxy_logger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
from OpenSSL import crypto
from typing_extensions import Self

from otaclient.ota_proxy import OTAFileCacheControl
from ota_proxy import OTAFileCacheControl

from .common import RetryTaskMap, get_backoff, urljoin_ensure_base
from .configs import config as cfg
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TestConfiguration:
OTACLIENT_STUB_MODULE_PATH = "otaclient.app.ota_client_stub"
OTACLIENT_SERVICE_MODULE_PATH = "otaclient.app.ota_client_service"
OTAMETA_MODULE_PATH = "otaclient.app.ota_metadata"
OTAPROXY_MODULE_PATH = "otaclient.ota_proxy"
OTAPROXY_MODULE_PATH = "ota_proxy"
CREATE_STANDBY_MODULE_PATH = "otaclient.app.create_standby"
MAIN_MODULE_PATH = "otaclient.app.main"

Expand Down
7 changes: 0 additions & 7 deletions tests/requirements.txt

This file was deleted.

4 changes: 2 additions & 2 deletions tests/test_ota_client_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import pytest
from pytest_mock import MockerFixture

from ota_proxy import OTAProxyContextProto
from ota_proxy.config import Config as otaproxyConfig
from otaclient.app.ota_client import OTAServicer
from otaclient.app.ota_client_call import OtaClientCall
from otaclient.app.ota_client_stub import (
Expand All @@ -34,8 +36,6 @@
from otaclient.app.proto import wrapper
from otaclient.configs.ecu_info import ECUInfo, parse_ecu_info
from otaclient.configs.proxy_info import ProxyInfo, parse_proxy_info
from otaclient.ota_proxy import OTAProxyContextProto
from otaclient.ota_proxy.config import Config as otaproxyConfig
from tests.conftest import cfg
from tests.utils import compare_message

Expand Down
2 changes: 1 addition & 1 deletion tests/test_ota_proxy/test_cache_control_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import pytest

from otaclient.ota_proxy import OTAFileCacheControl
from ota_proxy import OTAFileCacheControl


@pytest.mark.parametrize(
Expand Down
10 changes: 5 additions & 5 deletions tests/test_ota_proxy/test_cachedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@

import pytest

from otaclient.ota_proxy import config as cfg
from otaclient.ota_proxy.orm import NULL_TYPE
from otaclient.ota_proxy.ota_cache import CacheMeta, OTACacheDB
from otaclient.ota_proxy.utils import url_based_hash
from ota_proxy import config as cfg
from ota_proxy.orm import NULL_TYPE
from ota_proxy.ota_cache import CacheMeta, OTACacheDB
from ota_proxy.utils import url_based_hash

logger = logging.getLogger(__name__)


class TestORM:
@pytest.fixture(autouse=True)
def create_table_defs(self):
from otaclient.ota_proxy.orm import NULL_TYPE, ColumnDescriptor, ORMBase
from ota_proxy.orm import NULL_TYPE, ColumnDescriptor, ORMBase

@dataclass
class TableCls(ORMBase):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_ota_proxy/test_ota_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@

import pytest

from otaclient.ota_proxy import config as cfg
from otaclient.ota_proxy.db import CacheMeta, OTACacheDB
from otaclient.ota_proxy.ota_cache import CachingRegister, LRUCacheHelper
from otaclient.ota_proxy.utils import url_based_hash
from ota_proxy import config as cfg
from ota_proxy.db import CacheMeta, OTACacheDB
from ota_proxy.ota_cache import CachingRegister, LRUCacheHelper
from ota_proxy.utils import url_based_hash

logger = logging.getLogger(__name__)

Expand Down
Loading

1 comment on commit 96ec607

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/otaclient
   __init__.py5260%17, 19
   __main__.py110%16
src/otaclient/_utils
   __init__.py31583%69–71, 86, 88
   linux.py471176%45–47, 53, 63, 68, 70, 102–103, 127–128
   logging.py291451%41–44, 49–52, 54–55, 57–58, 62–63
   typing.py160100% 
src/otaclient/app
   __main__.py110%16
   common.py3833989%65, 145, 251, 254–256, 271, 278–280, 346–348, 358, 367–369, 475, 487, 606, 610, 698, 700, 732–734, 736, 762–765, 770, 774–778, 804–805
   configs.py750100% 
   downloader.py2684483%70, 83–84, 299, 304, 308, 326–327, 377–381, 400–402, 405–406, 409–410, 431–434, 438–439, 443–444, 448–449, 458, 533–535, 551, 571–573, 577, 579, 582, 587–589
   errors.py1120100% 
   interface.py50100% 
   log_setting.py53590%54, 56, 65–67
   main.py31293%45–46
   ota_client.py38813764%57–58, 78, 184–185, 198, 201, 205, 208, 245–248, 260–263, 266–267, 271–276, 286–289, 294–295, 297, 306, 309, 314–315, 318, 324, 326, 329, 371–374, 379, 383, 386, 399–402, 405–412, 415–422, 428–431, 460, 463–464, 466, 469–472, 474–475, 480–481, 484, 498–505, 512, 515–521, 568–571, 579, 615, 620–623, 628–630, 633–634, 636–637, 639–640, 642, 702–703, 706, 714–715, 718, 729–730, 733, 741–742, 745, 756, 775, 802, 821, 839
   ota_client_call.py38684%42–44, 80–82
   ota_client_service.py27388%55–57
   ota_client_stub.py39410972%76–78, 80–81, 89–92, 95–97, 101, 106–107, 109–110, 113, 115–116, 119–121, 124–125, 128–130, 135–140, 144, 147–151, 153–154, 162–164, 167, 204–206, 211, 247, 272, 275, 278, 382, 406, 408, 432, 478, 535, 605–606, 645, 664–666, 672–675, 679–681, 688–690, 693, 697–700, 753, 840–842, 849, 879–880, 883–887, 896–905, 912, 918, 921–922, 926, 929
   ota_metadata.py3143289%143, 148, 184–185, 195–196, 199, 211, 269, 279–282, 321–324, 404, 407, 415–417, 430, 439–440, 443–444, 717–718, 721, 725, 728
   ota_status.py14285%34, 42
   update_stats.py105298%159, 169
src/otaclient/app/boot_control
   __init__.py40100% 
   _common.py2337667%73–74, 95–97, 113–114, 134–135, 154–155, 174–175, 194–195, 210–211, 232, 240, 258, 266, 285–286, 289–290, 313, 315–324, 326–335, 337–339, 358, 361, 369, 377, 393–395, 397–402, 495, 500, 505, 618, 622–623, 626, 634, 636–637, 711–712, 722, 740
   _grub.py41712869%216, 264–267, 273–277, 314–315, 322–327, 330–336, 339, 342–343, 348, 350–352, 361–367, 369–370, 372–374, 383–385, 387–389, 468–469, 473–474, 526, 532, 558, 580, 584–585, 600–602, 626–629, 641, 645–647, 649–651, 710–713, 738–741, 764–767, 779–780, 783–784, 819, 825, 845–846, 848, 860, 863, 866, 869, 873–875, 893–896, 924–927, 932–940, 945–953
   _jetson_cboot.py27021420%66–67, 74–75, 93–102, 114, 121–122, 134, 140–141, 151–153, 165–166, 177–178, 181–182, 185–186, 189–193, 196–197, 201–202, 207–208, 210–214, 216–222, 224–225, 230, 233, 236–237, 240, 244–245, 249–250, 254, 257, 260, 264–270, 272–274, 279, 282, 285, 289, 296, 298–301, 314, 317, 321, 323–325, 329, 336, 338, 341, 347–348, 353, 361, 369–371, 380–381, 383–385, 391, 394–396, 400–401, 403, 406, 415–417, 420, 423, 426–431, 433–435, 438, 441, 445–450, 454–456, 461–462, 466–467, 470, 473, 476–477, 480, 483, 488, 491, 494–495, 497, 499, 502, 505, 507–508, 511–515, 520–521, 523, 531–535, 537, 540, 543, 554–555, 560, 570, 573–581, 586–594, 599–607, 613–615, 618, 621
   _jetson_common.py1426653%52, 76, 131–136, 138, 143–145, 150–153, 161–162, 169–170, 175–176, 192–193, 195–197, 200–202, 205, 209, 213, 217–219, 225–226, 228, 261, 287–288, 290–292, 296–299, 301–302, 304–308, 310, 317–318, 321, 323, 333, 336–337, 340, 342
   _rpi_boot.py25812252%84–86, 92–93, 95–97, 99, 102–103, 108–109, 118–119, 123, 125, 129, 133–136, 141–143, 147–150, 174–176, 182–184, 197–199, 205–207, 220–227, 229, 233–235, 238–241, 244–245, 250, 254, 258, 262, 296, 323–325, 335–338, 342–348, 388–390, 432–436, 455–458, 463, 466, 490–493, 498–506, 511–519, 533–536, 542–544, 547
   configs.py460100% 
   protocol.py40100% 
   selecter.py382631%44–46, 49–50, 54–55, 58–60, 63, 65, 69, 77–79, 81–82, 84–85, 89, 91–93, 95, 97
src/otaclient/app/create_standby
   __init__.py12558%28–30, 32, 34
   common.py2194380%62, 65–66, 70–72, 74, 78–79, 81, 127, 175–177, 179–181, 183, 186–189, 193, 204, 278–279, 281–286, 299, 354, 357–359, 375–376, 390, 394, 416–417
   interface.py50100% 
   rebuild_mode.py89198%103
src/otaclient/app/proto
   __init__.py30390%37, 44–45
   _common.py3984588%87, 165, 172, 184–186, 205, 210, 221, 257, 263, 268, 299, 303, 307, 402, 462, 469, 472, 492, 499, 501, 526, 532, 535, 537, 562, 568, 571, 573, 605, 609, 611, 625, 642, 669, 672, 676, 707, 713, 760–763, 765
   _ota_metafiles_wrapper.py841384%38, 41–43, 113–117, 123–126
   _otaclient_v2_pb2_wrapper.py2562391%86, 89–92, 131, 209–210, 212, 259, 262–263, 506–508, 512–513, 515, 518–519, 522–523, 586
   streamer.py42880%33, 48, 66–67, 72, 81–82, 100
   wrapper.py30100% 
src/otaclient/configs
   _common.py80100% 
   ecu_info.py57198%107
   proxy_info.py52296%88, 90
TOTAL5004119176% 

Tests Skipped Failures Errors Time
179 0 💤 0 ❌ 0 🔥 4m 53s ⏱️

Please sign in to comment.