Skip to content

Commit 0b340c6

Browse files
Bump pydantic from 1.10.12 to 2.1.1 (#640)
* Bump pydantic from 1.10.12 to 2.1.1 Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.12 to 2.1.1. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](pydantic/pydantic@v1.10.12...v2.1.1) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> * Add pydantic-settings * Update model.dict() -> model.model_dump() * Various Pydantic v1 -> v2 changes * Remove dotenv extra from pydantic This is provided by pydantic-settings now --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Graham Beckley <[email protected]>
1 parent 1c3b58a commit 0b340c6

File tree

16 files changed

+274
-167
lines changed

16 files changed

+274
-167
lines changed

jbi/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def traces_sampler(sampling_context: dict[str, Any]) -> float:
3232

3333

3434
sentry_sdk.init(
35-
dsn=settings.sentry_dsn,
35+
dsn=str(settings.sentry_dsn) if settings.sentry_dsn else None,
3636
traces_sampler=traces_sampler,
3737
)
3838

jbi/environment.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from pathlib import Path
88
from typing import Optional
99

10-
from pydantic import AnyUrl, BaseSettings
10+
from pydantic import AnyUrl
11+
from pydantic_settings import BaseSettings, SettingsConfigDict
1112

1213

1314
class Environment(str, Enum):
@@ -50,14 +51,10 @@ class Settings(BaseSettings):
5051
log_format: str = "json" # set to "text" for human-readable logs
5152

5253
# Sentry
53-
sentry_dsn: Optional[SentryDsn]
54+
sentry_dsn: Optional[SentryDsn] = None
5455
sentry_traces_sample_rate: float = 1.0
5556

56-
class Config:
57-
"""Pydantic configuration"""
58-
59-
env_file = ".env"
60-
env_file_encoding = "utf-8"
57+
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
6158

6259

6360
@lru_cache(maxsize=1)

jbi/log.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import time
88
from datetime import datetime
9+
from typing import Optional
910

1011
from fastapi import Request
1112
from pydantic import BaseModel
@@ -56,6 +57,7 @@ class RequestSummary(BaseModel):
5657
agent: str
5758
path: str
5859
method: str
60+
lang: Optional[str]
5961
querystring: str
6062
errno: int
6163
t: int
@@ -81,4 +83,4 @@ def format_request_summary_fields(
8183
time=datetime.fromtimestamp(current_time).isoformat(),
8284
status_code=status_code,
8385
rid=request.state.rid,
84-
).dict()
86+
).model_dump()

jbi/models.py

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import warnings
99
from collections import defaultdict
1010
from copy import copy
11-
from typing import DefaultDict, Literal, Mapping, Optional, TypedDict
11+
from typing import DefaultDict, Literal, Mapping, Optional
1212
from urllib.parse import ParseResult, urlparse
1313

14-
from pydantic import BaseModel, Extra, Field, validator
14+
from pydantic import BaseModel, ConfigDict, Extra, Field, RootModel, field_validator
15+
from typing_extensions import TypedDict
1516

1617
from jbi import Operation, steps
1718
from jbi.errors import ActionNotFoundError
@@ -40,8 +41,9 @@ class ActionSteps(BaseModel):
4041
"create_comment",
4142
]
4243

43-
@validator("*")
44-
def validate_steps(cls, function_names): # pylint: disable=no-self-argument
44+
@field_validator("*")
45+
@classmethod
46+
def validate_steps(cls, function_names: list[str]):
4547
"""Validate that all configure step functions exist in the steps module"""
4648

4749
invalid_functions = [
@@ -69,7 +71,7 @@ class ActionParams(BaseModel):
6971
jira_project_key: str
7072
steps: ActionSteps = ActionSteps()
7173
jira_components: JiraComponents = JiraComponents()
72-
labels_brackets: str = Field("no", enum=["yes", "no", "both"])
74+
labels_brackets: Literal["yes", "no", "both"] = "no"
7375
status_map: dict[str, str] = {}
7476
resolution_map: dict[str, str] = {}
7577
issue_type_map: dict[str, str] = {"task": "Task", "defect": "Bug"}
@@ -91,23 +93,24 @@ def jira_project_key(self):
9193
return self.parameters.jira_project_key
9294

9395

94-
class Actions(BaseModel):
96+
class Actions(RootModel):
9597
"""
9698
Actions is the container model for the list of actions in the configuration file
9799
"""
98100

99-
__root__: list[Action] = Field(..., min_items=1)
101+
root: list[Action] = Field(..., min_length=1)
100102

101103
@functools.cached_property
102104
def by_tag(self) -> Mapping[str, Action]:
103105
"""Build mapping of actions by lookup tag."""
104-
return {action.whiteboard_tag: action for action in self.__root__}
106+
# pylint: disable-next=not-an-iterable
107+
return {action.whiteboard_tag: action for action in self.root}
105108

106109
def __iter__(self):
107-
return iter(self.__root__)
110+
return iter(self.root) # pylint: disable=not-an-iterable
108111

109112
def __len__(self):
110-
return len(self.__root__)
113+
return len(self.root)
111114

112115
def __getitem__(self, item):
113116
return self.by_tag[item]
@@ -119,12 +122,12 @@ def get(self, tag: Optional[str]) -> Optional[Action]:
119122
@functools.cached_property
120123
def configured_jira_projects_keys(self) -> set[str]:
121124
"""Return the list of Jira project keys from all configured actions"""
122-
return {action.jira_project_key for action in self.__root__}
125+
# pylint: disable-next=not-an-iterable
126+
return {action.jira_project_key for action in self.root}
123127

124-
@validator("__root__")
125-
def validate_actions( # pylint: disable=no-self-argument
126-
cls, actions: list[Action]
127-
):
128+
@field_validator("root")
129+
@classmethod
130+
def validate_actions(cls, actions: list[Action]):
128131
"""
129132
Inspect the list of actions:
130133
- Validate that lookup tags are uniques
@@ -143,10 +146,7 @@ def validate_actions( # pylint: disable=no-self-argument
143146

144147
return actions
145148

146-
class Config:
147-
"""Pydantic configuration"""
148-
149-
keep_untouched = (functools.cached_property,)
149+
model_config = ConfigDict(ignored_types=(functools.cached_property,))
150150

151151

152152
class BugzillaWebhookUser(BaseModel):
@@ -169,48 +169,49 @@ class BugzillaWebhookEvent(BaseModel):
169169
"""Bugzilla Event Object"""
170170

171171
action: str
172-
time: Optional[datetime.datetime]
173-
user: Optional[BugzillaWebhookUser]
174-
changes: Optional[list[BugzillaWebhookEventChange]]
175-
target: Optional[str]
176-
routing_key: Optional[str]
172+
time: Optional[datetime.datetime] = None
173+
user: Optional[BugzillaWebhookUser] = None
174+
changes: Optional[list[BugzillaWebhookEventChange]] = None
175+
target: Optional[str] = None
176+
routing_key: Optional[str] = None
177177

178178
def changed_fields(self) -> list[str]:
179179
"""Returns the names of changed fields in a bug"""
180+
# pylint: disable-next=not-an-iterable
180181
return [c.field for c in self.changes] if self.changes else []
181182

182183

183184
class BugzillaWebhookComment(BaseModel):
184185
"""Bugzilla Comment Object"""
185186

186-
body: Optional[str]
187-
id: Optional[int]
188-
number: Optional[int]
189-
is_private: Optional[bool]
190-
creation_time: Optional[datetime.datetime]
187+
body: Optional[str] = None
188+
id: Optional[int] = None
189+
number: Optional[int] = None
190+
is_private: Optional[bool] = None
191+
creation_time: Optional[datetime.datetime] = None
191192

192193

193194
class BugzillaBug(BaseModel):
194195
"""Bugzilla Bug Object"""
195196

196197
id: int
197-
is_private: Optional[bool]
198-
type: Optional[str]
199-
product: Optional[str]
200-
component: Optional[str]
201-
whiteboard: Optional[str]
202-
keywords: Optional[list]
203-
flags: Optional[list]
204-
groups: Optional[list]
205-
status: Optional[str]
206-
resolution: Optional[str]
207-
see_also: Optional[list]
208-
summary: Optional[str]
209-
severity: Optional[str]
210-
priority: Optional[str]
211-
creator: Optional[str]
212-
assigned_to: Optional[str]
213-
comment: Optional[BugzillaWebhookComment]
198+
is_private: Optional[bool] = None
199+
type: Optional[str] = None
200+
product: Optional[str] = None
201+
component: Optional[str] = None
202+
whiteboard: Optional[str] = None
203+
keywords: Optional[list] = None
204+
flags: Optional[list] = None
205+
groups: Optional[list] = None
206+
status: Optional[str] = None
207+
resolution: Optional[str] = None
208+
see_also: Optional[list] = None
209+
summary: Optional[str] = None
210+
severity: Optional[str] = None
211+
priority: Optional[str] = None
212+
creator: Optional[str] = None
213+
assigned_to: Optional[str] = None
214+
comment: Optional[BugzillaWebhookComment] = None
214215

215216
@property
216217
def product_component(self) -> str:
@@ -297,8 +298,8 @@ class BugzillaComment(BaseModel):
297298
class BugzillaApiResponse(BaseModel):
298299
"""Bugzilla Response Object"""
299300

300-
faults: Optional[list]
301-
bugs: Optional[list[BugzillaBug]]
301+
faults: Optional[list] = None
302+
bugs: Optional[list[BugzillaBug]] = None
302303

303304

304305
class BugzillaWebhook(BaseModel):
@@ -326,7 +327,7 @@ def slug(self):
326327
class BugzillaWebhooksResponse(BaseModel):
327328
"""Bugzilla Webhooks List Response Object"""
328329

329-
webhooks: Optional[list[BugzillaWebhook]]
330+
webhooks: Optional[list[BugzillaWebhook]] = None
330331

331332

332333
class Context(BaseModel):
@@ -341,8 +342,8 @@ class JiraContext(Context):
341342
"""Logging context about Jira"""
342343

343344
project: str
344-
issue: Optional[str]
345-
labels: Optional[list[str]]
345+
issue: Optional[str] = None
346+
labels: Optional[list[str]] = None
346347

347348

348349
BugId = TypedDict("BugId", {"id": Optional[int]})
@@ -354,7 +355,7 @@ class RunnerContext(Context, extra=Extra.forbid):
354355
rid: str
355356
operation: Operation
356357
event: BugzillaWebhookEvent
357-
action: Optional[Action]
358+
action: Optional[Action] = None
358359
bug: BugId | BugzillaBug
359360

360361

@@ -364,7 +365,7 @@ class ActionContext(Context, extra=Extra.forbid):
364365
action: Action
365366
rid: str
366367
operation: Operation
367-
current_step: Optional[str]
368+
current_step: Optional[str] = None
368369
event: BugzillaWebhookEvent
369370
jira: JiraContext
370371
bug: BugzillaBug

jbi/runner.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def groups2operation(steps: ActionSteps):
4747
try:
4848
by_operation = {
4949
GROUP_TO_OPERATION[entry]: steps_list
50-
for entry, steps_list in steps.dict().items()
50+
for entry, steps_list in steps.model_dump().items()
5151
}
5252
except KeyError as err:
5353
raise ValueError(f"Unsupported entry in `steps`: {err}") from err
@@ -129,7 +129,7 @@ def __call__(self, context: ActionContext) -> ActionResult:
129129
response,
130130
extra={
131131
"response": response,
132-
**context.dict(),
132+
**context.model_dump(),
133133
},
134134
)
135135

@@ -165,12 +165,14 @@ def execute_action(
165165

166166
logger.debug(
167167
"Handling incoming request",
168-
extra=runner_context.dict(),
168+
extra=runner_context.model_dump(),
169169
)
170170
try:
171171
bug = bugzilla.get_service().refresh_bug_data(bug)
172172
except Exception as err:
173-
logger.exception("Failed to get bug: %s", err, extra=runner_context.dict())
173+
logger.exception(
174+
"Failed to get bug: %s", err, extra=runner_context.model_dump()
175+
)
174176
raise IgnoreInvalidRequestError(
175177
"bug not accessible or bugzilla down"
176178
) from err
@@ -195,7 +197,7 @@ def execute_action(
195197
event=event,
196198
operation=Operation.IGNORE,
197199
jira=JiraContext(project=action.jira_project_key, issue=linked_issue_key),
198-
extra={k: str(v) for k, v in action.parameters.dict().items()},
200+
extra={k: str(v) for k, v in action.parameters.model_dump().items()},
199201
)
200202

201203
if action_context.jira.issue is None:
@@ -242,7 +244,7 @@ def execute_action(
242244
"Execute action '%s' for Bug %s",
243245
action.whiteboard_tag,
244246
bug.id,
245-
extra=runner_context.update(operation=Operation.EXECUTE).dict(),
247+
extra=runner_context.update(operation=Operation.EXECUTE).model_dump(),
246248
)
247249
executor = Executor(parameters=action.parameters)
248250
handled, details = executor(context=action_context)
@@ -253,15 +255,15 @@ def execute_action(
253255
bug.id,
254256
extra=runner_context.update(
255257
operation=Operation.SUCCESS if handled else Operation.IGNORE
256-
).dict(),
258+
).model_dump(),
257259
)
258260
statsd.incr("jbi.bugzilla.processed.count")
259261
return details
260262
except IgnoreInvalidRequestError as exception:
261263
logger.info(
262264
"Ignore incoming request: %s",
263265
exception,
264-
extra=runner_context.update(operation=Operation.IGNORE).dict(),
266+
extra=runner_context.update(operation=Operation.IGNORE).model_dump(),
265267
)
266268
statsd.incr("jbi.bugzilla.ignored.count")
267269
raise

jbi/services/bugzilla.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def add_link_to_jira(self, context: ActionContext):
176176
"Link %r on Bug %s",
177177
jira_url,
178178
bug.id,
179-
extra=context.update(operation=Operation.LINK).dict(),
179+
extra=context.update(operation=Operation.LINK).model_dump(),
180180
)
181181
return self.client.update_bug(bug.id, see_also={"add": [jira_url]})
182182

0 commit comments

Comments
 (0)