charded 0.1.0__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.
charded-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: charded
3
+ Version: 0.1.0
4
+ Summary: charded
5
+ Author-email: Alex Kalaverin <alex@kalaver.in>
6
+ Project-URL: Homepage, https://kalaver.in
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: <3.13,>=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: charset-normalizer>=3.4.2
18
+ Requires-Dist: kain<0.2.0,>=0.1.3
19
+ Requires-Dist: python-magic>=0.4.27
20
+
21
+ # Simple project for Python 3.10+
22
+ #### All-in-one repository with supervisor, crontab, linters and many useful tools
23
+
24
+ **Version control** is handled using [Astral UV](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer) tool. When building the image, uv is sourced from the official repository by copying the binary. Installation on a developer's machine can be done in various ways, which we'll cover shortly.
25
+
26
+ **Managing the interpreter version**, project environment variables, and setting up the virtual environment is done with the [Mise](https://mise.jdx.dev/installing-mise.html) tool. It automatically install any interpreter version by reading it from the project description and/or the version bound by uv. It can also fetch the appropriate uv binary for the platform and architecture.
27
+
28
+ ## How to install required tools?
29
+
30
+ #### A. Quick start for test
31
+
32
+ Therefore, the quickest and most minimal way to get touch is to install mise on your system and prepare tools with mise, **not for development**:
33
+
34
+ 1. `brew install mise`
35
+
36
+ That's all, go to **Shell configuration** section.
37
+
38
+ #### B. Engineer full-featured setup
39
+
40
+ **True engineer way** it's prepare rust environment, build mise and configure shell:
41
+
42
+ 1. Install Cargo via [Rustup](https://doc.rust-lang.org/book/ch01-01-installation.html).
43
+ 2. Do not forget add cargo path for your shell:
44
+ - `export PATH="~/.cargo/bin:$PATH"`
45
+ 3. Install sccache to avoid electricity bills:
46
+ - `cargo install sccache`
47
+ 4. Activate sccache:
48
+ - `export RUSTC_WRAPPER="~/.cargo/bin/sccache"` (and add it to your shell)
49
+ 5. Install cargo packages updater and mise:
50
+ - `cargo install cargo-update mise`
51
+ 6. Install uv:
52
+ - `mise install uv@latest && mise use -g uv@latest`
53
+ 7. That's all, you have last version of optimized tools; for update all packages just run sometime:
54
+ - `rustup update && cargo install-update --all`
55
+
56
+ ### Shell configuration
57
+
58
+ 1. **Mise provide dotenv functionality** (automatic read per-project environment variables from .env file and from .mise.toml config) from the box with batteries, but your shell must have entry hook, add to your shell it, example for zsh (your can replace it for bash, fish, etc):
59
+ - `eval "$(mise activate zsh)"`
60
+ 2. Also you can want using autocompletion, same story for zsh:
61
+ - `eval "$(mise completion zsh && uv generate-shell-completion zsh)"`
62
+ 3. Restart your shell session:
63
+ - `exec "$SHELL"`
64
+
65
+ ### Kickstart
66
+
67
+ 1. Go to project root.
68
+ 2. Just run `make`:
69
+ - mise will mark project directory as trusted
70
+ - mise copy sample development environment variables to .env
71
+ - mise grab environment variables defined in project .env, evaluate it and provide to current shell session
72
+ - mise checks what project python versions is installed, otherwise download and install it
73
+ - uv make virtual environment in project root (`uv venv`)
74
+ - uv read project packages list, download, install and link it (via `uv sync` run, read Makefile)
75
+ - uv install pre-commit and pre-push hooks
76
+
77
+ ## Work with project
78
+
79
+ ### Warning about pip
80
+
81
+ **NEVER CALL pip, NEVER!** Instead it use native uv calls, [read uv manual](https://docs.astral.sh/uv/guides/projects/#managing-dependencies), it's very easy, for example:
82
+
83
+ 1. Set or change python version (when python 3.11 already installed), before run do not forget change your python version in pyproject.toml:
84
+ - `uv python pin 3.11 && make sync`
85
+
86
+ 2. If python 3.11 isn't installed, run `mise install python@3.11` and mise download and install python 3.11, recreate virtual environment with 3.11 context. Do not forget to pin python version by uv from previous step (and, may be you need to update your pyproject.toml).
87
+
88
+ 2. Just add new dependency:
89
+ - `uv add phpbb<=1.2`
90
+
91
+ 3. Add some development library:
92
+ - `uv add --group development backyard-memleak`
93
+
94
+ 4. Work with locally cloned repository:
95
+ - `uv add --editable ~/src/lib/chrome-v8-core`
96
+
97
+ ### Common workflow
98
+
99
+ 1. `make`:
100
+ - same as `mise install`, but also call `mise trust --yes` for initial deployment
101
+ - call `make sync`
102
+
103
+ 2. `make sync`
104
+ - drop and recreate .venv by `uv venv`
105
+ - read project dependencies graph from pyproject.toml and install it to virtual environment by `uv sync`)
106
+ - call `make freeze`
107
+
108
+ 3. `make freeze`:
109
+ - dump state to uv.lock by `uv lock`
110
+ - for development and debugging puproses uv save all used packages in current virtual environment to `packages.json` (with all development packages!) by `uv pip list`
111
+ - for repeatable production purposes uv save project dependencies to `packages.txt` with hashes for release builds strict version checks, read Dockerfile example (only project dependencies!) by `uv pip compile`
112
+
113
+ 4. `make upgrade`:
114
+ - read project dependencies graph from pyproject.toml
115
+ - fetch information about all updated packages, recreate dependencies graph and install it to virtual environment by `uv sync --upgrade`
116
+ - update `uv.lock` with updated packages version by `uv lock --upgrade`
117
+ - call `make freeze`
118
+ - show all installed packages in local virtual environment
119
+ - all you need it's just manually update versions in pyproject.toml
120
+
121
+ 5. `make check`:
122
+ It's non-destructive action, just run all checks and stop at first fail.
123
+
124
+ 6. `make lint`:
125
+ **Destructive action**, always commit all changes before run it. Runs all compatible linters with --fix and --edit mode, after it call `make check` for final polishing.
@@ -0,0 +1,105 @@
1
+ # Simple project for Python 3.10+
2
+ #### All-in-one repository with supervisor, crontab, linters and many useful tools
3
+
4
+ **Version control** is handled using [Astral UV](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer) tool. When building the image, uv is sourced from the official repository by copying the binary. Installation on a developer's machine can be done in various ways, which we'll cover shortly.
5
+
6
+ **Managing the interpreter version**, project environment variables, and setting up the virtual environment is done with the [Mise](https://mise.jdx.dev/installing-mise.html) tool. It automatically install any interpreter version by reading it from the project description and/or the version bound by uv. It can also fetch the appropriate uv binary for the platform and architecture.
7
+
8
+ ## How to install required tools?
9
+
10
+ #### A. Quick start for test
11
+
12
+ Therefore, the quickest and most minimal way to get touch is to install mise on your system and prepare tools with mise, **not for development**:
13
+
14
+ 1. `brew install mise`
15
+
16
+ That's all, go to **Shell configuration** section.
17
+
18
+ #### B. Engineer full-featured setup
19
+
20
+ **True engineer way** it's prepare rust environment, build mise and configure shell:
21
+
22
+ 1. Install Cargo via [Rustup](https://doc.rust-lang.org/book/ch01-01-installation.html).
23
+ 2. Do not forget add cargo path for your shell:
24
+ - `export PATH="~/.cargo/bin:$PATH"`
25
+ 3. Install sccache to avoid electricity bills:
26
+ - `cargo install sccache`
27
+ 4. Activate sccache:
28
+ - `export RUSTC_WRAPPER="~/.cargo/bin/sccache"` (and add it to your shell)
29
+ 5. Install cargo packages updater and mise:
30
+ - `cargo install cargo-update mise`
31
+ 6. Install uv:
32
+ - `mise install uv@latest && mise use -g uv@latest`
33
+ 7. That's all, you have last version of optimized tools; for update all packages just run sometime:
34
+ - `rustup update && cargo install-update --all`
35
+
36
+ ### Shell configuration
37
+
38
+ 1. **Mise provide dotenv functionality** (automatic read per-project environment variables from .env file and from .mise.toml config) from the box with batteries, but your shell must have entry hook, add to your shell it, example for zsh (your can replace it for bash, fish, etc):
39
+ - `eval "$(mise activate zsh)"`
40
+ 2. Also you can want using autocompletion, same story for zsh:
41
+ - `eval "$(mise completion zsh && uv generate-shell-completion zsh)"`
42
+ 3. Restart your shell session:
43
+ - `exec "$SHELL"`
44
+
45
+ ### Kickstart
46
+
47
+ 1. Go to project root.
48
+ 2. Just run `make`:
49
+ - mise will mark project directory as trusted
50
+ - mise copy sample development environment variables to .env
51
+ - mise grab environment variables defined in project .env, evaluate it and provide to current shell session
52
+ - mise checks what project python versions is installed, otherwise download and install it
53
+ - uv make virtual environment in project root (`uv venv`)
54
+ - uv read project packages list, download, install and link it (via `uv sync` run, read Makefile)
55
+ - uv install pre-commit and pre-push hooks
56
+
57
+ ## Work with project
58
+
59
+ ### Warning about pip
60
+
61
+ **NEVER CALL pip, NEVER!** Instead it use native uv calls, [read uv manual](https://docs.astral.sh/uv/guides/projects/#managing-dependencies), it's very easy, for example:
62
+
63
+ 1. Set or change python version (when python 3.11 already installed), before run do not forget change your python version in pyproject.toml:
64
+ - `uv python pin 3.11 && make sync`
65
+
66
+ 2. If python 3.11 isn't installed, run `mise install python@3.11` and mise download and install python 3.11, recreate virtual environment with 3.11 context. Do not forget to pin python version by uv from previous step (and, may be you need to update your pyproject.toml).
67
+
68
+ 2. Just add new dependency:
69
+ - `uv add phpbb<=1.2`
70
+
71
+ 3. Add some development library:
72
+ - `uv add --group development backyard-memleak`
73
+
74
+ 4. Work with locally cloned repository:
75
+ - `uv add --editable ~/src/lib/chrome-v8-core`
76
+
77
+ ### Common workflow
78
+
79
+ 1. `make`:
80
+ - same as `mise install`, but also call `mise trust --yes` for initial deployment
81
+ - call `make sync`
82
+
83
+ 2. `make sync`
84
+ - drop and recreate .venv by `uv venv`
85
+ - read project dependencies graph from pyproject.toml and install it to virtual environment by `uv sync`)
86
+ - call `make freeze`
87
+
88
+ 3. `make freeze`:
89
+ - dump state to uv.lock by `uv lock`
90
+ - for development and debugging puproses uv save all used packages in current virtual environment to `packages.json` (with all development packages!) by `uv pip list`
91
+ - for repeatable production purposes uv save project dependencies to `packages.txt` with hashes for release builds strict version checks, read Dockerfile example (only project dependencies!) by `uv pip compile`
92
+
93
+ 4. `make upgrade`:
94
+ - read project dependencies graph from pyproject.toml
95
+ - fetch information about all updated packages, recreate dependencies graph and install it to virtual environment by `uv sync --upgrade`
96
+ - update `uv.lock` with updated packages version by `uv lock --upgrade`
97
+ - call `make freeze`
98
+ - show all installed packages in local virtual environment
99
+ - all you need it's just manually update versions in pyproject.toml
100
+
101
+ 5. `make check`:
102
+ It's non-destructive action, just run all checks and stop at first fail.
103
+
104
+ 6. `make lint`:
105
+ **Destructive action**, always commit all changes before run it. Runs all compatible linters with --fix and --edit mode, after it call `make check` for final polishing.
@@ -0,0 +1,162 @@
1
+ [build-system]
2
+ build-backend = "setuptools.build_meta"
3
+ requires = [
4
+ "setuptools"
5
+ ]
6
+
7
+ [dependency-groups]
8
+ build = [
9
+ "setuptools"
10
+ ]
11
+ development = [
12
+ "better-exceptions>=0.3.3",
13
+ "pre-commit>=4.2.0",
14
+ "pipdeptree>=2.26.1"
15
+ ]
16
+ documentation = [
17
+ "sphinx>=8.1.3",
18
+ "sphinx-autodoc-typehints>=3.0.1",
19
+ "sphinx-rtd-dark-mode>=1.3.0",
20
+ "sphinx-rtd-theme>=3.0.2"
21
+ ]
22
+ linting = [
23
+ "bandit[toml]>=1.8.5",
24
+ "black>=25.1.0",
25
+ "dlint>=0.16.0",
26
+ "flake8-aaa>=0.17.0",
27
+ "flake8-bugbear>=23.3.12",
28
+ "flake8-comprehensions>=3.16.0",
29
+ "flake8-debugger>=4.1.2",
30
+ "flake8-eradicate>=1.4.0",
31
+ "flake8-expression-complexity>=0.0.11",
32
+ "flake8-fixme>=1.1.1",
33
+ "flake8-mutable>=1.2.0",
34
+ "flake8-pytest-style>=2.1.0",
35
+ "flake8-pytest>=1.4",
36
+ "flake8-simplify>=0.22.0",
37
+ "flake8-typing-imports>=1.12.0",
38
+ "flake8-use-fstring>=1.4",
39
+ "flake8-variables-names>=0.0.6",
40
+ "flakeheaven>=3.3.0",
41
+ "mypy>=1.16.1",
42
+ "pep8-naming>=0.13.2",
43
+ "ruff>=0.12.1",
44
+ "types-redis>=4.6.0.20241004",
45
+ "types-requests>=2.32.4.20250611",
46
+ "vulture>=2.14",
47
+ "yamlfix>=1.17.0",
48
+ "yamllint>=1.37.1"
49
+ ]
50
+ testing = [
51
+ "async-asgi-testclient>=1.4.11",
52
+ "faker>=37.4.0",
53
+ "pytest-asyncio>=1.0.0",
54
+ "pytest-cov>=6.2.1",
55
+ "pytest-dotenv>=0.5.2",
56
+ "pytest-mock>=3.14.1",
57
+ "pytest>=8.4.1"
58
+ ]
59
+
60
+ [project]
61
+ authors = [
62
+ {name = "Alex Kalaverin", email = "alex@kalaver.in"}
63
+ ]
64
+ classifiers = [
65
+ "Intended Audience :: Developers",
66
+ "Programming Language :: Python",
67
+ "Programming Language :: Python :: 3",
68
+ "Programming Language :: Python :: 3.10",
69
+ "Programming Language :: Python :: 3.11",
70
+ "Programming Language :: Python :: 3.12",
71
+ "License :: OSI Approved :: BSD License",
72
+ "Operating System :: OS Independent"
73
+ ]
74
+ dependencies = [
75
+ "charset-normalizer>=3.4.2",
76
+ "kain>=0.1.3,<0.2.0",
77
+ "python-magic>=0.4.27",
78
+ ]
79
+ description = "charded"
80
+ name = "charded"
81
+ readme = "README.md"
82
+ requires-python = ">=3.10,<3.13"
83
+ version = "0.1.0"
84
+
85
+ [project.urls]
86
+ Homepage = "https://kalaver.in"
87
+
88
+ [tool.bandit]
89
+ exclude_dirs = [".venv", ".git"]
90
+
91
+ [tool.black]
92
+ line-length = 89
93
+ target-version = ["py311"]
94
+
95
+ [tool.mypy]
96
+ ignore_missing_imports = true
97
+
98
+ [tool.pylint.messages_control]
99
+ enable = "all"
100
+
101
+ [tool.refurb]
102
+ ignore = [
103
+ ]
104
+
105
+ [tool.ruff]
106
+ extend = "etc/lint/ruff.toml"
107
+
108
+ [tool.ruff.lint.isort]
109
+ known-third-party = [
110
+ "kain"
111
+ ]
112
+ section-order = [
113
+ "future",
114
+ "standard-library",
115
+ "third-party",
116
+ "first-party",
117
+ "local-project",
118
+ "local-folder"
119
+ ]
120
+
121
+ [tool.ruff.lint.isort.sections]
122
+ local-project = ["charded"]
123
+
124
+ [tool.ruff.lint.per-file-ignores]
125
+ "contrib/*" = []
126
+ "docs/*" = []
127
+ "src/charded/string.py" = [
128
+ "N805", # First argument of a method should be named `self`
129
+ ]
130
+ "tests/*" = []
131
+
132
+ [tool.setuptools]
133
+ packages = ["charded"]
134
+
135
+ [tool.setuptools.package-dir]
136
+ "" = "src"
137
+
138
+ [tool.uv]
139
+ default-groups = [
140
+ "development"
141
+ ]
142
+
143
+ [tool.uv.sources]
144
+ kain = { path = "../kain", editable = true }
145
+
146
+ [tool.yamlfix]
147
+ allow_duplicate_keys = false
148
+ comments_min_spaces_from_content = 2
149
+ comments_require_starting_space = true
150
+ comments_whitelines = 1
151
+ explicit_start = true
152
+ indent_mapping = 2
153
+ indent_offset = 2
154
+ indent_sequence = 4
155
+ line_length = 89
156
+ none_representation = "null"
157
+ preserve_quotes = false
158
+ quote_basic_values = false
159
+ quote_representation = '"'
160
+ section_whitelines = 2
161
+ sequence_style = "block_style"
162
+ whitelines = 0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from charded.string import Str
2
+
3
+ __all__ = ("Str",)
@@ -0,0 +1,272 @@
1
+ from contextlib import suppress
2
+ from logging import getLogger
3
+ from math import log2
4
+ from re import findall
5
+
6
+ from kain.descriptors import pin, proxy_to
7
+ from kain.importer import optional
8
+ from kain.internals import Who, to_ascii, unique
9
+
10
+ logger = getLogger(__name__)
11
+
12
+
13
+ def generate_binary_offsets(x: int):
14
+ power = int(log2(x))
15
+
16
+ yield 0
17
+ for base in range(power):
18
+ bias = (2 ** (power - base))
19
+ for no in range(x // bias + 1):
20
+ if no % 2:
21
+ yield no * bias
22
+
23
+
24
+ def iter_by_binary_offsets(x: bytes | str, /, ordinate: bool):
25
+ getter = x.__getitem__
26
+ offset = len(x) - len(x) % 2 - 1
27
+
28
+ if offset != -1:
29
+ iterator = generate_binary_offsets(offset)
30
+
31
+ offsets = map(getter, iterator)
32
+ if ordinate:
33
+ offsets = map(ord, offsets)
34
+ yield from enumerate(offsets)
35
+
36
+
37
+ def replace_class_with_str(method, *args, **kw):
38
+ if args and isinstance(args[0], Str):
39
+ args = (str(args[0]), *args[1:])
40
+ return method(*args, **kw)
41
+
42
+
43
+ class External:
44
+
45
+ @pin.cls
46
+ def _charset_detect(cls):
47
+ return optional("charset_normalizer.detect")
48
+
49
+ @pin.cls
50
+ def _from_buffer(cls):
51
+ return optional("magic.from_buffer")
52
+
53
+ @pin.cls
54
+ def _from_content(cls):
55
+ return optional("magic.detect_from_content")
56
+
57
+ #
58
+
59
+ @pin.cls
60
+ def charset_detect(cls):
61
+ if detect := External._charset_detect:
62
+ def charset_detect(x: bytes) -> str:
63
+ return detect(x)
64
+ else:
65
+ def charset_detect(x: bytes) -> None: ...
66
+
67
+ return charset_detect
68
+
69
+ @pin.cls
70
+ def mime_reader(cls):
71
+ if (read := cls._from_buffer) and (detect := cls._from_content):
72
+ def mime_reader(x: bytes) -> dict[str, str]:
73
+ return {
74
+ "type": detect(x).mime_type,
75
+ "description": read(x)}
76
+ else:
77
+ def mime_reader(x: bytes) -> None: ...
78
+
79
+ return mime_reader
80
+
81
+
82
+ @proxy_to(
83
+ "text",
84
+ "__add__", "__contains__", "__eq__", "__format__", "__hash__", "__ge__",
85
+ "__getitem__", "__gt__", "__iter__", "__le__", "__len__", "__lt__", "__mod__",
86
+ "__mul__", "__ne__", "__rmod__", "__rmul__", "__sizeof__", "capitalize",
87
+ "casefold", "center", "count", "encode", "endswith", "expandtabs", "find",
88
+ "format", "format_map", "index", "isalnum", "isalpha", "isascii", "isdecimal",
89
+ "isdigit", "isidentifier", "islower", "isnumeric", "isprintable", "isspace",
90
+ "istitle", "isupper", "join", "ljust", "lower", "lstrip", "maketrans",
91
+ "partition", "removeprefix", "removesuffix", "replace", "rfind", "rindex",
92
+ "rjust", "rpartition", "rsplit", "rstrip", "split", "splitlines", "startswith",
93
+ "strip", "swapcase", "title", "translate", "upper", "zfill",
94
+ pin, pre=replace_class_with_str,
95
+ )
96
+ class Str:
97
+ """A class for handling bytes | str objects.
98
+
99
+ Autodetect charsets, autoencode/decode from bytes/text.
100
+ Just pass any bytes | str object to Str and use it:
101
+
102
+ x = Str(b'hello')
103
+ str(x) == 'hello'
104
+ bytes(x) == b'hello'
105
+
106
+ """
107
+
108
+ InternalCharset : str = "utf-8"
109
+ DetectionSizeLimit: int = 2 ** 20
110
+ DetectionScanLimit: int = 2 ** 10
111
+
112
+ to_ascii = staticmethod(to_ascii)
113
+
114
+ @classmethod
115
+ def to_bytes(cls, obj, *args, **kw):
116
+ return bytes(cls(obj, *args, **kw))
117
+
118
+ @classmethod
119
+ def to_text(cls, obj, *args, **kw):
120
+ return str(cls(obj, *args, **kw))
121
+
122
+ #
123
+
124
+ @classmethod
125
+ def downcast(cls, obj):
126
+ if isinstance(obj, bytes | str):
127
+ return obj
128
+
129
+ if isinstance(obj, int):
130
+ return str(obj)
131
+
132
+ msg = f"{Who(cls)} accept bytes | str, got {Who.Is(obj)}"
133
+ raise TypeError(msg)
134
+
135
+ @pin
136
+ def mime(self):
137
+ try:
138
+ return External.mime_reader(self.bytes)
139
+
140
+ except Exception: # noqa: BLE001 Do not catch blind exception: `Exception`
141
+ logger.warning(
142
+ f"something went wrong for {self.bytes[:2**10]!r}", exc_info=True)
143
+
144
+ #
145
+
146
+ def __bytes__(self):
147
+ return self.bytes
148
+
149
+ def __str__(self):
150
+ return self.text
151
+
152
+ def __init__(self, obj, /, charset=None):
153
+ self._object = obj
154
+ self._charset = charset or self.InternalCharset
155
+
156
+ @pin
157
+ def _value(self):
158
+ return self.downcast(self._object)
159
+
160
+ #
161
+
162
+ @pin
163
+ def external_charset(self):
164
+ return External.charset_detect(self.bytes)
165
+
166
+ @pin
167
+ def probe_order(self):
168
+ result = [self._charset]
169
+
170
+ if meta := self.external_charset:
171
+ result.append(meta["encoding"])
172
+
173
+ return tuple(unique([*result, "ascii"]))
174
+
175
+ @pin
176
+ def compatible_charset(self):
177
+ string = self._value
178
+
179
+ if isinstance(string, bytes):
180
+ method = string.decode
181
+ order = self.probe_order
182
+
183
+ else:
184
+ method = string.encode
185
+ order = tuple(reversed(self.probe_order))
186
+
187
+ for charset in filter(bool, order):
188
+ with suppress(UnicodeEncodeError, UnicodeDecodeError):
189
+ method(charset)
190
+ return charset
191
+
192
+ @pin
193
+ def charset(self):
194
+ string = self._value
195
+ is_bytes = isinstance(string, bytes)
196
+
197
+ read_limit = self.DetectionSizeLimit
198
+ if not is_bytes and len(string) <= read_limit:
199
+ read_limit = 0
200
+
201
+ default = "ascii"
202
+ if not string:
203
+ return default
204
+
205
+ collected = set()
206
+ scan_limit = self.DetectionScanLimit
207
+
208
+ for no, char in iter_by_binary_offsets(string, ordinate=not is_bytes):
209
+ if no > read_limit:
210
+ break
211
+
212
+ if char < 0x20: # noqa: PLR2004
213
+ if char in (0x9, 0xa, 0xc, 0xd):
214
+ continue
215
+ return "binary"
216
+
217
+ if char >= 0x7f: # noqa: PLR2004, ANSI Extended Border
218
+ if is_bytes:
219
+ default = "ansi"
220
+
221
+ elif char not in collected:
222
+ if len(collected) >= scan_limit:
223
+ return self.compatible_charset
224
+ collected.add(char)
225
+
226
+ return default if is_bytes else self.compatible_charset
227
+
228
+ #
229
+
230
+ @pin
231
+ def bytes(self):
232
+ string = self._value
233
+ return string if isinstance(string, bytes) else string.encode(self._charset)
234
+
235
+ @pin
236
+ def text(self):
237
+ string = self._value
238
+ if isinstance(string, str):
239
+ return string
240
+
241
+ charset = self.charset
242
+ if charset == "ansi":
243
+ charset = self.compatible_charset
244
+
245
+ if charset != "binary":
246
+ return string.decode(charset)
247
+
248
+ msg = f"couldn't {charset=} decode {string[:2**10]!r}"
249
+ if charset := self.charset:
250
+ msg = f"{msg}; {charset}"
251
+
252
+ raise ValueError(msg)
253
+
254
+ def __repr__(self):
255
+ string = self._value
256
+ size = f"{len(self.bytes):d}"
257
+
258
+ length = ""
259
+ charset = (f"{self.charset} ".upper()) if self.charset else ""
260
+
261
+ if isinstance(string, str) and len(self.bytes) != len(self.text):
262
+ length = f"={len(self.text):d}"
263
+
264
+ return (
265
+ f"<{charset}{Who(self, full=False)}"
266
+ f"[{Who(self._object, full=False)}"
267
+ f"({size})]{length} at {id(self):#x}>")
268
+
269
+ #
270
+
271
+ def tokenize(self, regex=r"([\w\d]+)"):
272
+ return findall(regex, self.text)
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: charded
3
+ Version: 0.1.0
4
+ Summary: charded
5
+ Author-email: Alex Kalaverin <alex@kalaver.in>
6
+ Project-URL: Homepage, https://kalaver.in
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: <3.13,>=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: charset-normalizer>=3.4.2
18
+ Requires-Dist: kain<0.2.0,>=0.1.3
19
+ Requires-Dist: python-magic>=0.4.27
20
+
21
+ # Simple project for Python 3.10+
22
+ #### All-in-one repository with supervisor, crontab, linters and many useful tools
23
+
24
+ **Version control** is handled using [Astral UV](https://docs.astral.sh/uv/getting-started/installation/#standalone-installer) tool. When building the image, uv is sourced from the official repository by copying the binary. Installation on a developer's machine can be done in various ways, which we'll cover shortly.
25
+
26
+ **Managing the interpreter version**, project environment variables, and setting up the virtual environment is done with the [Mise](https://mise.jdx.dev/installing-mise.html) tool. It automatically install any interpreter version by reading it from the project description and/or the version bound by uv. It can also fetch the appropriate uv binary for the platform and architecture.
27
+
28
+ ## How to install required tools?
29
+
30
+ #### A. Quick start for test
31
+
32
+ Therefore, the quickest and most minimal way to get touch is to install mise on your system and prepare tools with mise, **not for development**:
33
+
34
+ 1. `brew install mise`
35
+
36
+ That's all, go to **Shell configuration** section.
37
+
38
+ #### B. Engineer full-featured setup
39
+
40
+ **True engineer way** it's prepare rust environment, build mise and configure shell:
41
+
42
+ 1. Install Cargo via [Rustup](https://doc.rust-lang.org/book/ch01-01-installation.html).
43
+ 2. Do not forget add cargo path for your shell:
44
+ - `export PATH="~/.cargo/bin:$PATH"`
45
+ 3. Install sccache to avoid electricity bills:
46
+ - `cargo install sccache`
47
+ 4. Activate sccache:
48
+ - `export RUSTC_WRAPPER="~/.cargo/bin/sccache"` (and add it to your shell)
49
+ 5. Install cargo packages updater and mise:
50
+ - `cargo install cargo-update mise`
51
+ 6. Install uv:
52
+ - `mise install uv@latest && mise use -g uv@latest`
53
+ 7. That's all, you have last version of optimized tools; for update all packages just run sometime:
54
+ - `rustup update && cargo install-update --all`
55
+
56
+ ### Shell configuration
57
+
58
+ 1. **Mise provide dotenv functionality** (automatic read per-project environment variables from .env file and from .mise.toml config) from the box with batteries, but your shell must have entry hook, add to your shell it, example for zsh (your can replace it for bash, fish, etc):
59
+ - `eval "$(mise activate zsh)"`
60
+ 2. Also you can want using autocompletion, same story for zsh:
61
+ - `eval "$(mise completion zsh && uv generate-shell-completion zsh)"`
62
+ 3. Restart your shell session:
63
+ - `exec "$SHELL"`
64
+
65
+ ### Kickstart
66
+
67
+ 1. Go to project root.
68
+ 2. Just run `make`:
69
+ - mise will mark project directory as trusted
70
+ - mise copy sample development environment variables to .env
71
+ - mise grab environment variables defined in project .env, evaluate it and provide to current shell session
72
+ - mise checks what project python versions is installed, otherwise download and install it
73
+ - uv make virtual environment in project root (`uv venv`)
74
+ - uv read project packages list, download, install and link it (via `uv sync` run, read Makefile)
75
+ - uv install pre-commit and pre-push hooks
76
+
77
+ ## Work with project
78
+
79
+ ### Warning about pip
80
+
81
+ **NEVER CALL pip, NEVER!** Instead it use native uv calls, [read uv manual](https://docs.astral.sh/uv/guides/projects/#managing-dependencies), it's very easy, for example:
82
+
83
+ 1. Set or change python version (when python 3.11 already installed), before run do not forget change your python version in pyproject.toml:
84
+ - `uv python pin 3.11 && make sync`
85
+
86
+ 2. If python 3.11 isn't installed, run `mise install python@3.11` and mise download and install python 3.11, recreate virtual environment with 3.11 context. Do not forget to pin python version by uv from previous step (and, may be you need to update your pyproject.toml).
87
+
88
+ 2. Just add new dependency:
89
+ - `uv add phpbb<=1.2`
90
+
91
+ 3. Add some development library:
92
+ - `uv add --group development backyard-memleak`
93
+
94
+ 4. Work with locally cloned repository:
95
+ - `uv add --editable ~/src/lib/chrome-v8-core`
96
+
97
+ ### Common workflow
98
+
99
+ 1. `make`:
100
+ - same as `mise install`, but also call `mise trust --yes` for initial deployment
101
+ - call `make sync`
102
+
103
+ 2. `make sync`
104
+ - drop and recreate .venv by `uv venv`
105
+ - read project dependencies graph from pyproject.toml and install it to virtual environment by `uv sync`)
106
+ - call `make freeze`
107
+
108
+ 3. `make freeze`:
109
+ - dump state to uv.lock by `uv lock`
110
+ - for development and debugging puproses uv save all used packages in current virtual environment to `packages.json` (with all development packages!) by `uv pip list`
111
+ - for repeatable production purposes uv save project dependencies to `packages.txt` with hashes for release builds strict version checks, read Dockerfile example (only project dependencies!) by `uv pip compile`
112
+
113
+ 4. `make upgrade`:
114
+ - read project dependencies graph from pyproject.toml
115
+ - fetch information about all updated packages, recreate dependencies graph and install it to virtual environment by `uv sync --upgrade`
116
+ - update `uv.lock` with updated packages version by `uv lock --upgrade`
117
+ - call `make freeze`
118
+ - show all installed packages in local virtual environment
119
+ - all you need it's just manually update versions in pyproject.toml
120
+
121
+ 5. `make check`:
122
+ It's non-destructive action, just run all checks and stop at first fail.
123
+
124
+ 6. `make lint`:
125
+ **Destructive action**, always commit all changes before run it. Runs all compatible linters with --fix and --edit mode, after it call `make check` for final polishing.
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/charded/__init__.py
4
+ src/charded/string.py
5
+ src/charded.egg-info/PKG-INFO
6
+ src/charded.egg-info/SOURCES.txt
7
+ src/charded.egg-info/dependency_links.txt
8
+ src/charded.egg-info/requires.txt
9
+ src/charded.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ charset-normalizer>=3.4.2
2
+ kain<0.2.0,>=0.1.3
3
+ python-magic>=0.4.27
@@ -0,0 +1 @@
1
+ charded