cognite-extractor-utils 7.5.13__py3-none-any.whl → 7.6.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 cognite-extractor-utils might be problematic. Click here for more details.

Files changed (47) hide show
  1. cognite/extractorutils/__init__.py +1 -1
  2. cognite/extractorutils/_inner_util.py +1 -1
  3. cognite/extractorutils/base.py +120 -40
  4. cognite/extractorutils/configtools/__init__.py +4 -5
  5. cognite/extractorutils/configtools/_util.py +3 -2
  6. cognite/extractorutils/configtools/elements.py +213 -35
  7. cognite/extractorutils/configtools/loaders.py +68 -16
  8. cognite/extractorutils/configtools/validators.py +5 -1
  9. cognite/extractorutils/exceptions.py +11 -2
  10. cognite/extractorutils/metrics.py +17 -12
  11. cognite/extractorutils/statestore/__init__.py +77 -3
  12. cognite/extractorutils/statestore/_base.py +7 -3
  13. cognite/extractorutils/statestore/hashing.py +129 -15
  14. cognite/extractorutils/statestore/watermark.py +77 -87
  15. cognite/extractorutils/threading.py +30 -4
  16. cognite/extractorutils/unstable/__init__.py +5 -5
  17. cognite/extractorutils/unstable/configuration/__init__.py +3 -0
  18. cognite/extractorutils/unstable/configuration/exceptions.py +13 -2
  19. cognite/extractorutils/unstable/configuration/loaders.py +90 -19
  20. cognite/extractorutils/unstable/configuration/models.py +121 -7
  21. cognite/extractorutils/unstable/core/__init__.py +5 -0
  22. cognite/extractorutils/unstable/core/_dto.py +5 -3
  23. cognite/extractorutils/unstable/core/base.py +113 -4
  24. cognite/extractorutils/unstable/core/errors.py +41 -0
  25. cognite/extractorutils/unstable/core/logger.py +149 -0
  26. cognite/extractorutils/unstable/core/restart_policy.py +16 -2
  27. cognite/extractorutils/unstable/core/runtime.py +119 -36
  28. cognite/extractorutils/unstable/core/tasks.py +53 -1
  29. cognite/extractorutils/unstable/scheduling/__init__.py +13 -0
  30. cognite/extractorutils/unstable/scheduling/_scheduler.py +1 -1
  31. cognite/extractorutils/uploader/__init__.py +7 -5
  32. cognite/extractorutils/uploader/_base.py +4 -5
  33. cognite/extractorutils/uploader/assets.py +13 -8
  34. cognite/extractorutils/uploader/data_modeling.py +37 -2
  35. cognite/extractorutils/uploader/events.py +14 -9
  36. cognite/extractorutils/uploader/files.py +80 -21
  37. cognite/extractorutils/uploader/raw.py +12 -7
  38. cognite/extractorutils/uploader/time_series.py +58 -49
  39. cognite/extractorutils/uploader/upload_failure_handler.py +35 -2
  40. cognite/extractorutils/uploader_extractor.py +29 -6
  41. cognite/extractorutils/uploader_types.py +15 -1
  42. cognite/extractorutils/util.py +76 -23
  43. {cognite_extractor_utils-7.5.13.dist-info → cognite_extractor_utils-7.6.0.dist-info}/METADATA +1 -1
  44. cognite_extractor_utils-7.6.0.dist-info/RECORD +50 -0
  45. cognite_extractor_utils-7.5.13.dist-info/RECORD +0 -50
  46. {cognite_extractor_utils-7.5.13.dist-info → cognite_extractor_utils-7.6.0.dist-info}/WHEEL +0 -0
  47. {cognite_extractor_utils-7.5.13.dist-info → cognite_extractor_utils-7.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,7 @@
1
+ """
2
+ Module containing pre-built elements for common extractor configuration.
3
+ """
4
+
1
5
  # Copyright 2023 Cognite AS
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +22,7 @@ from dataclasses import dataclass, field
18
22
  from datetime import timedelta
19
23
  from enum import Enum
20
24
  from logging.handlers import TimedRotatingFileHandler
25
+ from pathlib import Path
21
26
  from time import sleep
22
27
  from typing import Any
23
28
  from urllib.parse import urljoin, urlparse
@@ -55,7 +60,7 @@ _logger = logging.getLogger(__name__)
55
60
  @dataclass
56
61
  class CertificateConfig:
57
62
  """
58
- Configuration parameters for certificates
63
+ Configuration parameters for certificates.
59
64
  """
60
65
 
61
66
  path: str
@@ -66,7 +71,7 @@ class CertificateConfig:
66
71
  @dataclass
67
72
  class AuthenticatorConfig:
68
73
  """
69
- Configuration parameters for an OIDC flow
74
+ Configuration parameters for an OIDC flow.
70
75
  """
71
76
 
72
77
  client_id: str
@@ -84,7 +89,7 @@ class AuthenticatorConfig:
84
89
  @dataclass
85
90
  class ConnectionConfig:
86
91
  """
87
- Configuration parameters for the global_config python SDK settings
92
+ Configuration parameters for the global_config python SDK settings.
88
93
  """
89
94
 
90
95
  disable_gzip: bool = False
@@ -101,6 +106,7 @@ class ConnectionConfig:
101
106
  class EitherIdConfig:
102
107
  """
103
108
  Configuration parameter representing an ID in CDF, which can either be an external or internal ID.
109
+
104
110
  An EitherId can only hold one ID type, not both.
105
111
  """
106
112
 
@@ -109,23 +115,35 @@ class EitherIdConfig:
109
115
 
110
116
  @property
111
117
  def either_id(self) -> EitherId:
118
+ """
119
+ Returns an EitherId object based on the current configuration.
120
+
121
+ Raises:
122
+ TypeError: If both id and external_id are None, or if both are set.
123
+ """
112
124
  return EitherId(id=self.id, external_id=self.external_id)
113
125
 
114
126
 
115
127
  class TimeIntervalConfig(yaml.YAMLObject):
116
128
  """
117
- Configuration parameter for setting a time interval
129
+ Configuration parameter for setting a time interval.
118
130
  """
119
131
 
120
132
  def __init__(self, expression: str) -> None:
121
133
  self._interval, self._expression = TimeIntervalConfig._parse_expression(expression)
122
134
 
123
135
  def __eq__(self, other: object) -> bool:
136
+ """
137
+ Two TimeIntervalConfig objects are equal if they have the same number of seconds in their interval.
138
+ """
124
139
  if not isinstance(other, TimeIntervalConfig):
125
140
  return NotImplemented
126
141
  return self._interval == other._interval
127
142
 
128
143
  def __hash__(self) -> int:
144
+ """
145
+ Hash function for TimeIntervalConfig based on the number of seconds in the interval.
146
+ """
129
147
  return hash(self._interval)
130
148
 
131
149
  @classmethod
@@ -147,42 +165,75 @@ class TimeIntervalConfig(yaml.YAMLObject):
147
165
 
148
166
  @property
149
167
  def seconds(self) -> int:
168
+ """
169
+ Time interval as number of seconds.
170
+ """
150
171
  return self._interval
151
172
 
152
173
  @property
153
174
  def minutes(self) -> float:
175
+ """
176
+ Time interval as number of minutes.
177
+
178
+ This is a float since the underlying interval is in seconds.
179
+ """
154
180
  return self._interval / 60
155
181
 
156
182
  @property
157
183
  def hours(self) -> float:
184
+ """
185
+ Time interval as number of hours.
186
+
187
+ This is a float since the underlying interval is in seconds.
188
+ """
158
189
  return self._interval / (60 * 60)
159
190
 
160
191
  @property
161
192
  def days(self) -> float:
193
+ """
194
+ Time interval as number of days.
195
+
196
+ This is a float since the underlying interval is in seconds.
197
+ """
162
198
  return self._interval / (60 * 60 * 24)
163
199
 
164
200
  @property
165
201
  def timedelta(self) -> timedelta:
202
+ """
203
+ Time interval as a timedelta object.
204
+ """
166
205
  days = self._interval // (60 * 60 * 24)
167
206
  seconds = self._interval % (60 * 60 * 24)
168
207
  return timedelta(days=days, seconds=seconds)
169
208
 
170
209
  def __int__(self) -> int:
210
+ """
211
+ Returns the time interval as a number of seconds.
212
+ """
171
213
  return int(self._interval)
172
214
 
173
215
  def __float__(self) -> float:
216
+ """
217
+ Returns the time interval as a number of seconds.
218
+ """
174
219
  return float(self._interval)
175
220
 
176
221
  def __str__(self) -> str:
222
+ """
223
+ Returns the time interval as a human readable string.
224
+ """
177
225
  return self._expression
178
226
 
179
227
  def __repr__(self) -> str:
228
+ """
229
+ Returns the time interval as a human readable string.
230
+ """
180
231
  return self._expression
181
232
 
182
233
 
183
234
  class FileSizeConfig(yaml.YAMLObject):
184
235
  """
185
- Configuration parameter for setting a file size
236
+ Configuration parameter for setting a file size.
186
237
  """
187
238
 
188
239
  def __init__(self, expression: str) -> None:
@@ -218,50 +269,89 @@ class FileSizeConfig(yaml.YAMLObject):
218
269
 
219
270
  @property
220
271
  def bytes(self) -> int:
272
+ """
273
+ File size in bytes.
274
+ """
221
275
  return self._bytes
222
276
 
223
277
  @property
224
278
  def kilobytes(self) -> float:
279
+ """
280
+ File size in kilobytes.
281
+ """
225
282
  return self._bytes / 1000
226
283
 
227
284
  @property
228
285
  def megabytes(self) -> float:
286
+ """
287
+ File size in megabytes.
288
+ """
229
289
  return self._bytes / 1_000_000
230
290
 
231
291
  @property
232
292
  def gigabytes(self) -> float:
293
+ """
294
+ File size in gigabytes.
295
+ """
233
296
  return self._bytes / 1_000_000_000
234
297
 
235
298
  @property
236
299
  def terabytes(self) -> float:
300
+ """
301
+ File size in terabytes.
302
+ """
237
303
  return self._bytes / 1_000_000_000_000
238
304
 
239
305
  @property
240
306
  def kibibytes(self) -> float:
307
+ """
308
+ File size in kibibytes (1024 bytes).
309
+ """
241
310
  return self._bytes / 1024
242
311
 
243
312
  @property
244
313
  def mebibytes(self) -> float:
314
+ """
315
+ File size in mebibytes (1024 kibibytes).
316
+ """
245
317
  return self._bytes / 1_048_576
246
318
 
247
319
  @property
248
320
  def gibibytes(self) -> float:
321
+ """
322
+ File size in gibibytes (1024 mebibytes).
323
+ """
249
324
  return self._bytes / 1_073_741_824
250
325
 
251
326
  @property
252
327
  def tebibytes(self) -> float:
328
+ """
329
+ File size in tebibytes (1024 gibibytes).
330
+ """
253
331
  return self._bytes / 1_099_511_627_776
254
332
 
255
333
  def __int__(self) -> int:
334
+ """
335
+ Returns the file size as bytes.
336
+ """
256
337
  return int(self._bytes)
257
338
 
258
339
  def __float__(self) -> float:
340
+ """
341
+ Returns the file size as bytes.
342
+ """
259
343
  return float(self._bytes)
260
344
 
261
345
  def __str__(self) -> str:
346
+ """
347
+ Returns the file size as a human readable string.
348
+ """
262
349
  return self._expression
263
350
 
264
351
  def __repr__(self) -> str:
352
+ """
353
+ Returns the file size as a human readable string.
354
+ """
265
355
  return self._expression
266
356
 
267
357
 
@@ -280,7 +370,7 @@ def _validate_https_url(value: str, name: str) -> None:
280
370
  @dataclass
281
371
  class CogniteConfig:
282
372
  """
283
- Configuration parameters for CDF connection, such as project name, host address and authentication
373
+ Configuration parameters for CDF connection, such as project name, host address and authentication.
284
374
  """
285
375
 
286
376
  project: str
@@ -289,7 +379,7 @@ class CogniteConfig:
289
379
  data_set_id: int | None = None
290
380
  data_set_external_id: str | None = None
291
381
  extraction_pipeline: EitherIdConfig | None = None
292
- timeout: TimeIntervalConfig = TimeIntervalConfig("30s")
382
+ timeout: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("30s"))
293
383
  connection: ConnectionConfig = field(default_factory=ConnectionConfig)
294
384
  security_categories: list[int] | None = None
295
385
  external_id_prefix: str = ""
@@ -301,6 +391,20 @@ class CogniteConfig:
301
391
  token_custom_args: dict[str, str] | None = None,
302
392
  use_experimental_sdk: bool = False,
303
393
  ) -> CogniteClient:
394
+ """
395
+ Creates a CogniteClient based on the configuration.
396
+
397
+ Args:
398
+ client_name: Name of the client, set as the x-cdp-app header in the requests.
399
+ token_custom_args: Additional arguments to pass to the token request, such as resource or audience.
400
+ use_experimental_sdk: If True, use the experimental SDK instead of the stable one.
401
+
402
+ Returns:
403
+ A CogniteClient instance configured with the provided parameters.
404
+
405
+ Raises:
406
+ InvalidConfigError: If the configuration is invalid, such as missing project name or invalid authority URL.
407
+ """
304
408
  from cognite.client.config import global_config
305
409
 
306
410
  global_config.disable_pypi_version_check = True
@@ -389,6 +493,15 @@ class CogniteConfig:
389
493
  return CogniteClient(client_config)
390
494
 
391
495
  def get_data_set(self, cdf_client: CogniteClient) -> DataSet | None:
496
+ """
497
+ Retrieves the DataSet object based on the configuration.
498
+
499
+ Args:
500
+ cdf_client: An instance of CogniteClient to use for retrieving the DataSet.
501
+
502
+ Returns:
503
+ DataSet object if data_set, data_set_id, or data_set_external_id is provided; otherwise None.
504
+ """
392
505
  if self.data_set_external_id:
393
506
  logging.getLogger(__name__).warning(
394
507
  "Using data-set-external-id is deprecated, please use data-set/external-id instead"
@@ -408,6 +521,18 @@ class CogniteConfig:
408
521
  )
409
522
 
410
523
  def get_extraction_pipeline(self, cdf_client: CogniteClient) -> ExtractionPipeline | None:
524
+ """
525
+ Retrieves the ExtractionPipeline object based on the configuration.
526
+
527
+ Args:
528
+ cdf_client: An instance of CogniteClient to use for retrieving the ExtractionPipeline.
529
+
530
+ Returns:
531
+ ExtractionPipeline object if extraction_pipeline is provided, otherwise None.
532
+
533
+ Raises:
534
+ ValueError: If the extraction pipeline with the specified ID is not found.
535
+ """
411
536
  if not self.extraction_pipeline:
412
537
  return None
413
538
 
@@ -436,7 +561,7 @@ class _FileLoggingConfig:
436
561
  @dataclass
437
562
  class LoggingConfig:
438
563
  """
439
- Logging settings, such as log levels and path to log file
564
+ Logging settings, such as log levels and path to log file.
440
565
  """
441
566
 
442
567
  console: _ConsoleLoggingConfig | None
@@ -448,7 +573,7 @@ class LoggingConfig:
448
573
 
449
574
  def setup_logging(self, suppress_console: bool = False) -> None:
450
575
  """
451
- Sets up the default logger in the logging package to be configured as defined in this config object
576
+ Sets up the default logger in the logging package to be configured as defined in this config object.
452
577
 
453
578
  Args:
454
579
  suppress_console: Don't log to console regardless of config. Useful when running an extractor as a Windows
@@ -509,9 +634,10 @@ class _PushGatewayConfig:
509
634
  password: str | None
510
635
 
511
636
  clear_after: TimeIntervalConfig | None
512
- push_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
637
+ push_interval: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("30s"))
513
638
 
514
639
 
640
+ @dataclass
515
641
  class _PromServerConfig:
516
642
  port: int = 9000
517
643
  host: str = "0.0.0.0"
@@ -524,14 +650,15 @@ class _CogniteMetricsConfig:
524
650
  asset_external_id: str | None
525
651
  data_set: EitherIdConfig | None = None
526
652
 
527
- push_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
653
+ push_interval: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("30s"))
528
654
 
529
655
 
530
656
  @dataclass
531
657
  class MetricsConfig:
532
658
  """
533
- Destination(s) for metrics, including options for one or several Prometheus push gateways, and pushing as CDF Time
534
- Series.
659
+ Destination(s) for metrics.
660
+
661
+ Including options for one or several Prometheus push gateways, and pushing as CDF Time Series.
535
662
  """
536
663
 
537
664
  push_gateways: list[_PushGatewayConfig] | None
@@ -539,6 +666,13 @@ class MetricsConfig:
539
666
  server: _PromServerConfig | None
540
667
 
541
668
  def start_pushers(self, cdf_client: CogniteClient, cancellation_token: CancellationToken | None = None) -> None:
669
+ """
670
+ Starts the configured metrics pushers.
671
+
672
+ Args:
673
+ cdf_client: An instance of CogniteClient to use for pushing metrics to CDF.
674
+ cancellation_token: Optional cancellation token to stop the pushers gracefully.
675
+ """
542
676
  self._pushers: list[AbstractMetricsPusher] = []
543
677
  self._clear_on_stop: dict[PrometheusPusher, int] = {}
544
678
 
@@ -587,6 +721,11 @@ class MetricsConfig:
587
721
  start_http_server(self.server.port, self.server.host, registry=REGISTRY)
588
722
 
589
723
  def stop_pushers(self) -> None:
724
+ """
725
+ DEPRECATED. Use cancellation_token to stop pushers instead.
726
+
727
+ Manually stop pushers and clear gateways if configured.
728
+ """
590
729
  pushers = self.__dict__.get("_pushers") or []
591
730
 
592
731
  for pusher in pushers:
@@ -597,11 +736,15 @@ class MetricsConfig:
597
736
  _logger.debug("Waiting %d seconds before clearing gateways", wait_time)
598
737
 
599
738
  sleep(wait_time)
600
- for pusher in self._clear_on_stop.keys():
739
+ for pusher in self._clear_on_stop:
601
740
  pusher.clear_gateway()
602
741
 
603
742
 
604
743
  class ConfigType(Enum):
744
+ """
745
+ Type of configuration, either local or remote.
746
+ """
747
+
605
748
  LOCAL = "local"
606
749
  REMOTE = "remote"
607
750
 
@@ -617,7 +760,7 @@ class _BaseConfig:
617
760
  @dataclass
618
761
  class BaseConfig(_BaseConfig):
619
762
  """
620
- Basis for an extractor config, containing config version, ``CogniteConfig`` and ``LoggingConfig``
763
+ Basis for an extractor config, containing config version, ``CogniteConfig`` and ``LoggingConfig``.
621
764
  """
622
765
 
623
766
  version: str | int | None
@@ -627,7 +770,7 @@ class BaseConfig(_BaseConfig):
627
770
  @dataclass
628
771
  class RawDestinationConfig:
629
772
  """
630
- Configuration parameters for using Raw
773
+ Configuration parameters for using Raw.
631
774
  """
632
775
 
633
776
  database: str
@@ -637,26 +780,26 @@ class RawDestinationConfig:
637
780
  @dataclass
638
781
  class RawStateStoreConfig(RawDestinationConfig):
639
782
  """
640
- Configuration of a state store based on CDF RAW
783
+ Configuration of a state store based on CDF RAW.
641
784
  """
642
785
 
643
- upload_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
786
+ upload_interval: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("30s"))
644
787
 
645
788
 
646
789
  @dataclass
647
790
  class LocalStateStoreConfig:
648
791
  """
649
- Configuration of a state store using a local JSON file
792
+ Configuration of a state store using a local JSON file.
650
793
  """
651
794
 
652
- path: str
653
- save_interval: TimeIntervalConfig = TimeIntervalConfig("30s")
795
+ path: Path
796
+ save_interval: TimeIntervalConfig = field(default_factory=lambda: TimeIntervalConfig("30s"))
654
797
 
655
798
 
656
799
  @dataclass
657
800
  class StateStoreConfig:
658
801
  """
659
- Configuration of the State Store, containing ``LocalStateStoreConfig`` or ``RawStateStoreConfig``
802
+ Configuration of the State Store, containing ``LocalStateStoreConfig`` or ``RawStateStoreConfig``.
660
803
  """
661
804
 
662
805
  raw: RawStateStoreConfig | None = None
@@ -675,6 +818,7 @@ class StateStoreConfig:
675
818
  cdf_client: CogniteClient object to use in case of a RAW state store (ignored otherwise)
676
819
  default_to_local: If true, return a LocalStateStore if no state store is configured. Otherwise return a
677
820
  NoStateStore
821
+ cancellation_token: Cancellation token to pass to created state stores
678
822
 
679
823
  Returns:
680
824
  An (uninitialized) state store
@@ -695,8 +839,11 @@ class StateStoreConfig:
695
839
  )
696
840
 
697
841
  if self.local:
842
+ if self.local.path.is_dir():
843
+ raise ValueError(f"{self.local.path} is a directory, and not a file")
844
+
698
845
  return LocalStateStore(
699
- file_path=self.local.path,
846
+ file_path=str(self.local.path),
700
847
  save_interval=self.local.save_interval.seconds,
701
848
  cancellation_token=cancellation_token,
702
849
  )
@@ -708,12 +855,22 @@ class StateStoreConfig:
708
855
 
709
856
 
710
857
  class RegExpFlag(Enum):
858
+ """
859
+ Flags for regular expressions.
860
+ """
861
+
711
862
  IGNORECASE = "ignore-case"
712
863
  IC = "i"
713
864
  ASCII = "ascii-only"
714
865
  A = "a"
715
866
 
716
867
  def get_regex_flag(self) -> int:
868
+ """
869
+ Returns the corresponding regex flag for the enum value.
870
+
871
+ Returns:
872
+ The regex flag corresponding to the enum value.
873
+ """
717
874
  if self in (RegExpFlag.IGNORECASE, RegExpFlag.IC):
718
875
  return re.IGNORECASE
719
876
  elif self.value in (RegExpFlag.ASCII, RegExpFlag.A):
@@ -724,7 +881,7 @@ class RegExpFlag(Enum):
724
881
  @dataclass
725
882
  class IgnorePattern:
726
883
  """
727
- Configuration for regexp for ignore pattern
884
+ Configuration for regexp for ignore pattern.
728
885
  """
729
886
 
730
887
  pattern: str
@@ -744,6 +901,12 @@ class IgnorePattern:
744
901
  return re.compile(self.pattern, flag)
745
902
 
746
903
  def __post_init__(self) -> None:
904
+ """
905
+ Validate the configuration after initialization.
906
+
907
+ Raises:
908
+ ValueError: If both 'options' and 'flags' are specified, or if neither is specified.
909
+ """
747
910
  if self.options is not None and self.flags is not None:
748
911
  raise ValueError("Only one of either 'options' or 'flags' can be specified.")
749
912
  if self.options is None and self.flags is None:
@@ -757,19 +920,27 @@ class IgnorePattern:
757
920
 
758
921
  class CastableInt(int):
759
922
  """
760
- Represents an integer in a config schema. Difference from regular int is that the
761
- value if this type can be either a string or an integer in the yaml file.
923
+ Represents an integer in a config schema.
924
+
925
+ The difference from regular int is that the value if this type can be either a string or an integer in the yaml
926
+ file.
762
927
  """
763
928
 
764
929
  def __new__(cls, value: Any) -> "CastableInt":
765
930
  """
766
- Returns value as is if it's int. If it's str or bytes try to convert to int.
767
- Raises ValueError if conversion is unsuccessful or value is of not supported type.
931
+ Returns value as is if it's int.
768
932
 
769
- Type check is required to avoid unexpected behaviour, such as implictly casting booleans,
770
- floats and other types supported by standard int.
771
- """
933
+ If it's str or bytes try to convert to int.
934
+
935
+ Type check is required to avoid unexpected behavior, such as implicitly casting booleans, floats and other types
936
+ supported by standard int.
772
937
 
938
+ Args:
939
+ value: The value to be casted to int.
940
+
941
+ Raises:
942
+ ValueError: If the value can not be converted to an int.
943
+ """
773
944
  if not isinstance(value, int | str | bytes):
774
945
  raise ValueError(f"CastableInt cannot be created form value {value!r} of type {type(value)!r}.")
775
946
 
@@ -778,14 +949,21 @@ class CastableInt(int):
778
949
 
779
950
  class PortNumber(CastableInt):
780
951
  """
781
- A subclass of int to be used in config schemas. It represents a valid port number (0 to 65535) and allows the value
782
- to be of either str or int type. If the value is not a valid port number raises a ValueError at instantiation.
952
+ Represents a port number in a config schema.
953
+
954
+ It represents a valid port number (0 to 65535) and allows the value to be of either str or int type. If the value is
955
+ not a valid port number raises a ValueError at instantiation.
783
956
  """
784
957
 
785
958
  def __new__(cls, value: Any) -> "PortNumber":
786
959
  """
787
- Try to convert the `value` to int. If successful, check if it's within a valid range for a port number.
788
- Raises ValueError if conversion to int or validation is unsuccessful.
960
+ Try to cast the value to an integer and validate it as a port number.
961
+
962
+ Args:
963
+ value: The value to be casted to a port number.
964
+
965
+ Raises:
966
+ ValueError: If the value is not a valid port number (not an int or out of range).
789
967
  """
790
968
  value = super().__new__(cls, value)
791
969