toolify 0.2.0__tar.gz → 1.0.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.
- {toolify-0.2.0 → toolify-1.0.0}/LICENSE +2 -2
- toolify-1.0.0/PKG-INFO +100 -0
- toolify-1.0.0/README.md +61 -0
- toolify-1.0.0/pyproject.toml +68 -0
- toolify-1.0.0/setup.py +3 -0
- toolify-1.0.0/tests/test_ai.py +223 -0
- toolify-1.0.0/tests/test_tools.py +103 -0
- toolify-1.0.0/tests/test_youtube.py +190 -0
- toolify-1.0.0/toolify/__init__.py +35 -0
- toolify-1.0.0/toolify/ai/__init__.py +15 -0
- toolify-1.0.0/toolify/ai/ai.py +15 -0
- toolify-1.0.0/toolify/ai/huggingface.py +439 -0
- toolify-1.0.0/toolify/audio/__init__.py +15 -0
- toolify-1.0.0/toolify/audio/audio.py +199 -0
- toolify-1.0.0/toolify/plots/__init__.py +7 -0
- toolify-1.0.0/toolify/plots/plots.py +75 -0
- toolify-1.0.0/toolify/tools/__init__.py +12 -0
- toolify-1.0.0/toolify/tools/constants.py +178 -0
- toolify-1.0.0/toolify/tools/tools.py +369 -0
- toolify-1.0.0/toolify/youtube/__init__.py +21 -0
- toolify-1.0.0/toolify/youtube/youtube.py +453 -0
- toolify-1.0.0/toolify.egg-info/PKG-INFO +100 -0
- toolify-1.0.0/toolify.egg-info/SOURCES.txt +25 -0
- toolify-1.0.0/toolify.egg-info/requires.txt +20 -0
- toolify-0.2.0/PKG-INFO +0 -107
- toolify-0.2.0/README.md +0 -74
- toolify-0.2.0/setup.py +0 -33
- toolify-0.2.0/toolify/__init__.py +0 -1
- toolify-0.2.0/toolify/tools-old.py +0 -234
- toolify-0.2.0/toolify/tools.py +0 -292
- toolify-0.2.0/toolify.egg-info/PKG-INFO +0 -107
- toolify-0.2.0/toolify.egg-info/SOURCES.txt +0 -11
- toolify-0.2.0/toolify.egg-info/requires.txt +0 -2
- {toolify-0.2.0 → toolify-1.0.0}/setup.cfg +0 -0
- {toolify-0.2.0 → toolify-1.0.0}/toolify.egg-info/dependency_links.txt +0 -0
- {toolify-0.2.0 → toolify-1.0.0}/toolify.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Amr Abdelsamea
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
toolify-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toolify
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Utilities for terminal output, Arabic text, audio, plotting, Hugging Face, and YouTube.
|
|
5
|
+
Author-email: Amr Abdelsamea <amr.abdelsamee33@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Amr-abdelsamee/toolify
|
|
8
|
+
Project-URL: Source, https://github.com/Amr-abdelsamee/toolify
|
|
9
|
+
Project-URL: Documentation, https://amr-abdelsamee.github.io/toolify/
|
|
10
|
+
Project-URL: Issues, https://github.com/Amr-abdelsamee/toolify/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/Amr-abdelsamee/toolify/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: tools,arabic,audio,plotting,youtube,huggingface
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: python-bidi==0.6.6
|
|
21
|
+
Requires-Dist: arabic-reshaper==3.0.0
|
|
22
|
+
Requires-Dist: youtube-transcript-api==1.1.0
|
|
23
|
+
Requires-Dist: yt-dlp==2025.10.22
|
|
24
|
+
Requires-Dist: tqdm==4.67.1
|
|
25
|
+
Requires-Dist: matplotlib==3.11.0
|
|
26
|
+
Requires-Dist: librosa==0.11.0
|
|
27
|
+
Requires-Dist: numpy==2.4.6
|
|
28
|
+
Requires-Dist: soundfile==0.14.0
|
|
29
|
+
Requires-Dist: huggingface-hub==1.21.0
|
|
30
|
+
Requires-Dist: hf-transfer==0.1.9
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: build; extra == "dev"
|
|
34
|
+
Requires-Dist: twine; extra == "dev"
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# Toolify
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/toolify/)
|
|
43
|
+
[](https://pypi.org/project/toolify/)
|
|
44
|
+
[](https://amr-abdelsamee.github.io/toolify/)
|
|
45
|
+
[](https://github.com/Amr-abdelsamee/toolify/blob/main/LICENSE)
|
|
46
|
+
|
|
47
|
+
Toolify is a Python utility library for terminal output, Arabic text handling,
|
|
48
|
+
logging, plotting, audio inspection, Hugging Face downloads, and YouTube media
|
|
49
|
+
downloads.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
| Module | Purpose |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `toolify.tools` | Colored output, Arabic text, tables, logging, and confirmation |
|
|
56
|
+
| `toolify.plots` | Line-plot generation |
|
|
57
|
+
| `toolify.audio` | Silence detection, spectrograms, and duration helpers |
|
|
58
|
+
| `toolify.ai` | Hugging Face repository inspection and downloads |
|
|
59
|
+
| `toolify.youtube` | YouTube video, transcript, and playlist utilities |
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
Install the latest release from PyPI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install toolify
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Python 3.11 or newer is required. FFmpeg is also required when YouTube video
|
|
70
|
+
and audio streams need to be merged.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
The complete guides and API reference are available in the
|
|
76
|
+
[Toolify documentation](https://amr-abdelsamee.github.io/toolify/).
|
|
77
|
+
|
|
78
|
+
- [Installation](https://amr-abdelsamee.github.io/toolify/getting-started/installation/)
|
|
79
|
+
- [Quick start](https://amr-abdelsamee.github.io/toolify/getting-started/quickstart/)
|
|
80
|
+
- [User guides](https://amr-abdelsamee.github.io/toolify/guides/tools/)
|
|
81
|
+
- [API reference](https://amr-abdelsamee.github.io/toolify/reference/tools/)
|
|
82
|
+
- [Changelog](https://github.com/Amr-abdelsamee/toolify/blob/main/CHANGELOG.md)
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/Amr-abdelsamee/toolify.git
|
|
88
|
+
cd toolify
|
|
89
|
+
pip install -e ".[dev,docs]"
|
|
90
|
+
python -m pytest -m "not integration"
|
|
91
|
+
mkdocs serve
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
See the [contributing guide](https://amr-abdelsamee.github.io/toolify/development/contributing/)
|
|
95
|
+
and [release guide](https://amr-abdelsamee.github.io/toolify/development/releasing/).
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Toolify is licensed under the
|
|
100
|
+
[MIT License](https://github.com/Amr-abdelsamee/toolify/blob/main/LICENSE).
|
toolify-1.0.0/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Toolify
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/toolify/)
|
|
4
|
+
[](https://pypi.org/project/toolify/)
|
|
5
|
+
[](https://amr-abdelsamee.github.io/toolify/)
|
|
6
|
+
[](https://github.com/Amr-abdelsamee/toolify/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Toolify is a Python utility library for terminal output, Arabic text handling,
|
|
9
|
+
logging, plotting, audio inspection, Hugging Face downloads, and YouTube media
|
|
10
|
+
downloads.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
| Module | Purpose |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `toolify.tools` | Colored output, Arabic text, tables, logging, and confirmation |
|
|
17
|
+
| `toolify.plots` | Line-plot generation |
|
|
18
|
+
| `toolify.audio` | Silence detection, spectrograms, and duration helpers |
|
|
19
|
+
| `toolify.ai` | Hugging Face repository inspection and downloads |
|
|
20
|
+
| `toolify.youtube` | YouTube video, transcript, and playlist utilities |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the latest release from PyPI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install toolify
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Python 3.11 or newer is required. FFmpeg is also required when YouTube video
|
|
31
|
+
and audio streams need to be merged.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Documentation
|
|
35
|
+
|
|
36
|
+
The complete guides and API reference are available in the
|
|
37
|
+
[Toolify documentation](https://amr-abdelsamee.github.io/toolify/).
|
|
38
|
+
|
|
39
|
+
- [Installation](https://amr-abdelsamee.github.io/toolify/getting-started/installation/)
|
|
40
|
+
- [Quick start](https://amr-abdelsamee.github.io/toolify/getting-started/quickstart/)
|
|
41
|
+
- [User guides](https://amr-abdelsamee.github.io/toolify/guides/tools/)
|
|
42
|
+
- [API reference](https://amr-abdelsamee.github.io/toolify/reference/tools/)
|
|
43
|
+
- [Changelog](https://github.com/Amr-abdelsamee/toolify/blob/main/CHANGELOG.md)
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/Amr-abdelsamee/toolify.git
|
|
49
|
+
cd toolify
|
|
50
|
+
pip install -e ".[dev,docs]"
|
|
51
|
+
python -m pytest -m "not integration"
|
|
52
|
+
mkdocs serve
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
See the [contributing guide](https://amr-abdelsamee.github.io/toolify/development/contributing/)
|
|
56
|
+
and [release guide](https://amr-abdelsamee.github.io/toolify/development/releasing/).
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
Toolify is licensed under the
|
|
61
|
+
[MIT License](https://github.com/Amr-abdelsamee/toolify/blob/main/LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "toolify"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Utilities for terminal output, Arabic text, audio, plotting, Hugging Face, and YouTube."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Amr Abdelsamea", email = "amr.abdelsamee33@gmail.com" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
keywords = [
|
|
18
|
+
"tools",
|
|
19
|
+
"arabic",
|
|
20
|
+
"audio",
|
|
21
|
+
"plotting",
|
|
22
|
+
"youtube",
|
|
23
|
+
"huggingface",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
dependencies = [
|
|
34
|
+
"python-bidi==0.6.6",
|
|
35
|
+
"arabic-reshaper==3.0.0",
|
|
36
|
+
"youtube-transcript-api==1.1.0",
|
|
37
|
+
"yt-dlp==2025.10.22",
|
|
38
|
+
"tqdm==4.67.1",
|
|
39
|
+
"matplotlib==3.11.0",
|
|
40
|
+
"librosa==0.11.0",
|
|
41
|
+
"numpy==2.4.6",
|
|
42
|
+
"soundfile==0.14.0",
|
|
43
|
+
"huggingface-hub==1.21.0",
|
|
44
|
+
"hf-transfer==0.1.9",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
dev = [
|
|
49
|
+
"pytest",
|
|
50
|
+
"build",
|
|
51
|
+
"twine",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
docs = [
|
|
55
|
+
"mkdocs-material",
|
|
56
|
+
"mkdocstrings[python]",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Homepage = "https://github.com/Amr-abdelsamee/toolify"
|
|
61
|
+
Source = "https://github.com/Amr-abdelsamee/toolify"
|
|
62
|
+
Documentation = "https://amr-abdelsamee.github.io/toolify/"
|
|
63
|
+
Issues = "https://github.com/Amr-abdelsamee/toolify/issues"
|
|
64
|
+
Changelog = "https://github.com/Amr-abdelsamee/toolify/blob/main/CHANGELOG.md"
|
|
65
|
+
|
|
66
|
+
[tool.setuptools.packages.find]
|
|
67
|
+
include = ["toolify*"]
|
|
68
|
+
exclude = ["tests*", "docs*"]
|
toolify-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import toolify.ai.huggingface as hf
|
|
8
|
+
from toolify.ai import (
|
|
9
|
+
get_hf_model_size,
|
|
10
|
+
get_hf_dataset_size,
|
|
11
|
+
download_hf_model,
|
|
12
|
+
download_hf_dataset,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
GB = 1024**3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_ai_public_imports():
|
|
20
|
+
assert callable(get_hf_model_size)
|
|
21
|
+
assert callable(get_hf_dataset_size)
|
|
22
|
+
assert callable(download_hf_model)
|
|
23
|
+
assert callable(download_hf_dataset)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_hf_model_size_falls_back_when_safetensors_size_is_none(monkeypatch):
|
|
27
|
+
"""This test catches the real bug:
|
|
28
|
+
safetensors metadata exists but total size cannot be extracted.
|
|
29
|
+
The fallback must reset total_bytes to 0, not keep it as None.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
class FakeHfApi:
|
|
33
|
+
def __init__(self, token=None):
|
|
34
|
+
self.token = token
|
|
35
|
+
|
|
36
|
+
def get_safetensors_metadata(self, repo_id, revision=None):
|
|
37
|
+
# No total_size, no metadata["total_size"], no files_metadata sizes.
|
|
38
|
+
return SimpleNamespace()
|
|
39
|
+
|
|
40
|
+
def model_info(self, repo_id, revision=None, files_metadata=True):
|
|
41
|
+
return SimpleNamespace(
|
|
42
|
+
siblings=[
|
|
43
|
+
SimpleNamespace(size=1 * GB),
|
|
44
|
+
SimpleNamespace(size=2 * GB),
|
|
45
|
+
SimpleNamespace(size=None),
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
monkeypatch.setattr(
|
|
50
|
+
hf,
|
|
51
|
+
"_require_huggingface_hub",
|
|
52
|
+
lambda: (FakeHfApi, None),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
size = hf.get_hf_model_size("org/test-model", verbose=False)
|
|
56
|
+
|
|
57
|
+
assert size == 3.0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_get_hf_model_size_uses_safetensors_when_valid(monkeypatch):
|
|
61
|
+
class FakeHfApi:
|
|
62
|
+
def __init__(self, token=None):
|
|
63
|
+
self.token = token
|
|
64
|
+
|
|
65
|
+
def get_safetensors_metadata(self, repo_id, revision=None):
|
|
66
|
+
return SimpleNamespace(total_size=4 * GB)
|
|
67
|
+
|
|
68
|
+
def model_info(self, *args, **kwargs):
|
|
69
|
+
raise AssertionError("model_info should not be called")
|
|
70
|
+
|
|
71
|
+
monkeypatch.setattr(
|
|
72
|
+
hf,
|
|
73
|
+
"_require_huggingface_hub",
|
|
74
|
+
lambda: (FakeHfApi, None),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
size = hf.get_hf_model_size("org/test-model", verbose=False)
|
|
78
|
+
|
|
79
|
+
assert size == 4.0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_get_hf_dataset_size(monkeypatch):
|
|
83
|
+
class FakeHfApi:
|
|
84
|
+
def __init__(self, token=None):
|
|
85
|
+
self.token = token
|
|
86
|
+
|
|
87
|
+
def dataset_info(self, repo_id, revision=None, expand=None):
|
|
88
|
+
assert expand == ["usedStorage"]
|
|
89
|
+
return SimpleNamespace(usedStorage=5 * GB)
|
|
90
|
+
|
|
91
|
+
monkeypatch.setattr(
|
|
92
|
+
hf,
|
|
93
|
+
"_require_huggingface_hub",
|
|
94
|
+
lambda: (FakeHfApi, None),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
size = hf.get_hf_dataset_size("org/test-dataset", verbose=False)
|
|
98
|
+
|
|
99
|
+
assert size == 5.0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_download_hf_model_uses_local_dir(monkeypatch, tmp_path):
|
|
103
|
+
calls = {}
|
|
104
|
+
|
|
105
|
+
class FakeHfApi:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def fake_snapshot_download(**kwargs):
|
|
109
|
+
calls.update(kwargs)
|
|
110
|
+
|
|
111
|
+
local_dir = Path(kwargs["local_dir"])
|
|
112
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
(local_dir / "README.md").write_text("fake model", encoding="utf-8")
|
|
114
|
+
|
|
115
|
+
return str(local_dir)
|
|
116
|
+
|
|
117
|
+
monkeypatch.setattr(
|
|
118
|
+
hf,
|
|
119
|
+
"_require_huggingface_hub",
|
|
120
|
+
lambda: (FakeHfApi, fake_snapshot_download),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
target_dir = tmp_path / "custom_model"
|
|
124
|
+
|
|
125
|
+
path = hf.download_hf_model(
|
|
126
|
+
"org/test-model",
|
|
127
|
+
local_dir=target_dir,
|
|
128
|
+
show_size=False,
|
|
129
|
+
verbose=False,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
assert path == target_dir
|
|
133
|
+
assert path.exists()
|
|
134
|
+
assert (path / "README.md").exists()
|
|
135
|
+
assert calls["repo_id"] == "org/test-model"
|
|
136
|
+
assert calls["repo_type"] == "model"
|
|
137
|
+
assert Path(calls["local_dir"]) == target_dir
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_download_hf_dataset_uses_base_dir(monkeypatch, tmp_path):
|
|
141
|
+
calls = {}
|
|
142
|
+
|
|
143
|
+
class FakeHfApi:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
def fake_snapshot_download(**kwargs):
|
|
147
|
+
calls.update(kwargs)
|
|
148
|
+
|
|
149
|
+
local_dir = Path(kwargs["local_dir"])
|
|
150
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
(local_dir / "README.md").write_text("fake dataset", encoding="utf-8")
|
|
152
|
+
|
|
153
|
+
return str(local_dir)
|
|
154
|
+
|
|
155
|
+
monkeypatch.setattr(
|
|
156
|
+
hf,
|
|
157
|
+
"_require_huggingface_hub",
|
|
158
|
+
lambda: (FakeHfApi, fake_snapshot_download),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
path = hf.download_hf_dataset(
|
|
162
|
+
"org/test-dataset",
|
|
163
|
+
base_dir=tmp_path / "datasets",
|
|
164
|
+
show_size=False,
|
|
165
|
+
verbose=False,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
expected = tmp_path / "datasets" / "org_test-dataset"
|
|
169
|
+
|
|
170
|
+
assert path == expected
|
|
171
|
+
assert path.exists()
|
|
172
|
+
assert (path / "README.md").exists()
|
|
173
|
+
assert calls["repo_id"] == "org/test-dataset"
|
|
174
|
+
assert calls["repo_type"] == "dataset"
|
|
175
|
+
assert Path(calls["local_dir"]) == expected
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_download_hf_repo_rejects_invalid_repo_type(tmp_path):
|
|
179
|
+
with pytest.raises(ValueError):
|
|
180
|
+
hf.download_hf_repo(
|
|
181
|
+
"org/test-repo",
|
|
182
|
+
repo_type="invalid", # type: ignore[arg-type]
|
|
183
|
+
base_dir=tmp_path,
|
|
184
|
+
verbose=False,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@pytest.mark.integration
|
|
189
|
+
def test_integration_get_real_hf_model_size():
|
|
190
|
+
"""Real Hugging Face test.
|
|
191
|
+
|
|
192
|
+
Run only when:
|
|
193
|
+
RUN_HF_INTEGRATION=1 python -m pytest tests/test_ai.py -m integration -v
|
|
194
|
+
"""
|
|
195
|
+
if os.getenv("RUN_HF_INTEGRATION") != "1":
|
|
196
|
+
pytest.skip("Set RUN_HF_INTEGRATION=1 to run Hugging Face integration tests.")
|
|
197
|
+
|
|
198
|
+
size = hf.get_hf_model_size(
|
|
199
|
+
"Qwen/Qwen3-Reranker-0.6B",
|
|
200
|
+
verbose=True,
|
|
201
|
+
raise_on_error=True,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
assert size is not None
|
|
205
|
+
assert size > 0
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.mark.integration
|
|
209
|
+
def test_integration_download_model_readme_only(tmp_path):
|
|
210
|
+
"""Downloads only tiny files from a real model repo."""
|
|
211
|
+
if os.getenv("RUN_HF_INTEGRATION") != "1":
|
|
212
|
+
pytest.skip("Set RUN_HF_INTEGRATION=1 to run Hugging Face integration tests.")
|
|
213
|
+
|
|
214
|
+
path = hf.download_hf_model(
|
|
215
|
+
"Qwen/Qwen3-Reranker-0.6B",
|
|
216
|
+
local_dir=tmp_path / "qwen_reranker_test",
|
|
217
|
+
allow_patterns=["README.md", "config.json"],
|
|
218
|
+
show_size=False,
|
|
219
|
+
verbose=True,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
assert path.exists()
|
|
223
|
+
assert any(path.iterdir())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import pytest
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from toolify.tools import (
|
|
7
|
+
pct,
|
|
8
|
+
print_table,
|
|
9
|
+
setup_logger,
|
|
10
|
+
strip_tashkeel,
|
|
11
|
+
confirm,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_strip_tashkeel_removes_arabic_diacritics():
|
|
16
|
+
text = "مُحَمَّدٌ"
|
|
17
|
+
result = strip_tashkeel(text)
|
|
18
|
+
|
|
19
|
+
assert result == "محمد"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_strip_tashkeel_removes_special_symbols_by_default():
|
|
23
|
+
text = "ا^ل>س<لام؞"
|
|
24
|
+
result = strip_tashkeel(text)
|
|
25
|
+
|
|
26
|
+
assert result == "السلام"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_strip_tashkeel_can_keep_special_symbols():
|
|
30
|
+
text = "ا^ل>س<لام؞"
|
|
31
|
+
result = strip_tashkeel(text, remove_special_symbols=False)
|
|
32
|
+
|
|
33
|
+
assert result == "ا^ل>س<لام؞"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_pct_prints_plain_text_when_ec_false(capsys):
|
|
37
|
+
pct("Hello Toolify", ec=False)
|
|
38
|
+
|
|
39
|
+
captured = capsys.readouterr()
|
|
40
|
+
|
|
41
|
+
assert captured.out == "Hello Toolify\n"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_pct_prints_with_emoji_when_ec_false(capsys):
|
|
45
|
+
pct("Done", ec=False, emoji="success", end_emoji="fire")
|
|
46
|
+
|
|
47
|
+
captured = capsys.readouterr()
|
|
48
|
+
|
|
49
|
+
assert captured.out == "✅ Done 🔥\n"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_pct_custom_end(capsys):
|
|
53
|
+
pct("Hello", ec=False, end="")
|
|
54
|
+
|
|
55
|
+
captured = capsys.readouterr()
|
|
56
|
+
|
|
57
|
+
assert captured.out == "Hello"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_print_table_outputs_headers_and_rows(capsys):
|
|
61
|
+
headers = ["Name", "Score"]
|
|
62
|
+
rows = [
|
|
63
|
+
["Ali", 95],
|
|
64
|
+
["Sara", 88],
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
print_table(headers, rows)
|
|
68
|
+
|
|
69
|
+
captured = capsys.readouterr()
|
|
70
|
+
|
|
71
|
+
assert "Name" in captured.out
|
|
72
|
+
assert "Score" in captured.out
|
|
73
|
+
assert "Ali" in captured.out
|
|
74
|
+
assert "Sara" in captured.out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_setup_logger_creates_log_file(tmp_path):
|
|
78
|
+
date_str = datetime.now().strftime("%Y_%m_%d")
|
|
79
|
+
log_file = tmp_path /f"test.log"
|
|
80
|
+
logger = setup_logger(__name__, str(log_file))
|
|
81
|
+
log_file = tmp_path /f"test__{date_str}.log"
|
|
82
|
+
logger.info("Hello logger")
|
|
83
|
+
|
|
84
|
+
assert isinstance(logger, logging.Logger)
|
|
85
|
+
assert log_file.exists()
|
|
86
|
+
|
|
87
|
+
content = log_file.read_text(encoding="utf-8")
|
|
88
|
+
assert "Hello logger" in content
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_confirm_returns_none_when_user_confirms(monkeypatch):
|
|
92
|
+
monkeypatch.setattr("builtins.input", lambda _: "yes")
|
|
93
|
+
|
|
94
|
+
result = confirm(message="Continue? ")
|
|
95
|
+
|
|
96
|
+
assert result is None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_confirm_exits_when_user_declines(monkeypatch):
|
|
100
|
+
monkeypatch.setattr("builtins.input", lambda _: "no")
|
|
101
|
+
|
|
102
|
+
with pytest.raises(SystemExit):
|
|
103
|
+
confirm(message="Continue? ")
|