Skip to content

Commit

Permalink
fix: use env var REGISTRY_URL and PROFILE for deps without registry_u…
Browse files Browse the repository at this point in the history
…rl set

also moved profile/config_file related settings to config.py

closes #81
  • Loading branch information
hfudev committed Feb 25, 2025
1 parent 2d3be69 commit 374db85
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 85 deletions.
4 changes: 2 additions & 2 deletions idf_component_manager/cli/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from idf_component_manager.core import ComponentManager
from idf_component_manager.utils import VersionSolverResolution
from idf_component_tools import warn
from idf_component_tools.config import ConfigManager, ProfileItem
from idf_component_tools.config import ConfigManager, ProfileItem, get_profile
from idf_component_tools.errors import FatalError
from idf_component_tools.registry.client_errors import APIClientError
from idf_component_tools.registry.service_details import get_api_client, get_profile
from idf_component_tools.registry.service_details import get_api_client

from .constants import get_profile_option, get_project_dir_option
from .utils import add_options, deprecated_option
Expand Down
71 changes: 69 additions & 2 deletions idf_component_tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

from idf_component_tools.constants import (
IDF_COMPONENT_REGISTRY_URL,
IDF_COMPONENT_STORAGE_URL,
)
from idf_component_tools.errors import FatalError
from idf_component_tools.errors import FatalError, NoSuchProfile
from idf_component_tools.utils import (
Annotated,
BaseModel,
Expand All @@ -32,6 +33,7 @@
)

from .build_system_tools import get_idf_version
from .environment import ComponentManagerSettings

RegistryUrlField = Annotated[
t.Union[
Expand Down Expand Up @@ -106,6 +108,10 @@ def config_dir() -> Path:
return Path(os.environ.get('IDF_TOOLS_PATH') or Path.home() / '.espressif')


def config_file() -> Path:
return config_dir() / 'idf_component_manager.yml'


def root_managed_components_dir() -> Path:
return config_dir() / 'root_managed_components' / f'idf{get_idf_version()}'

Expand All @@ -116,7 +122,7 @@ class ConfigError(FatalError):

class ConfigManager:
def __init__(self, path=None):
self.config_path = Path(path) if path else (config_dir() / 'idf_component_manager.yml')
self.config_path = Path(path) if path else config_file()
self._yaml = YAML()
self._raw_data: CommentedMap = None # Storage for CommentedMap from the config file

Expand Down Expand Up @@ -191,3 +197,64 @@ def _update_data(self, config: Config) -> None:
del profile_values[field_name]

self._raw_data['profiles'] = profiles


def get_profile(
profile_name: t.Optional[str] = None,
config_path: t.Optional[str] = None,
) -> ProfileItem:
config_manager = ConfigManager(path=config_path)
config = config_manager.load()
_profile_name = ComponentManagerSettings().PROFILE or profile_name or 'default'

if (
_profile_name == 'default' and config.profiles.get(_profile_name) is None
) or not _profile_name:
return ProfileItem() # empty profile

if _profile_name in config.profiles:
return config.profiles[_profile_name] or ProfileItem()

raise NoSuchProfile(
f'Profile "{_profile_name}" not found in config file: {config_manager.config_path}'
)


def get_registry_url(profile: t.Optional[ProfileItem] = None) -> str:
"""
Env var > profile settings > default
"""
if profile is None:
profile = get_profile()

return (
ComponentManagerSettings().REGISTRY_URL
or (profile.registry_url if profile else IDF_COMPONENT_REGISTRY_URL)
or IDF_COMPONENT_REGISTRY_URL
)


def get_storage_urls(profile: t.Optional[ProfileItem] = None) -> t.List[str]:
"""
Env var > profile settings > default
"""
if profile is None:
profile = get_profile()

storage_url_env = ComponentManagerSettings().STORAGE_URL # fool mypy
if storage_url_env:
storage_urls = storage_url_env.split(';')
else:
storage_urls = profile.storage_urls or []

res = [] # sequence matters, the first url goes first
for url in storage_urls:
url = url.strip()
if url == 'default':
_url = IDF_COMPONENT_STORAGE_URL
else:
_url = url

if _url not in res:
res.append(_url)
return res
4 changes: 4 additions & 0 deletions idf_component_tools/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ class RunningEnvironmentError(FatalError):

class WarningAsExceptionError(FatalError):
pass


class NoSuchProfile(FatalError):
pass
68 changes: 2 additions & 66 deletions idf_component_tools/registry/service_details.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,17 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

"""Helper function to init API client"""

import typing as t

from idf_component_tools import ComponentManagerSettings
from idf_component_tools.config import ConfigManager, ProfileItem
from idf_component_tools.constants import (
IDF_COMPONENT_REGISTRY_URL,
IDF_COMPONENT_STORAGE_URL,
)
from idf_component_tools.errors import FatalError
from idf_component_tools.config import ProfileItem, get_profile, get_registry_url, get_storage_urls

from .api_client import APIClient
from .multi_storage_client import MultiStorageClient


class NoSuchProfile(FatalError):
pass


def get_profile(
profile_name: t.Optional[str] = None,
config_path: t.Optional[str] = None,
) -> ProfileItem:
config_manager = ConfigManager(path=config_path)
config = config_manager.load()
_profile_name = ComponentManagerSettings().PROFILE or profile_name or 'default'

if (
_profile_name == 'default' and config.profiles.get(_profile_name) is None
) or not _profile_name:
return ProfileItem() # empty profile

if _profile_name in config.profiles:
return config.profiles[_profile_name] or ProfileItem()

raise NoSuchProfile(
f'Profile "{profile_name}" not found in config file: {config_manager.config_path}'
)


def get_registry_url(profile: t.Optional[ProfileItem] = None) -> str:
"""
Env var > profile settings > default
"""
return (
ComponentManagerSettings().REGISTRY_URL
or (profile.registry_url if profile else IDF_COMPONENT_REGISTRY_URL)
or IDF_COMPONENT_REGISTRY_URL
)


def get_storage_urls(profile: t.Optional[ProfileItem] = None) -> t.List[str]:
"""
Env var > profile settings > default
"""
storage_url_env = ComponentManagerSettings().STORAGE_URL
if storage_url_env:
_storage_urls = [url.strip() for url in storage_url_env.split(';') if url.strip()]
else:
_storage_urls = profile.storage_urls if profile else []

res = [] # sequence matters, the first url goes first
for url in _storage_urls:
if url == 'default':
_url = IDF_COMPONENT_STORAGE_URL
else:
_url = url

if _url not in res:
res.append(_url)

return res


def get_api_client(
registry_url: t.Optional[str] = None,
*,
Expand Down
3 changes: 2 additions & 1 deletion idf_component_tools/sources/web_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_format_from_path,
unpack_archive,
)
from idf_component_tools.config import get_registry_url
from idf_component_tools.constants import (
DEFAULT_NAMESPACE,
IDF_COMPONENT_REGISTRY_URL,
Expand Down Expand Up @@ -93,7 +94,7 @@ def download_archive(url: str, download_dir: str, save_original_filename: bool =

class WebServiceSource(BaseSource):
registry_url: str = Field(
default=IDF_COMPONENT_REGISTRY_URL,
default_factory=get_registry_url,
validation_alias=AliasChoices(
'registry_url',
'service_url',
Expand Down
1 change: 1 addition & 0 deletions idf_component_tools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
# so we need to convert them to string
_http_url_adapter = TypeAdapter(HttpUrl)
UrlField = Annotated[
# return with trailing slash
str, BeforeValidator(lambda value: str(_http_url_adapter.validate_python(value)))
]

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Changelog = "https://github.com/espressif/idf-component-manager/blob/main/CHANGE
# Tools #
#########
[tool.mypy]
python_version = "3.7"
python_version = "3.8"
plugins = [
"pydantic.mypy"
]
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from idf_component_manager.core import ComponentManager
from idf_component_tools import HINT_LEVEL, ComponentManagerSettings, get_logger
from idf_component_tools.config import config_file
from idf_component_tools.hash_tools.constants import HASH_FILENAME
from idf_component_tools.registry.api_client import APIClient
from idf_component_tools.registry.api_models import TaskStatus
Expand Down Expand Up @@ -463,3 +464,21 @@ def component_name():
if 'USE_REGISTRY' in os.environ:
return f'test_{str(uuid4())}'
return 'test'


@pytest.fixture
def isolate_idf_component_manager_yml(tmp_path):
config_path = config_file()
backup_path = tmp_path / 'idf_component_manager.yml'

do_exist = config_path.is_file()
if do_exist:
shutil.move(config_path, backup_path)
yield
shutil.move(backup_path, config_path)
else:
yield
try:
config_path.unlink()
except FileNotFoundError:
pass
5 changes: 1 addition & 4 deletions tests/test_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@

from idf_component_tools import LOGGING_NAMESPACE
from idf_component_tools.__version__ import __version__
from idf_component_tools.config import get_registry_url, get_storage_urls
from idf_component_tools.constants import IDF_COMPONENT_REGISTRY_URL
from idf_component_tools.registry.api_client import APIClient
from idf_component_tools.registry.base_client import user_agent
from idf_component_tools.registry.client_errors import APIClientError
from idf_component_tools.registry.multi_storage_client import MultiStorageClient
from idf_component_tools.registry.request_processor import join_url
from idf_component_tools.registry.service_details import (
get_registry_url,
get_storage_urls,
)
from idf_component_tools.registry.storage_client import StorageClient
from idf_component_tools.semver import Version
from tests.network_test_utils import use_vcr_or_real_env
Expand Down
4 changes: 1 addition & 3 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

import json
Expand All @@ -10,8 +10,6 @@
ConfigError,
ConfigManager,
ProfileItem,
)
from idf_component_tools.registry.service_details import (
get_registry_url,
get_storage_urls,
)
Expand Down
3 changes: 1 addition & 2 deletions tests/test_prepare_dep_dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ def test_dependencies_with_partial_mirror(tmp_path, monkeypatch):
assert lock_data['dependencies']['example/cmp']
assert lock_data['dependencies']['example/cmp']['source']['type'] == 'service'
assert (
lock_data['dependencies']['example/cmp']['source']['registry_url']
== IDF_COMPONENT_REGISTRY_URL
lock_data['dependencies']['example/cmp']['source']['registry_url'] == 'https://notexist.me/'
)
assert lock_data['dependencies']['example/cmp']['version'] == '3.0.3'

Expand Down
7 changes: 3 additions & 4 deletions tests/test_profile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

import json
Expand All @@ -8,17 +8,16 @@
from jsonref import requests
from pytest import fixture, raises, warns

from idf_component_tools.config import Config, ConfigError
from idf_component_tools.config import Config, ConfigError, get_profile
from idf_component_tools.constants import (
DEFAULT_NAMESPACE,
IDF_COMPONENT_STAGING_REGISTRY_URL,
)
from idf_component_tools.errors import NoSuchProfile
from idf_component_tools.messages import UserDeprecationWarning
from idf_component_tools.registry.client_errors import APIClientError
from idf_component_tools.registry.service_details import (
NoSuchProfile,
get_api_client,
get_profile,
get_storage_client,
)

Expand Down
49 changes: 49 additions & 0 deletions tests/test_registry_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import textwrap

import pytest

from idf_component_tools.config import config_file
from idf_component_tools.errors import NoSuchProfile
from idf_component_tools.sources import WebServiceSource


def test_set_by_env_var(monkeypatch):
monkeypatch.setenv('IDF_COMPONENT_REGISTRY_URL', 'https://foo.com')
source = WebServiceSource()
assert source.registry_url == 'https://foo.com'

source = WebServiceSource(registry_url='https://bar.com')
assert source.registry_url == 'https://bar.com'


def test_set_by_profile(
monkeypatch,
isolate_idf_component_manager_yml, # noqa: ARG001
):
monkeypatch.setenv('IDF_COMPONENT_PROFILE', 'notexist')
with pytest.raises(NoSuchProfile):
WebServiceSource()

config_file().write_text(
textwrap.dedent("""
profiles:
default:
registry_url: https://foo.com
bar:
registry_url: https://bar.com
""")
)

monkeypatch.delenv('IDF_COMPONENT_PROFILE')
source = WebServiceSource()
assert source.registry_url == 'https://foo.com/'

monkeypatch.setenv('IDF_COMPONENT_PROFILE', 'bar')
source = WebServiceSource()
assert source.registry_url == 'https://bar.com/'

source = WebServiceSource(registry_url='https://baz.com')
assert source.registry_url == 'https://baz.com'

0 comments on commit 374db85

Please sign in to comment.