{"id":19179588,"url":"https://github.com/animenosekai/cain","last_synced_at":"2025-09-03T23:42:35.823Z","repository":{"id":187602282,"uuid":"632109090","full_name":"Animenosekai/cain","owner":"Animenosekai","description":"A small yet powerful data format ✨","archived":false,"fork":false,"pushed_at":"2023-12-21T21:59:04.000Z","size":823,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-10T09:18:37.691Z","etag":null,"topics":["cain","data","format","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Animenosekai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-24T18:19:49.000Z","updated_at":"2023-09-04T14:50:08.000Z","dependencies_parsed_at":"2024-11-09T10:43:47.514Z","dependency_job_id":"327762e4-86cb-48d1-ab46-b26a9c151665","html_url":"https://github.com/Animenosekai/cain","commit_stats":null,"previous_names":["animenosekai/cain"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Animenosekai/cain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Animenosekai%2Fcain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Animenosekai%2Fcain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Animenosekai%2Fcain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Animenosekai%2Fcain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Animenosekai","download_url":"https://codeload.github.com/Animenosekai/cain/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Animenosekai%2Fcain/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273529289,"owners_count":25121823,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cain","data","format","python"],"created_at":"2024-11-09T10:43:34.673Z","updated_at":"2025-09-03T23:42:35.773Z","avatar_url":"https://github.com/Animenosekai.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cain\n\n\u003cimg align=\"right\" src=\"./assets/cain.png\" height=\"220px\"\u003e\n\n***A small yet powerful data format ✨***\n\n\u003cbr\u003e\n\u003cbr\u003e\n\n[![PyPI version](https://badge.fury.io/py/cain.svg)](https://pypi.org/project/cain/)\n[![Downloads](https://static.pepy.tech/personalized-badge/cain?period=total\u0026units=international_system\u0026left_color=grey\u0026right_color=blue\u0026left_text=Total%20Downloads)](https://pepy.tech/project/cain)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/cain)](https://pypistats.org/packages/cain)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cain)](https://pypi.org/project/cain/)\n[![PyPI - Status](https://img.shields.io/pypi/status/cain)](https://pypi.org/project/cain/)\n[![GitHub - License](https://img.shields.io/github/license/Animenosekai/cain)](https://github.com/Animenosekai/cain/blob/master/LICENSE)\n[![GitHub top language](https://img.shields.io/github/languages/top/Animenosekai/cain)](https://github.com/Animenosekai/cain)\n[![CodeQL Checks Badge](https://github.com/Animenosekai/cain/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Animenosekai/cain/actions/workflows/codeql-analysis.yml)\n![Code Size](https://img.shields.io/github/languages/code-size/Animenosekai/cain)\n![Repo Size](https://img.shields.io/github/repo-size/Animenosekai/cain)\n![Issues](https://img.shields.io/github/issues/Animenosekai/cain)\n\n## Index\n\n- [Index](#index)\n- [Purpose](#purpose)\n  - [Comparison](#comparison)\n    - [JSON](#json)\n    - [Cain](#cain-1)\n- [Getting Started](#getting-started)\n  - [Prerequisites](#prerequisites)\n- [Installing](#installing)\n  - [Option 1: From PyPI](#option-1-from-pypi)\n  - [Option 2: From Git](#option-2-from-git)\n- [Usage](#usage)\n  - [Python](#python)\n    - [Encoding](#encoding)\n    - [Decoding](#decoding)\n    - [Handling Schemas](#handling-schemas)\n      - [Encoding](#encoding-1)\n      - [Decoding](#decoding-1)\n    - [Custom Encoder](#custom-encoder)\n  - [CLI](#cli)\n    - [Examples](#examples)\n- [Deployment](#deployment)\n- [Contributing](#contributing)\n- [Authors](#authors)\n- [Licensing](#licensing)\n\n## Purpose\n\nCain is a new data interchange format which aims at providing the smallest possible size to encode data.\n\nIt is based on pre-defined schemas which leverages the need to specify it within the final encoded data.\n\n\u003e **Note**  \n\u003e Look at the [*SPECIFICATIONS*](https://github.com/Animenosekai/cain/blob/main/SPECIFICATIONS.md) file for more information on the purpose and idea behind this project.\n\n### Comparison\n\nFor example, we consider the following object:\n\n```python\n{\n    \"b\": 3,\n    \"c\": 5.5,\n    \"d\": True,\n    \"e\": {\n        \"f\": False,\n        # \"g\": b\"Hello world\"\n        \"h\": \"HELLO WORLD\",\n        \"i\": \"Hi!\",\n        \"j\": [1, 2, 3, 1, 1],\n        \"k\": (1, \"hello\", True),\n        \"l\": None,\n        \"m\": \"Yay\",\n        \"n\": \"Hi\",\n        \"o\": 2,\n        \"p\": None\n    }\n}\n```\n\n#### JSON\n\nThis is the expected result from a minified JSON encoding:\n\n```json\n{\"b\":3,\"c\":5.5,\"d\":true,\"e\":{\"f\":false,\"h\":\"HELLO WORLD\",\"i\":\"Hi!\",\"j\":[1,2,3,1,1],\"k\":[1,\"hello\",true],\"l\":null,\"m\":\"Yay\",\"n\":\"Hi\",\"o\":2,\"p\":null}}\n```\n\n#### Cain\n\nThis is the expected result from the Cain data format:\n\n```cain\n\\x00\\x00\\x03\\x00\\x00\\xb0@\\x01\\x00\\x00HELLO WORLD\\x00Hi!\\x00\\x00\\x05\\x00\\x00\\x00\\x01\\x00\\x02\\x00\\x03\\x00\\x01\\x00\\x01\\x00\\x00\\x01hello\\x00\\x01\\x00\\x01\\x00Yay\\x00\\x00Hi\\x00\\x01\\x00\\x02\n```\n\n\u003e **Note**  \n\u003e This is 56.76% smaller than the JSON version ✨\n\n***Moreover, objects which can't be encoded using JSON (bytes, set, range, etc.) or wrongly encoded using JSON (ex: tuple) are working out of the box with Cain!***\n\n## Getting Started\n\nThese instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.\n\n### Prerequisites\n\nYou will need Python 3 to use this module\n\n```bash\nMinimum required versions: 3.9\nIncompatible versions:     2\n```\n\nAlways check if your Python version works with `cain` before using it in production.\n\n## Installing\n\n### Option 1: From PyPI\n\n```bash\npip install --upgrade cain\n```\n\n\u003e This will install the latest version from PyPI\n\n### Option 2: From Git\n\n```bash\npip install --upgrade git+https://github.com/Animenosekai/cain.git\n```\n\n\u003e This will install the latest development version from the git repository\n\nYou can check if you successfully installed it by printing out its version:\n\n```bash\n$ cain --version\n1.1\n```\n\n## Usage\n\n### Python\n\nThe main entry point ([cain.py](./cain/cain.py)) provides an API familiar to users of the standard library `json` module. The different datatype also present a very pythonic way of handling data to keep a nice and clean codebase.\n\n#### Encoding\n\nEncoding basic Python object hierarchies:\n\n```python\n\u003e\u003e\u003e import cain\n\u003e\u003e\u003e from cain.types import Object, Optional\n\u003e\u003e\u003e cain.dumps({\"a\": 2}, Object[{\"a\": int}])\nb'\\x00\\x00\\x02'\n\u003e\u003e\u003e class TestObject(Object):\n...     bar: tuple[str, Optional[str], float, int]\n...\n\u003e\u003e\u003e cain.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}], list[str, TestObject])\nb'\\x00foo\\x00\\x00\\x00baz\\x00\\x00\\x00\\x00\\x80?\\x00\\x02'\n\u003e\u003e\u003e print(cain.dumps(\"\\\"foo\\bar\", str))\nb'\"foo\\x08ar\\x00'\n\u003e\u003e\u003e print(cain.dumps('\\u1234', str))\nb'\\xe1\\x88\\xb4\\x00'\n\u003e\u003e\u003e print(cain.dumps('\\\\', str))\nb'\\\\\\x00'\n\u003e\u003e\u003e schema = list[str, Object[{\"bar\": tuple[str, Optional[str], float, int]}]]\n\u003e\u003e\u003e with open('test.cain', 'w+b') as fp:\n...     cain.dump(['foo', {'bar': ('baz', None, 1.0, 2)}], fp, schema)\n...\n\u003e\u003e\u003e from cain.types import Int\n\u003e\u003e\u003e from cain.types.numbers import unsigned\n\u003e\u003e\u003e Int[unsigned].encode(4)\nb'\\x00\\x04'\n```\n\nYou can also add a header using the `include_header` parameter to add a header containing the schema for the encoding data. This gives a more portable output but increases its size.\n\n#### Decoding\n\nDecoding Cain:\n\n```python\n\u003e\u003e\u003e import cain\n\u003e\u003e\u003e from cain.types import Optional, Object\n\u003e\u003e\u003e schema = list[str, Object[{\"bar\": tuple[str, Optional[str], float, int]}]]\n\u003e\u003e\u003e cain.loads(b'\\x00foo\\x00\\x00\\x00baz\\x00\\x00\\x00\\x00\\x80?\\x00\\x02', schema)\n['foo', {'bar': ('baz', None, 1.0, 2)}]\n\u003e\u003e\u003e with open('test.cain', 'r+b') as fp:\n...     cain.load(fp, schema)\n...\n['foo', {'bar': ('baz', None, 1.0, 2)}]\n\u003e\u003e\u003e from cain.types import Int\n\u003e\u003e\u003e from cain.types.numbers import unsigned\n\u003e\u003e\u003e Int[unsigned].decode(b'\\x00\\x04')\n4\n```\n\n#### Handling Schemas\n\nIf you want to dynamically encode/decode data with the Cain format, it is also possible to encode/decode the schema.\n\nThis is especially useful when developing a public API for example.\n\n##### Encoding\n\n```python\n\u003e\u003e\u003e import cain\n\u003e\u003e\u003e from cain.types import Object, Optional\n\u003e\u003e\u003e cain.encode_schema(Object[{\"a\": int}])\nb'\\x00\\x00\\x01\\x00\\x00a\\x00\\x00\\x01\\x00\\x00\\x01\\x03\\x00\\x01\\x02\\x00\\x00\\x00\\x00\\x06\\x00\\x00\\x00\\x00\\x16'\n\u003e\u003e\u003e class TestObject(Object):\n...     bar: tuple[str, Optional[str], float, int]\n...\n\u003e\u003e\u003e cain.encode_schema(list[str, TestObject])\nb'\\x01\\x02\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x00...\\x00\\x16\\x01\\x00TestObject\\x00\\x00\\x00'\n```\n\n##### Decoding\n\n```python\n\u003e\u003e\u003e import cain\n\u003e\u003e\u003e cain.decode_schema(b'\\x00\\x00\\x01\\x00\\x00a\\x00\\x00\\x01\\x00\\x00\\x01\\x03\\x00\\x01\\x02\\x00\\x00\\x00\\x00\\x06\\x00\\x00\\x00\\x00\\x00\\x16\\x00')\nObject\u003c{'a': Int}\u003e\n\u003e\u003e\u003e cain.decode_schema(b'\\x01\\x02\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x00...\\x00\\x16\\x01\\x00TestObject\\x00\\x00\\x00')\nArray[String, TestObject]\n```\n\n#### Custom Encoder\n\nYou can also create your own encoders:\n\n```python\n\u003e\u003e\u003e import typing\n\u003e\u003e\u003e from cain.model import Datatype\n\u003e\u003e\u003e class MyObject(Datatype):\n...     @classmethod         # *args contains the args passed here : MyObject[args]\n...     def _encode(cls, value: typing.Any,*args) -\u003e bytes:\n...         ... #  your custom encoding\n...         return b'encoded data'\n...     #\n...     @classmethod\n...     def _decode(cls, value: bytes, *args) -\u003e typing.Tuple[typing.Any, bytes]:\n...         ... #  `value` contains more than just the value you should decode\n...         ... #  try to only decode the first few bytes\n...         ... #  your custom decoding\n...         return 'decoded data', value # the rest of the value that you didn't decode\n... # you can now use `MyObject` in your schemas and encode/decode from it\n```\n\n\u003e **Warning**  \n\u003e Keep in mind that custom datatypes outside of subclasses of `Object` won't be able to be encoded by the Type encoder (used in schema headers for example)\n\n### CLI\n\nCain has a pretty complete command-line interface, which lets you manipulate and interact with the Cain data format easily.\n\nFor more information, head over to your console and enter:\n\n```bash\ncain --help\n```\n\nOr\n\n```bash\ncain \u003caction\u003e --help\n```\n\n#### Examples\n\n\u003e Example usage of the CLI\n\nPreparing the schema:\n\n```python\n# test.py\nfrom cain import Object\nclass Test(Object):\n    username: str\n    favorite_number: int\n```\n\nTrying to encode with a Python schema:\n\n```bash\ncain encode '{\"username\": \"Anise\", \"favorite_number\": 2}' --schema=\"test.py\" --schema-name=\"Test\" --include-header --output=\"test.cain\"\n```\n\nTrying to decode the previous file:\n\n```bash\n$ cain decode test.cain\n{\n    \"favorite_number\": 2,\n    \"username\": \"Anise\"\n}\n```\n\nLooking up at its schema:\n\n```bash\n$ cain schema lookup test.cain --schema-header\n{\n    \"index\": 22,\n    \"name\": \"Test\",\n    \"annotations_keys\": [\n        \"username\",\n        \"favorite_number\"\n    ],\n    \"annotations_values\": [\n        {\n            \"index\": 26,\n            \"name\": null,\n            \"annotations_keys\": [],\n            \"annotations_values\": [],\n            \"arguments\": [],\n            \"datatype\": \"String\"\n        },\n        {\n            \"index\": 6,\n            \"name\": null,\n            \"annotations_keys\": [],\n            \"annotations_values\": [],\n            \"arguments\": [],\n            \"datatype\": \"Int\"\n        }\n    ],\n    \"arguments\": [],\n    \"datatype\": \"Object\"\n}\n```\n\nExporting its schema:\n\n```bash\ncain schema export test.cain --schema-header --output test.cainschema\n```\n\nTrying to encode another object with the exported schema:\n\n```bash\n$ cain encode '{\"username\": \"yay\", \"favorite_number\": 3}' --schema=test.cainschema\n\\x00\\x00\\x03yay\\x00\n```\n\nEncoding \"Hello world\":\n\n```bash\n$ cain encode '\"Hello world\"' --schema=\"str\" --schema-eval\nHello world\\x00\n$ cain encode '[\"Hello\", \"world\"]' --schema=\"list[str]\" --schema-eval\n\\x00\\x02\\x00\\x00Hello\\x00world\\x00\n```\n\n## Deployment\n\nThis module is currently in development and might contain bugs.\n\nThis comes with a few disadvantages (for example, it takes a longer time to encode objects with Cain than with the standard `json` module) but this is expected to improve over time.\n\nPlease verify and test the module thoroughly before releasing anything at a production stage.\n\nFeel free to report any issue you might encounter on Cain's GitHub page.\n\n## Contributing\n\nPull requests are welcome. For major changes, please open a discussion first to discuss what you would like to change.\n\nPlease make sure to update the tests accordingly.\n\n## Authors\n\n- **Animenosekai** - *Initial work* - [Animenosekai](https://github.com/Animenosekai)\n\n## Licensing\n\nThis software is licensed under the MIT License. See the [*LICENSE*](./LICENSE) file for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanimenosekai%2Fcain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanimenosekai%2Fcain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanimenosekai%2Fcain/lists"}