# sain
a dependency-free library which implements a few of Rust's core crates purely in Python.
It offers a few of the core Rust features such as `Vec<T>`, `Result<T, E>`, `Option<T>` and more. See the equivalent type section below.
a few `std` types are implemented. Check the [project documentation](https://nxtlo.github.io/sain/sain.html)
## Install
You'll need Python 3.10 or higher.
PyPI
```sh
pip install sain
```
## Overview
More examples in [examples](https://github.com/nxtlo/sain/tree/master/examples)
### no `try/except`
Rust doesn't have exception, But `Option<T>` which handles None's, and `Result<T, E>` for returning and propagating errors.
we can easily achieve the same results in Python
```py
from __future__ import annotations
from sain import Option, Result, Ok, Err
from sain.collections import Vec, Bytes
from sain.convert import Into
from dataclasses import dataclass, field
# A chunk of data. the from protocol allows users to convert the chunk into bytes.
# similar to Rust's Into trait.
@dataclass
class Chunk(Into[bytes]):
tag: str
data: Bytes
# convert a chunk into bytes.
# in Rust, this consumes `self`, But in Python it copies it.
def into(self) -> bytes:
return self.data.to_bytes()
@dataclass
class BlobStore:
pos: int
# A buffer that contains chunks of bytes over which we might
# lazily load from somewhere. This buffer can hold up to 1024 chunks.
buffer: Vec[Chunk] = field(default_factory=lambda: Vec[Chunk].with_capacity(1024))
def put(self, tag: str, data: bytes) -> Result[None, str]:
chunk = Chunk(tag, Bytes.from_bytes(data))
# push_within_capacity returns `Result[None, Chunk]`.
# It returns the chunk that got failed to be pushed,
# we try to push if there's space, mapping the error to
# a string.
return self.buffer.push_within_capacity(chunk).map_err(
lambda chunk: "No more capacity to push chunk: " + str(chunk)
)
def next_chunk(self) -> Option[Chunk]:
chunk = self.buffer.get(self.pos)
self.pos += 1
return chunk
def main() -> None:
blobs = BlobStore(0)
# upload a blob matching any errors.
match blobs.put("c1", b"first chunk"):
case Ok(_):
print("chunk pushed succefully.")
case Err(why):
print(why)
# or just
blobs.put("c2", b"second chunk").unwrap()
# Read back the chunks, and map it to string.
# In rust, you would do something similar to
# * while let Some(chunk) = option.map(String::from_utf8_lossy) { ... } *
while (chunk := blobs.next_chunk()).is_some():
print(chunk.map(Chunk.into))
# use an iterator over the chunks
for pos, chunk in blobs.buffer.iter().enumerate():
print(pos, chunk.data)
```
## built-in types
| name in Rust | name in Python | note | restrictions |
| ----------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| Option\<T>, Some(T), None | Option[T], Some(T), Some(None) | Some(None) has the same layout as `None` in Rust | |
| Result\<T, E>, Ok(T), Err(E) | Result[T, E], Ok(T), Err(E) | | |
| Vec\<T> | Vec[T] | | |
| HashMap\<K, V> | HashMap[K, V] | | |
| bytes::Bytes | Bytes | | |
| LazyLock\<T> | Lazy[T] | | |
| OnceLock\<T> | Once[T] | | |
| Box\<T> | Box[T] | this isn't a heap box, [See]([https://nxtlo.github.io/sain/sain/boxed.html](https://nxtlo.github.io/sain/sain/boxed.html)) | |
| MaybeUninit\<T> | MaybeUninit[T] | they serve the same purpose, but slightly different | |
| &dyn Default | Default[T] | | |
| &dyn Error | Error | | |
| &dyn Iterator\<T> | Iterator[T] | | |
| Iter\<'a, T> | Iter[T] | collections called by `.iter()` are built from this type | |
| iter::once::\<T>() | iter.once[T] | | |
| iter::empty::\<T>() | iter.empty[T] | | |
| iter::repeat::\<T>() | iter.repeat[T] | | |
| cfg!() | cfg() | runtime cfg, not all predictions are supported | |
| #[cfg_attr] | @cfg_attr() | runtime cfg, not all predictions are supported | |
| #[doc] | @doc() | the docs get generated at runtime | |
| todo!() | todo() | | |
| #[deprecated] | @deprecated() | will get removed when it get stabilized in `warnings` in Python `3.13` | |
| unimplemented!() | @unimplemented() | | |
## Notes
Since Rust is a compiled language, Whatever predict in `cfg` and `cfg_attr` returns False will not compile.
But there's no such thing as this in Python, So `RuntimeError` will be raised and whatever was predicated will not run.
Raw data
{
"_id": null,
"home_page": "https://github.com/nxtlo/sain",
"name": "sain",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "Rust, config, typing, utilities",
"author": "nxtlo",
"author_email": "dhmony-99@hotmail.com",
"download_url": "https://files.pythonhosted.org/packages/49/f4/90661061ca6a10c602c5f02b7b2c7206bd2b741aace1d34ca27335d46380/sain-1.2.0.tar.gz",
"platform": null,
"description": "# sain\n\na dependency-free library which implements a few of Rust's core crates purely in Python.\nIt offers a few of the core Rust features such as `Vec<T>`, `Result<T, E>`, `Option<T>` and more. See the equivalent type section below.\n\na few `std` types are implemented. Check the [project documentation](https://nxtlo.github.io/sain/sain.html)\n\n## Install\n\nYou'll need Python 3.10 or higher.\n\nPyPI\n\n```sh\npip install sain\n```\n\n## Overview\n\nMore examples in [examples](https://github.com/nxtlo/sain/tree/master/examples)\n\n### no `try/except`\n\nRust doesn't have exception, But `Option<T>` which handles None's, and `Result<T, E>` for returning and propagating errors.\nwe can easily achieve the same results in Python\n\n```py\nfrom __future__ import annotations\n\nfrom sain import Option, Result, Ok, Err\nfrom sain.collections import Vec, Bytes\nfrom sain.convert import Into\n\nfrom dataclasses import dataclass, field\n\n\n# A chunk of data. the from protocol allows users to convert the chunk into bytes.\n# similar to Rust's Into trait.\n@dataclass\nclass Chunk(Into[bytes]):\n tag: str\n data: Bytes\n\n # convert a chunk into bytes.\n # in Rust, this consumes `self`, But in Python it copies it.\n def into(self) -> bytes:\n return self.data.to_bytes()\n\n\n@dataclass\nclass BlobStore:\n pos: int\n # A buffer that contains chunks of bytes over which we might\n # lazily load from somewhere. This buffer can hold up to 1024 chunks.\n buffer: Vec[Chunk] = field(default_factory=lambda: Vec[Chunk].with_capacity(1024))\n\n def put(self, tag: str, data: bytes) -> Result[None, str]:\n chunk = Chunk(tag, Bytes.from_bytes(data))\n # push_within_capacity returns `Result[None, Chunk]`.\n # It returns the chunk that got failed to be pushed,\n # we try to push if there's space, mapping the error to\n # a string.\n return self.buffer.push_within_capacity(chunk).map_err(\n lambda chunk: \"No more capacity to push chunk: \" + str(chunk)\n )\n\n def next_chunk(self) -> Option[Chunk]:\n chunk = self.buffer.get(self.pos)\n self.pos += 1\n return chunk\n\n\ndef main() -> None:\n blobs = BlobStore(0)\n\n # upload a blob matching any errors.\n match blobs.put(\"c1\", b\"first chunk\"):\n case Ok(_):\n print(\"chunk pushed succefully.\")\n case Err(why):\n print(why)\n\n # or just\n blobs.put(\"c2\", b\"second chunk\").unwrap()\n\n # Read back the chunks, and map it to string.\n # In rust, you would do something similar to\n # * while let Some(chunk) = option.map(String::from_utf8_lossy) { ... } *\n while (chunk := blobs.next_chunk()).is_some():\n print(chunk.map(Chunk.into))\n\n # use an iterator over the chunks\n for pos, chunk in blobs.buffer.iter().enumerate():\n print(pos, chunk.data)\n\n\n```\n\n## built-in types\n\n| name in Rust | name in Python | note | restrictions |\n| ----------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------- |\n| Option\\<T>, Some(T), None | Option[T], Some(T), Some(None) | Some(None) has the same layout as `None` in Rust | |\n| Result\\<T, E>, Ok(T), Err(E) | Result[T, E], Ok(T), Err(E) | | |\n| Vec\\<T> | Vec[T] | | |\n| HashMap\\<K, V> | HashMap[K, V] | | |\n| bytes::Bytes | Bytes | | |\n| LazyLock\\<T> | Lazy[T] | | |\n| OnceLock\\<T> | Once[T] | | |\n| Box\\<T> | Box[T] | this isn't a heap box, [See]([https://nxtlo.github.io/sain/sain/boxed.html](https://nxtlo.github.io/sain/sain/boxed.html)) | |\n| MaybeUninit\\<T> | MaybeUninit[T] | they serve the same purpose, but slightly different | |\n| &dyn Default | Default[T] | | |\n| &dyn Error | Error | | |\n| &dyn Iterator\\<T> | Iterator[T] | | |\n| Iter\\<'a, T> | Iter[T] | collections called by `.iter()`\u00a0are built from this type | |\n| iter::once::\\<T>() | iter.once[T] | | |\n| iter::empty::\\<T>() | iter.empty[T] | | |\n| iter::repeat::\\<T>() | iter.repeat[T] | | |\n| cfg!() | cfg() | runtime cfg, not all predictions are supported | |\n| #[cfg_attr] | @cfg_attr() | runtime cfg, not all predictions are supported | |\n| #[doc] | @doc() | the docs get generated at runtime | |\n| todo!() | todo() | | |\n| #[deprecated] | @deprecated() | will get removed when it get stabilized in `warnings`\u00a0in Python `3.13` | |\n| unimplemented!() | @unimplemented() | | |\n\n## Notes\n\nSince Rust is a compiled language, Whatever predict in `cfg` and `cfg_attr` returns False will not compile.\n\nBut there's no such thing as this in Python, So `RuntimeError` will be raised and whatever was predicated will not run.\n",
"bugtrack_url": null,
"license": "BSD-3-Clause license",
"summary": "Standard Rust core types implementations for Python.",
"version": "1.2.0",
"project_urls": {
"Homepage": "https://github.com/nxtlo/sain",
"Repository": "https://github.com/nxtlo/sain"
},
"split_keywords": [
"rust",
" config",
" typing",
" utilities"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "107f5dc6c5d3cb5f0505c26d81396ddf3d9d94b0f8b115c0f4441e046ac49832",
"md5": "28fbdd76d6f0fea63567fe5c7d1a594c",
"sha256": "c335868ecbcfadccc0414f7ecc96b91387ca15a92d1b15c0211adb6c07f3e422"
},
"downloads": -1,
"filename": "sain-1.2.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "28fbdd76d6f0fea63567fe5c7d1a594c",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 69045,
"upload_time": "2024-09-09T11:55:16",
"upload_time_iso_8601": "2024-09-09T11:55:16.948969Z",
"url": "https://files.pythonhosted.org/packages/10/7f/5dc6c5d3cb5f0505c26d81396ddf3d9d94b0f8b115c0f4441e046ac49832/sain-1.2.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "49f490661061ca6a10c602c5f02b7b2c7206bd2b741aace1d34ca27335d46380",
"md5": "ed4dc09d458ed688bdc2a409c6f0ee26",
"sha256": "45ffb3b4ab55b0a76793596c389837e44a3d05b11408b9644481d6acbeb96895"
},
"downloads": -1,
"filename": "sain-1.2.0.tar.gz",
"has_sig": false,
"md5_digest": "ed4dc09d458ed688bdc2a409c6f0ee26",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 46213,
"upload_time": "2024-09-09T11:55:18",
"upload_time_iso_8601": "2024-09-09T11:55:18.727498Z",
"url": "https://files.pythonhosted.org/packages/49/f4/90661061ca6a10c602c5f02b7b2c7206bd2b741aace1d34ca27335d46380/sain-1.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-09 11:55:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "nxtlo",
"github_project": "sain",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "sain"
}