flpit 0.2.1.dev4__tar.gz → 0.2.1.dev5__tar.gz
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.
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/PKG-INFO +2 -1
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/pyproject.toml +2 -1
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/core/linq.py +24 -9
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/json.py +16 -10
- flpit-0.2.1.dev4/src/flpit/io.py +0 -212
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/.gitignore +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/LICENSE +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/README.md +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flp/__init__.py +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/__init__.py +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/core/__init__.py +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/flp.py +0 -0
- {flpit-0.2.1.dev4 → flpit-0.2.1.dev5}/src/flpit/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flpit
|
|
3
|
-
Version: 0.2.1.
|
|
3
|
+
Version: 0.2.1.dev5
|
|
4
4
|
Summary: Fluent LINQ for Python — flip your iterables
|
|
5
5
|
Project-URL: Homepage, https://github.com/riemar/flpit
|
|
6
6
|
Project-URL: Repository, https://github.com/riemar/flpit
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
17
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
19
|
Classifier: Typing :: Typed
|
|
19
20
|
Requires-Python: >=3.12
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flpit"
|
|
3
|
-
version = "0.2.1.
|
|
3
|
+
version = "0.2.1.dev5"
|
|
4
4
|
description = "Fluent LINQ for Python — flip your iterables"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -14,6 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3.12",
|
|
15
15
|
"Programming Language :: Python :: 3.13",
|
|
16
16
|
"Programming Language :: Python :: 3.14",
|
|
17
|
+
"Programming Language :: Python :: 3.15",
|
|
17
18
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
19
|
"Typing :: Typed"
|
|
19
20
|
]
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from io import BytesIO, StringIO
|
|
4
|
+
|
|
2
5
|
import decimal
|
|
3
6
|
from decimal import Decimal, Overflow, Inexact
|
|
7
|
+
from contextlib import AbstractContextManager
|
|
4
8
|
|
|
5
9
|
import builtins
|
|
6
10
|
from collections import UserList
|
|
@@ -23,11 +27,9 @@ from typing import (
|
|
|
23
27
|
Union,
|
|
24
28
|
override,
|
|
25
29
|
overload,
|
|
26
|
-
cast as typing_cast,
|
|
27
|
-
GenericAlias
|
|
30
|
+
cast as typing_cast, BinaryIO,
|
|
28
31
|
)
|
|
29
32
|
|
|
30
|
-
from flpit.io import MemoryByteWriter
|
|
31
33
|
from flpit.json import JsonConvert, JsonSerializerOptions
|
|
32
34
|
|
|
33
35
|
TItem = TypeVar("TItem")
|
|
@@ -124,8 +126,7 @@ def _guard_empty(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
124
126
|
return wrapper
|
|
125
127
|
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
def dotnet_context() -> decimal._ContextManager: # awesome that type was not intended
|
|
129
|
+
def dotnet_context() -> AbstractContextManager[decimal.Context]:
|
|
129
130
|
# 1. Create and configure pure Context object
|
|
130
131
|
ctx = decimal.getcontext().copy()
|
|
131
132
|
ctx.prec = max(ctx.prec, 29)
|
|
@@ -636,15 +637,23 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
636
637
|
return FlpIt[T](JsonConvert.deserialize(data, list[item_type], jso))
|
|
637
638
|
|
|
638
639
|
def to_json_bytes[T](self, item_type: type[T], jso: JsonSerializerOptions|None=None) -> bytes:
|
|
639
|
-
writer =
|
|
640
|
+
writer = BytesIO()
|
|
640
641
|
JsonConvert.serialize_to(self, item_type, writer, jso)
|
|
641
642
|
return writer.getvalue()
|
|
642
643
|
|
|
643
644
|
def to_json[T](self, item_type: type[T], jso: JsonSerializerOptions|None=None) -> str:
|
|
644
|
-
writer =
|
|
645
|
+
writer = StringIO()
|
|
645
646
|
JsonConvert.serialize_to(self, item_type, writer, jso)
|
|
646
647
|
return writer.getvalue()
|
|
647
648
|
|
|
649
|
+
def write_json[T](
|
|
650
|
+
self,
|
|
651
|
+
item_type: type[T],
|
|
652
|
+
output: BinaryIO,
|
|
653
|
+
jso: JsonSerializerOptions | None = None,
|
|
654
|
+
) -> None:
|
|
655
|
+
JsonConvert.serialize_to(self, item_type, output, jso)
|
|
656
|
+
|
|
648
657
|
|
|
649
658
|
from functools import partial
|
|
650
659
|
|
|
@@ -1220,4 +1229,10 @@ class FlpList(Collection[TItem], Generic[TItem]):
|
|
|
1220
1229
|
def to_json[T](self, item_type: type[T], jso: JsonSerializerOptions|None=None) -> str:
|
|
1221
1230
|
return JsonConvert.serialize(self.__list.data, list[item_type], jso)
|
|
1222
1231
|
|
|
1223
|
-
|
|
1232
|
+
def write_json[T](
|
|
1233
|
+
self,
|
|
1234
|
+
item_type: type[T],
|
|
1235
|
+
output: BinaryIO,
|
|
1236
|
+
jso: JsonSerializerOptions | None = None,
|
|
1237
|
+
) -> None:
|
|
1238
|
+
JsonConvert.serialize_to(self, item_type, output, jso)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections.abc import Iterable
|
|
2
4
|
import dataclasses
|
|
3
5
|
import re
|
|
@@ -9,12 +11,10 @@ from dataclasses import MISSING, dataclass
|
|
|
9
11
|
from enum import StrEnum
|
|
10
12
|
from functools import lru_cache
|
|
11
13
|
from hashlib import blake2s
|
|
12
|
-
from typing import Annotated, Any, ForwardRef, get_args, get_origin, get_type_hints, Callable
|
|
14
|
+
from typing import Annotated, Any, ForwardRef, get_args, get_origin, get_type_hints, Callable
|
|
13
15
|
|
|
14
16
|
import msgspec
|
|
15
17
|
|
|
16
|
-
from flpit.io import ByteWriter, TextWriter
|
|
17
|
-
|
|
18
18
|
|
|
19
19
|
class JsonNamingPolicy(StrEnum):
|
|
20
20
|
lower = "lower" # pyrefly: ignore [bad-override-mutable-attribute]
|
|
@@ -600,17 +600,23 @@ class JsonConvert:
|
|
|
600
600
|
def serialize_to[T](
|
|
601
601
|
it: Iterable[T],
|
|
602
602
|
target: type[T],
|
|
603
|
-
output:
|
|
604
|
-
jso: JsonSerializerOptions | None = None
|
|
603
|
+
output: typing.BinaryIO | typing.TextIO,
|
|
604
|
+
jso: JsonSerializerOptions | None = None,
|
|
605
605
|
) -> None:
|
|
606
|
+
import io
|
|
606
607
|
if jso is None:
|
|
607
608
|
jso = JsonSerializerOptions()
|
|
608
609
|
|
|
609
|
-
if isinstance(output,
|
|
610
|
-
write
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
610
|
+
if isinstance(output, io.TextIOBase):
|
|
611
|
+
def write(data: bytes) -> None:
|
|
612
|
+
output.write(data.decode("utf-8"))
|
|
613
|
+
|
|
614
|
+
elif isinstance(
|
|
615
|
+
output,
|
|
616
|
+
(io.BufferedIOBase, io.RawIOBase),
|
|
617
|
+
):
|
|
618
|
+
def write(data: bytes) -> None:
|
|
619
|
+
output.write(data)
|
|
614
620
|
else:
|
|
615
621
|
raise TypeError("output must be ByteWriter or TextWriter")
|
|
616
622
|
|
flpit-0.2.1.dev4/src/flpit/io.py
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
from types import TracebackType
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
from io import StringIO, TextIOWrapper
|
|
4
|
-
from typing import Any, BinaryIO, Iterable
|
|
5
|
-
|
|
6
|
-
import msgspec
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# ---------- Text layer ----------
|
|
10
|
-
|
|
11
|
-
class StringBuilder:
|
|
12
|
-
__slots__ = ("_buffer", "_length")
|
|
13
|
-
|
|
14
|
-
def __init__(self, initial_value: str = ""):
|
|
15
|
-
self._buffer = StringIO()
|
|
16
|
-
self._length = 0
|
|
17
|
-
self.append(initial_value)
|
|
18
|
-
|
|
19
|
-
def append(self, text: str) -> "StringBuilder":
|
|
20
|
-
self._buffer.write(text)
|
|
21
|
-
self._length += len(text)
|
|
22
|
-
return self
|
|
23
|
-
|
|
24
|
-
def append_line(self, text: str = "") -> "StringBuilder":
|
|
25
|
-
return self.append(text).append("\n")
|
|
26
|
-
|
|
27
|
-
def clear(self) -> None:
|
|
28
|
-
self._buffer.seek(0)
|
|
29
|
-
self._buffer.truncate(0)
|
|
30
|
-
self._length = 0
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def length(self) -> int:
|
|
34
|
-
return self._length
|
|
35
|
-
|
|
36
|
-
def to_string(self) -> str:
|
|
37
|
-
return self._buffer.getvalue()
|
|
38
|
-
|
|
39
|
-
def __str__(self) -> str:
|
|
40
|
-
return self.to_string()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class TextWriter(ABC):
|
|
44
|
-
@abstractmethod
|
|
45
|
-
def write(self, text: str) -> int:
|
|
46
|
-
...
|
|
47
|
-
|
|
48
|
-
def write_line(self, text: str = "") -> None:
|
|
49
|
-
self.write(text)
|
|
50
|
-
self.write("\n")
|
|
51
|
-
|
|
52
|
-
@abstractmethod
|
|
53
|
-
def flush(self) -> None:
|
|
54
|
-
...
|
|
55
|
-
|
|
56
|
-
@abstractmethod
|
|
57
|
-
def close(self) -> None:
|
|
58
|
-
...
|
|
59
|
-
|
|
60
|
-
def __enter__(self):
|
|
61
|
-
return self
|
|
62
|
-
|
|
63
|
-
def __exit__(
|
|
64
|
-
self,
|
|
65
|
-
exc_type: type[BaseException] | None,
|
|
66
|
-
exc_val: BaseException | None,
|
|
67
|
-
exc_tb: TracebackType | None,
|
|
68
|
-
) -> bool | None:
|
|
69
|
-
self.close()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class StringWriter(TextWriter):
|
|
73
|
-
def __init__(self):
|
|
74
|
-
self._builder = StringBuilder()
|
|
75
|
-
self._closed = False
|
|
76
|
-
|
|
77
|
-
def write(self, text: str) -> int:
|
|
78
|
-
if self._closed:
|
|
79
|
-
raise ValueError("Writer is closed")
|
|
80
|
-
self._builder.append(text)
|
|
81
|
-
return len(text)
|
|
82
|
-
|
|
83
|
-
def flush(self) -> None:
|
|
84
|
-
if self._closed:
|
|
85
|
-
raise ValueError("Writer is closed")
|
|
86
|
-
|
|
87
|
-
def close(self) -> None:
|
|
88
|
-
self._closed = True
|
|
89
|
-
|
|
90
|
-
def getvalue(self) -> str:
|
|
91
|
-
return self._builder.to_string()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class StreamWriter(TextWriter):
|
|
95
|
-
"""Text writer over a binary stream; closing it closes the stream."""
|
|
96
|
-
|
|
97
|
-
def __init__(self, stream: BinaryIO, encoding: str = "utf-8"):
|
|
98
|
-
self._stream = TextIOWrapper(stream, encoding=encoding)
|
|
99
|
-
|
|
100
|
-
def write(self, text: str) -> int:
|
|
101
|
-
return self._stream.write(text)
|
|
102
|
-
|
|
103
|
-
def flush(self) -> None:
|
|
104
|
-
self._stream.flush()
|
|
105
|
-
|
|
106
|
-
def close(self) -> None:
|
|
107
|
-
self._stream.close()
|
|
108
|
-
|
|
109
|
-
#===================================
|
|
110
|
-
# ---------- Binary layer ----------
|
|
111
|
-
#===================================
|
|
112
|
-
|
|
113
|
-
class ByteWriter(ABC):
|
|
114
|
-
"""write() must consume or copy the supplied bytes before returning."""
|
|
115
|
-
|
|
116
|
-
@abstractmethod
|
|
117
|
-
def write(self, data: bytes | bytearray | memoryview) -> None:
|
|
118
|
-
...
|
|
119
|
-
|
|
120
|
-
@abstractmethod
|
|
121
|
-
def flush(self) -> None:
|
|
122
|
-
...
|
|
123
|
-
|
|
124
|
-
class MemoryByteWriter(ByteWriter):
|
|
125
|
-
def __init__(self):
|
|
126
|
-
self._buffer = bytearray()
|
|
127
|
-
|
|
128
|
-
def write(self, data: bytes | bytearray | memoryview) -> None:
|
|
129
|
-
self._buffer.extend(data)
|
|
130
|
-
|
|
131
|
-
def flush(self) -> None:
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
def getvalue(self) -> bytes:
|
|
135
|
-
return bytes(self._buffer)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class StreamByteWriter(ByteWriter):
|
|
139
|
-
"""Adapter for a Python binary stream. The caller owns the stream."""
|
|
140
|
-
|
|
141
|
-
def __init__(self, stream: BinaryIO):
|
|
142
|
-
self._stream = stream
|
|
143
|
-
|
|
144
|
-
def write(self, data: bytes | bytearray | memoryview) -> None:
|
|
145
|
-
view = memoryview(data)
|
|
146
|
-
|
|
147
|
-
while view:
|
|
148
|
-
count = self._stream.write(view)
|
|
149
|
-
if count is None or count <= 0:
|
|
150
|
-
raise OSError("Stream failed to write data")
|
|
151
|
-
view = view[count:]
|
|
152
|
-
|
|
153
|
-
def flush(self) -> None:
|
|
154
|
-
self._stream.flush()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class BufferedByteWriter(ByteWriter):
|
|
158
|
-
"""Adds explicit, bounded-size output buffering to another writer."""
|
|
159
|
-
|
|
160
|
-
def __init__(self, destination: ByteWriter, chunk_size: int = 1 << 20):
|
|
161
|
-
if chunk_size < 1:
|
|
162
|
-
raise ValueError("chunk_size must be positive")
|
|
163
|
-
|
|
164
|
-
self._destination = destination
|
|
165
|
-
self._chunk_size = chunk_size
|
|
166
|
-
self._buffer = bytearray()
|
|
167
|
-
|
|
168
|
-
def write(self, data: bytes | bytearray | memoryview) -> None:
|
|
169
|
-
view = memoryview(data)
|
|
170
|
-
|
|
171
|
-
while view:
|
|
172
|
-
count = min(
|
|
173
|
-
self._chunk_size - len(self._buffer),
|
|
174
|
-
len(view),
|
|
175
|
-
)
|
|
176
|
-
self._buffer.extend(view[:count])
|
|
177
|
-
view = view[count:]
|
|
178
|
-
|
|
179
|
-
if len(self._buffer) == self._chunk_size:
|
|
180
|
-
self._flush_buffer()
|
|
181
|
-
|
|
182
|
-
def _flush_buffer(self) -> None:
|
|
183
|
-
if self._buffer:
|
|
184
|
-
self._destination.write(self._buffer)
|
|
185
|
-
self._buffer.clear()
|
|
186
|
-
|
|
187
|
-
def flush(self) -> None:
|
|
188
|
-
self._flush_buffer()
|
|
189
|
-
self._destination.flush()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# ---------- JSON serialization policy ----------
|
|
193
|
-
|
|
194
|
-
def write_json_array(
|
|
195
|
-
items: Iterable[Any],
|
|
196
|
-
output: ByteWriter,
|
|
197
|
-
) -> None:
|
|
198
|
-
encoder = msgspec.json.Encoder()
|
|
199
|
-
scratch = bytearray()
|
|
200
|
-
first = True
|
|
201
|
-
|
|
202
|
-
output.write(b"[")
|
|
203
|
-
|
|
204
|
-
for item in items:
|
|
205
|
-
if not first:
|
|
206
|
-
output.write(b",")
|
|
207
|
-
|
|
208
|
-
encoder.encode_into(item, scratch)
|
|
209
|
-
output.write(scratch)
|
|
210
|
-
first = False
|
|
211
|
-
|
|
212
|
-
output.write(b"]")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|