Skip to content

Commit 5db0e8a

Browse files
authored
Merge branch 'main' into chore/invalid_status
2 parents d0ce202 + 261c441 commit 5db0e8a

12 files changed

+75
-64
lines changed

ops/charm.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ class RelationCreatedEvent(RelationEvent):
466466
can occur before units for those applications have started. All existing
467467
relations should be established before start.
468468
"""
469-
unit: None
469+
unit: None # pyright: ignore[reportIncompatibleVariableOverride]
470470
"""Always ``None``."""
471471

472472

@@ -481,7 +481,7 @@ class RelationJoinedEvent(RelationEvent):
481481
remote ``private-address`` setting, which is always available when
482482
the relation is created and is by convention not deleted.
483483
"""
484-
unit: model.Unit
484+
unit: model.Unit # pyright: ignore[reportIncompatibleVariableOverride]
485485
"""The remote unit that has triggered this event."""
486486

487487

@@ -523,7 +523,7 @@ class RelationDepartedEvent(RelationEvent):
523523
Once all callback methods bound to this event have been run for such a
524524
relation, the unit agent will fire the :class:`RelationBrokenEvent`.
525525
"""
526-
unit: model.Unit
526+
unit: model.Unit # pyright: ignore[reportIncompatibleVariableOverride]
527527
"""The remote unit that has triggered this event."""
528528

529529
def __init__(self, handle: 'Handle', relation: 'model.Relation',
@@ -580,7 +580,7 @@ class RelationBrokenEvent(RelationEvent):
580580
bound to this event is being executed, it is guaranteed that no remote units
581581
are currently known locally.
582582
"""
583-
unit: None
583+
unit: None # pyright: ignore[reportIncompatibleVariableOverride]
584584
"""Always ``None``."""
585585

586586

ops/model.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,17 +277,18 @@ def get_secret(self, *, id: Optional[str] = None, label: Optional[str] = None) -
277277
return Secret(self._backend, id=id, label=label, content=content)
278278

279279

280+
if typing.TYPE_CHECKING:
281+
# (entity type, name): instance.
282+
_WeakCacheType = weakref.WeakValueDictionary[
283+
Tuple['UnitOrApplicationType', str],
284+
Optional[Union['Unit', 'Application']]]
285+
286+
280287
class _ModelCache:
281288
def __init__(self, meta: 'ops.charm.CharmMeta', backend: '_ModelBackend'):
282-
if typing.TYPE_CHECKING:
283-
# (entity type, name): instance.
284-
_weakcachetype = weakref.WeakValueDictionary[
285-
Tuple['UnitOrApplicationType', str],
286-
Optional[Union['Unit', 'Application']]]
287-
288289
self._meta = meta
289290
self._backend = backend
290-
self._weakrefs: _weakcachetype = weakref.WeakValueDictionary()
291+
self._weakrefs: _WeakCacheType = weakref.WeakValueDictionary()
291292

292293
@typing.overload
293294
def get(self, entity_type: Type['Unit'], name: str) -> 'Unit': ... # noqa

ops/pebble.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,7 +1614,7 @@ def _websocket_to_writer(ws: '_WebSocket', writer: '_WebsocketWriter',
16141614
break
16151615

16161616
if encoding is not None:
1617-
chunk = chunk.decode(encoding)
1617+
chunk = typing.cast(bytes, chunk).decode(encoding)
16181618
writer.write(chunk)
16191619

16201620

@@ -2019,7 +2019,7 @@ def _wait_change_using_wait(self, change_id: ChangeID, timeout: Optional[float])
20192019

20202020
def _wait_change(self, change_id: ChangeID, timeout: Optional[float] = None) -> Change:
20212021
"""Call the wait-change API endpoint directly."""
2022-
query = {}
2022+
query: Dict[str, Any] = {}
20232023
if timeout is not None:
20242024
query['timeout'] = _format_timeout(timeout)
20252025

@@ -2255,7 +2255,7 @@ def _encode_multipart(self, metadata: Dict[str, Any], path: str,
22552255
elif isinstance(source, bytes):
22562256
source_io: _AnyStrFileLikeIO = io.BytesIO(source)
22572257
else:
2258-
source_io: _AnyStrFileLikeIO = source
2258+
source_io: _AnyStrFileLikeIO = source # type: ignore
22592259
boundary = binascii.hexlify(os.urandom(16))
22602260
path_escaped = path.replace('"', '\\"').encode('utf-8') # NOQA: test_quote_backslashes
22612261
content_type = f"multipart/form-data; boundary=\"{boundary.decode('utf-8')}\"" # NOQA: test_quote_backslashes
@@ -2736,7 +2736,7 @@ def get_checks(
27362736
Returns:
27372737
List of :class:`CheckInfo` objects.
27382738
"""
2739-
query = {}
2739+
query: Dict[str, Any] = {}
27402740
if level is not None:
27412741
query['level'] = level.value
27422742
if names:

ops/storage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import subprocess
2323
from datetime import timedelta
2424
from pathlib import Path
25-
from typing import Any, Callable, Generator, List, Optional, Tuple, Union
25+
from typing import Any, Callable, Generator, List, Optional, Tuple, Union, cast
2626

2727
import yaml # pyright: ignore[reportMissingModuleSource]
2828

@@ -205,7 +205,7 @@ def notices(self, event_path: Optional[str] = None) -> '_NoticeGenerator':
205205
if not rows:
206206
break
207207
for row in rows:
208-
yield tuple(row)
208+
yield cast(_Notice, tuple(row))
209209

210210

211211
class JujuStorage:

ops/testing.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import shutil
2929
import signal
3030
import tempfile
31+
import typing
3132
import uuid
3233
import warnings
3334
from contextlib import contextmanager
@@ -3020,7 +3021,7 @@ def push(
30203021
file_path.write_bytes(source)
30213022
else:
30223023
# If source is binary, open file in binary mode and ignore encoding param
3023-
is_binary = isinstance(source.read(0), bytes)
3024+
is_binary = isinstance(source.read(0), bytes) # type: ignore
30243025
open_mode = 'wb' if is_binary else 'w'
30253026
open_encoding = None if is_binary else encoding
30263027
with file_path.open(open_mode, encoding=open_encoding) as f:
@@ -3144,7 +3145,7 @@ def _transform_exec_handler_output(self,
31443145
f"exec handler must return bytes if encoding is None,"
31453146
f"not {data.__class__.__name__}")
31463147
else:
3147-
return io.StringIO(data)
3148+
return io.StringIO(typing.cast(str, data))
31483149

31493150
def exec(
31503151
self,

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ reportMissingModuleSource = false
3838
reportPrivateUsage = false
3939
reportUnnecessaryIsInstance = false
4040
reportUnnecessaryComparison = false
41+
disableBytesTypePromotions = false
4142
stubPath = ""

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ flake8-builtins~=2.1
66
pyproject-flake8~=6.1
77
pep8-naming~=0.13
88
pytest~=7.2
9-
pyright==1.1.317
9+
pyright==1.1.345
1010
pytest-operator~=0.23
1111
coverage[toml]~=7.0
1212
typing_extensions~=4.2

test/test_charm.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ def test_observe_decorated_method(self):
101101
# way we know of to cleanly decorate charm event observers.
102102
events: typing.List[ops.EventBase] = []
103103

104-
def dec(fn: typing.Callable[['MyCharm', ops.EventBase], None] # noqa: F821
105-
) -> typing.Callable[..., None]:
104+
def dec(fn: typing.Any) -> typing.Callable[..., None]:
106105
# simple decorator that appends to the nonlocal
107106
# `events` list all events it receives
108107
@functools.wraps(fn)

test/test_framework.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,31 @@ def _on_event(self, event: ops.EventBase):
887887
'ObjectWithStorage[obj]/on/event[1]']))
888888

889889

890+
MutableTypesTestCase = typing.Tuple[
891+
typing.Callable[[], typing.Any], # Called to get operand A.
892+
typing.Any, # Operand B.
893+
typing.Any, # Expected result.
894+
typing.Callable[[typing.Any, typing.Any], None], # Operation to perform.
895+
typing.Callable[[typing.Any, typing.Any], typing.Any], # Validation to perform.
896+
]
897+
898+
ComparisonOperationsTestCase = typing.Tuple[
899+
typing.Any, # Operand A.
900+
typing.Any, # Operand B.
901+
typing.Callable[[typing.Any, typing.Any], bool], # Operation to test.
902+
bool, # Result of op(A, B).
903+
bool, # Result of op(B, A).
904+
]
905+
906+
SetOperationsTestCase = typing.Tuple[
907+
typing.Set[str], # A set to test an operation against (other_set).
908+
# An operation to test.
909+
typing.Callable[[typing.Set[str], typing.Set[str]], typing.Set[str]],
910+
typing.Set[str], # The expected result of operation(obj._stored.set, other_set).
911+
typing.Set[str], # The expected result of operation(other_set, obj._stored.set).
912+
]
913+
914+
890915
class TestStoredState(BaseTestCase):
891916

892917
def setUp(self):
@@ -1116,14 +1141,7 @@ def test_mutable_types(self):
11161141
# Test and validation functions in a list of tuples.
11171142
# Assignment and keywords like del are not supported in lambdas
11181143
# so functions are used instead.
1119-
test_case = typing.Tuple[
1120-
typing.Callable[[], typing.Any], # Called to get operand A.
1121-
typing.Any, # Operand B.
1122-
typing.Any, # Expected result.
1123-
typing.Callable[[typing.Any, typing.Any], None], # Operation to perform.
1124-
typing.Callable[[typing.Any, typing.Any], typing.Any], # Validation to perform.
1125-
]
1126-
test_operations: typing.List[test_case] = [(
1144+
test_operations: typing.List[MutableTypesTestCase] = [(
11271145
lambda: {},
11281146
None,
11291147
{},
@@ -1336,14 +1354,7 @@ def save_snapshot(self, value: typing.Union[ops.StoredStateData, ops.EventBase])
13361354
framework_copy.close()
13371355

13381356
def test_comparison_operations(self):
1339-
test_case = typing.Tuple[
1340-
typing.Any, # Operand A.
1341-
typing.Any, # Operand B.
1342-
typing.Callable[[typing.Any, typing.Any], bool], # Operation to test.
1343-
bool, # Result of op(A, B).
1344-
bool, # Result of op(B, A).
1345-
]
1346-
test_operations: typing.List[test_case] = [(
1357+
test_operations: typing.List[ComparisonOperationsTestCase] = [(
13471358
{"1"},
13481359
{"1", "2"},
13491360
lambda a, b: a < b,
@@ -1436,14 +1447,7 @@ class SomeObject(ops.Object):
14361447
self.assertEqual(op(b, obj._stored.a), op_ba)
14371448

14381449
def test_set_operations(self):
1439-
test_case = typing.Tuple[
1440-
typing.Set[str], # A set to test an operation against (other_set).
1441-
# An operation to test.
1442-
typing.Callable[[typing.Set[str], typing.Set[str]], typing.Set[str]],
1443-
typing.Set[str], # The expected result of operation(obj._stored.set, other_set).
1444-
typing.Set[str], # The expected result of operation(other_set, obj._stored.set).
1445-
]
1446-
test_operations: typing.List[test_case] = [(
1450+
test_operations: typing.List[SetOperationsTestCase] = [(
14471451
{"1"},
14481452
lambda a, b: a | b,
14491453
{"1", "a", "b"},

test/test_model.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,7 +2378,14 @@ def test_unresolved_ingress_addresses(self):
23782378
self.assertEqual(binding.network.ingress_addresses, ['foo.bar.baz.com'])
23792379

23802380

2381-
_metric_and_label_pair = typing.Tuple[typing.Dict[str, float], typing.Dict[str, str]]
2381+
_MetricAndLabelPair = typing.Tuple[typing.Dict[str, float], typing.Dict[str, str]]
2382+
2383+
2384+
_ValidMetricsTestCase = typing.Tuple[
2385+
typing.Mapping[str, typing.Union[int, float]],
2386+
typing.Mapping[str, str],
2387+
typing.List[typing.List[str]],
2388+
]
23822389

23832390

23842391
class TestModelBackend(unittest.TestCase):
@@ -2851,12 +2858,8 @@ def test_juju_log(self):
28512858
[['juju-log', '--log-level', 'BAR', '--', 'foo']])
28522859

28532860
def test_valid_metrics(self):
2854-
_caselist = typing.List[typing.Tuple[
2855-
typing.Mapping[str, typing.Union[int, float]],
2856-
typing.Mapping[str, str],
2857-
typing.List[typing.List[str]]]]
28582861
fake_script(self, 'add-metric', 'exit 0')
2859-
test_cases: _caselist = [(
2862+
test_cases: typing.List[_ValidMetricsTestCase] = [(
28602863
OrderedDict([('foo', 42), ('b-ar', 4.5), ('ba_-z', 4.5), ('a', 1)]),
28612864
OrderedDict([('de', 'ad'), ('be', 'ef_ -')]),
28622865
[['add-metric', '--labels', 'de=ad,be=ef_ -',
@@ -2871,7 +2874,7 @@ def test_valid_metrics(self):
28712874
self.assertEqual(fake_script_calls(self, clear=True), expected_calls)
28722875

28732876
def test_invalid_metric_names(self):
2874-
invalid_inputs: typing.List[_metric_and_label_pair] = [
2877+
invalid_inputs: typing.List[_MetricAndLabelPair] = [
28752878
({'': 4.2}, {}),
28762879
({'1': 4.2}, {}),
28772880
({'1': -4.2}, {}),
@@ -2890,7 +2893,7 @@ def test_invalid_metric_names(self):
28902893
self.backend.add_metrics(metrics, labels)
28912894

28922895
def test_invalid_metric_values(self):
2893-
invalid_inputs: typing.List[_metric_and_label_pair] = [
2896+
invalid_inputs: typing.List[_MetricAndLabelPair] = [
28942897
({'a': float('+inf')}, {}),
28952898
({'a': float('-inf')}, {}),
28962899
({'a': float('nan')}, {}),
@@ -2902,7 +2905,7 @@ def test_invalid_metric_values(self):
29022905
self.backend.add_metrics(metrics, labels)
29032906

29042907
def test_invalid_metric_labels(self):
2905-
invalid_inputs: typing.List[_metric_and_label_pair] = [
2908+
invalid_inputs: typing.List[_MetricAndLabelPair] = [
29062909
({'foo': 4.2}, {'': 'baz'}),
29072910
({'foo': 4.2}, {',bar': 'baz'}),
29082911
({'foo': 4.2}, {'b=a=r': 'baz'}),
@@ -2913,7 +2916,7 @@ def test_invalid_metric_labels(self):
29132916
self.backend.add_metrics(metrics, labels)
29142917

29152918
def test_invalid_metric_label_values(self):
2916-
invalid_inputs: typing.List[_metric_and_label_pair] = [
2919+
invalid_inputs: typing.List[_MetricAndLabelPair] = [
29172920
({'foo': 4.2}, {'bar': ''}),
29182921
({'foo': 4.2}, {'bar': 'b,az'}),
29192922
({'foo': 4.2}, {'bar': 'b=az'}),

test/test_pebble.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,7 +2425,7 @@ def _parse_write_multipart(self,
24252425
for part in message.walk():
24262426
name = part.get_param('name', header='Content-Disposition')
24272427
if name == 'request':
2428-
req = json.loads(part.get_payload())
2428+
req = json.loads(typing.cast(str, part.get_payload()))
24292429
elif name == 'files':
24302430
# decode=True, ironically, avoids decoding bytes to str
24312431
content = part.get_payload(decode=True)
@@ -3092,10 +3092,11 @@ def test_wait_exit_nonzero(self):
30923092
process = self.client.exec(['false'])
30933093
with self.assertRaises(pebble.ExecError) as cm:
30943094
process.wait()
3095-
self.assertEqual(cm.exception.command, ['false'])
3096-
self.assertEqual(cm.exception.exit_code, 1)
3097-
self.assertEqual(cm.exception.stdout, None)
3098-
self.assertEqual(cm.exception.stderr, None)
3095+
exc = typing.cast(pebble.ExecError[str], cm.exception)
3096+
self.assertEqual(exc.command, ['false'])
3097+
self.assertEqual(exc.exit_code, 1)
3098+
self.assertIsNone(exc.stdout)
3099+
self.assertIsNone(exc.stderr)
30993100

31003101
self.assertEqual(self.client.requests, [
31013102
('POST', '/v1/exec', None, self.build_exec_data(['false'])),

test/test_real_pebble.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ def test_exec_wait_output(self):
172172
with self.assertRaises(pebble.ExecError) as cm:
173173
process = self.client.exec(['/bin/sh', '-c', 'echo OUT; echo ERR >&2; exit 42'])
174174
process.wait_output()
175-
self.assertEqual(cm.exception.exit_code, 42)
176-
self.assertEqual(cm.exception.stdout, 'OUT\n')
177-
self.assertEqual(cm.exception.stderr, 'ERR\n')
175+
exc = typing.cast(pebble.ExecError[str], cm.exception)
176+
self.assertEqual(exc.exit_code, 42)
177+
self.assertEqual(exc.stdout, 'OUT\n')
178+
self.assertEqual(exc.stderr, 'ERR\n')
178179

179180
def test_exec_send_stdin(self):
180181
process = self.client.exec(['awk', '{ print toupper($0) }'], stdin='foo\nBar\n')

0 commit comments

Comments
 (0)