Skip to content

Commit 3315a89

Browse files
committed
Change import dependency graph
config/__init__.py now only imports objects for the public API get_service function is now defined in services/__init__.py
1 parent dea33bd commit 3315a89

34 files changed

Lines changed: 220 additions & 241 deletions

bugwarrior/collect.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from collections.abc import Iterable, Iterator
22
import copy
3-
from functools import cache
4-
from importlib.metadata import entry_points
53
import json
64
import logging
75
import multiprocessing
@@ -11,7 +9,7 @@
119
from jinja2 import Template
1210
from taskw.task import Task
1311

14-
from bugwarrior.types import CollectedIssue, CollectionErrorData, TaskwarriorData
12+
from bugwarrior.services import CollectedIssue, CollectionErrorData, get_service
1513

1614
if TYPE_CHECKING:
1715
from bugwarrior.config.validation import Config
@@ -24,26 +22,6 @@
2422
SERVICE_FINISHED_ERROR = 1
2523

2624

27-
@cache
28-
def get_service(service_name: str) -> type["Service"]:
29-
try:
30-
(service,) = entry_points(group='bugwarrior.service', name=service_name)
31-
except ValueError as e:
32-
if service_name in [
33-
'activecollab',
34-
'activecollab2',
35-
'megaplan',
36-
'teamlab',
37-
'versionone',
38-
]:
39-
log.warning(f"The {service_name} service has been removed.")
40-
raise ValueError(
41-
f"Configured service '{service_name}' not found. "
42-
"Is it installed? Or misspelled?"
43-
) from e
44-
return service.load()
45-
46-
4725
def get_service_instances(conf: "Config") -> list["Service"]:
4826
return [
4927
get_service(service_config.service)(service_config, conf.main)
@@ -116,8 +94,7 @@ def aggregate_issues(
11694
while currently_running > 0:
11795
issue = queue.get(True)
11896
try:
119-
record = TaskConstructor(issue).get_data_to_sync()
120-
yield record
97+
yield TaskConstructor(issue).get_data_to_sync()
12198
except AttributeError:
12299
if isinstance(issue, tuple):
123100
currently_running -= 1
@@ -132,7 +109,7 @@ def aggregate_issues(
132109

133110

134111
def make_unique_identifier(
135-
unique_keys: Iterable[str], taskwarrior_data: TaskwarriorData
112+
unique_keys: Iterable[str], taskwarrior_data: dict[str, Any]
136113
) -> str:
137114
"""For a given issue, make an identifier from its unique keys.
138115

bugwarrior/command.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from lockfile.pidlockfile import PIDLockFile
1313

1414
from bugwarrior.collect import aggregate_issues, get_service
15-
from bugwarrior.config import get_config_path, get_keyring, load_config
15+
from bugwarrior.config.load import get_config_path, load_config
16+
from bugwarrior.config.secrets import get_keyring
1617
from bugwarrior.db import get_defined_udas_as_strings, synchronize
1718

1819
if TYPE_CHECKING:

bugwarrior/config/__init__.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,7 @@
44
"""
55

66
from .data import BugwarriorData
7-
from .load import BUGWARRIORRC, get_config_path, load_config # noqa: F401
8-
from .schema import (
9-
ConfigList, # noqa: F401
10-
ExpandedPath, # noqa: F401
11-
MainSectionConfig,
12-
NoSchemeUrl, # noqa: F401
13-
Priority, # noqa: F401
14-
ServiceConfig,
15-
StrippedTrailingSlashUrl, # noqa: F401
16-
TaskrcPath, # noqa: F401
17-
UnsupportedOption, # noqa: F401
18-
)
19-
from .secrets import get_keyring # noqa: F401
7+
from .schema import MainSectionConfig, ServiceConfig
208

219
# NOTE: __all__ determines the stable, public API.
2210
__all__ = [BugwarriorData.__name__, MainSectionConfig.__name__, ServiceConfig.__name__]

bugwarrior/config/validation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import Field, TypeAdapter, ValidationError
66
from pydantic_core import ErrorDetails
77

8-
from bugwarrior.collect import get_service
8+
from bugwarrior.services import get_service
99

1010
from .schema import BaseConfig, Hooks, MainSectionConfig, Notifications, ServiceConfig
1111

bugwarrior/db.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
from taskw import TaskWarriorShellout
99
from taskw.exceptions import TaskwarriorError
1010

11-
from bugwarrior.collect import get_service
1211
from bugwarrior.notifications import send_notification
13-
from bugwarrior.types import CollectedIssue, CollectionErrorData
12+
from bugwarrior.services import CollectedIssue, CollectionErrorData, get_service
1413

1514
if TYPE_CHECKING:
1615
from bugwarrior.config.schema import MainSectionConfig

bugwarrior/services/__init__.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@
66
import abc
77
from collections.abc import Iterable, Iterator
88
import datetime
9+
from functools import cache
10+
from importlib.metadata import entry_points
911
import logging
1012
import math
1113
import os
1214
import re
13-
from typing import Any, Generic, Optional, TypeVar
15+
from typing import Any, Generic, NamedTuple, Optional, TypeVar
1416
import zoneinfo
1517

1618
from dateutil.parser import parse as parse_date
1719
import dogpile.cache
1820
from jinja2 import Template
1921
import requests
2022

21-
from bugwarrior.config import schema, secrets
22-
from bugwarrior.types import TaskwarriorData
23+
from bugwarrior.config.schema import MainSectionConfig, Priority, ServiceConfig
24+
from bugwarrior.config.secrets import get_service_password
2325

2426
log = logging.getLogger(__name__)
2527

@@ -41,6 +43,17 @@
4143
LATEST_API_VERSION = 1.0
4244

4345

46+
class CollectedIssue(NamedTuple):
47+
taskwarrior_data: dict[str, Any]
48+
target: str
49+
identifier: str
50+
51+
52+
class CollectionErrorData(NamedTuple):
53+
error_message: str
54+
target: str
55+
56+
4457
class URLShortener:
4558
_instance = None
4659

@@ -57,7 +70,7 @@ def shorten(self, url: str) -> str:
5770
return requests.get(base, params=dict(url=url)).text.strip()
5871

5972

60-
def get_processed_url(main_config: schema.MainSectionConfig, url: str) -> str:
73+
def get_processed_url(main_config: MainSectionConfig, url: str) -> str:
6174
"""Returns a URL with conditional processing.
6275
6376
If the following config key are set:
@@ -107,22 +120,22 @@ class Issue(abc.ABC):
107120
def __init__(
108121
self,
109122
foreign_record: dict[str, Any],
110-
config: schema.ServiceConfig,
111-
main_config: schema.MainSectionConfig,
123+
config: ServiceConfig,
124+
main_config: MainSectionConfig,
112125
extra: dict[str, Any],
113126
) -> None:
114127
#: Data retrieved from the external service.
115128
self.record = foreign_record
116129
#: An object whose attributes are this service's configuration values.
117-
self.config: schema.ServiceConfig = config
130+
self.config = config
118131
#: An object whose attributes are the
119132
#: :ref:`common_configuration:Main Section` configuration values.
120-
self.main_config: schema.MainSectionConfig = main_config
133+
self.main_config = main_config
121134
#: Data computed by the :class:`Service` class.
122135
self.extra = extra
123136

124137
@abc.abstractmethod
125-
def to_taskwarrior(self) -> TaskwarriorData:
138+
def to_taskwarrior(self) -> dict[str, Any]:
126139
"""Transform a foreign record into a taskwarrior dictionary."""
127140
raise NotImplementedError()
128141

@@ -167,7 +180,7 @@ def get_tags_from_labels(
167180

168181
return tags
169182

170-
def get_priority(self) -> schema.Priority:
183+
def get_priority(self) -> Priority:
171184
"""Return the priority of this issue, falling back to ``default_priority`` configuration."""
172185
return self.PRIORITY_MAP.get(
173186
self.record.get('priority'), self.config.default_priority
@@ -253,11 +266,9 @@ class Service(abc.ABC, Generic[T_Issue]):
253266
#: Which class should this service instantiate for holding these issues?
254267
ISSUE_CLASS: type[T_Issue]
255268
#: Which class defines this service's configuration options?
256-
CONFIG_SCHEMA: type[schema.ServiceConfig]
269+
CONFIG_SCHEMA: type[ServiceConfig]
257270

258-
def __init__(
259-
self, config: schema.ServiceConfig, main_config: schema.MainSectionConfig
260-
) -> None:
271+
def __init__(self, config: ServiceConfig, main_config: MainSectionConfig) -> None:
261272
over_version = math.floor(LATEST_API_VERSION) + 1
262273
if self.API_VERSION >= over_version:
263274
raise ValueError(
@@ -286,9 +297,7 @@ def get_secret(self, key: str, login: str = 'nousername') -> str:
286297
password = getattr(self.config, key)
287298
keyring_service = self.get_keyring_service(self.config)
288299
if not password or password.startswith("@oracle:"):
289-
password = secrets.get_service_password(
290-
keyring_service, login, oracle=password
291-
)
300+
password = get_service_password(keyring_service, login, oracle=password)
292301
return password
293302

294303
def get_issue_for_record(
@@ -362,7 +371,7 @@ def issues(self) -> Iterator[T_Issue]:
362371

363372
@staticmethod
364373
@abc.abstractmethod
365-
def get_keyring_service(config: schema.ServiceConfig) -> str:
374+
def get_keyring_service(config: ServiceConfig) -> str:
366375
"""Return the keyring name for this service."""
367376
raise NotImplementedError
368377

@@ -386,5 +395,25 @@ def json_response(response: requests.Response) -> Any:
386395
return response.json()
387396

388397

398+
@cache
399+
def get_service(service_name: str) -> type["Service"]:
400+
try:
401+
(service,) = entry_points(group='bugwarrior.service', name=service_name)
402+
except ValueError as e:
403+
if service_name in [
404+
'activecollab',
405+
'activecollab2',
406+
'megaplan',
407+
'teamlab',
408+
'versionone',
409+
]:
410+
log.warning(f"The {service_name} service has been removed.")
411+
raise ValueError(
412+
f"Configured service '{service_name}' not found. "
413+
"Is it installed? Or misspelled?"
414+
) from e
415+
return service.load()
416+
417+
389418
# NOTE: __all__ determines the stable, public API.
390419
__all__ = [Client.__name__, Issue.__name__, Service.__name__]

bugwarrior/services/azuredevops.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,21 @@
88
from pydantic import BeforeValidator
99
import requests
1010

11-
from bugwarrior import config
11+
from bugwarrior.config import schema
1212
from bugwarrior.services import Client, Issue, Service
1313

1414
log = logging.getLogger(__name__)
1515

1616
EscapedStr = Annotated[str, BeforeValidator(quote)]
1717

1818

19-
class AzureDevopsConfig(config.ServiceConfig):
19+
class AzureDevopsConfig(schema.ServiceConfig):
2020
service: Literal['azuredevops']
2121
PAT: str
2222
project: EscapedStr
2323
organization: EscapedStr
2424

25-
host: config.NoSchemeUrl = 'dev.azure.com'
25+
host: schema.NoSchemeUrl = 'dev.azure.com'
2626
wiql_filter: str = ''
2727

2828

@@ -135,9 +135,9 @@ class AzureDevopsIssue(Issue):
135135
}
136136
UNIQUE_KEY = (URL,)
137137

138-
PRIORITY_MAP: dict[str, config.Priority] = {"1": "H", "2": "M", "3": "L", "4": "L"}
138+
PRIORITY_MAP: dict[str, schema.Priority] = {"1": "H", "2": "M", "3": "L", "4": "L"}
139139

140-
def get_priority(self) -> config.Priority:
140+
def get_priority(self) -> schema.Priority:
141141
value = self.record["fields"].get(
142142
"Microsoft.VSTS.Common.Priority", self.config.default_priority
143143
)
@@ -188,7 +188,7 @@ class AzureDevopsService(Service[AzureDevopsIssue]):
188188
CONFIG_SCHEMA = AzureDevopsConfig
189189

190190
def __init__(
191-
self, config: AzureDevopsConfig, main_config: config.MainSectionConfig
191+
self, config: AzureDevopsConfig, main_config: schema.MainSectionConfig
192192
) -> None:
193193
super().__init__(config, main_config)
194194
self.client = AzureDevopsClient(

bugwarrior/services/bitbucket.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
from pydantic import model_validator
55
import requests
66

7-
from bugwarrior import config
7+
from bugwarrior.config import schema
88
from bugwarrior.services import Client, Issue, Service
99

1010
log = logging.getLogger(__name__)
1111

1212

13-
class BitbucketConfig(config.ServiceConfig):
13+
class BitbucketConfig(schema.ServiceConfig):
1414
_DEPRECATE_FILTER_MERGE_REQUESTS = True
1515
filter_merge_requests: Union[bool, Literal['Undefined']] = 'Undefined'
1616

@@ -24,8 +24,8 @@ class BitbucketConfig(config.ServiceConfig):
2424
key: str
2525
secret: str
2626

27-
include_repos: config.ConfigList = []
28-
exclude_repos: config.ConfigList = []
27+
include_repos: schema.ConfigList = []
28+
exclude_repos: schema.ConfigList = []
2929
include_merge_requests: Union[bool, Literal['Undefined']] = 'Undefined'
3030
project_owner_prefix: bool = False
3131

@@ -88,7 +88,7 @@ class BitbucketService(Service[BitbucketIssue]):
8888
BASE_URL = 'https://bitbucket.org/'
8989

9090
def __init__(
91-
self, config: BitbucketConfig, main_config: config.MainSectionConfig
91+
self, config: BitbucketConfig, main_config: schema.MainSectionConfig
9292
) -> None:
9393
super().__init__(config, main_config)
9494

0 commit comments

Comments
 (0)