| Name | ckanext-unfold JSON |
| Version |
2.1.0
JSON |
| download |
| home_page | None |
| Summary | Provides previews for multiple archive formats |
| upload_time | 2025-11-05 08:38:57 |
| maintainer | None |
| docs_url | None |
| author | None |
| requires_python | None |
| license | AGPL |
| keywords |
ckan
|
| VCS |
 |
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
|
[](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml)
# ckanext-unfold
Enhance your CKAN experience with our extension that enables seamless previews of various archive formats, ensuring easy access and efficient data management.

Features:
- Represents an archive as a file tree
- Supports the following archive formats: ZIP, ZIPX, JAR, RAR, CBR, 7Z, TAR, TAR.XZ, TAR.GZ, TAR.BZ2, DEB, RPM, A, AR, LIB
- Password-protected archives support for RAR format
- Caching the file tree for faster access
- File and folder search
- Support local and remote files
- Support for large archives
## Requirements
CKAN >= 2.10
Python >= 3.11
Redis (for caching)
## Configuration
```ini
ckan.plugins = unfold
ckan.views.default_views = unfold_view
```
### Settings
See the [config declaration](./ckanext/unfold/config_declaration.yaml) file.
## Signals
The extension provides the following signals for customization and extension:
- `unfold:register_format_adapters`: Register custom adapters for specific file formats.
- `unfold:get_adapter_for_resource`: Get a custom adapter for a specific resource.
### Registering a custom adapter
You can register your own adapter for a specific file format by using the `unfold:register_format_adapters` signal.
In fact, it doesn't have to be an archive format — you can register an adapter for any file format that makes sense to be represented as a file tree. To create your own adapter, you need to inherit from `adapters.BaseAdapter` and implement the required methods.
We're providing a simple example adapter below. The node list generation is up to the developer.
```py
from ckanext.unfold.adapters import BaseAdapter
from ckanext.unfold.types import Node
class ExampleAdapter(BaseAdapter):
def get_node_list(self) -> list[Node]:
"""Return list of nodes representing the archive structure.
Ensure, that your implementation handles both local and remote files
based on the `self.remote` attribute.
"""
return self.get_mock_node_list()
def get_mock_node_list(self) -> list[Node]:
return [
unf_types.Node(
id="example_folder/",
text="example_folder",
icon="fa fa-folder",
parent="#",
),
unf_types.Node(
id="example_folder/example_file.txt",
text="example_file.txt",
icon="fa fa-file-text",
parent="example_folder/",
a_attr={"href": "http://example.com/example_file.txt", "target": "_blank"},
data={"type": "file", "size": "50 KB", "modified_at": "26/08/2021 - 20:13"},
),
unf_types.Node(
id="example_folder/example_file.pdf",
text="example_file.pdf",
icon="fa fa-file-pdf",
parent="example_folder/",
data={"type": "file", "size": "1.2 MB", "modified_at": "01/01/2024 - 00:00"},
),
unf_types.Node(
id="another_file.docx",
text="another_file.docx",
icon="fa fa-file-word",
parent="#",
data={"type": "file", "size": "1.0 MB", "modified_at": "01/01/2024 - 00:00"},
),
]
```
Then, you need to **register** your adapter using the signal. Each adapter registration function should accept a single argument, which is the adapter registry.
```py
class ExamplePlugin(p.SingletonPlugin):
...
p.implements(p.ISignal)
# ISignal
def get_signal_subscriptions(self) -> types.SignalMapping:
return {
tk.signals.ckanext.signal("unfold:register_format_adapters"): [
self._register_format_adapters
],
}
@classmethod
def _register_format_adapters(cls, adapters: type[unf_adapters.Registry]) -> None:
adapters.update({"my.format": ExampleAdapter})
```
Each adapter is responsible for handling a specific file format. The key in the registry dictionary is the file format, and the value is the adapter class.
> [!NOTE]
> 1. You can register multiple adapters for different file formats.
> 2. This way, you can replace existing adapters by registering your own adapter for the same format.
The result preview will look like this:

## Getting a custom adapter for a resource
Sometimes, you may want to provide a custom adapter for a specific resource based on some criteria, such as resource metadata or other attributes. Or you may want not to preview certain resources. You can do this by listening to the `unfold:get_adapter_for_resource` signal and returning your custom adapter when the criteria are met.
```py
...
class ExamplePlugin(p.SingletonPlugin):
...
p.implements(p.ISignal)
# ISignal
def get_signal_subscriptions(self) -> types.SignalMapping:
return {
tk.signals.ckanext.signal("unfold:get_adapter_for_resource"): [
self._get_adapter_for_resource
],
}
@classmethod
def _get_adapter_for_resource(cls, resource: dict[str, str]) -> type[BaseAdapter] | None | bool:
if resource.get("format", "").lower() == "my.format":
return ExampleAdapter
return None
```
1. Return an adapter class if you want to provide a custom adapter for the resource.
2. If you return `None`, another extension may provide an adapter, or the default adapter lookup mechanism will be used.
3. If you return `False` from the signal handler, it will prevent further processing, and no adapter will be used for that resource.
## Dependencies
Working with different archive formats requires different tools:
### RAR, CBR
It depends on `unrar` command-line utility to do the actual decompression. Note that by default it expect it to be in `PATH`.
If unrar launching fails, you need to fix this.
Alternatively, `rarfile` can also use either [unar](https://theunarchiver.com/command-line) from [TheUnarchiver](https://theunarchiver.com/) or
[bsdtar](https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1) from [libarchive](https://www.libarchive.org/) as
decompression backend. From those unar is preferred as bsdtar has very limited support for RAR archives.
It depends on [cryptography](https://pypi.org/project/cryptography/) or [PyCryptodome](https://pypi.org/project/pycryptodome/)
modules to process archives with password-protected headers.
### 7Z
We are using [`py7zr`](https://py7zr.readthedocs.io/) library.
The py7zr depends on several external libraries. You should install these libraries with py7zr.
There are `PyCryptodome`, `PyZstd`, `PyPPMd`, `bcj-cffi`, `texttable`, and `multivolumefile`.
These packages are automatically installed when installing with pip command.
For extra information, please visit the [official documentation](https://py7zr.readthedocs.io/en/latest/user_guide.html#dependencies),
especially the dependencies section.
### ZIP, ZIPX, JAR
We are using built-in library [`zipfile`](https://docs.python.org/3/library/zipfile.html). Please consider referring to the official documentation for more information.
### TAR, TAR.XZ, TAR.GZ, TAR.BZ2
We are using built-in library [`tarfile`](https://docs.python.org/3/library/tarfile.html). Please consider referring to the official documentation for more information.
### RPM
We are using [`rpmfile`](https://github.com/srossross/rpmfile) library.
If you want to use rpmfile with zstd compressed rpms, you'll need to install the [`zstandard`](https://pypi.org/project/zstandard/) module.
### DEB, A, AR, LIB
We are using [`ar`](https://github.com/vidstige/ar) library. Please consider referring to the official documentation for more information.
## License
[AGPL](https://www.gnu.org/licenses/agpl-3.0.en.html)
Raw data
{
"_id": null,
"home_page": null,
"name": "ckanext-unfold",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": "DataShades <datashades@linkdigital.com.au>",
"keywords": "CKAN",
"author": null,
"author_email": "DataShades <datashades@linkdigital.com.au>, Oleksandr Cherniavskyi <mutantsan@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/cd/35/78692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965/ckanext_unfold-2.1.0.tar.gz",
"platform": null,
"description": "[](https://github.com/DataShades/ckanext-unfold/actions/workflows/test.yml)\n\n# ckanext-unfold\n\nEnhance your CKAN experience with our extension that enables seamless previews of various archive formats, ensuring easy access and efficient data management.\n\n\n\nFeatures:\n- Represents an archive as a file tree\n- Supports the following archive formats: ZIP, ZIPX, JAR, RAR, CBR, 7Z, TAR, TAR.XZ, TAR.GZ, TAR.BZ2, DEB, RPM, A, AR, LIB\n- Password-protected archives support for RAR format\n- Caching the file tree for faster access\n- File and folder search\n- Support local and remote files\n- Support for large archives\n\n## Requirements\n\nCKAN >= 2.10\n\nPython >= 3.11\n\nRedis (for caching)\n\n## Configuration\n\n```ini\nckan.plugins = unfold\nckan.views.default_views = unfold_view\n```\n\n### Settings\n\nSee the [config declaration](./ckanext/unfold/config_declaration.yaml) file.\n\n## Signals\n\nThe extension provides the following signals for customization and extension:\n- `unfold:register_format_adapters`: Register custom adapters for specific file formats.\n- `unfold:get_adapter_for_resource`: Get a custom adapter for a specific resource.\n\n### Registering a custom adapter\n\nYou can register your own adapter for a specific file format by using the `unfold:register_format_adapters` signal.\n\nIn fact, it doesn't have to be an archive format \u2014 you can register an adapter for any file format that makes sense to be represented as a file tree. To create your own adapter, you need to inherit from `adapters.BaseAdapter` and implement the required methods.\n\nWe're providing a simple example adapter below. The node list generation is up to the developer.\n\n```py\nfrom ckanext.unfold.adapters import BaseAdapter\nfrom ckanext.unfold.types import Node\n\nclass ExampleAdapter(BaseAdapter):\n def get_node_list(self) -> list[Node]:\n \"\"\"Return list of nodes representing the archive structure.\n\n Ensure, that your implementation handles both local and remote files\n based on the `self.remote` attribute.\n \"\"\"\n return self.get_mock_node_list()\n\n def get_mock_node_list(self) -> list[Node]:\n return [\n unf_types.Node(\n id=\"example_folder/\",\n text=\"example_folder\",\n icon=\"fa fa-folder\",\n parent=\"#\",\n ),\n unf_types.Node(\n id=\"example_folder/example_file.txt\",\n text=\"example_file.txt\",\n icon=\"fa fa-file-text\",\n parent=\"example_folder/\",\n a_attr={\"href\": \"http://example.com/example_file.txt\", \"target\": \"_blank\"},\n data={\"type\": \"file\", \"size\": \"50 KB\", \"modified_at\": \"26/08/2021 - 20:13\"},\n ),\n unf_types.Node(\n id=\"example_folder/example_file.pdf\",\n text=\"example_file.pdf\",\n icon=\"fa fa-file-pdf\",\n parent=\"example_folder/\",\n data={\"type\": \"file\", \"size\": \"1.2 MB\", \"modified_at\": \"01/01/2024 - 00:00\"},\n ),\n unf_types.Node(\n id=\"another_file.docx\",\n text=\"another_file.docx\",\n icon=\"fa fa-file-word\",\n parent=\"#\",\n data={\"type\": \"file\", \"size\": \"1.0 MB\", \"modified_at\": \"01/01/2024 - 00:00\"},\n ),\n ]\n```\n\nThen, you need to **register** your adapter using the signal. Each adapter registration function should accept a single argument, which is the adapter registry.\n\n```py\nclass ExamplePlugin(p.SingletonPlugin):\n ...\n\n p.implements(p.ISignal)\n\n # ISignal\n def get_signal_subscriptions(self) -> types.SignalMapping:\n return {\n tk.signals.ckanext.signal(\"unfold:register_format_adapters\"): [\n self._register_format_adapters\n ],\n }\n\n @classmethod\n def _register_format_adapters(cls, adapters: type[unf_adapters.Registry]) -> None:\n adapters.update({\"my.format\": ExampleAdapter})\n```\n\nEach adapter is responsible for handling a specific file format. The key in the registry dictionary is the file format, and the value is the adapter class.\n\n> [!NOTE]\n> 1. You can register multiple adapters for different file formats.\n> 2. This way, you can replace existing adapters by registering your own adapter for the same format.\n\nThe result preview will look like this:\n\n\n\n## Getting a custom adapter for a resource\n\nSometimes, you may want to provide a custom adapter for a specific resource based on some criteria, such as resource metadata or other attributes. Or you may want not to preview certain resources. You can do this by listening to the `unfold:get_adapter_for_resource` signal and returning your custom adapter when the criteria are met.\n\n```py\n...\nclass ExamplePlugin(p.SingletonPlugin):\n ...\n\n p.implements(p.ISignal)\n\n # ISignal\n def get_signal_subscriptions(self) -> types.SignalMapping:\n return {\n tk.signals.ckanext.signal(\"unfold:get_adapter_for_resource\"): [\n self._get_adapter_for_resource\n ],\n }\n\n @classmethod\n def _get_adapter_for_resource(cls, resource: dict[str, str]) -> type[BaseAdapter] | None | bool:\n if resource.get(\"format\", \"\").lower() == \"my.format\":\n return ExampleAdapter\n\n return None\n```\n\n1. Return an adapter class if you want to provide a custom adapter for the resource.\n2. If you return `None`, another extension may provide an adapter, or the default adapter lookup mechanism will be used.\n3. If you return `False` from the signal handler, it will prevent further processing, and no adapter will be used for that resource.\n\n## Dependencies\n\nWorking with different archive formats requires different tools:\n\n### RAR, CBR\n\nIt depends on `unrar` command-line utility to do the actual decompression. Note that by default it expect it to be in `PATH`.\nIf unrar launching fails, you need to fix this.\n\nAlternatively, `rarfile` can also use either [unar](https://theunarchiver.com/command-line) from [TheUnarchiver](https://theunarchiver.com/) or\n[bsdtar](https://github.com/libarchive/libarchive/wiki/ManPageBsdtar1) from [libarchive](https://www.libarchive.org/) as\ndecompression backend. From those unar is preferred as bsdtar has very limited support for RAR archives.\n\nIt depends on [cryptography](https://pypi.org/project/cryptography/) or [PyCryptodome](https://pypi.org/project/pycryptodome/)\nmodules to process archives with password-protected headers.\n\n### 7Z\n\nWe are using [`py7zr`](https://py7zr.readthedocs.io/) library.\n\nThe py7zr depends on several external libraries. You should install these libraries with py7zr.\nThere are `PyCryptodome`, `PyZstd`, `PyPPMd`, `bcj-cffi`, `texttable`, and `multivolumefile`.\nThese packages are automatically installed when installing with pip command.\n\nFor extra information, please visit the [official documentation](https://py7zr.readthedocs.io/en/latest/user_guide.html#dependencies),\nespecially the dependencies section.\n\n### ZIP, ZIPX, JAR\n\nWe are using built-in library [`zipfile`](https://docs.python.org/3/library/zipfile.html). Please consider referring to the official documentation for more information.\n\n### TAR, TAR.XZ, TAR.GZ, TAR.BZ2\n\nWe are using built-in library [`tarfile`](https://docs.python.org/3/library/tarfile.html). Please consider referring to the official documentation for more information.\n\n### RPM\n\nWe are using [`rpmfile`](https://github.com/srossross/rpmfile) library.\n\nIf you want to use rpmfile with zstd compressed rpms, you'll need to install the [`zstandard`](https://pypi.org/project/zstandard/) module.\n\n### DEB, A, AR, LIB\n\nWe are using [`ar`](https://github.com/vidstige/ar) library. Please consider referring to the official documentation for more information.\n\n## License\n\n[AGPL](https://www.gnu.org/licenses/agpl-3.0.en.html)\n",
"bugtrack_url": null,
"license": "AGPL",
"summary": "Provides previews for multiple archive formats",
"version": "2.1.0",
"project_urls": {
"Homepage": "https://github.com/DataShades/ckanext-unfold"
},
"split_keywords": [
"ckan"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "9364f82007a1fde4531b392a0a9de00710d719afb75b388d3d9490ae849ac4de",
"md5": "562d761f2eab48f6340b05dfb5ebd48b",
"sha256": "15a0692b0178da5489b7e0a13084056c3b0e633458affe895193cfee0649e731"
},
"downloads": -1,
"filename": "ckanext_unfold-2.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "562d761f2eab48f6340b05dfb5ebd48b",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 77725,
"upload_time": "2025-11-05T08:38:55",
"upload_time_iso_8601": "2025-11-05T08:38:55.989838Z",
"url": "https://files.pythonhosted.org/packages/93/64/f82007a1fde4531b392a0a9de00710d719afb75b388d3d9490ae849ac4de/ckanext_unfold-2.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "cd3578692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965",
"md5": "99c0add97000c832e4a25aa62ee257e0",
"sha256": "c58265eda6e8c7019175f907ee697872ee650202f0819c342e610fd72eabc2d4"
},
"downloads": -1,
"filename": "ckanext_unfold-2.1.0.tar.gz",
"has_sig": false,
"md5_digest": "99c0add97000c832e4a25aa62ee257e0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 73036,
"upload_time": "2025-11-05T08:38:57",
"upload_time_iso_8601": "2025-11-05T08:38:57.611023Z",
"url": "https://files.pythonhosted.org/packages/cd/35/78692dd905b3283a3fde5b65a189704e3bd418bac13172b3a8e65cb40965/ckanext_unfold-2.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-05 08:38:57",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "DataShades",
"github_project": "ckanext-unfold",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "ckanext-unfold"
}