dtypeflow 1.0.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.
Files changed (43) hide show
  1. dtypeflow/__init__.py +5 -0
  2. dtypeflow/core/__init__.py +1 -0
  3. dtypeflow/core/candidate/__init__.py +40 -0
  4. dtypeflow/core/candidate/base.py +37 -0
  5. dtypeflow/core/candidate/factory.py +258 -0
  6. dtypeflow/core/candidate/primitive.py +79 -0
  7. dtypeflow/core/dataset.py +46 -0
  8. dtypeflow/core/dtype/__init__.py +70 -0
  9. dtypeflow/core/dtype/base.py +14 -0
  10. dtypeflow/core/dtype/hierachy.md +51 -0
  11. dtypeflow/core/dtype/nested/__init__.py +5 -0
  12. dtypeflow/core/dtype/nested/dictionary.py +14 -0
  13. dtypeflow/core/dtype/primitive/__init__.py +39 -0
  14. dtypeflow/core/dtype/primitive/base.py +6 -0
  15. dtypeflow/core/dtype/primitive/boolean.py +20 -0
  16. dtypeflow/core/dtype/primitive/float.py +40 -0
  17. dtypeflow/core/dtype/primitive/integer/__init__.py +31 -0
  18. dtypeflow/core/dtype/primitive/integer/base.py +17 -0
  19. dtypeflow/core/dtype/primitive/integer/signed.py +37 -0
  20. dtypeflow/core/dtype/primitive/integer/unsigned.py +37 -0
  21. dtypeflow/core/dtype/primitive/string.py +14 -0
  22. dtypeflow/core/override/__init__.py +41 -0
  23. dtypeflow/core/override/base.py +22 -0
  24. dtypeflow/core/override/primitive.py +158 -0
  25. dtypeflow/core/profile/__init__.py +39 -0
  26. dtypeflow/core/profile/base.py +50 -0
  27. dtypeflow/core/profile/primitive.py +166 -0
  28. dtypeflow/core/profiler.py +169 -0
  29. dtypeflow/core/schema.py +33 -0
  30. dtypeflow/core/table.py +29 -0
  31. dtypeflow/py.typed +0 -0
  32. dtypeflow/tui/__init__.py +17 -0
  33. dtypeflow/tui/app.py +747 -0
  34. dtypeflow/tui/app.tcss +220 -0
  35. dtypeflow/tui/compatibility.py +38 -0
  36. dtypeflow/tui/dto.py +44 -0
  37. dtypeflow/tui/service.py +213 -0
  38. dtypeflow/utils.py +32 -0
  39. dtypeflow-1.0.0.dist-info/METADATA +93 -0
  40. dtypeflow-1.0.0.dist-info/RECORD +43 -0
  41. dtypeflow-1.0.0.dist-info/WHEEL +4 -0
  42. dtypeflow-1.0.0.dist-info/entry_points.txt +3 -0
  43. dtypeflow-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from decimal import Decimal
4
+ from typing import ClassVar, Final, final
5
+
6
+ from typing_extensions import override
7
+
8
+ from dtypeflow.core.dtype.base import Sizeable
9
+
10
+ from .base import Primitive
11
+
12
+
13
+ class Float(Primitive, Sizeable):
14
+ MAX_ABSOLUTE_VALUE: ClassVar[Decimal]
15
+ MAX_SIGNIFICANT_DIGITS: ClassVar[int]
16
+
17
+ @override
18
+ def __repr__(self) -> str:
19
+ return f"Float(±{self.MAX_ABSOLUTE_VALUE} (≈ {self.MAX_SIGNIFICANT_DIGITS} significant digits))"
20
+
21
+
22
+ @final
23
+ class Float16(Float):
24
+ SIZE: Final[int] = 2
25
+ MAX_ABSOLUTE_VALUE: Final[Decimal] = Decimal(65504)
26
+ MAX_SIGNIFICANT_DIGITS: Final[int] = 4
27
+
28
+
29
+ @final
30
+ class Float32(Float):
31
+ SIZE: Final[int] = 4
32
+ MAX_ABSOLUTE_VALUE: Final[Decimal] = Decimal("3.4028235e38")
33
+ MAX_SIGNIFICANT_DIGITS: Final[int] = 7
34
+
35
+
36
+ @final
37
+ class Float64(Float):
38
+ SIZE: Final[int] = 8
39
+ MAX_ABSOLUTE_VALUE: Final[Decimal] = Decimal("1.7976931348623157e308")
40
+ MAX_SIGNIFICANT_DIGITS: Final[int] = 15
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from .base import Integer as IntegerDtype
4
+ from .signed import (
5
+ Int8 as Int8Dtype,
6
+ Int16 as Int16Dtype,
7
+ Int32 as Int32Dtype,
8
+ Int64 as Int64Dtype,
9
+ Signed as SignedDtype,
10
+ )
11
+ from .unsigned import (
12
+ UInt8 as UInt8Dtype,
13
+ UInt16 as UInt16Dtype,
14
+ UInt32 as UInt32Dtype,
15
+ UInt64 as UInt64Dtype,
16
+ Unsigned as UnsignedDtype,
17
+ )
18
+
19
+ __all__: list[str] = [
20
+ "Int8Dtype",
21
+ "Int16Dtype",
22
+ "Int32Dtype",
23
+ "Int64Dtype",
24
+ "IntegerDtype",
25
+ "SignedDtype",
26
+ "UInt8Dtype",
27
+ "UInt16Dtype",
28
+ "UInt32Dtype",
29
+ "UInt64Dtype",
30
+ "UnsignedDtype",
31
+ ]
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import ClassVar
4
+
5
+ from typing_extensions import override
6
+
7
+ from dtypeflow.core.dtype.base import Sizeable
8
+ from dtypeflow.core.dtype.primitive.base import Primitive
9
+
10
+
11
+ class Integer(Primitive, Sizeable):
12
+ LOWER_BOUND: ClassVar[int]
13
+ HIGHER_BOUND: ClassVar[int]
14
+
15
+ @override
16
+ def __repr__(self) -> str:
17
+ return f"{self.__class__.__name__}({self.LOWER_BOUND} – {self.HIGHER_BOUND})"
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Final, final
4
+
5
+ from dtypeflow.core.dtype.base import Sizeable
6
+ from dtypeflow.core.dtype.primitive.integer.base import Integer
7
+
8
+
9
+ class Signed(Integer, Sizeable): ...
10
+
11
+
12
+ @final
13
+ class Int8(Signed):
14
+ SIZE: Final[int] = 1
15
+ LOWER_BOUND: Final[int] = -128
16
+ HIGHER_BOUND: Final[int] = 127
17
+
18
+
19
+ @final
20
+ class Int16(Signed):
21
+ SIZE: Final[int] = 2
22
+ LOWER_BOUND: Final[int] = -32768
23
+ HIGHER_BOUND: Final[int] = 32767
24
+
25
+
26
+ @final
27
+ class Int32(Signed):
28
+ SIZE: Final[int] = 4
29
+ LOWER_BOUND: Final[int] = -2147483648
30
+ HIGHER_BOUND: Final[int] = 2147483647
31
+
32
+
33
+ @final
34
+ class Int64(Signed):
35
+ SIZE: Final[int] = 8
36
+ LOWER_BOUND: Final[int] = -9223372036854775808
37
+ HIGHER_BOUND: Final[int] = 9223372036854775807
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Final, final
4
+
5
+ from dtypeflow.core.dtype.base import Sizeable
6
+ from dtypeflow.core.dtype.primitive.integer.base import Integer
7
+
8
+
9
+ class Unsigned(Integer, Sizeable): ...
10
+
11
+
12
+ @final
13
+ class UInt8(Unsigned):
14
+ SIZE: Final[int] = 1
15
+ LOWER_BOUND: Final[int] = 0
16
+ HIGHER_BOUND: Final[int] = 255
17
+
18
+
19
+ @final
20
+ class UInt16(Unsigned):
21
+ SIZE: Final[int] = 2
22
+ LOWER_BOUND: Final[int] = 0
23
+ HIGHER_BOUND: Final[int] = 65535
24
+
25
+
26
+ @final
27
+ class UInt32(Unsigned):
28
+ SIZE: Final[int] = 4
29
+ LOWER_BOUND: Final[int] = 0
30
+ HIGHER_BOUND: Final[int] = 4294967295
31
+
32
+
33
+ @final
34
+ class UInt64(Unsigned):
35
+ SIZE: Final[int] = 8
36
+ LOWER_BOUND: Final[int] = 0
37
+ HIGHER_BOUND: Final[int] = 18446744073709551615
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import final
4
+
5
+ from typing_extensions import override
6
+
7
+ from .base import Primitive
8
+
9
+
10
+ @final
11
+ class String(Primitive):
12
+ @override
13
+ def __repr__(self) -> str:
14
+ return f"{self.__class__.__name__}()"
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from .base import Override
4
+ from .primitive import (
5
+ Boolean as BooleanOverride,
6
+ Dictionary as DictionaryOverride,
7
+ Float as FloatOverride,
8
+ Float16 as Float16Override,
9
+ Float32 as Float32Override,
10
+ Float64 as Float64Override,
11
+ Int8 as Int8Override,
12
+ Int16 as Int16Override,
13
+ Int32 as Int32Override,
14
+ Int64 as Int64Override,
15
+ Integer as IntegerOverride,
16
+ String as StringOverride,
17
+ UInt8 as UInt8Override,
18
+ UInt16 as UInt16Override,
19
+ UInt32 as UInt32Override,
20
+ UInt64 as UInt64Override,
21
+ )
22
+
23
+ __all__: list[str] = [
24
+ "BooleanOverride",
25
+ "DictionaryOverride",
26
+ "Float16Override",
27
+ "Float32Override",
28
+ "Float64Override",
29
+ "FloatOverride",
30
+ "Int8Override",
31
+ "Int16Override",
32
+ "Int32Override",
33
+ "Int64Override",
34
+ "IntegerOverride",
35
+ "Override",
36
+ "StringOverride",
37
+ "UInt8Override",
38
+ "UInt16Override",
39
+ "UInt32Override",
40
+ "UInt64Override",
41
+ ]
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ import pyarrow as pa
8
+
9
+
10
+ class Override(ABC):
11
+ def __init__(self, column: str, /) -> None:
12
+ self._column: str = column
13
+
14
+ @property
15
+ def column(self) -> str:
16
+ return self._column
17
+
18
+ @abstractmethod
19
+ def transform(self, *values: str | None) -> pa.Array: ...
20
+
21
+ def __repr__(self) -> str:
22
+ return f"{type(self).__name__}(column={self._column!r})"
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+
3
+ from decimal import Decimal, InvalidOperation
4
+
5
+ import pyarrow as pa
6
+
7
+ from .base import Override
8
+
9
+
10
+ class Boolean(Override):
11
+ def __init__(self, column: str, /, *, true_value: str, false_value: str) -> None:
12
+ super().__init__(column)
13
+ if (true_value := true_value.strip().lower()) == (
14
+ false_value := false_value.strip().lower()
15
+ ):
16
+ msg = f"true_value and false_value must differ for column {column!r}: {true_value!r}."
17
+ raise ValueError(msg)
18
+ self._true_value: str = true_value
19
+ self._false_value: str = false_value
20
+
21
+ def _coerce(self, value: str | None, /) -> bool | None:
22
+ if value is None or not value.strip():
23
+ return None
24
+ if (value := value.strip().lower()) not in (
25
+ self._true_value,
26
+ self._false_value,
27
+ ):
28
+ msg = f"{value!r} is neither {self._true_value!r} nor {self._false_value!r} for column {self.column!r}."
29
+ raise ValueError(msg)
30
+ return value == self._true_value
31
+
32
+ def transform(self, *values: str | None) -> pa.Array:
33
+ return pa.array([self._coerce(value) for value in values], type=pa.bool_())
34
+
35
+
36
+ class Integer(Override):
37
+ def _coerce(self, value: str | None, /) -> int | None:
38
+ if value is None or not value.strip():
39
+ return None
40
+ stripped = value.strip()
41
+ try:
42
+ return int(Decimal(stripped))
43
+ except InvalidOperation:
44
+ msg = f"{stripped!r} is not a valid number for column {self.column!r}."
45
+ raise ValueError(msg) from None
46
+
47
+
48
+ class Int8(Integer):
49
+ def transform(self, *values: str | None) -> pa.Array:
50
+ return pa.array([self._coerce(value) for value in values], type=pa.int8())
51
+
52
+
53
+ class Int16(Integer):
54
+ def transform(self, *values: str | None) -> pa.Array:
55
+ return pa.array([self._coerce(value) for value in values], type=pa.int16())
56
+
57
+
58
+ class Int32(Integer):
59
+ def transform(self, *values: str | None) -> pa.Array:
60
+ return pa.array([self._coerce(value) for value in values], type=pa.int32())
61
+
62
+
63
+ class Int64(Integer):
64
+ def transform(self, *values: str | None) -> pa.Array:
65
+ return pa.array([self._coerce(value) for value in values], type=pa.int64())
66
+
67
+
68
+ class UInt8(Integer):
69
+ def transform(self, *values: str | None) -> pa.Array:
70
+ return pa.array([self._coerce(value) for value in values], type=pa.uint8())
71
+
72
+
73
+ class UInt16(Integer):
74
+ def transform(self, *values: str | None) -> pa.Array:
75
+ return pa.array([self._coerce(value) for value in values], type=pa.uint16())
76
+
77
+
78
+ class UInt32(Integer):
79
+ def transform(self, *values: str | None) -> pa.Array:
80
+ return pa.array([self._coerce(value) for value in values], type=pa.uint32())
81
+
82
+
83
+ class UInt64(Integer):
84
+ def transform(self, *values: str | None) -> pa.Array:
85
+ return pa.array([self._coerce(value) for value in values], type=pa.uint64())
86
+
87
+
88
+ class Float(Override):
89
+ def _coerce(self, value: str | None, /) -> float | None:
90
+ if value is None or not value.strip():
91
+ return None
92
+ return float(value.strip())
93
+
94
+
95
+ class Float16(Float):
96
+ def transform(self, *values: str | None) -> pa.Array:
97
+ return pa.array([self._coerce(value) for value in values], type=pa.float16())
98
+
99
+
100
+ class Float32(Float):
101
+ def transform(self, *values: str | None) -> pa.Array:
102
+ return pa.array([self._coerce(value) for value in values], type=pa.float32())
103
+
104
+
105
+ class Float64(Float):
106
+ def transform(self, *values: str | None) -> pa.Array:
107
+ return pa.array([self._coerce(value) for value in values], type=pa.float64())
108
+
109
+
110
+ class String(Override):
111
+ def _coerce(self, value: str | None, /) -> str | None:
112
+ if value is None or not value.strip():
113
+ return None
114
+ return value.strip()
115
+
116
+ def transform(self, *values: str | None) -> pa.Array:
117
+ return pa.array([self._coerce(value) for value in values], type=pa.string())
118
+
119
+
120
+ class Dictionary(String):
121
+ def __init__(self, column: str, /, *categories: str, ordered: bool = False) -> None:
122
+ super().__init__(column)
123
+ if not categories:
124
+ msg = f"categories must be provided for column {column!r}."
125
+ raise ValueError(msg)
126
+ if len(set(categories)) != len(categories):
127
+ msg = f"categories must not contain duplicates for column {column!r}: {categories!r}."
128
+ raise ValueError(msg)
129
+ self._ordered: bool = ordered
130
+ self._categories: tuple[str, ...] = categories
131
+
132
+ def _dictionary_index_type(self, num_unique: int, /) -> pa.DataType:
133
+ if num_unique <= 2**8:
134
+ return pa.int8()
135
+ if num_unique <= 2**16:
136
+ return pa.int16()
137
+ if num_unique <= 2**32:
138
+ return pa.int32()
139
+ return pa.int64()
140
+
141
+ def transform(self, *values: str | None) -> pa.Array:
142
+ coerced: list[str | None] = [self._coerce(value) for value in values]
143
+ if missing := (
144
+ {value for value in coerced if value is not None} - set(self._categories)
145
+ ):
146
+ msg = f"values {sorted(missing)!r} for column {self.column!r} are not in the provided categories {self._categories!r}."
147
+ raise ValueError(msg)
148
+ rank: dict[str, int] = {
149
+ category: index for (index, category) in enumerate(self._categories)
150
+ }
151
+ return pa.DictionaryArray.from_arrays(
152
+ pa.array(
153
+ [None if value is None else rank[value] for value in coerced],
154
+ type=self._dictionary_index_type(len(self._categories)),
155
+ ),
156
+ pa.array(self._categories, type=pa.string()),
157
+ ordered=self._ordered,
158
+ )
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from .base import Profile
4
+ from .primitive import (
5
+ Boolean as BooleanProfile,
6
+ Float as FloatProfile,
7
+ Float16 as Float16Profile,
8
+ Float32 as Float32Profile,
9
+ Float64 as Float64Profile,
10
+ Int8 as Int8Profile,
11
+ Int16 as Int16Profile,
12
+ Int32 as Int32Profile,
13
+ Int64 as Int64Profile,
14
+ Integer as IntegerProfile,
15
+ String as StringProfile,
16
+ UInt8 as UInt8Profile,
17
+ UInt16 as UInt16Profile,
18
+ UInt32 as UInt32Profile,
19
+ UInt64 as UInt64Profile,
20
+ )
21
+
22
+ __all__: list[str] = [
23
+ "BooleanProfile",
24
+ "Float16Profile",
25
+ "Float32Profile",
26
+ "Float64Profile",
27
+ "FloatProfile",
28
+ "Int8Profile",
29
+ "Int16Profile",
30
+ "Int32Profile",
31
+ "Int64Profile",
32
+ "IntegerProfile",
33
+ "Profile",
34
+ "StringProfile",
35
+ "UInt8Profile",
36
+ "UInt16Profile",
37
+ "UInt32Profile",
38
+ "UInt64Profile",
39
+ ]
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, ClassVar
5
+
6
+ if TYPE_CHECKING:
7
+ from dtypeflow.core.candidate import Candidate
8
+ from dtypeflow.core.dtype import Dtype
9
+
10
+
11
+ class Profile(ABC):
12
+ DTYPE: ClassVar[type[Dtype]]
13
+
14
+ def __init__(
15
+ self,
16
+ *values: str | None,
17
+ uniques: tuple[int, ...],
18
+ non_nan: tuple[int, ...],
19
+ nan: tuple[int, ...],
20
+ ) -> None:
21
+ self._values: tuple[str | None, ...] = values
22
+ self._non_nan_indices: tuple[int, ...] = non_nan
23
+ self._nan_indices: tuple[int, ...] = nan
24
+ self._uniques: tuple[int, ...] = uniques
25
+ self._candidates: tuple[Candidate, ...] | None = None
26
+
27
+ def uniques(self, *, n: int | None = None) -> tuple[str, ...]:
28
+ if n is None:
29
+ return tuple(self._values[index] for index in self._uniques)
30
+ return tuple(self._values[index] for index in self._uniques[:n])
31
+
32
+ @property
33
+ def num_unique(self) -> int:
34
+ return len(self._uniques)
35
+
36
+ @property
37
+ def total(self) -> int:
38
+ return len(self._non_nan_indices)
39
+
40
+ @property
41
+ def candidates(self) -> tuple[Candidate, ...]:
42
+ if self._candidates is None:
43
+ self._candidates: tuple[Candidate, ...] = self._compute_candidates()
44
+ return self._candidates
45
+
46
+ @abstractmethod
47
+ def _compute_candidates(self) -> tuple[Candidate, ...]: ...
48
+
49
+ @abstractmethod
50
+ def __repr__(self) -> str: ...
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ from decimal import Decimal
4
+ from typing import TYPE_CHECKING, ClassVar, Final, cast
5
+
6
+ from dtypeflow.core.candidate import candidate_factory
7
+ from dtypeflow.core.dtype import (
8
+ BooleanDtype,
9
+ Dtype,
10
+ Float16Dtype,
11
+ Float32Dtype,
12
+ Float64Dtype,
13
+ FloatDtype,
14
+ Int8Dtype,
15
+ Int16Dtype,
16
+ Int32Dtype,
17
+ Int64Dtype,
18
+ IntegerDtype,
19
+ StringDtype,
20
+ UInt8Dtype,
21
+ UInt16Dtype,
22
+ UInt32Dtype,
23
+ UInt64Dtype,
24
+ )
25
+
26
+ from .base import Profile
27
+
28
+ if TYPE_CHECKING:
29
+ from dtypeflow.core.candidate import Candidate
30
+
31
+
32
+ class Boolean(Profile):
33
+ DTYPE: Final[type[Dtype]] = BooleanDtype
34
+
35
+ def _compute_candidates(self) -> tuple[Candidate, ...]:
36
+ return candidate_factory(
37
+ self.DTYPE, num_unique=self.num_unique, total=self.total,
38
+ )
39
+
40
+ def __repr__(self) -> str:
41
+ return f"{type(self).__name__}(num_unique={self.num_unique})"
42
+
43
+
44
+ class Integer(Profile):
45
+ DTYPE: ClassVar[type[Dtype]] = IntegerDtype
46
+
47
+ @property
48
+ def min(self) -> int:
49
+ return min(int(value) for value in self.uniques())
50
+
51
+ @property
52
+ def max(self) -> int:
53
+ return max(int(value) for value in self.uniques())
54
+
55
+ def _compute_candidates(self) -> tuple[Candidate, ...]:
56
+ return candidate_factory(
57
+ self.DTYPE,
58
+ num_unique=self.num_unique,
59
+ total=self.total,
60
+ min_value=self.min,
61
+ max_value=self.max,
62
+ )
63
+
64
+ def __repr__(self) -> str:
65
+ return f"{type(self).__name__}(min={self.min}, max={self.max}, num_unique={self.num_unique})"
66
+
67
+
68
+ class UInt8(Integer):
69
+ DTYPE: Final[type[Dtype]] = UInt8Dtype
70
+
71
+
72
+ class UInt16(Integer):
73
+ DTYPE: Final[type[Dtype]] = UInt16Dtype
74
+
75
+
76
+ class UInt32(Integer):
77
+ DTYPE: Final[type[Dtype]] = UInt32Dtype
78
+
79
+
80
+ class UInt64(Integer):
81
+ DTYPE: Final[type[Dtype]] = UInt64Dtype
82
+
83
+
84
+ class Int8(Integer):
85
+ DTYPE: Final[type[Dtype]] = Int8Dtype
86
+
87
+
88
+ class Int16(Integer):
89
+ DTYPE: Final[type[Dtype]] = Int16Dtype
90
+
91
+
92
+ class Int32(Integer):
93
+ DTYPE: Final[type[Dtype]] = Int32Dtype
94
+
95
+
96
+ class Int64(Integer):
97
+ DTYPE: Final[type[Dtype]] = Int64Dtype
98
+
99
+
100
+ class Float(Profile):
101
+ DTYPE: ClassVar[type[Dtype]] = FloatDtype
102
+
103
+ @property
104
+ def min(self) -> Decimal:
105
+ return min(Decimal(value) for value in self.uniques())
106
+
107
+ @property
108
+ def max(self) -> Decimal:
109
+ return max(Decimal(value) for value in self.uniques())
110
+
111
+ @property
112
+ def precision(self) -> int:
113
+ return max(len(Decimal(value).as_tuple().digits) for value in self.uniques())
114
+
115
+ @property
116
+ def scale(self) -> int:
117
+ exponents: list[int] = [
118
+ cast("int", Decimal(value).as_tuple().exponent) for value in self.uniques()
119
+ ]
120
+ return max([0, *(-exponent for exponent in exponents)])
121
+
122
+ def _compute_candidates(self) -> tuple[Candidate, ...]:
123
+ return candidate_factory(
124
+ self.DTYPE,
125
+ num_unique=self.num_unique,
126
+ total=self.total,
127
+ min_value=self.min,
128
+ max_value=self.max,
129
+ precision=self.precision,
130
+ scale=self.scale,
131
+ )
132
+
133
+ def __repr__(self) -> str:
134
+ return f"{type(self).__name__}(min={self.min}, max={self.max}, precision={self.precision}, scale={self.scale}, num_unique={self.num_unique})"
135
+
136
+
137
+ class Float16(Float):
138
+ DTYPE: Final[type[Dtype]] = Float16Dtype
139
+
140
+
141
+ class Float32(Float):
142
+ DTYPE: Final[type[Dtype]] = Float32Dtype
143
+
144
+
145
+ class Float64(Float):
146
+ DTYPE: Final[type[Dtype]] = Float64Dtype
147
+
148
+
149
+ class String(Profile):
150
+ DTYPE: Final[type[Dtype]] = StringDtype
151
+
152
+ @property
153
+ def min_length(self) -> int:
154
+ return min(len(value) for value in self.uniques())
155
+
156
+ @property
157
+ def max_length(self) -> int:
158
+ return max(len(value) for value in self.uniques())
159
+
160
+ def _compute_candidates(self) -> tuple[Candidate, ...]:
161
+ return candidate_factory(
162
+ self.DTYPE, num_unique=self.num_unique, total=self.total,
163
+ )
164
+
165
+ def __repr__(self) -> str:
166
+ return f"{type(self).__name__}(min_length={self.min_length}, max_length={self.max_length}, num_unique={self.num_unique})"