xai-review 0.22.0__py3-none-any.whl → 0.23.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xai-review might be problematic. Click here for more details.

@@ -116,14 +116,14 @@ def render_unified(
116
116
  added_new_positions = file.added_line_numbers()
117
117
  removed_old_positions = file.removed_line_numbers()
118
118
 
119
- def in_context(old_no: int | None, new_no: int | None) -> bool:
119
+ def in_context(inner_old_no: int | None, inner_new_no: int | None) -> bool:
120
120
  """Check if an unchanged line falls within context radius."""
121
121
  if context <= 0:
122
122
  return False
123
- if include_added and new_no is not None:
123
+ if include_added and inner_new_no is not None:
124
124
  if any(abs(new_no - a) <= context for a in added_new_positions):
125
125
  return True
126
- if include_removed and old_no is not None:
126
+ if include_removed and inner_old_no is not None:
127
127
  if any(abs(old_no - r) <= context for r in removed_old_positions):
128
128
  return True
129
129
  return False
@@ -1,4 +1,4 @@
1
- from pydantic import BaseModel, Field
1
+ from pydantic import BaseModel, Field, field_serializer
2
2
 
3
3
  from ai_review.config import settings
4
4
  from ai_review.libs.template.render import render_template
@@ -24,29 +24,18 @@ class PromptContextSchema(BaseModel):
24
24
  labels: list[str] = Field(default_factory=list)
25
25
  changed_files: list[str] = Field(default_factory=list)
26
26
 
27
- @property
28
- def render_values(self) -> dict[str, str]:
29
- return {
30
- "review_title": self.review_title,
31
- "review_description": self.review_description,
32
-
33
- "review_author_name": self.review_author_name,
34
- "review_author_username": self.review_author_username,
35
-
36
- "review_reviewer": self.review_reviewer,
37
- "review_reviewers": ", ".join(self.review_reviewers),
38
- "review_reviewers_usernames": ", ".join(self.review_reviewers_usernames),
39
-
40
- "review_assignees": ", ".join(self.review_assignees),
41
- "review_assignees_usernames": ", ".join(self.review_assignees_usernames),
42
-
43
- "source_branch": self.source_branch,
44
- "target_branch": self.target_branch,
45
-
46
- "labels": ", ".join(self.labels),
47
- "changed_files": ", ".join(self.changed_files),
48
- }
27
+ @field_serializer(
28
+ "review_reviewers",
29
+ "review_reviewers_usernames",
30
+ "review_assignees",
31
+ "review_assignees_usernames",
32
+ "labels",
33
+ "changed_files",
34
+ when_used="always"
35
+ )
36
+ def list_of_strings_serializer(self, value: list[str]) -> str:
37
+ return ", ".join(value)
49
38
 
50
39
  def apply_format(self, prompt: str) -> str:
51
- values = {**self.render_values, **settings.prompt.context}
40
+ values = {**self.model_dump(), **settings.prompt.context}
52
41
  return render_template(prompt, values, settings.prompt.context_placeholder)
@@ -5,6 +5,7 @@ from ai_review.services.prompt.schema import PromptContextSchema
5
5
 
6
6
 
7
7
  def test_apply_format_inserts_variables() -> None:
8
+ """Ensures simple string fields are correctly substituted into the template."""
8
9
  context = PromptContextSchema(
9
10
  review_title="My Review",
10
11
  review_author_username="nikita"
@@ -15,6 +16,7 @@ def test_apply_format_inserts_variables() -> None:
15
16
 
16
17
 
17
18
  def test_apply_format_with_lists() -> None:
19
+ """Ensures list fields are serialized as CSV strings and substituted into the template."""
18
20
  context = PromptContextSchema(
19
21
  review_reviewers=["Alice", "Bob"],
20
22
  review_reviewers_usernames=["alice", "bob"],
@@ -35,6 +37,7 @@ def test_apply_format_with_lists() -> None:
35
37
 
36
38
 
37
39
  def test_apply_format_handles_missing_fields() -> None:
40
+ """Ensures missing fields are replaced with empty strings."""
38
41
  context = PromptContextSchema()
39
42
  template = "Title: <<review_title>>, Reviewer: <<review_reviewer>>"
40
43
  result = context.apply_format(template)
@@ -42,6 +45,7 @@ def test_apply_format_handles_missing_fields() -> None:
42
45
 
43
46
 
44
47
  def test_apply_format_unknown_placeholder_kept() -> None:
48
+ """Ensures unknown placeholders remain unchanged in the template."""
45
49
  context = PromptContextSchema()
46
50
  template = "Unknown: <<does_not_exist>>"
47
51
  result = context.apply_format(template)
@@ -49,6 +53,7 @@ def test_apply_format_unknown_placeholder_kept() -> None:
49
53
 
50
54
 
51
55
  def test_apply_format_multiple_occurrences() -> None:
56
+ """Ensures multiple occurrences of the same placeholder are all replaced."""
52
57
  context = PromptContextSchema(review_title="My Review")
53
58
  template = "<<review_title>> again <<review_title>>"
54
59
  result = context.apply_format(template)
@@ -56,6 +61,7 @@ def test_apply_format_multiple_occurrences() -> None:
56
61
 
57
62
 
58
63
  def test_apply_format_override_from_settings(monkeypatch: pytest.MonkeyPatch) -> None:
64
+ """Ensures values from settings.prompt.context override local model values."""
59
65
  monkeypatch.setitem(settings.prompt.context, "review_title", "Overridden")
60
66
  context = PromptContextSchema(review_title="Local Value")
61
67
  template = "Title: <<review_title>>"
@@ -64,8 +70,67 @@ def test_apply_format_override_from_settings(monkeypatch: pytest.MonkeyPatch) ->
64
70
 
65
71
 
66
72
  def test_apply_format_prefers_override_even_if_empty(monkeypatch: pytest.MonkeyPatch) -> None:
73
+ """Ensures overrides take precedence even if the override value is empty."""
67
74
  monkeypatch.setitem(settings.prompt.context, "review_title", "")
68
75
  context = PromptContextSchema(review_title="Local Value")
69
76
  template = "Title: <<review_title>>"
70
77
  result = context.apply_format(template)
71
78
  assert result == "Title: "
79
+
80
+
81
+ def test_apply_format_empty_list_serializes_to_empty_string() -> None:
82
+ """Ensures empty lists are serialized to empty strings."""
83
+ context = PromptContextSchema(labels=[])
84
+ template = "Labels: <<labels>>"
85
+ result = context.apply_format(template)
86
+ assert result == "Labels: "
87
+
88
+
89
+ def test_apply_format_single_element_list() -> None:
90
+ """Ensures lists with a single element are serialized without extra separators."""
91
+ context = PromptContextSchema(labels=["bug"])
92
+ template = "Labels: <<labels>>"
93
+ result = context.apply_format(template)
94
+ assert result == "Labels: bug"
95
+
96
+
97
+ def test_apply_format_list_with_spaces() -> None:
98
+ """Ensures list items containing spaces are preserved in serialization."""
99
+ context = PromptContextSchema(labels=[" bug ", " feature "])
100
+ template = "Labels: <<labels>>"
101
+ result = context.apply_format(template)
102
+ assert result == "Labels: bug , feature "
103
+
104
+
105
+ def test_apply_format_placeholder_case_sensitive() -> None:
106
+ """Ensures placeholder matching is case-sensitive."""
107
+ context = PromptContextSchema(review_title="My Review")
108
+ template = "Title: <<Review_Title>>"
109
+ result = context.apply_format(template)
110
+ assert result == "Title: <<Review_Title>>"
111
+
112
+
113
+ def test_apply_format_override_with_none(monkeypatch: pytest.MonkeyPatch) -> None:
114
+ """Ensures None in overrides is treated as an empty string."""
115
+ monkeypatch.setitem(settings.prompt.context, "review_title", None)
116
+ context = PromptContextSchema(review_title="Local Value")
117
+ template = "Title: <<review_title>>"
118
+ result = context.apply_format(template)
119
+ assert result == "Title: "
120
+
121
+
122
+ def test_apply_format_placeholder_inside_word() -> None:
123
+ """Ensures placeholders inside words are still replaced correctly."""
124
+ context = PromptContextSchema(review_title="REV")
125
+ template = "prefix-<<review_title>>-suffix"
126
+ result = context.apply_format(template)
127
+ assert result == "prefix-REV-suffix"
128
+
129
+
130
+ def test_apply_format_large_list() -> None:
131
+ """Ensures large lists are serialized correctly without truncation."""
132
+ context = PromptContextSchema(labels=[str(index) for index in range(100)])
133
+ template = "Labels: <<labels>>"
134
+ result = context.apply_format(template)
135
+ assert result.startswith("Labels: 0, 1, 2")
136
+ assert "99" in result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xai-review
3
- Version: 0.22.0
3
+ Version: 0.23.0
4
4
  Summary: AI-powered code review tool
5
5
  Author-email: Nikita Filonov <nikita.filonov@example.com>
6
6
  Maintainer-email: Nikita Filonov <nikita.filonov@example.com>
@@ -209,7 +209,7 @@ jobs:
209
209
  runs-on: ubuntu-latest
210
210
  steps:
211
211
  - uses: actions/checkout@v4
212
- - uses: Nikita-Filonov/ai-review@v0.22.0
212
+ - uses: Nikita-Filonov/ai-review@v0.23.0
213
213
  with:
214
214
  review-command: ${{ inputs.review-command }}
215
215
  env:
@@ -94,7 +94,7 @@ ai_review/services/cost/schema.py,sha256=K3uCIMMxGL8AaIPh4a-d0mT5uIJuk3f805DkP8o
94
94
  ai_review/services/cost/service.py,sha256=-qbGePoL0oYnEC60Q5gQtd1IH8ucsOiF4349ueZl7Ts,2186
95
95
  ai_review/services/cost/types.py,sha256=VyQiF5uH5T7wYlOqkvxlCOjHnjWRu4CMo8j26hQ2Alo,341
96
96
  ai_review/services/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
- ai_review/services/diff/renderers.py,sha256=tEUml-uqsi5FNoU2NYjxehsZHU61dTPR2VnFi7QVzV4,5861
97
+ ai_review/services/diff/renderers.py,sha256=Z5_ViB9KJu0o3k8TBtymbUqLv9QATt1gbqT2hrysJMQ,5885
98
98
  ai_review/services/diff/schema.py,sha256=17GAQY1-ySwREJ1-NKNKgBcstMJ5Hb42FcFG2p7i6Rs,94
99
99
  ai_review/services/diff/service.py,sha256=yRb4e0fZcgFTGkAZKm5q8Gw4rWxc3nyFtpBw7ahlnw8,3581
100
100
  ai_review/services/diff/tools.py,sha256=YHmH6Ult_rucCd563UhG0geMzqrPhqKFZKyug79xNuA,1963
@@ -117,7 +117,7 @@ ai_review/services/llm/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
117
117
  ai_review/services/llm/openai/client.py,sha256=c3DWwLnwTheERdSGnMiQIbg5SaICouUAGClcQZSh1fE,1159
118
118
  ai_review/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
119
  ai_review/services/prompt/adapter.py,sha256=YgD8Cf73pwPOnKxq9QSlbYr8wySfDxJE_3fIrRWfkUo,984
120
- ai_review/services/prompt/schema.py,sha256=x-1KRFOK3HdZozXJNPh-Bp_JrZ2AIdAPD44lhWG5g9k,1863
120
+ ai_review/services/prompt/schema.py,sha256=ttla_lkvBQ-jfaZXzWSAAUrJROYRsozHHd5IoZc59zA,1324
121
121
  ai_review/services/prompt/service.py,sha256=58OJ6nIPSgXQyAqUXkWAXYAbNz7vMRemtllvD7bvQv0,2218
122
122
  ai_review/services/prompt/tools.py,sha256=-gS74mVM3OZPKWQkY9_QfStkfxaUfssDbJ3Bdi4AQ74,636
123
123
  ai_review/services/prompt/types.py,sha256=uVcvW8ZuwmM02MjCmw6Rg-IW5pIT3MeEYl0Vl-jzV4M,913
@@ -203,7 +203,7 @@ ai_review/tests/suites/services/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
203
203
  ai_review/tests/suites/services/llm/test_factory.py,sha256=_i_UFtG_WGT3jpBDm20Hb0rFTFrfPuiFJhhSrlvUlVQ,1120
204
204
  ai_review/tests/suites/services/prompt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
205
  ai_review/tests/suites/services/prompt/test_adapter.py,sha256=9KZOFQmZUs3l_cW7Q5LIMPs4i4J-gOCQ6VrlDPR0ImU,2156
206
- ai_review/tests/suites/services/prompt/test_schema.py,sha256=DQyv5gUJ2VkxaD9wiKLS18ECopvvdKvF4sg3MTGcKs8,2547
206
+ ai_review/tests/suites/services/prompt/test_schema.py,sha256=rm2__LA2_4qQwSmNAZ_Wnpy11T3yYRkYUkRUrqxUQKE,5421
207
207
  ai_review/tests/suites/services/prompt/test_service.py,sha256=WXYKwDHMmWD6ew1awiEzmoxEJtQBqxvOgiyK8Ii9Mhw,6755
208
208
  ai_review/tests/suites/services/prompt/test_tools.py,sha256=_yNZoBATvPU5enWNIopbjY8lVVjfaB_46kNIKODhCW4,1981
209
209
  ai_review/tests/suites/services/review/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -222,9 +222,9 @@ ai_review/tests/suites/services/vcs/github/__init__.py,sha256=47DEQpj8HBSa-_TImW
222
222
  ai_review/tests/suites/services/vcs/github/test_service.py,sha256=c2sjecm4qzqYXuO9j6j35NQyJzqDpnXIJImRTcpkyHo,4378
223
223
  ai_review/tests/suites/services/vcs/gitlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
224
224
  ai_review/tests/suites/services/vcs/gitlab/test_service.py,sha256=0dqgL5whzjcP-AQ4adP_12QfkYm_ZtdtMotmYm8Se7Y,4449
225
- xai_review-0.22.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
226
- xai_review-0.22.0.dist-info/METADATA,sha256=EiToDxby1doaJtTzkJKs23egzQ2umboze9sYLcPTGgg,10872
227
- xai_review-0.22.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
228
- xai_review-0.22.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
229
- xai_review-0.22.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
230
- xai_review-0.22.0.dist-info/RECORD,,
225
+ xai_review-0.23.0.dist-info/licenses/LICENSE,sha256=p-v8m7Kmz4KKc7PcvsGiGEmCw9AiSXY4_ylOPy_u--Y,11343
226
+ xai_review-0.23.0.dist-info/METADATA,sha256=6GjvVteV4O_MPSSgTB4dNhlbS7iNKlGO5Uei0Y6JAuQ,10872
227
+ xai_review-0.23.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
228
+ xai_review-0.23.0.dist-info/entry_points.txt,sha256=JyC5URanMi5io5P_PXQf7H_I1OGIpk5cZQhaPQ0g4Zs,53
229
+ xai_review-0.23.0.dist-info/top_level.txt,sha256=sTsZbfzLoqvRZKdKa-BcxWvjlHdrpbeJ6DrGY0EuR0E,10
230
+ xai_review-0.23.0.dist-info/RECORD,,