omextra 0.0.0.dev503__tar.gz → 0.0.0.dev505__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.
- {omextra-0.0.0.dev503/omextra.egg-info → omextra-0.0.0.dev505}/PKG-INFO +2 -2
- omextra-0.0.0.dev505/omextra/io/buffers/DESIGN.md +253 -0
- omextra-0.0.0.dev505/omextra/io/buffers/adapters.py +294 -0
- omextra-0.0.0.dev505/omextra/io/buffers/all.py +63 -0
- omextra-0.0.0.dev505/omextra/io/buffers/errors.py +54 -0
- omextra-0.0.0.dev505/omextra/io/buffers/framing.py +187 -0
- omextra-0.0.0.dev505/omextra/io/buffers/linear.py +202 -0
- omextra-0.0.0.dev505/omextra/io/buffers/reading.py +149 -0
- omextra-0.0.0.dev505/omextra/io/buffers/scanning.py +110 -0
- omextra-0.0.0.dev505/omextra/io/buffers/segmented.py +637 -0
- omextra-0.0.0.dev505/omextra/io/buffers/types.py +263 -0
- omextra-0.0.0.dev505/omextra/io/buffers/utils.py +76 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/_dataclasses.py +8 -8
- omextra-0.0.0.dev505/omextra/text/antlr/cli/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505/omextra.egg-info}/PKG-INFO +2 -2
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra.egg-info/SOURCES.txt +12 -0
- omextra-0.0.0.dev505/omextra.egg-info/requires.txt +1 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/pyproject.toml +3 -2
- omextra-0.0.0.dev503/omextra.egg-info/requires.txt +0 -1
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/LICENSE +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/MANIFEST.in +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/README.md +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/.omlish-manifests.json +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/README.md +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/__about__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/LICENSE +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/all.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/api.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/core.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/events.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/files.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/runner.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bluelet/sockets.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/asyncs/bridge.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/collections/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/collections/hamt/LICENSE +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/collections/hamt/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/collections/hamt/_hamt.c +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/defs.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/dynamic.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/LICENSE +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/ast.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/errors.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/parsing.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/scanning.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/goyaml/tokens.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/Json.g4 +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/_antlr/JsonLexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/_antlr/JsonListener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/_antlr/JsonParser.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/_antlr/JsonVisitor.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json/_antlr/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/Json5.g4 +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/_antlr/Json5Lexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/_antlr/Json5Listener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/_antlr/Json5Parser.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/_antlr/Json5Visitor.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/_antlr/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/formats/json5/parsing.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/io/__init__.py +0 -0
- {omextra-0.0.0.dev503/omextra/specs → omextra-0.0.0.dev505/omextra/io/buffers}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/io/trampoline.py +0 -0
- {omextra-0.0.0.dev503/omextra/specs/proto → omextra-0.0.0.dev505/omextra/specs}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/Protobuf3.g4 +0 -0
- {omextra-0.0.0.dev503/omextra/specs/proto/_antlr → omextra-0.0.0.dev505/omextra/specs/proto}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/_antlr/Protobuf3Lexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/_antlr/Protobuf3Listener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/_antlr/Protobuf3Parser.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/_antlr/Protobuf3Visitor.py +0 -0
- {omextra-0.0.0.dev503/omextra/sql → omextra-0.0.0.dev505/omextra/specs/proto/_antlr}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/nodes.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/specs/proto/parsing.py +0 -0
- {omextra-0.0.0.dev503/omextra/sql/parsing → omextra-0.0.0.dev505/omextra/sql}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/Minisql.g4 +0 -0
- {omextra-0.0.0.dev503/omextra/sql/parsing/_antlr → omextra-0.0.0.dev505/omextra/sql/parsing}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/_antlr/MinisqlLexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/_antlr/MinisqlListener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/_antlr/MinisqlParser.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/_antlr/MinisqlVisitor.py +0 -0
- {omextra-0.0.0.dev503/omextra/text → omextra-0.0.0.dev505/omextra/sql/parsing/_antlr}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/sql/parsing/parsing.py +0 -0
- {omextra-0.0.0.dev503/omextra/text/abnf/docs → omextra-0.0.0.dev505/omextra/text}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/LICENSE +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/base.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/core.py +0 -0
- {omextra-0.0.0.dev503/omextra/text/antlr/cli → omextra-0.0.0.dev505/omextra/text/abnf/docs}/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/docs/rfc5234.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/docs/rfc7405.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/errors.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/grammars.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/internal.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/matches.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/meta.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/ops.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/opto.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/parsing.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/utils.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/abnf/visitors.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/BufferedTokenStream.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/CommonTokenFactory.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/CommonTokenStream.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/FileStream.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/InputStream.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/IntervalSet.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/LICENSE.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/LL1Analyzer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/Lexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/ListTokenSource.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/Parser.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/ParserInterpreter.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/ParserRuleContext.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/PredictionContext.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/Recognizer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/RuleContext.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/StdinStream.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/Token.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/TokenStreamRewriter.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/Utils.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/_all.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/_pygrun.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATN.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNConfig.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNConfigSet.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNDeserializationOptions.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNDeserializer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNSimulator.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNState.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ATNType.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/LexerATNSimulator.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/LexerAction.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/LexerActionExecutor.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/ParserATNSimulator.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/PredictionMode.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/SemanticContext.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/Transition.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/atn/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/dfa/DFA.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/dfa/DFASerializer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/dfa/DFAState.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/dfa/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/error/DiagnosticErrorListener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/error/ErrorListener.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/error/ErrorStrategy.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/error/Errors.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/error/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/Chunk.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/ParseTreeMatch.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/ParseTreePattern.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/ParseTreePatternMatcher.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/RuleTagToken.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/TokenTagToken.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/Tree.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/Trees.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/tree/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/xpath/XPath.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/xpath/XPathLexer.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/_runtime/xpath/__init__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/cli/__main__.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/cli/cli.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/cli/consts.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/cli/gen.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/delimit.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/dot.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/errors.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/input.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/parsing.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/runtime.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra/text/antlr/utils.py +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra.egg-info/dependency_links.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra.egg-info/entry_points.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/omextra.egg-info/top_level.txt +0 -0
- {omextra-0.0.0.dev503 → omextra-0.0.0.dev505}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omextra
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev505
|
|
4
4
|
Summary: omextra
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Requires-Python: >=3.13
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: omlish==0.0.0.
|
|
17
|
+
Requires-Dist: omlish==0.0.0.dev505
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
20
|
# Overview
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Buffer System Design Notes — Historical Architecture Summary
|
|
2
|
+
|
|
3
|
+
This document captures the design rationale, constraints, and evolution of a **general-purpose byte buffer subsystem**
|
|
4
|
+
intended to support a **Netty-like, pipeline-oriented protocol toolkit in Python**. It is meant to serve as durable
|
|
5
|
+
architectural context for future development and onboarding, not as API documentation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Problem Statement & Goals
|
|
10
|
+
|
|
11
|
+
The goal was to design a **foundational buffer layer** suitable for building protocol stacks in Python with the
|
|
12
|
+
following constraints:
|
|
13
|
+
|
|
14
|
+
- **Pure Python, no external dependencies**, compatible with Python 3.8+
|
|
15
|
+
- **Embeddable**: usable inside arbitrary runtimes (sync, async, fork-only, threaded, event-loop driven, or none at all)
|
|
16
|
+
- **Independent of asyncio / async-await**, but adaptable to them
|
|
17
|
+
- **Composable** and **pipeline-friendly**, favoring small chained transforms over large monolithic objects
|
|
18
|
+
- **Correctness and resilience first** (OOM avoidance, bounded growth, clear error signaling)
|
|
19
|
+
- **Low copy where possible**, but never at the expense of clarity or safety
|
|
20
|
+
- Explicitly **not performance-maximal**; Python-appropriate efficiency is sufficient
|
|
21
|
+
|
|
22
|
+
The buffer layer is treated as *infrastructure*: it must be robust enough that higher-level protocol code never needs to
|
|
23
|
+
second-guess its behavior.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Design Philosophy
|
|
28
|
+
|
|
29
|
+
### Composition over inheritance Rather than building large stateful readers/writers, the design favors:
|
|
30
|
+
- Small buffer objects with explicit operations (`find`, `split_to`, `coalesce`, etc.)
|
|
31
|
+
- Stateless helper functions layered atop buffers (binary reads, framing, decoding)
|
|
32
|
+
- Pipeline codecs that operate purely on buffers + views
|
|
33
|
+
|
|
34
|
+
### Explicit boundaries
|
|
35
|
+
- **Copy boundaries are explicit** (`tobytes`, `read_bytes`)
|
|
36
|
+
- **Mutation vs. presentation** is clearly distinguished
|
|
37
|
+
- **Ephemeral vs. stable views** are explicitly defined
|
|
38
|
+
|
|
39
|
+
### “Everything needs a timeout / limit” All operations that can grow memory unboundedly must be limitable:
|
|
40
|
+
- Per-buffer `max_bytes`
|
|
41
|
+
- Per-frame `max_size`
|
|
42
|
+
- Clear error types when limits are exceeded
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Core Abstractions
|
|
47
|
+
|
|
48
|
+
### BytesBuffer (conceptual) A readable byte container with:
|
|
49
|
+
- Logical length (`__len__`)
|
|
50
|
+
- Search (`find`, `rfind`) — *stream-correct*, across internal segmentation
|
|
51
|
+
- Non-consuming inspection (`peek`, `segments`)
|
|
52
|
+
- Consuming operations (`advance`, `split_to`)
|
|
53
|
+
- Contiguity guarantee (`coalesce(n)`)
|
|
54
|
+
|
|
55
|
+
Buffers are **not sequences** and are **not random-access containers** in the general sense:
|
|
56
|
+
- They may support indexed access incidentally
|
|
57
|
+
- But they are optimized for *prefix-oriented, streaming access*
|
|
58
|
+
- They are not intended for arbitrary slicing or mutation
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 4. Two Concrete Buffer Backends
|
|
63
|
+
|
|
64
|
+
### SegmentedBytesBuffer A list-of-segments design (bytes / bytearray chunks):
|
|
65
|
+
|
|
66
|
+
**Strengths**
|
|
67
|
+
- Avoids pathological “large buffer pinned by tiny tail”
|
|
68
|
+
- Stable zero-copy views for `split_to`
|
|
69
|
+
- Natural fit for network chunking and streaming
|
|
70
|
+
|
|
71
|
+
**Tradeoffs**
|
|
72
|
+
- Search and coalescing require careful logic
|
|
73
|
+
- Segment count must be managed (coalescing / heuristics)
|
|
74
|
+
|
|
75
|
+
### LinearBytesBuffer A single `bytearray` + read/write indices:
|
|
76
|
+
|
|
77
|
+
**Strengths**
|
|
78
|
+
- Fast scanning and header parsing
|
|
79
|
+
- Naturally contiguous prefix
|
|
80
|
+
- Efficient `reserve` / `commit`
|
|
81
|
+
|
|
82
|
+
**Tradeoffs**
|
|
83
|
+
- `split_to` must copy to keep views stable
|
|
84
|
+
- Needs compaction heuristics to avoid growth from head advancement
|
|
85
|
+
|
|
86
|
+
These two backends intentionally cover different workload shapes; both conform to the same conceptual interface.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 5. Views & Lifetime Rules
|
|
91
|
+
|
|
92
|
+
### BytesView / SegmentedBytesView Objects returned from `split_to`:
|
|
93
|
+
- Represent bytes **removed** from the buffer
|
|
94
|
+
- Must remain **stable forever**
|
|
95
|
+
- May internally reference original segments or copies
|
|
96
|
+
|
|
97
|
+
### Ephemeral views Returned from:
|
|
98
|
+
- `peek`
|
|
99
|
+
- `segments`
|
|
100
|
+
- `coalesce`
|
|
101
|
+
|
|
102
|
+
Rules:
|
|
103
|
+
- Valid only until the next buffer mutation
|
|
104
|
+
- Never safe to hold across writes / advances
|
|
105
|
+
- Always exposed as `memoryview`
|
|
106
|
+
|
|
107
|
+
This mirrors real-world behavior in systems like Netty (retained slices) but without refcounting.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 6. Coalescing Semantics (Key Design Point)
|
|
112
|
+
|
|
113
|
+
### `coalesce(n)` Guarantees the first `n` readable bytes are contiguous.
|
|
114
|
+
|
|
115
|
+
- **Non-consuming**
|
|
116
|
+
- May restructure internal storage
|
|
117
|
+
- Copies *only if necessary*, and only the minimal prefix
|
|
118
|
+
- Disallowed while a reservation is outstanding
|
|
119
|
+
|
|
120
|
+
Rationale:
|
|
121
|
+
- Python lacks efficient per-byte iteration
|
|
122
|
+
- Many operations (binary parsing, decoding) require contiguity
|
|
123
|
+
- Copying should happen *close to storage*, not in higher-level code
|
|
124
|
+
|
|
125
|
+
Unlike Netty or Tokio (where coalescing is implicit or pattern-based), this is an explicit primitive.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 7. Reservation Model (`reserve` / `commit`)
|
|
130
|
+
|
|
131
|
+
Designed to support:
|
|
132
|
+
- Zero-copy reads from drivers
|
|
133
|
+
- Predictable memory behavior
|
|
134
|
+
|
|
135
|
+
Rules:
|
|
136
|
+
- Only one outstanding reservation at a time
|
|
137
|
+
- While reserved:
|
|
138
|
+
- No reshaping operations allowed
|
|
139
|
+
- No coalescing, splitting, advancing, or writing
|
|
140
|
+
- `commit(n)` appends exactly `n` bytes
|
|
141
|
+
- Reservation buffers are **temporary**, not views into live storage
|
|
142
|
+
- Avoids Python `BufferError` pinning issues
|
|
143
|
+
|
|
144
|
+
This design was informed directly by pitfalls discovered when using `BytesIO.getbuffer()`.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 8. Limits & Error Taxonomy
|
|
149
|
+
|
|
150
|
+
### Core error types
|
|
151
|
+
- `NeedMoreData`: insufficient bytes, retry later
|
|
152
|
+
- `BufferTooLarge`: buffer growth exceeded cap
|
|
153
|
+
- `FrameTooLarge`: single frame exceeded size limit
|
|
154
|
+
- `OutstandingReserve` / `NoOutstandingReserve`: invalid state transitions
|
|
155
|
+
|
|
156
|
+
Design choice:
|
|
157
|
+
- Limit errors subclass `ValueError`
|
|
158
|
+
- State errors subclass `RuntimeError`
|
|
159
|
+
- Preserves compatibility while enabling semantic distinction
|
|
160
|
+
|
|
161
|
+
### Limits
|
|
162
|
+
- `max_bytes` on buffers (optional, default None)
|
|
163
|
+
- `max_size` on framers/codecs
|
|
164
|
+
|
|
165
|
+
These limits are enforced eagerly to prevent memory exhaustion.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 9. Framing & Search
|
|
170
|
+
|
|
171
|
+
### Longest-match delimiter framing A dedicated codec layer implements:
|
|
172
|
+
- Overlapping delimiters (`\r` vs `\r\n`)
|
|
173
|
+
- Longest-match semantics
|
|
174
|
+
- Deferred emission when ambiguity exists
|
|
175
|
+
- Explicit `final=True` flush at EOF
|
|
176
|
+
|
|
177
|
+
Key insight: > Delimiter resolution must live *above* the buffer but *below* protocol logic.
|
|
178
|
+
|
|
179
|
+
The buffer’s `find/rfind` remain simple, single-needle primitives; framing logic resolves ambiguity.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 10. Binary Read Helpers
|
|
184
|
+
|
|
185
|
+
Implemented as **pure functions atop buffers**, not methods:
|
|
186
|
+
- `peek_exact`
|
|
187
|
+
- `take`
|
|
188
|
+
- `read_bytes`
|
|
189
|
+
- `read_u8`, `read_u16_be`, `read_u32_le`, etc.
|
|
190
|
+
|
|
191
|
+
All rely on:
|
|
192
|
+
- `coalesce(n)` for contiguity
|
|
193
|
+
- `advance(n)` for consumption
|
|
194
|
+
- `NeedMoreData` for retry signaling
|
|
195
|
+
|
|
196
|
+
This keeps the buffer surface small while enabling rich protocol parsing.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 11. File-Like Adapters & Interop
|
|
201
|
+
|
|
202
|
+
Adapters exist to bridge:
|
|
203
|
+
- File-like objects → buffers
|
|
204
|
+
- Buffers → file-like readers/writers
|
|
205
|
+
|
|
206
|
+
Key properties:
|
|
207
|
+
- Policy-driven behavior (`raise`, `return_partial`, `block`)
|
|
208
|
+
- No reliance on `io` abstractions in the core
|
|
209
|
+
- Explicit handling of `BytesIO.getbuffer()` pinning hazards
|
|
210
|
+
|
|
211
|
+
Interop is intentionally *ugly but isolated*; the core remains clean.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 12. Relation to Other Ecosystems
|
|
216
|
+
|
|
217
|
+
### Netty
|
|
218
|
+
- Netty’s buffer complexity is split across many types (heap, direct, composite)
|
|
219
|
+
- This design captures the *behavioral essence* (segmentation, coalescing, stable views) without refcounting
|
|
220
|
+
|
|
221
|
+
### Tokio / Rust
|
|
222
|
+
- Similar semantics exist implicitly (`Buf::chunk`, `advance`)
|
|
223
|
+
- Python benefits from explicit coalescing due to higher call overhead
|
|
224
|
+
|
|
225
|
+
### Twisted / asyncio
|
|
226
|
+
- Older designs rely on callbacks and file-like objects
|
|
227
|
+
- This system is explicitly designed *post-async/await*, but not dependent on it
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 13. What This Enables Next
|
|
232
|
+
|
|
233
|
+
With the buffer layer stabilized, higher-level work can proceed safely:
|
|
234
|
+
- Length-prefixed framing
|
|
235
|
+
- HTTP/1 streaming parsing
|
|
236
|
+
- Binary protocol codecs
|
|
237
|
+
- Pipeline lifecycle (EOF, errors, close)
|
|
238
|
+
- Transport drivers (async, sync, custom)
|
|
239
|
+
|
|
240
|
+
The buffer layer is now considered **foundationally complete**: additional features should be justified by concrete
|
|
241
|
+
protocol needs, not speculation.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 14. Key Takeaway
|
|
246
|
+
|
|
247
|
+
This buffer system is intentionally:
|
|
248
|
+
- **Explicit**, not magical
|
|
249
|
+
- **Predictable**, not clever
|
|
250
|
+
- **Composable**, not monolithic
|
|
251
|
+
|
|
252
|
+
It trades a small amount of convenience for long-term correctness and clarity — exactly what is needed to support a
|
|
253
|
+
robust, Netty-like protocol toolkit in Python.
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# ruff: noqa: UP006 UP007 UP045
|
|
2
|
+
# @omlish-lite
|
|
3
|
+
import io
|
|
4
|
+
import time
|
|
5
|
+
import typing as ta
|
|
6
|
+
|
|
7
|
+
from .errors import NeedMoreData
|
|
8
|
+
from .segmented import SegmentedBytesView
|
|
9
|
+
from .types import BytesLike
|
|
10
|
+
from .types import BytesView
|
|
11
|
+
from .types import BytesViewLike
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FileLikeRawBytesReader:
|
|
18
|
+
"""
|
|
19
|
+
Adapter: file-like object -> RawBytesReader-style `read1`.
|
|
20
|
+
|
|
21
|
+
This is intentionally permissive: it duck-types common file-like APIs.
|
|
22
|
+
|
|
23
|
+
Notes:
|
|
24
|
+
- If the object has `read1`, we use it.
|
|
25
|
+
- Otherwise we fall back to `read`.
|
|
26
|
+
- This is a *raw* reader: it makes no buffering guarantees beyond what the wrapped object provides.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, f: ta.Any) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
self._f = f
|
|
33
|
+
|
|
34
|
+
def read1(self, n: int = -1, /) -> bytes:
|
|
35
|
+
f = self._f
|
|
36
|
+
if hasattr(f, 'read1'):
|
|
37
|
+
return ta.cast(bytes, f.read1(n))
|
|
38
|
+
return ta.cast(bytes, f.read(n))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FileLikeBufferedBytesReader(FileLikeRawBytesReader):
|
|
42
|
+
"""
|
|
43
|
+
Adapter: file-like object -> BufferedBytesReader-style `read/readall`.
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
- Uses `readall` if present; otherwise uses `read()` with `-1`.
|
|
47
|
+
- Does not impose additional buffering; it reflects the wrapped object's behavior.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def read(self, n: int = -1, /) -> bytes:
|
|
51
|
+
return ta.cast(bytes, self._f.read(n))
|
|
52
|
+
|
|
53
|
+
def readall(self) -> bytes:
|
|
54
|
+
f = self._f
|
|
55
|
+
if hasattr(f, 'readall'):
|
|
56
|
+
return ta.cast(bytes, f.readall())
|
|
57
|
+
return ta.cast(bytes, f.read())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BytesBufferReaderAdapter:
|
|
61
|
+
"""
|
|
62
|
+
Adapter: BytesBuffer -> file-like reader methods (`read1`, `read`, `readall`).
|
|
63
|
+
|
|
64
|
+
This adapter is policy-driven for how it behaves when insufficient bytes are available. The core buffer is
|
|
65
|
+
intentionally non-blocking; blocking behavior (if desired) must be provided via a `fill` callback that supplies more
|
|
66
|
+
bytes into the buffer.
|
|
67
|
+
|
|
68
|
+
`policy`:
|
|
69
|
+
- 'raise': raise NeedMoreData if fewer than `n` bytes are available (for n >= 0).
|
|
70
|
+
- 'return_partial': return whatever is available (possibly b'') up to `n`.
|
|
71
|
+
- 'block': repeatedly call `fill()` until satisfied or until `fill()` signals EOF.
|
|
72
|
+
|
|
73
|
+
`fill`:
|
|
74
|
+
- Callable that writes more bytes into the underlying MutableBytesBuffer and returns:
|
|
75
|
+
* True -> made progress / more data may be available
|
|
76
|
+
* False -> EOF / no more data will arrive
|
|
77
|
+
- Only used when policy == 'block'.
|
|
78
|
+
|
|
79
|
+
This adapter exists for interop with legacy code that expects file-like objects, but the core design remains
|
|
80
|
+
independent from `io` and blocking semantics.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
buf: ta.Any,
|
|
86
|
+
*,
|
|
87
|
+
policy: ta.Literal['raise', 'return_partial', 'block'] = 'raise',
|
|
88
|
+
fill: ta.Optional[ta.Callable[[], bool]] = None,
|
|
89
|
+
block_sleep: ta.Union[ta.Callable[[], None], float, None] = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__()
|
|
92
|
+
|
|
93
|
+
self._buf = buf
|
|
94
|
+
self._policy = policy
|
|
95
|
+
self._fill = fill
|
|
96
|
+
self._block_sleep = block_sleep
|
|
97
|
+
|
|
98
|
+
if self._policy == 'block' and self._fill is None:
|
|
99
|
+
raise ValueError('policy=block requires fill')
|
|
100
|
+
|
|
101
|
+
def _on_block(self) -> None:
|
|
102
|
+
if (bs := self._block_sleep) is None:
|
|
103
|
+
return
|
|
104
|
+
elif callable(bs):
|
|
105
|
+
bs()
|
|
106
|
+
else:
|
|
107
|
+
time.sleep(bs)
|
|
108
|
+
|
|
109
|
+
def read1(self, n: int = -1, /) -> bytes:
|
|
110
|
+
return self.read(n)
|
|
111
|
+
|
|
112
|
+
def read(self, n: int = -1, /) -> bytes:
|
|
113
|
+
buf = self._buf
|
|
114
|
+
|
|
115
|
+
if n is None or n < 0:
|
|
116
|
+
return self.readall()
|
|
117
|
+
|
|
118
|
+
if n == 0:
|
|
119
|
+
return b''
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
ln = len(buf)
|
|
123
|
+
if ln >= n:
|
|
124
|
+
v = buf.split_to(n)
|
|
125
|
+
return ta.cast(bytes, v.tobytes())
|
|
126
|
+
|
|
127
|
+
if self._policy == 'return_partial':
|
|
128
|
+
if ln == 0:
|
|
129
|
+
return b''
|
|
130
|
+
v = buf.split_to(ln)
|
|
131
|
+
return ta.cast(bytes, v.tobytes())
|
|
132
|
+
|
|
133
|
+
if self._policy == 'raise':
|
|
134
|
+
raise NeedMoreData
|
|
135
|
+
|
|
136
|
+
# block
|
|
137
|
+
if not ta.cast('ta.Callable[[], bool]', self._fill)():
|
|
138
|
+
# EOF
|
|
139
|
+
if ln == 0:
|
|
140
|
+
return b''
|
|
141
|
+
v = buf.split_to(ln)
|
|
142
|
+
return ta.cast(bytes, v.tobytes())
|
|
143
|
+
|
|
144
|
+
self._on_block()
|
|
145
|
+
|
|
146
|
+
def readall(self) -> bytes:
|
|
147
|
+
buf = self._buf
|
|
148
|
+
parts: ta.List[bytes] = []
|
|
149
|
+
|
|
150
|
+
while True:
|
|
151
|
+
ln = len(buf)
|
|
152
|
+
if ln:
|
|
153
|
+
v = buf.split_to(ln)
|
|
154
|
+
parts.append(ta.cast(bytes, v.tobytes()))
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if self._policy == 'block':
|
|
158
|
+
if not ta.cast('ta.Callable[[], bool]', self._fill)():
|
|
159
|
+
break
|
|
160
|
+
self._on_block()
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
return b''.join(parts)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class BytesBufferWriterAdapter:
|
|
169
|
+
"""
|
|
170
|
+
Adapter: file-like writer sink <- BytesBuffer / bytes-like.
|
|
171
|
+
|
|
172
|
+
This is intentionally small and dumb: it exists to bridge into code expecting a `.write(...)`
|
|
173
|
+
method on an object.
|
|
174
|
+
|
|
175
|
+
If given a BytesView-like object, it writes segment-by-segment to avoid materializing copies
|
|
176
|
+
when the sink can accept multiple writes efficiently.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self, f: ta.Any) -> None:
|
|
180
|
+
super().__init__()
|
|
181
|
+
|
|
182
|
+
self._f = f
|
|
183
|
+
|
|
184
|
+
def write(self, data: ta.Any) -> int:
|
|
185
|
+
f = self._f
|
|
186
|
+
|
|
187
|
+
if isinstance(data, (bytes, bytearray, memoryview)):
|
|
188
|
+
b = data.tobytes() if isinstance(data, memoryview) else bytes(data)
|
|
189
|
+
return ta.cast(int, f.write(b))
|
|
190
|
+
|
|
191
|
+
if isinstance(data, BytesViewLike):
|
|
192
|
+
total = 0
|
|
193
|
+
for mv in data.segments():
|
|
194
|
+
total += ta.cast(int, f.write(bytes(mv)))
|
|
195
|
+
return total
|
|
196
|
+
|
|
197
|
+
if isinstance(data, BytesView):
|
|
198
|
+
b = data.tobytes()
|
|
199
|
+
return ta.cast(int, f.write(b))
|
|
200
|
+
|
|
201
|
+
raise TypeError(data)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class BytesIoBytesBuffer:
|
|
205
|
+
"""
|
|
206
|
+
BytesBuffer/MutableBytesBuffer implementation backed by `io.BytesIO`, using `getbuffer()`.
|
|
207
|
+
|
|
208
|
+
This exists primarily for interoperability with code that already produces/consumes `BytesIO`,
|
|
209
|
+
and to demonstrate how `getbuffer()` can expose a non-copying `memoryview` of internal storage.
|
|
210
|
+
|
|
211
|
+
Caveat (important):
|
|
212
|
+
- Any exported `memoryview` from `BytesIO.getbuffer()` can pin the BytesIO against resizing.
|
|
213
|
+
- If a caller holds onto a view and we attempt to grow/resize internally, `BytesIO` may raise
|
|
214
|
+
`BufferError`. We surface that as a RuntimeError.
|
|
215
|
+
|
|
216
|
+
This backing is therefore best suited for controlled scenarios; it is *not* the recommended
|
|
217
|
+
default buffer backend for pynetty (segmented/bytearray backends are more predictable).
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(self) -> None:
|
|
221
|
+
super().__init__()
|
|
222
|
+
|
|
223
|
+
self._bio = io.BytesIO()
|
|
224
|
+
self._rpos = 0
|
|
225
|
+
|
|
226
|
+
# reserve/commit state
|
|
227
|
+
self._resv: ta.Optional[bytearray] = None
|
|
228
|
+
self._resv_len = 0
|
|
229
|
+
|
|
230
|
+
def __len__(self) -> int:
|
|
231
|
+
return self._bio.getbuffer().nbytes - self._rpos
|
|
232
|
+
|
|
233
|
+
def peek(self) -> memoryview:
|
|
234
|
+
mv = self._bio.getbuffer()
|
|
235
|
+
return mv[self._rpos:]
|
|
236
|
+
|
|
237
|
+
def segments(self) -> ta.Sequence[memoryview]:
|
|
238
|
+
mv = self._bio.getbuffer()
|
|
239
|
+
seg = mv[self._rpos:]
|
|
240
|
+
return (seg,) if len(seg) else ()
|
|
241
|
+
|
|
242
|
+
def advance(self, n: int, /) -> None:
|
|
243
|
+
if n < 0 or n > len(self):
|
|
244
|
+
raise ValueError(n)
|
|
245
|
+
self._rpos += n
|
|
246
|
+
# Optional compaction heuristic: if we've consumed a lot, rebuild a smaller BytesIO.
|
|
247
|
+
# This may fail if someone holds a getbuffer() view (BufferError).
|
|
248
|
+
if self._rpos and self._rpos >= 65536 and self._rpos >= (self._bio.getbuffer().nbytes // 2):
|
|
249
|
+
try:
|
|
250
|
+
remaining = self._bio.getbuffer()[self._rpos:].tobytes()
|
|
251
|
+
self._bio = io.BytesIO(remaining)
|
|
252
|
+
self._rpos = 0
|
|
253
|
+
except BufferError as e:
|
|
254
|
+
raise RuntimeError('BytesIO buffer is pinned by an exported view') from e
|
|
255
|
+
|
|
256
|
+
def split_to(self, n: int, /) -> SegmentedBytesView:
|
|
257
|
+
if n < 0 or n > len(self):
|
|
258
|
+
raise ValueError(n)
|
|
259
|
+
mv = self._bio.getbuffer()
|
|
260
|
+
out = mv[self._rpos:self._rpos + n]
|
|
261
|
+
self._rpos += n
|
|
262
|
+
return SegmentedBytesView((out,))
|
|
263
|
+
|
|
264
|
+
def write(self, data: BytesLike, /) -> None:
|
|
265
|
+
if not data:
|
|
266
|
+
return
|
|
267
|
+
if isinstance(data, memoryview):
|
|
268
|
+
data = data.tobytes()
|
|
269
|
+
try:
|
|
270
|
+
self._bio.seek(0, io.SEEK_END)
|
|
271
|
+
self._bio.write(ta.cast(bytes, data))
|
|
272
|
+
except BufferError as e:
|
|
273
|
+
raise RuntimeError('BytesIO buffer is pinned by an exported view') from e
|
|
274
|
+
|
|
275
|
+
def reserve(self, n: int, /) -> memoryview:
|
|
276
|
+
if n < 0:
|
|
277
|
+
raise ValueError(n)
|
|
278
|
+
if self._resv is not None:
|
|
279
|
+
raise RuntimeError('outstanding reserve')
|
|
280
|
+
b = bytearray(n)
|
|
281
|
+
self._resv = b
|
|
282
|
+
self._resv_len = n
|
|
283
|
+
return memoryview(b)
|
|
284
|
+
|
|
285
|
+
def commit(self, n: int, /) -> None:
|
|
286
|
+
if self._resv is None:
|
|
287
|
+
raise RuntimeError('no outstanding reserve')
|
|
288
|
+
if n < 0 or n > self._resv_len:
|
|
289
|
+
raise ValueError(n)
|
|
290
|
+
b = self._resv
|
|
291
|
+
self._resv = None
|
|
292
|
+
self._resv_len = 0
|
|
293
|
+
if n:
|
|
294
|
+
self.write(memoryview(b)[:n])
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from .adapters import ( # noqa
|
|
2
|
+
FileLikeRawBytesReader,
|
|
3
|
+
FileLikeBufferedBytesReader,
|
|
4
|
+
BytesBufferReaderAdapter,
|
|
5
|
+
BytesBufferWriterAdapter,
|
|
6
|
+
BytesIoBytesBuffer,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .errors import ( # noqa
|
|
10
|
+
BuffersError,
|
|
11
|
+
NeedMoreData,
|
|
12
|
+
BufferLimitError,
|
|
13
|
+
BufferTooLarge,
|
|
14
|
+
FrameTooLarge,
|
|
15
|
+
BufferStateError,
|
|
16
|
+
OutstandingReserve,
|
|
17
|
+
NoOutstandingReserve,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .framing import ( # noqa
|
|
21
|
+
LongestMatchDelimiterFramer,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .linear import ( # noqa
|
|
25
|
+
LinearBytesBuffer,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from .reading import ( # noqa
|
|
29
|
+
peek_u8,
|
|
30
|
+
read_u8,
|
|
31
|
+
peek_u16_be,
|
|
32
|
+
read_u16_be,
|
|
33
|
+
peek_u16_le,
|
|
34
|
+
read_u16_le,
|
|
35
|
+
peek_u32_be,
|
|
36
|
+
read_u32_be,
|
|
37
|
+
peek_u32_le,
|
|
38
|
+
read_u32_le,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from .scanning import ( # noqa
|
|
42
|
+
ScanningBytesBuffer,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
from .segmented import ( # noqa
|
|
46
|
+
SegmentedBytesView,
|
|
47
|
+
SegmentedBytesBuffer,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from .types import ( # noqa
|
|
51
|
+
BytesLike,
|
|
52
|
+
|
|
53
|
+
BytesView,
|
|
54
|
+
BytesBuffer,
|
|
55
|
+
MutableBytesBuffer,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
from .utils import ( # noqa
|
|
59
|
+
can_bytes,
|
|
60
|
+
iter_bytes_segments,
|
|
61
|
+
to_bytes,
|
|
62
|
+
bytes_len,
|
|
63
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @omlish-lite
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BuffersError(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NeedMoreData(BuffersError): # noqa
|
|
12
|
+
"""
|
|
13
|
+
Raised when an operation cannot complete because insufficient bytes are currently buffered.
|
|
14
|
+
|
|
15
|
+
This is intentionally distinct from EOF: it means "try again after feeding more bytes".
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BufferLimitError(ValueError, BuffersError): # noqa
|
|
20
|
+
"""
|
|
21
|
+
Base class for buffer/framing limit violations.
|
|
22
|
+
|
|
23
|
+
Subclasses inherit from ValueError so existing tests expecting ValueError continue to pass.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BufferTooLarge(BufferLimitError): # noqa
|
|
28
|
+
"""
|
|
29
|
+
Buffered data exceeded a configured cap without finding a boundary that would allow progress.
|
|
30
|
+
|
|
31
|
+
Typically indicates an unframed stream, a missing delimiter, or an upstream not enforcing limits.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FrameTooLarge(BufferLimitError): # noqa
|
|
36
|
+
"""
|
|
37
|
+
A single decoded frame (payload before its boundary delimiter/length) exceeded a configured max size.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BufferStateError(RuntimeError, BuffersError): # noqa
|
|
42
|
+
"""
|
|
43
|
+
Base class for invalid buffer state transitions (e.g., coalescing while a reservation is outstanding).
|
|
44
|
+
|
|
45
|
+
Subclasses inherit from RuntimeError so existing tests expecting RuntimeError continue to pass.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OutstandingReserve(BufferStateError): # noqa
|
|
50
|
+
"""A reserve() is outstanding; an operation requiring stable storage cannot proceed."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NoOutstandingReserve(BufferStateError): # noqa
|
|
54
|
+
"""commit() was called without a preceding reserve()."""
|