"
+
+ __repr__ = __str__
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
diff --git a/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/__init__.py b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..45d081921ad29104bedd336dbf04fa86e1e48b7a
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/__init__.py
@@ -0,0 +1,287 @@
+import os
+from hashlib import md5
+
+import pytest
+
+from fsspec.implementations.local import LocalFileSystem
+from fsspec.tests.abstract.copy import AbstractCopyTests # noqa
+from fsspec.tests.abstract.get import AbstractGetTests # noqa
+from fsspec.tests.abstract.put import AbstractPutTests # noqa
+
+
+class BaseAbstractFixtures:
+ """
+ Abstract base class containing fixtures that are used by but never need to
+ be overridden in derived filesystem-specific classes to run the abstract
+ tests on such filesystems.
+ """
+
+ @pytest.fixture
+ def fs_bulk_operations_scenario_0(self, fs, fs_join, fs_path):
+ """
+ Scenario on remote filesystem that is used for many cp/get/put tests.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._bulk_operations_scenario_0(fs, fs_join, fs_path)
+ yield source
+ fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def fs_glob_edge_cases_files(self, fs, fs_join, fs_path):
+ """
+ Scenario on remote filesystem that is used for glob edge cases cp/get/put tests.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._glob_edge_cases_files(fs, fs_join, fs_path)
+ yield source
+ fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def fs_dir_and_file_with_same_name_prefix(self, fs, fs_join, fs_path):
+ """
+ Scenario on remote filesystem that is used to check cp/get/put on directory
+ and file with the same name prefixes.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._dir_and_file_with_same_name_prefix(fs, fs_join, fs_path)
+ yield source
+ fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def fs_10_files_with_hashed_names(self, fs, fs_join, fs_path):
+ """
+ Scenario on remote filesystem that is used to check cp/get/put files order
+ when source and destination are lists.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._10_files_with_hashed_names(fs, fs_join, fs_path)
+ yield source
+ fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def fs_target(self, fs, fs_join, fs_path):
+ """
+ Return name of remote directory that does not yet exist to copy into.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ target = fs_join(fs_path, "target")
+ yield target
+ if fs.exists(target):
+ fs.rm(target, recursive=True)
+
+ @pytest.fixture
+ def local_bulk_operations_scenario_0(self, local_fs, local_join, local_path):
+ """
+ Scenario on local filesystem that is used for many cp/get/put tests.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._bulk_operations_scenario_0(local_fs, local_join, local_path)
+ yield source
+ local_fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def local_glob_edge_cases_files(self, local_fs, local_join, local_path):
+ """
+ Scenario on local filesystem that is used for glob edge cases cp/get/put tests.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._glob_edge_cases_files(local_fs, local_join, local_path)
+ yield source
+ local_fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def local_dir_and_file_with_same_name_prefix(
+ self, local_fs, local_join, local_path
+ ):
+ """
+ Scenario on local filesystem that is used to check cp/get/put on directory
+ and file with the same name prefixes.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._dir_and_file_with_same_name_prefix(
+ local_fs, local_join, local_path
+ )
+ yield source
+ local_fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def local_10_files_with_hashed_names(self, local_fs, local_join, local_path):
+ """
+ Scenario on local filesystem that is used to check cp/get/put files order
+ when source and destination are lists.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ source = self._10_files_with_hashed_names(local_fs, local_join, local_path)
+ yield source
+ local_fs.rm(source, recursive=True)
+
+ @pytest.fixture
+ def local_target(self, local_fs, local_join, local_path):
+ """
+ Return name of local directory that does not yet exist to copy into.
+
+ Cleans up at the end of each test it which it is used.
+ """
+ target = local_join(local_path, "target")
+ yield target
+ if local_fs.exists(target):
+ local_fs.rm(target, recursive=True)
+
+ def _glob_edge_cases_files(self, some_fs, some_join, some_path):
+ """
+ Scenario that is used for glob edge cases cp/get/put tests.
+ Creates the following directory and file structure:
+
+ 📁 source
+ ├── 📄 file1
+ ├── 📄 file2
+ ├── 📁 subdir0
+ │ ├── 📄 subfile1
+ │ ├── 📄 subfile2
+ │ └── 📁 nesteddir
+ │ └── 📄 nestedfile
+ └── 📁 subdir1
+ ├── 📄 subfile1
+ ├── 📄 subfile2
+ └── 📁 nesteddir
+ └── 📄 nestedfile
+ """
+ source = some_join(some_path, "source")
+ some_fs.touch(some_join(source, "file1"))
+ some_fs.touch(some_join(source, "file2"))
+
+ for subdir_idx in range(2):
+ subdir = some_join(source, f"subdir{subdir_idx}")
+ nesteddir = some_join(subdir, "nesteddir")
+ some_fs.makedirs(nesteddir)
+ some_fs.touch(some_join(subdir, "subfile1"))
+ some_fs.touch(some_join(subdir, "subfile2"))
+ some_fs.touch(some_join(nesteddir, "nestedfile"))
+
+ return source
+
+ def _bulk_operations_scenario_0(self, some_fs, some_join, some_path):
+ """
+ Scenario that is used for many cp/get/put tests. Creates the following
+ directory and file structure:
+
+ 📁 source
+ ├── 📄 file1
+ ├── 📄 file2
+ └── 📁 subdir
+ ├── 📄 subfile1
+ ├── 📄 subfile2
+ └── 📁 nesteddir
+ └── 📄 nestedfile
+ """
+ source = some_join(some_path, "source")
+ subdir = some_join(source, "subdir")
+ nesteddir = some_join(subdir, "nesteddir")
+ some_fs.makedirs(nesteddir)
+ some_fs.touch(some_join(source, "file1"))
+ some_fs.touch(some_join(source, "file2"))
+ some_fs.touch(some_join(subdir, "subfile1"))
+ some_fs.touch(some_join(subdir, "subfile2"))
+ some_fs.touch(some_join(nesteddir, "nestedfile"))
+ return source
+
+ def _dir_and_file_with_same_name_prefix(self, some_fs, some_join, some_path):
+ """
+ Scenario that is used to check cp/get/put on directory and file with
+ the same name prefixes. Creates the following directory and file structure:
+
+ 📁 source
+ ├── 📄 subdir.txt
+ └── 📁 subdir
+ └── 📄 subfile.txt
+ """
+ source = some_join(some_path, "source")
+ subdir = some_join(source, "subdir")
+ file = some_join(source, "subdir.txt")
+ subfile = some_join(subdir, "subfile.txt")
+ some_fs.makedirs(subdir)
+ some_fs.touch(file)
+ some_fs.touch(subfile)
+ return source
+
+ def _10_files_with_hashed_names(self, some_fs, some_join, some_path):
+ """
+ Scenario that is used to check cp/get/put files order when source and
+ destination are lists. Creates the following directory and file structure:
+
+ 📁 source
+ └── 📄 {hashed([0-9])}.txt
+ """
+ source = some_join(some_path, "source")
+ for i in range(10):
+ hashed_i = md5(str(i).encode("utf-8")).hexdigest()
+ path = some_join(source, f"{hashed_i}.txt")
+ some_fs.pipe(path=path, value=f"{i}".encode("utf-8"))
+ return source
+
+
+class AbstractFixtures(BaseAbstractFixtures):
+ """
+ Abstract base class containing fixtures that may be overridden in derived
+ filesystem-specific classes to run the abstract tests on such filesystems.
+
+ For any particular filesystem some of these fixtures must be overridden,
+ such as ``fs`` and ``fs_path``, and others may be overridden if the
+ default functions here are not appropriate, such as ``fs_join``.
+ """
+
+ @pytest.fixture
+ def fs(self):
+ raise NotImplementedError("This function must be overridden in derived classes")
+
+ @pytest.fixture
+ def fs_join(self):
+ """
+ Return a function that joins its arguments together into a path.
+
+ Most fsspec implementations join paths in a platform-dependent way,
+ but some will override this to always use a forward slash.
+ """
+ return os.path.join
+
+ @pytest.fixture
+ def fs_path(self):
+ raise NotImplementedError("This function must be overridden in derived classes")
+
+ @pytest.fixture(scope="class")
+ def local_fs(self):
+ # Maybe need an option for auto_mkdir=False? This is only relevant
+ # for certain implementations.
+ return LocalFileSystem(auto_mkdir=True)
+
+ @pytest.fixture
+ def local_join(self):
+ """
+ Return a function that joins its arguments together into a path, on
+ the local filesystem.
+ """
+ return os.path.join
+
+ @pytest.fixture
+ def local_path(self, tmpdir):
+ return tmpdir
+
+ @pytest.fixture
+ def supports_empty_directories(self):
+ """
+ Return whether this implementation supports empty directories.
+ """
+ return True
+
+ @pytest.fixture
+ def fs_sanitize_path(self):
+ return lambda x: x
diff --git a/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/common.py b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..22e7c4140404ab2a8928689721419cf05c2760b9
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/common.py
@@ -0,0 +1,175 @@
+GLOB_EDGE_CASES_TESTS = {
+ "argnames": ("path", "recursive", "maxdepth", "expected"),
+ "argvalues": [
+ ("fil?1", False, None, ["file1"]),
+ ("fil?1", True, None, ["file1"]),
+ ("file[1-2]", False, None, ["file1", "file2"]),
+ ("file[1-2]", True, None, ["file1", "file2"]),
+ ("*", False, None, ["file1", "file2"]),
+ (
+ "*",
+ True,
+ None,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir0/nesteddir/nestedfile",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ ("*", True, 1, ["file1", "file2"]),
+ (
+ "*",
+ True,
+ 2,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ ],
+ ),
+ ("*1", False, None, ["file1"]),
+ (
+ "*1",
+ True,
+ None,
+ [
+ "file1",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ ("*1", True, 2, ["file1", "subdir1/subfile1", "subdir1/subfile2"]),
+ (
+ "**",
+ False,
+ None,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir0/nesteddir/nestedfile",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ (
+ "**",
+ True,
+ None,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir0/nesteddir/nestedfile",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ ("**", True, 1, ["file1", "file2"]),
+ (
+ "**",
+ True,
+ 2,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir0/nesteddir/nestedfile",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ (
+ "**",
+ False,
+ 2,
+ [
+ "file1",
+ "file2",
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ ],
+ ),
+ ("**/*1", False, None, ["file1", "subdir0/subfile1", "subdir1/subfile1"]),
+ (
+ "**/*1",
+ True,
+ None,
+ [
+ "file1",
+ "subdir0/subfile1",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ ("**/*1", True, 1, ["file1"]),
+ (
+ "**/*1",
+ True,
+ 2,
+ ["file1", "subdir0/subfile1", "subdir1/subfile1", "subdir1/subfile2"],
+ ),
+ ("**/*1", False, 2, ["file1", "subdir0/subfile1", "subdir1/subfile1"]),
+ ("**/subdir0", False, None, []),
+ ("**/subdir0", True, None, ["subfile1", "subfile2", "nesteddir/nestedfile"]),
+ ("**/subdir0/nested*", False, 2, []),
+ ("**/subdir0/nested*", True, 2, ["nestedfile"]),
+ ("subdir[1-2]", False, None, []),
+ ("subdir[1-2]", True, None, ["subfile1", "subfile2", "nesteddir/nestedfile"]),
+ ("subdir[1-2]", True, 2, ["subfile1", "subfile2"]),
+ ("subdir[0-1]", False, None, []),
+ (
+ "subdir[0-1]",
+ True,
+ None,
+ [
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir0/nesteddir/nestedfile",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ "subdir1/nesteddir/nestedfile",
+ ],
+ ),
+ (
+ "subdir[0-1]/*fil[e]*",
+ False,
+ None,
+ [
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ ],
+ ),
+ (
+ "subdir[0-1]/*fil[e]*",
+ True,
+ None,
+ [
+ "subdir0/subfile1",
+ "subdir0/subfile2",
+ "subdir1/subfile1",
+ "subdir1/subfile2",
+ ],
+ ),
+ ],
+}
diff --git a/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/copy.py b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/copy.py
new file mode 100644
index 0000000000000000000000000000000000000000..e39e57e5f7d52bfda8ab5e2398b04cc2303630a0
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/copy.py
@@ -0,0 +1,557 @@
+from hashlib import md5
+from itertools import product
+
+import pytest
+
+from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS
+
+
+class AbstractCopyTests:
+ def test_copy_file_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1a
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ fs.touch(fs_join(target, "dummy"))
+ assert fs.isdir(target)
+
+ target_file2 = fs_join(target, "file2")
+ target_subfile1 = fs_join(target, "subfile1")
+
+ # Copy from source directory
+ fs.cp(fs_join(source, "file2"), target)
+ assert fs.isfile(target_file2)
+
+ # Copy from sub directory
+ fs.cp(fs_join(source, "subdir", "subfile1"), target)
+ assert fs.isfile(target_subfile1)
+
+ # Remove copied files
+ fs.rm([target_file2, target_subfile1])
+ assert not fs.exists(target_file2)
+ assert not fs.exists(target_subfile1)
+
+ # Repeat with trailing slash on target
+ fs.cp(fs_join(source, "file2"), target + "/")
+ assert fs.isdir(target)
+ assert fs.isfile(target_file2)
+
+ fs.cp(fs_join(source, "subdir", "subfile1"), target + "/")
+ assert fs.isfile(target_subfile1)
+
+ def test_copy_file_to_new_directory(
+ self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target
+ ):
+ # Copy scenario 1b
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ fs.cp(
+ fs_join(source, "subdir", "subfile1"), fs_join(target, "newdir/")
+ ) # Note trailing slash
+ assert fs.isdir(target)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+
+ def test_copy_file_to_file_in_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1c
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ fs.touch(fs_join(target, "dummy"))
+ assert fs.isdir(target)
+
+ fs.cp(fs_join(source, "subdir", "subfile1"), fs_join(target, "newfile"))
+ assert fs.isfile(fs_join(target, "newfile"))
+
+ def test_copy_file_to_file_in_new_directory(
+ self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target
+ ):
+ # Copy scenario 1d
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ fs.cp(
+ fs_join(source, "subdir", "subfile1"), fs_join(target, "newdir", "newfile")
+ )
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "newfile"))
+
+ def test_copy_directory_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1e
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = target + "/" if target_slash else target
+
+ # Without recursive does nothing
+ fs.cp(s, t)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # With recursive
+ fs.cp(s, t, recursive=True)
+ if source_slash:
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert fs.isdir(fs_join(target, "nesteddir"))
+ assert fs.isfile(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ fs_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert fs.isdir(fs_join(target, "subdir"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "subdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "subdir", "nesteddir", "nestedfile"))
+
+ fs.rm(fs_join(target, "subdir"), recursive=True)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # Limit recursive by maxdepth
+ fs.cp(s, t, recursive=True, maxdepth=1)
+ if source_slash:
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.exists(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert fs.isdir(fs_join(target, "subdir"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "subdir", "nesteddir"))
+
+ fs.rm(fs_join(target, "subdir"), recursive=True)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_copy_directory_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1f
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = fs_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive does nothing
+ fs.cp(s, t)
+ if supports_empty_directories:
+ assert fs.ls(target) == []
+ else:
+ with pytest.raises(FileNotFoundError):
+ fs.ls(target)
+
+ # With recursive
+ fs.cp(s, t, recursive=True)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "newdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # Limit recursive by maxdepth
+ fs.cp(s, t, recursive=True, maxdepth=1)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ def test_copy_glob_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1g
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ # Without recursive
+ fs.cp(fs_join(source, "subdir", "*"), t)
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.isdir(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.cp(fs_join(source, "subdir", glob), t, recursive=recursive)
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert fs.isdir(fs_join(target, "nesteddir"))
+ assert fs.isfile(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ fs_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # Limit recursive by maxdepth
+ fs.cp(
+ fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1
+ )
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.exists(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_copy_glob_to_new_directory(
+ self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target
+ ):
+ # Copy scenario 1h
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ for target_slash in [False, True]:
+ t = fs_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive
+ fs.cp(fs_join(source, "subdir", "*"), t)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.cp(fs_join(source, "subdir", glob), t, recursive=recursive)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "newdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # Limit recursive by maxdepth
+ fs.cp(
+ fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1
+ )
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ @pytest.mark.parametrize(
+ GLOB_EDGE_CASES_TESTS["argnames"],
+ GLOB_EDGE_CASES_TESTS["argvalues"],
+ )
+ def test_copy_glob_edge_cases(
+ self,
+ path,
+ recursive,
+ maxdepth,
+ expected,
+ fs,
+ fs_join,
+ fs_glob_edge_cases_files,
+ fs_target,
+ fs_sanitize_path,
+ ):
+ # Copy scenario 1g
+ source = fs_glob_edge_cases_files
+
+ target = fs_target
+
+ for new_dir, target_slash in product([True, False], [True, False]):
+ fs.mkdir(target)
+
+ t = fs_join(target, "newdir") if new_dir else target
+ t = t + "/" if target_slash else t
+
+ fs.copy(fs_join(source, path), t, recursive=recursive, maxdepth=maxdepth)
+
+ output = fs.find(target)
+ if new_dir:
+ prefixed_expected = [
+ fs_sanitize_path(fs_join(target, "newdir", p)) for p in expected
+ ]
+ else:
+ prefixed_expected = [
+ fs_sanitize_path(fs_join(target, p)) for p in expected
+ ]
+ assert sorted(output) == sorted(prefixed_expected)
+
+ try:
+ fs.rm(target, recursive=True)
+ except FileNotFoundError:
+ pass
+
+ def test_copy_list_of_files_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ fs_target,
+ supports_empty_directories,
+ ):
+ # Copy scenario 2a
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ source_files = [
+ fs_join(source, "file1"),
+ fs_join(source, "file2"),
+ fs_join(source, "subdir", "subfile1"),
+ ]
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ fs.cp(source_files, t)
+ assert fs.isfile(fs_join(target, "file1"))
+ assert fs.isfile(fs_join(target, "file2"))
+ assert fs.isfile(fs_join(target, "subfile1"))
+
+ fs.rm(
+ [
+ fs_join(target, "file1"),
+ fs_join(target, "file2"),
+ fs_join(target, "subfile1"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_copy_list_of_files_to_new_directory(
+ self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target
+ ):
+ # Copy scenario 2b
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ source_files = [
+ fs_join(source, "file1"),
+ fs_join(source, "file2"),
+ fs_join(source, "subdir", "subfile1"),
+ ]
+
+ fs.cp(source_files, fs_join(target, "newdir") + "/") # Note trailing slash
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "file1"))
+ assert fs.isfile(fs_join(target, "newdir", "file2"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+
+ def test_copy_two_files_new_directory(
+ self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target
+ ):
+ # This is a duplicate of test_copy_list_of_files_to_new_directory and
+ # can eventually be removed.
+ source = fs_bulk_operations_scenario_0
+
+ target = fs_target
+ assert not fs.exists(target)
+ fs.cp([fs_join(source, "file1"), fs_join(source, "file2")], target)
+
+ assert fs.isdir(target)
+ assert fs.isfile(fs_join(target, "file1"))
+ assert fs.isfile(fs_join(target, "file2"))
+
+ def test_copy_directory_without_files_with_same_name_prefix(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ fs_dir_and_file_with_same_name_prefix,
+ supports_empty_directories,
+ ):
+ # Create the test dirs
+ source = fs_dir_and_file_with_same_name_prefix
+ target = fs_target
+
+ # Test without glob
+ fs.cp(fs_join(source, "subdir"), target, recursive=True)
+
+ assert fs.isfile(fs_join(target, "subfile.txt"))
+ assert not fs.isfile(fs_join(target, "subdir.txt"))
+
+ fs.rm([fs_join(target, "subfile.txt")])
+ if supports_empty_directories:
+ assert fs.ls(target) == []
+ else:
+ assert not fs.exists(target)
+
+ # Test with glob
+ fs.cp(fs_join(source, "subdir*"), target, recursive=True)
+
+ assert fs.isdir(fs_join(target, "subdir"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile.txt"))
+ assert fs.isfile(fs_join(target, "subdir.txt"))
+
+ def test_copy_with_source_and_destination_as_list(
+ self, fs, fs_target, fs_join, fs_10_files_with_hashed_names
+ ):
+ # Create the test dir
+ source = fs_10_files_with_hashed_names
+ target = fs_target
+
+ # Create list of files for source and destination
+ source_files = []
+ destination_files = []
+ for i in range(10):
+ hashed_i = md5(str(i).encode("utf-8")).hexdigest()
+ source_files.append(fs_join(source, f"{hashed_i}.txt"))
+ destination_files.append(fs_join(target, f"{hashed_i}.txt"))
+
+ # Copy and assert order was kept
+ fs.copy(path1=source_files, path2=destination_files)
+
+ for i in range(10):
+ file_content = fs.cat(destination_files[i]).decode("utf-8")
+ assert file_content == str(i)
diff --git a/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/get.py b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/get.py
new file mode 100644
index 0000000000000000000000000000000000000000..851ab81ee581e74cac41c64c83ef0af75826d6b0
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/get.py
@@ -0,0 +1,587 @@
+from hashlib import md5
+from itertools import product
+
+import pytest
+
+from fsspec.implementations.local import make_path_posix
+from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS
+
+
+class AbstractGetTests:
+ def test_get_file_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1a
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+ assert local_fs.isdir(target)
+
+ target_file2 = local_join(target, "file2")
+ target_subfile1 = local_join(target, "subfile1")
+
+ # Copy from source directory
+ fs.get(fs_join(source, "file2"), target)
+ assert local_fs.isfile(target_file2)
+
+ # Copy from sub directory
+ fs.get(fs_join(source, "subdir", "subfile1"), target)
+ assert local_fs.isfile(target_subfile1)
+
+ # Remove copied files
+ local_fs.rm([target_file2, target_subfile1])
+ assert not local_fs.exists(target_file2)
+ assert not local_fs.exists(target_subfile1)
+
+ # Repeat with trailing slash on target
+ fs.get(fs_join(source, "file2"), target + "/")
+ assert local_fs.isdir(target)
+ assert local_fs.isfile(target_file2)
+
+ fs.get(fs_join(source, "subdir", "subfile1"), target + "/")
+ assert local_fs.isfile(target_subfile1)
+
+ def test_get_file_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1b
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ fs.get(
+ fs_join(source, "subdir", "subfile1"), local_join(target, "newdir/")
+ ) # Note trailing slash
+
+ assert local_fs.isdir(target)
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+
+ def test_get_file_to_file_in_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1c
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ fs.get(fs_join(source, "subdir", "subfile1"), local_join(target, "newfile"))
+ assert local_fs.isfile(local_join(target, "newfile"))
+
+ def test_get_file_to_file_in_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1d
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ fs.get(
+ fs_join(source, "subdir", "subfile1"),
+ local_join(target, "newdir", "newfile"),
+ )
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "newfile"))
+
+ def test_get_directory_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1e
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+ assert local_fs.isdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = target + "/" if target_slash else target
+
+ # Without recursive does nothing
+ fs.get(s, t)
+ assert local_fs.ls(target) == []
+
+ # With recursive
+ fs.get(s, t, recursive=True)
+ if source_slash:
+ assert local_fs.isfile(local_join(target, "subfile1"))
+ assert local_fs.isfile(local_join(target, "subfile2"))
+ assert local_fs.isdir(local_join(target, "nesteddir"))
+ assert local_fs.isfile(local_join(target, "nesteddir", "nestedfile"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(
+ [
+ local_join(target, "subfile1"),
+ local_join(target, "subfile2"),
+ local_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert local_fs.isdir(local_join(target, "subdir"))
+ assert local_fs.isfile(local_join(target, "subdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "subdir", "subfile2"))
+ assert local_fs.isdir(local_join(target, "subdir", "nesteddir"))
+ assert local_fs.isfile(
+ local_join(target, "subdir", "nesteddir", "nestedfile")
+ )
+
+ local_fs.rm(local_join(target, "subdir"), recursive=True)
+ assert local_fs.ls(target) == []
+
+ # Limit recursive by maxdepth
+ fs.get(s, t, recursive=True, maxdepth=1)
+ if source_slash:
+ assert local_fs.isfile(local_join(target, "subfile1"))
+ assert local_fs.isfile(local_join(target, "subfile2"))
+ assert not local_fs.exists(local_join(target, "nesteddir"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(
+ [
+ local_join(target, "subfile1"),
+ local_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert local_fs.isdir(local_join(target, "subdir"))
+ assert local_fs.isfile(local_join(target, "subdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "subdir", "subfile2"))
+ assert not local_fs.exists(local_join(target, "subdir", "nesteddir"))
+
+ local_fs.rm(local_join(target, "subdir"), recursive=True)
+ assert local_fs.ls(target) == []
+
+ def test_get_directory_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1f
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = local_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive does nothing
+ fs.get(s, t)
+ assert local_fs.ls(target) == []
+
+ # With recursive
+ fs.get(s, t, recursive=True)
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile2"))
+ assert local_fs.isdir(local_join(target, "newdir", "nesteddir"))
+ assert local_fs.isfile(
+ local_join(target, "newdir", "nesteddir", "nestedfile")
+ )
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(local_join(target, "newdir"), recursive=True)
+ assert local_fs.ls(target) == []
+
+ # Limit recursive by maxdepth
+ fs.get(s, t, recursive=True, maxdepth=1)
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile2"))
+ assert not local_fs.exists(local_join(target, "newdir", "nesteddir"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(local_join(target, "newdir"), recursive=True)
+ assert not local_fs.exists(local_join(target, "newdir"))
+
+ def test_get_glob_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1g
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ # Without recursive
+ fs.get(fs_join(source, "subdir", "*"), t)
+ assert local_fs.isfile(local_join(target, "subfile1"))
+ assert local_fs.isfile(local_join(target, "subfile2"))
+ assert not local_fs.isdir(local_join(target, "nesteddir"))
+ assert not local_fs.exists(local_join(target, "nesteddir", "nestedfile"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(
+ [
+ local_join(target, "subfile1"),
+ local_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert local_fs.ls(target) == []
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.get(fs_join(source, "subdir", glob), t, recursive=recursive)
+ assert local_fs.isfile(local_join(target, "subfile1"))
+ assert local_fs.isfile(local_join(target, "subfile2"))
+ assert local_fs.isdir(local_join(target, "nesteddir"))
+ assert local_fs.isfile(local_join(target, "nesteddir", "nestedfile"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(
+ [
+ local_join(target, "subfile1"),
+ local_join(target, "subfile2"),
+ local_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ assert local_fs.ls(target) == []
+
+ # Limit recursive by maxdepth
+ fs.get(
+ fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1
+ )
+ assert local_fs.isfile(local_join(target, "subfile1"))
+ assert local_fs.isfile(local_join(target, "subfile2"))
+ assert not local_fs.exists(local_join(target, "nesteddir"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+
+ local_fs.rm(
+ [
+ local_join(target, "subfile1"),
+ local_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert local_fs.ls(target) == []
+
+ def test_get_glob_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1h
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ for target_slash in [False, True]:
+ t = fs_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive
+ fs.get(fs_join(source, "subdir", "*"), t)
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile2"))
+ assert not local_fs.exists(local_join(target, "newdir", "nesteddir"))
+ assert not local_fs.exists(
+ local_join(target, "newdir", "nesteddir", "nestedfile")
+ )
+ assert not local_fs.exists(local_join(target, "subdir"))
+ assert not local_fs.exists(local_join(target, "newdir", "subdir"))
+
+ local_fs.rm(local_join(target, "newdir"), recursive=True)
+ assert local_fs.ls(target) == []
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.get(fs_join(source, "subdir", glob), t, recursive=recursive)
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile2"))
+ assert local_fs.isdir(local_join(target, "newdir", "nesteddir"))
+ assert local_fs.isfile(
+ local_join(target, "newdir", "nesteddir", "nestedfile")
+ )
+ assert not local_fs.exists(local_join(target, "subdir"))
+ assert not local_fs.exists(local_join(target, "newdir", "subdir"))
+
+ local_fs.rm(local_join(target, "newdir"), recursive=True)
+ assert not local_fs.exists(local_join(target, "newdir"))
+
+ # Limit recursive by maxdepth
+ fs.get(
+ fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1
+ )
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile2"))
+ assert not local_fs.exists(local_join(target, "newdir", "nesteddir"))
+ assert not local_fs.exists(local_join(target, "subdir"))
+ assert not local_fs.exists(local_join(target, "newdir", "subdir"))
+
+ local_fs.rm(local_fs.ls(target, detail=False), recursive=True)
+ assert not local_fs.exists(local_join(target, "newdir"))
+
+ @pytest.mark.parametrize(
+ GLOB_EDGE_CASES_TESTS["argnames"],
+ GLOB_EDGE_CASES_TESTS["argvalues"],
+ )
+ def test_get_glob_edge_cases(
+ self,
+ path,
+ recursive,
+ maxdepth,
+ expected,
+ fs,
+ fs_join,
+ fs_glob_edge_cases_files,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 1g
+ source = fs_glob_edge_cases_files
+
+ target = local_target
+
+ for new_dir, target_slash in product([True, False], [True, False]):
+ local_fs.mkdir(target)
+
+ t = local_join(target, "newdir") if new_dir else target
+ t = t + "/" if target_slash else t
+
+ fs.get(fs_join(source, path), t, recursive=recursive, maxdepth=maxdepth)
+
+ output = local_fs.find(target)
+ if new_dir:
+ prefixed_expected = [
+ make_path_posix(local_join(target, "newdir", p)) for p in expected
+ ]
+ else:
+ prefixed_expected = [
+ make_path_posix(local_join(target, p)) for p in expected
+ ]
+ assert sorted(output) == sorted(prefixed_expected)
+
+ try:
+ local_fs.rm(target, recursive=True)
+ except FileNotFoundError:
+ pass
+
+ def test_get_list_of_files_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 2a
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ source_files = [
+ fs_join(source, "file1"),
+ fs_join(source, "file2"),
+ fs_join(source, "subdir", "subfile1"),
+ ]
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ fs.get(source_files, t)
+ assert local_fs.isfile(local_join(target, "file1"))
+ assert local_fs.isfile(local_join(target, "file2"))
+ assert local_fs.isfile(local_join(target, "subfile1"))
+
+ local_fs.rm(
+ [
+ local_join(target, "file1"),
+ local_join(target, "file2"),
+ local_join(target, "subfile1"),
+ ],
+ recursive=True,
+ )
+ assert local_fs.ls(target) == []
+
+ def test_get_list_of_files_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_bulk_operations_scenario_0,
+ local_fs,
+ local_join,
+ local_target,
+ ):
+ # Copy scenario 2b
+ source = fs_bulk_operations_scenario_0
+
+ target = local_target
+ local_fs.mkdir(target)
+
+ source_files = [
+ fs_join(source, "file1"),
+ fs_join(source, "file2"),
+ fs_join(source, "subdir", "subfile1"),
+ ]
+
+ fs.get(source_files, local_join(target, "newdir") + "/") # Note trailing slash
+ assert local_fs.isdir(local_join(target, "newdir"))
+ assert local_fs.isfile(local_join(target, "newdir", "file1"))
+ assert local_fs.isfile(local_join(target, "newdir", "file2"))
+ assert local_fs.isfile(local_join(target, "newdir", "subfile1"))
+
+ def test_get_directory_recursive(
+ self, fs, fs_join, fs_path, local_fs, local_join, local_target
+ ):
+ # https://github.com/fsspec/filesystem_spec/issues/1062
+ # Recursive cp/get/put of source directory into non-existent target directory.
+ src = fs_join(fs_path, "src")
+ src_file = fs_join(src, "file")
+ fs.mkdir(src)
+ fs.touch(src_file)
+
+ target = local_target
+
+ # get without slash
+ assert not local_fs.exists(target)
+ for loop in range(2):
+ fs.get(src, target, recursive=True)
+ assert local_fs.isdir(target)
+
+ if loop == 0:
+ assert local_fs.isfile(local_join(target, "file"))
+ assert not local_fs.exists(local_join(target, "src"))
+ else:
+ assert local_fs.isfile(local_join(target, "file"))
+ assert local_fs.isdir(local_join(target, "src"))
+ assert local_fs.isfile(local_join(target, "src", "file"))
+
+ local_fs.rm(target, recursive=True)
+
+ # get with slash
+ assert not local_fs.exists(target)
+ for loop in range(2):
+ fs.get(src + "/", target, recursive=True)
+ assert local_fs.isdir(target)
+ assert local_fs.isfile(local_join(target, "file"))
+ assert not local_fs.exists(local_join(target, "src"))
+
+ def test_get_directory_without_files_with_same_name_prefix(
+ self,
+ fs,
+ fs_join,
+ local_fs,
+ local_join,
+ local_target,
+ fs_dir_and_file_with_same_name_prefix,
+ ):
+ # Create the test dirs
+ source = fs_dir_and_file_with_same_name_prefix
+ target = local_target
+
+ # Test without glob
+ fs.get(fs_join(source, "subdir"), target, recursive=True)
+
+ assert local_fs.isfile(local_join(target, "subfile.txt"))
+ assert not local_fs.isfile(local_join(target, "subdir.txt"))
+
+ local_fs.rm([local_join(target, "subfile.txt")])
+ assert local_fs.ls(target) == []
+
+ # Test with glob
+ fs.get(fs_join(source, "subdir*"), target, recursive=True)
+
+ assert local_fs.isdir(local_join(target, "subdir"))
+ assert local_fs.isfile(local_join(target, "subdir", "subfile.txt"))
+ assert local_fs.isfile(local_join(target, "subdir.txt"))
+
+ def test_get_with_source_and_destination_as_list(
+ self,
+ fs,
+ fs_join,
+ local_fs,
+ local_join,
+ local_target,
+ fs_10_files_with_hashed_names,
+ ):
+ # Create the test dir
+ source = fs_10_files_with_hashed_names
+ target = local_target
+
+ # Create list of files for source and destination
+ source_files = []
+ destination_files = []
+ for i in range(10):
+ hashed_i = md5(str(i).encode("utf-8")).hexdigest()
+ source_files.append(fs_join(source, f"{hashed_i}.txt"))
+ destination_files.append(
+ make_path_posix(local_join(target, f"{hashed_i}.txt"))
+ )
+
+ # Copy and assert order was kept
+ fs.get(rpath=source_files, lpath=destination_files)
+
+ for i in range(10):
+ file_content = local_fs.cat(destination_files[i]).decode("utf-8")
+ assert file_content == str(i)
diff --git a/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/put.py b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/put.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fc349977f0384d9fc86126498be5c6ad99a21d3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/tests/abstract/put.py
@@ -0,0 +1,591 @@
+from hashlib import md5
+from itertools import product
+
+import pytest
+
+from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS
+
+
+class AbstractPutTests:
+ def test_put_file_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_join,
+ local_bulk_operations_scenario_0,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1a
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ fs.touch(fs_join(target, "dummy"))
+ assert fs.isdir(target)
+
+ target_file2 = fs_join(target, "file2")
+ target_subfile1 = fs_join(target, "subfile1")
+
+ # Copy from source directory
+ fs.put(local_join(source, "file2"), target)
+ assert fs.isfile(target_file2)
+
+ # Copy from sub directory
+ fs.put(local_join(source, "subdir", "subfile1"), target)
+ assert fs.isfile(target_subfile1)
+
+ # Remove copied files
+ fs.rm([target_file2, target_subfile1])
+ assert not fs.exists(target_file2)
+ assert not fs.exists(target_subfile1)
+
+ # Repeat with trailing slash on target
+ fs.put(local_join(source, "file2"), target + "/")
+ assert fs.isdir(target)
+ assert fs.isfile(target_file2)
+
+ fs.put(local_join(source, "subdir", "subfile1"), target + "/")
+ assert fs.isfile(target_subfile1)
+
+ def test_put_file_to_new_directory(
+ self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0
+ ):
+ # Copy scenario 1b
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ fs.put(
+ local_join(source, "subdir", "subfile1"), fs_join(target, "newdir/")
+ ) # Note trailing slash
+ assert fs.isdir(target)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+
+ def test_put_file_to_file_in_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_join,
+ supports_empty_directories,
+ local_bulk_operations_scenario_0,
+ ):
+ # Copy scenario 1c
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ fs.touch(fs_join(target, "dummy"))
+ assert fs.isdir(target)
+
+ fs.put(local_join(source, "subdir", "subfile1"), fs_join(target, "newfile"))
+ assert fs.isfile(fs_join(target, "newfile"))
+
+ def test_put_file_to_file_in_new_directory(
+ self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0
+ ):
+ # Copy scenario 1d
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ fs.put(
+ local_join(source, "subdir", "subfile1"),
+ fs_join(target, "newdir", "newfile"),
+ )
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "newfile"))
+
+ def test_put_directory_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_bulk_operations_scenario_0,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1e
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = target + "/" if target_slash else target
+
+ # Without recursive does nothing
+ fs.put(s, t)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # With recursive
+ fs.put(s, t, recursive=True)
+ if source_slash:
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert fs.isdir(fs_join(target, "nesteddir"))
+ assert fs.isfile(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ fs_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert fs.isdir(fs_join(target, "subdir"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "subdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "subdir", "nesteddir", "nestedfile"))
+
+ fs.rm(fs_join(target, "subdir"), recursive=True)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # Limit recursive by maxdepth
+ fs.put(s, t, recursive=True, maxdepth=1)
+ if source_slash:
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.exists(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ else:
+ assert fs.isdir(fs_join(target, "subdir"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "subdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "subdir", "nesteddir"))
+
+ fs.rm(fs_join(target, "subdir"), recursive=True)
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_put_directory_to_new_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_bulk_operations_scenario_0,
+ supports_empty_directories,
+ ):
+ # Copy scenario 1f
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ for source_slash, target_slash in zip([False, True], [False, True]):
+ s = fs_join(source, "subdir")
+ if source_slash:
+ s += "/"
+ t = fs_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive does nothing
+ fs.put(s, t)
+ if supports_empty_directories:
+ assert fs.ls(target) == []
+ else:
+ with pytest.raises(FileNotFoundError):
+ fs.ls(target)
+
+ # With recursive
+ fs.put(s, t, recursive=True)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "newdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # Limit recursive by maxdepth
+ fs.put(s, t, recursive=True, maxdepth=1)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ def test_put_glob_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_join,
+ supports_empty_directories,
+ local_bulk_operations_scenario_0,
+ ):
+ # Copy scenario 1g
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ # Without recursive
+ fs.put(local_join(source, "subdir", "*"), t)
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.isdir(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.put(local_join(source, "subdir", glob), t, recursive=recursive)
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert fs.isdir(fs_join(target, "nesteddir"))
+ assert fs.isfile(fs_join(target, "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ fs_join(target, "nesteddir"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ # Limit recursive by maxdepth
+ fs.put(
+ local_join(source, "subdir", glob),
+ t,
+ recursive=recursive,
+ maxdepth=1,
+ )
+ assert fs.isfile(fs_join(target, "subfile1"))
+ assert fs.isfile(fs_join(target, "subfile2"))
+ assert not fs.exists(fs_join(target, "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+
+ fs.rm(
+ [
+ fs_join(target, "subfile1"),
+ fs_join(target, "subfile2"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_put_glob_to_new_directory(
+ self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0
+ ):
+ # Copy scenario 1h
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ for target_slash in [False, True]:
+ t = fs_join(target, "newdir")
+ if target_slash:
+ t += "/"
+
+ # Without recursive
+ fs.put(local_join(source, "subdir", "*"), t)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # With recursive
+ for glob, recursive in zip(["*", "**"], [True, False]):
+ fs.put(local_join(source, "subdir", glob), t, recursive=recursive)
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert fs.isdir(fs_join(target, "newdir", "nesteddir"))
+ assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ # Limit recursive by maxdepth
+ fs.put(
+ local_join(source, "subdir", glob),
+ t,
+ recursive=recursive,
+ maxdepth=1,
+ )
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile2"))
+ assert not fs.exists(fs_join(target, "newdir", "nesteddir"))
+ assert not fs.exists(fs_join(target, "subdir"))
+ assert not fs.exists(fs_join(target, "newdir", "subdir"))
+
+ fs.rm(fs_join(target, "newdir"), recursive=True)
+ assert not fs.exists(fs_join(target, "newdir"))
+
+ @pytest.mark.parametrize(
+ GLOB_EDGE_CASES_TESTS["argnames"],
+ GLOB_EDGE_CASES_TESTS["argvalues"],
+ )
+ def test_put_glob_edge_cases(
+ self,
+ path,
+ recursive,
+ maxdepth,
+ expected,
+ fs,
+ fs_join,
+ fs_target,
+ local_glob_edge_cases_files,
+ local_join,
+ fs_sanitize_path,
+ ):
+ # Copy scenario 1g
+ source = local_glob_edge_cases_files
+
+ target = fs_target
+
+ for new_dir, target_slash in product([True, False], [True, False]):
+ fs.mkdir(target)
+
+ t = fs_join(target, "newdir") if new_dir else target
+ t = t + "/" if target_slash else t
+
+ fs.put(local_join(source, path), t, recursive=recursive, maxdepth=maxdepth)
+
+ output = fs.find(target)
+ if new_dir:
+ prefixed_expected = [
+ fs_sanitize_path(fs_join(target, "newdir", p)) for p in expected
+ ]
+ else:
+ prefixed_expected = [
+ fs_sanitize_path(fs_join(target, p)) for p in expected
+ ]
+ assert sorted(output) == sorted(prefixed_expected)
+
+ try:
+ fs.rm(target, recursive=True)
+ except FileNotFoundError:
+ pass
+
+ def test_put_list_of_files_to_existing_directory(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_join,
+ local_bulk_operations_scenario_0,
+ supports_empty_directories,
+ ):
+ # Copy scenario 2a
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+ if not supports_empty_directories:
+ # Force target directory to exist by adding a dummy file
+ dummy = fs_join(target, "dummy")
+ fs.touch(dummy)
+ assert fs.isdir(target)
+
+ source_files = [
+ local_join(source, "file1"),
+ local_join(source, "file2"),
+ local_join(source, "subdir", "subfile1"),
+ ]
+
+ for target_slash in [False, True]:
+ t = target + "/" if target_slash else target
+
+ fs.put(source_files, t)
+ assert fs.isfile(fs_join(target, "file1"))
+ assert fs.isfile(fs_join(target, "file2"))
+ assert fs.isfile(fs_join(target, "subfile1"))
+
+ fs.rm(
+ [
+ fs_join(target, "file1"),
+ fs_join(target, "file2"),
+ fs_join(target, "subfile1"),
+ ],
+ recursive=True,
+ )
+ assert fs.ls(target, detail=False) == (
+ [] if supports_empty_directories else [dummy]
+ )
+
+ def test_put_list_of_files_to_new_directory(
+ self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0
+ ):
+ # Copy scenario 2b
+ source = local_bulk_operations_scenario_0
+
+ target = fs_target
+ fs.mkdir(target)
+
+ source_files = [
+ local_join(source, "file1"),
+ local_join(source, "file2"),
+ local_join(source, "subdir", "subfile1"),
+ ]
+
+ fs.put(source_files, fs_join(target, "newdir") + "/") # Note trailing slash
+ assert fs.isdir(fs_join(target, "newdir"))
+ assert fs.isfile(fs_join(target, "newdir", "file1"))
+ assert fs.isfile(fs_join(target, "newdir", "file2"))
+ assert fs.isfile(fs_join(target, "newdir", "subfile1"))
+
+ def test_put_directory_recursive(
+ self, fs, fs_join, fs_target, local_fs, local_join, local_path
+ ):
+ # https://github.com/fsspec/filesystem_spec/issues/1062
+ # Recursive cp/get/put of source directory into non-existent target directory.
+ src = local_join(local_path, "src")
+ src_file = local_join(src, "file")
+ local_fs.mkdir(src)
+ local_fs.touch(src_file)
+
+ target = fs_target
+
+ # put without slash
+ assert not fs.exists(target)
+ for loop in range(2):
+ fs.put(src, target, recursive=True)
+ assert fs.isdir(target)
+
+ if loop == 0:
+ assert fs.isfile(fs_join(target, "file"))
+ assert not fs.exists(fs_join(target, "src"))
+ else:
+ assert fs.isfile(fs_join(target, "file"))
+ assert fs.isdir(fs_join(target, "src"))
+ assert fs.isfile(fs_join(target, "src", "file"))
+
+ fs.rm(target, recursive=True)
+
+ # put with slash
+ assert not fs.exists(target)
+ for loop in range(2):
+ fs.put(src + "/", target, recursive=True)
+ assert fs.isdir(target)
+ assert fs.isfile(fs_join(target, "file"))
+ assert not fs.exists(fs_join(target, "src"))
+
+ def test_put_directory_without_files_with_same_name_prefix(
+ self,
+ fs,
+ fs_join,
+ fs_target,
+ local_join,
+ local_dir_and_file_with_same_name_prefix,
+ supports_empty_directories,
+ ):
+ # Create the test dirs
+ source = local_dir_and_file_with_same_name_prefix
+ target = fs_target
+
+ # Test without glob
+ fs.put(local_join(source, "subdir"), fs_target, recursive=True)
+
+ assert fs.isfile(fs_join(fs_target, "subfile.txt"))
+ assert not fs.isfile(fs_join(fs_target, "subdir.txt"))
+
+ fs.rm([fs_join(target, "subfile.txt")])
+ if supports_empty_directories:
+ assert fs.ls(target) == []
+ else:
+ assert not fs.exists(target)
+
+ # Test with glob
+ fs.put(local_join(source, "subdir*"), fs_target, recursive=True)
+
+ assert fs.isdir(fs_join(fs_target, "subdir"))
+ assert fs.isfile(fs_join(fs_target, "subdir", "subfile.txt"))
+ assert fs.isfile(fs_join(fs_target, "subdir.txt"))
+
+ def test_copy_with_source_and_destination_as_list(
+ self, fs, fs_target, fs_join, local_join, local_10_files_with_hashed_names
+ ):
+ # Create the test dir
+ source = local_10_files_with_hashed_names
+ target = fs_target
+
+ # Create list of files for source and destination
+ source_files = []
+ destination_files = []
+ for i in range(10):
+ hashed_i = md5(str(i).encode("utf-8")).hexdigest()
+ source_files.append(local_join(source, f"{hashed_i}.txt"))
+ destination_files.append(fs_join(target, f"{hashed_i}.txt"))
+
+ # Copy and assert order was kept
+ fs.put(lpath=source_files, rpath=destination_files)
+
+ for i in range(10):
+ file_content = fs.cat(destination_files[i]).decode("utf-8")
+ assert file_content == str(i)
diff --git a/.venv/lib/python3.10/site-packages/fsspec/transaction.py b/.venv/lib/python3.10/site-packages/fsspec/transaction.py
new file mode 100644
index 0000000000000000000000000000000000000000..77293f63ecc5f611e19d849ef236d53e9c258efc
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/transaction.py
@@ -0,0 +1,90 @@
+from collections import deque
+
+
+class Transaction:
+ """Filesystem transaction write context
+
+ Gathers files for deferred commit or discard, so that several write
+ operations can be finalized semi-atomically. This works by having this
+ instance as the ``.transaction`` attribute of the given filesystem
+ """
+
+ def __init__(self, fs, **kwargs):
+ """
+ Parameters
+ ----------
+ fs: FileSystem instance
+ """
+ self.fs = fs
+ self.files = deque()
+
+ def __enter__(self):
+ self.start()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """End transaction and commit, if exit is not due to exception"""
+ # only commit if there was no exception
+ self.complete(commit=exc_type is None)
+ if self.fs:
+ self.fs._intrans = False
+ self.fs._transaction = None
+ self.fs = None
+
+ def start(self):
+ """Start a transaction on this FileSystem"""
+ self.files = deque() # clean up after previous failed completions
+ self.fs._intrans = True
+
+ def complete(self, commit=True):
+ """Finish transaction: commit or discard all deferred files"""
+ while self.files:
+ f = self.files.popleft()
+ if commit:
+ f.commit()
+ else:
+ f.discard()
+ self.fs._intrans = False
+ self.fs._transaction = None
+ self.fs = None
+
+
+class FileActor:
+ def __init__(self):
+ self.files = []
+
+ def commit(self):
+ for f in self.files:
+ f.commit()
+ self.files.clear()
+
+ def discard(self):
+ for f in self.files:
+ f.discard()
+ self.files.clear()
+
+ def append(self, f):
+ self.files.append(f)
+
+
+class DaskTransaction(Transaction):
+ def __init__(self, fs):
+ """
+ Parameters
+ ----------
+ fs: FileSystem instance
+ """
+ import distributed
+
+ super().__init__(fs)
+ client = distributed.default_client()
+ self.files = client.submit(FileActor, actor=True).result()
+
+ def complete(self, commit=True):
+ """Finish transaction: commit or discard all deferred files"""
+ if commit:
+ self.files.commit().result()
+ else:
+ self.files.discard().result()
+ self.fs._intrans = False
+ self.fs = None
diff --git a/.venv/lib/python3.10/site-packages/fsspec/utils.py b/.venv/lib/python3.10/site-packages/fsspec/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba3f80be36d4aa4f2f56df10549c1dff6b72e43b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/fsspec/utils.py
@@ -0,0 +1,742 @@
+from __future__ import annotations
+
+import contextlib
+import logging
+import math
+import os
+import pathlib
+import re
+import sys
+import tempfile
+from functools import partial
+from hashlib import md5
+from importlib.metadata import version
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Iterable,
+ Iterator,
+ Sequence,
+ TypeVar,
+)
+from urllib.parse import urlsplit
+
+if TYPE_CHECKING:
+ from typing_extensions import TypeGuard
+
+ from fsspec.spec import AbstractFileSystem
+
+
+DEFAULT_BLOCK_SIZE = 5 * 2**20
+
+T = TypeVar("T")
+
+
+def infer_storage_options(
+ urlpath: str, inherit_storage_options: dict[str, Any] | None = None
+) -> dict[str, Any]:
+ """Infer storage options from URL path and merge it with existing storage
+ options.
+
+ Parameters
+ ----------
+ urlpath: str or unicode
+ Either local absolute file path or URL (hdfs://namenode:8020/file.csv)
+ inherit_storage_options: dict (optional)
+ Its contents will get merged with the inferred information from the
+ given path
+
+ Returns
+ -------
+ Storage options dict.
+
+ Examples
+ --------
+ >>> infer_storage_options('/mnt/datasets/test.csv') # doctest: +SKIP
+ {"protocol": "file", "path", "/mnt/datasets/test.csv"}
+ >>> infer_storage_options(
+ ... 'hdfs://username:pwd@node:123/mnt/datasets/test.csv?q=1',
+ ... inherit_storage_options={'extra': 'value'},
+ ... ) # doctest: +SKIP
+ {"protocol": "hdfs", "username": "username", "password": "pwd",
+ "host": "node", "port": 123, "path": "/mnt/datasets/test.csv",
+ "url_query": "q=1", "extra": "value"}
+ """
+ # Handle Windows paths including disk name in this special case
+ if (
+ re.match(r"^[a-zA-Z]:[\\/]", urlpath)
+ or re.match(r"^[a-zA-Z0-9]+://", urlpath) is None
+ ):
+ return {"protocol": "file", "path": urlpath}
+
+ parsed_path = urlsplit(urlpath)
+ protocol = parsed_path.scheme or "file"
+ if parsed_path.fragment:
+ path = "#".join([parsed_path.path, parsed_path.fragment])
+ else:
+ path = parsed_path.path
+ if protocol == "file":
+ # Special case parsing file protocol URL on Windows according to:
+ # https://msdn.microsoft.com/en-us/library/jj710207.aspx
+ windows_path = re.match(r"^/([a-zA-Z])[:|]([\\/].*)$", path)
+ if windows_path:
+ path = "%s:%s" % windows_path.groups()
+
+ if protocol in ["http", "https"]:
+ # for HTTP, we don't want to parse, as requests will anyway
+ return {"protocol": protocol, "path": urlpath}
+
+ options: dict[str, Any] = {"protocol": protocol, "path": path}
+
+ if parsed_path.netloc:
+ # Parse `hostname` from netloc manually because `parsed_path.hostname`
+ # lowercases the hostname which is not always desirable (e.g. in S3):
+ # https://github.com/dask/dask/issues/1417
+ options["host"] = parsed_path.netloc.rsplit("@", 1)[-1].rsplit(":", 1)[0]
+
+ if protocol in ("s3", "s3a", "gcs", "gs"):
+ options["path"] = options["host"] + options["path"]
+ else:
+ options["host"] = options["host"]
+ if parsed_path.port:
+ options["port"] = parsed_path.port
+ if parsed_path.username:
+ options["username"] = parsed_path.username
+ if parsed_path.password:
+ options["password"] = parsed_path.password
+
+ if parsed_path.query:
+ options["url_query"] = parsed_path.query
+ if parsed_path.fragment:
+ options["url_fragment"] = parsed_path.fragment
+
+ if inherit_storage_options:
+ update_storage_options(options, inherit_storage_options)
+
+ return options
+
+
+def update_storage_options(
+ options: dict[str, Any], inherited: dict[str, Any] | None = None
+) -> None:
+ if not inherited:
+ inherited = {}
+ collisions = set(options) & set(inherited)
+ if collisions:
+ for collision in collisions:
+ if options.get(collision) != inherited.get(collision):
+ raise KeyError(
+ f"Collision between inferred and specified storage "
+ f"option:\n{collision}"
+ )
+ options.update(inherited)
+
+
+# Compression extensions registered via fsspec.compression.register_compression
+compressions: dict[str, str] = {}
+
+
+def infer_compression(filename: str) -> str | None:
+ """Infer compression, if available, from filename.
+
+ Infer a named compression type, if registered and available, from filename
+ extension. This includes builtin (gz, bz2, zip) compressions, as well as
+ optional compressions. See fsspec.compression.register_compression.
+ """
+ extension = os.path.splitext(filename)[-1].strip(".").lower()
+ if extension in compressions:
+ return compressions[extension]
+ return None
+
+
+def build_name_function(max_int: float) -> Callable[[int], str]:
+ """Returns a function that receives a single integer
+ and returns it as a string padded by enough zero characters
+ to align with maximum possible integer
+
+ >>> name_f = build_name_function(57)
+
+ >>> name_f(7)
+ '07'
+ >>> name_f(31)
+ '31'
+ >>> build_name_function(1000)(42)
+ '0042'
+ >>> build_name_function(999)(42)
+ '042'
+ >>> build_name_function(0)(0)
+ '0'
+ """
+ # handle corner cases max_int is 0 or exact power of 10
+ max_int += 1e-8
+
+ pad_length = int(math.ceil(math.log10(max_int)))
+
+ def name_function(i: int) -> str:
+ return str(i).zfill(pad_length)
+
+ return name_function
+
+
+def seek_delimiter(file: IO[bytes], delimiter: bytes, blocksize: int) -> bool:
+ r"""Seek current file to file start, file end, or byte after delimiter seq.
+
+ Seeks file to next chunk delimiter, where chunks are defined on file start,
+ a delimiting sequence, and file end. Use file.tell() to see location afterwards.
+ Note that file start is a valid split, so must be at offset > 0 to seek for
+ delimiter.
+
+ Parameters
+ ----------
+ file: a file
+ delimiter: bytes
+ a delimiter like ``b'\n'`` or message sentinel, matching file .read() type
+ blocksize: int
+ Number of bytes to read from the file at once.
+
+
+ Returns
+ -------
+ Returns True if a delimiter was found, False if at file start or end.
+
+ """
+
+ if file.tell() == 0:
+ # beginning-of-file, return without seek
+ return False
+
+ # Interface is for binary IO, with delimiter as bytes, but initialize last
+ # with result of file.read to preserve compatibility with text IO.
+ last: bytes | None = None
+ while True:
+ current = file.read(blocksize)
+ if not current:
+ # end-of-file without delimiter
+ return False
+ full = last + current if last else current
+ try:
+ if delimiter in full:
+ i = full.index(delimiter)
+ file.seek(file.tell() - (len(full) - i) + len(delimiter))
+ return True
+ elif len(current) < blocksize:
+ # end-of-file without delimiter
+ return False
+ except (OSError, ValueError):
+ pass
+ last = full[-len(delimiter) :]
+
+
+def read_block(
+ f: IO[bytes],
+ offset: int,
+ length: int | None,
+ delimiter: bytes | None = None,
+ split_before: bool = False,
+) -> bytes:
+ """Read a block of bytes from a file
+
+ Parameters
+ ----------
+ f: File
+ Open file
+ offset: int
+ Byte offset to start read
+ length: int
+ Number of bytes to read, read through end of file if None
+ delimiter: bytes (optional)
+ Ensure reading starts and stops at delimiter bytestring
+ split_before: bool (optional)
+ Start/stop read *before* delimiter bytestring.
+
+
+ If using the ``delimiter=`` keyword argument we ensure that the read
+ starts and stops at delimiter boundaries that follow the locations
+ ``offset`` and ``offset + length``. If ``offset`` is zero then we
+ start at zero, regardless of delimiter. The bytestring returned WILL
+ include the terminating delimiter string.
+
+ Examples
+ --------
+
+ >>> from io import BytesIO # doctest: +SKIP
+ >>> f = BytesIO(b'Alice, 100\\nBob, 200\\nCharlie, 300') # doctest: +SKIP
+ >>> read_block(f, 0, 13) # doctest: +SKIP
+ b'Alice, 100\\nBo'
+
+ >>> read_block(f, 0, 13, delimiter=b'\\n') # doctest: +SKIP
+ b'Alice, 100\\nBob, 200\\n'
+
+ >>> read_block(f, 10, 10, delimiter=b'\\n') # doctest: +SKIP
+ b'Bob, 200\\nCharlie, 300'
+ """
+ if delimiter:
+ f.seek(offset)
+ found_start_delim = seek_delimiter(f, delimiter, 2**16)
+ if length is None:
+ return f.read()
+ start = f.tell()
+ length -= start - offset
+
+ f.seek(start + length)
+ found_end_delim = seek_delimiter(f, delimiter, 2**16)
+ end = f.tell()
+
+ # Adjust split location to before delimiter if seek found the
+ # delimiter sequence, not start or end of file.
+ if found_start_delim and split_before:
+ start -= len(delimiter)
+
+ if found_end_delim and split_before:
+ end -= len(delimiter)
+
+ offset = start
+ length = end - start
+
+ f.seek(offset)
+
+ # TODO: allow length to be None and read to the end of the file?
+ assert length is not None
+ b = f.read(length)
+ return b
+
+
+def tokenize(*args: Any, **kwargs: Any) -> str:
+ """Deterministic token
+
+ (modified from dask.base)
+
+ >>> tokenize([1, 2, '3'])
+ '9d71491b50023b06fc76928e6eddb952'
+
+ >>> tokenize('Hello') == tokenize('Hello')
+ True
+ """
+ if kwargs:
+ args += (kwargs,)
+ try:
+ h = md5(str(args).encode())
+ except ValueError:
+ # FIPS systems: https://github.com/fsspec/filesystem_spec/issues/380
+ h = md5(str(args).encode(), usedforsecurity=False)
+ return h.hexdigest()
+
+
+def stringify_path(filepath: str | os.PathLike[str] | pathlib.Path) -> str:
+ """Attempt to convert a path-like object to a string.
+
+ Parameters
+ ----------
+ filepath: object to be converted
+
+ Returns
+ -------
+ filepath_str: maybe a string version of the object
+
+ Notes
+ -----
+ Objects supporting the fspath protocol are coerced according to its
+ __fspath__ method.
+
+ For backwards compatibility with older Python version, pathlib.Path
+ objects are specially coerced.
+
+ Any other object is passed through unchanged, which includes bytes,
+ strings, buffers, or anything else that's not even path-like.
+ """
+ if isinstance(filepath, str):
+ return filepath
+ elif hasattr(filepath, "__fspath__"):
+ return filepath.__fspath__()
+ elif isinstance(filepath, pathlib.Path):
+ return str(filepath)
+ elif hasattr(filepath, "path"):
+ return filepath.path
+ else:
+ return filepath # type: ignore[return-value]
+
+
+def make_instance(
+ cls: Callable[..., T], args: Sequence[Any], kwargs: dict[str, Any]
+) -> T:
+ inst = cls(*args, **kwargs)
+ inst._determine_worker() # type: ignore[attr-defined]
+ return inst
+
+
+def common_prefix(paths: Iterable[str]) -> str:
+ """For a list of paths, find the shortest prefix common to all"""
+ parts = [p.split("/") for p in paths]
+ lmax = min(len(p) for p in parts)
+ end = 0
+ for i in range(lmax):
+ end = all(p[i] == parts[0][i] for p in parts)
+ if not end:
+ break
+ i += end
+ return "/".join(parts[0][:i])
+
+
+def other_paths(
+ paths: list[str],
+ path2: str | list[str],
+ exists: bool = False,
+ flatten: bool = False,
+) -> list[str]:
+ """In bulk file operations, construct a new file tree from a list of files
+
+ Parameters
+ ----------
+ paths: list of str
+ The input file tree
+ path2: str or list of str
+ Root to construct the new list in. If this is already a list of str, we just
+ assert it has the right number of elements.
+ exists: bool (optional)
+ For a str destination, it is already exists (and is a dir), files should
+ end up inside.
+ flatten: bool (optional)
+ Whether to flatten the input directory tree structure so that the output files
+ are in the same directory.
+
+ Returns
+ -------
+ list of str
+ """
+
+ if isinstance(path2, str):
+ path2 = path2.rstrip("/")
+
+ if flatten:
+ path2 = ["/".join((path2, p.split("/")[-1])) for p in paths]
+ else:
+ cp = common_prefix(paths)
+ if exists:
+ cp = cp.rsplit("/", 1)[0]
+ if not cp and all(not s.startswith("/") for s in paths):
+ path2 = ["/".join([path2, p]) for p in paths]
+ else:
+ path2 = [p.replace(cp, path2, 1) for p in paths]
+ else:
+ assert len(paths) == len(path2)
+ return path2
+
+
+def is_exception(obj: Any) -> bool:
+ return isinstance(obj, BaseException)
+
+
+def isfilelike(f: Any) -> TypeGuard[IO[bytes]]:
+ for attr in ["read", "close", "tell"]:
+ if not hasattr(f, attr):
+ return False
+ return True
+
+
+def get_protocol(url: str) -> str:
+ url = stringify_path(url)
+ parts = re.split(r"(\:\:|\://)", url, 1)
+ if len(parts) > 1:
+ return parts[0]
+ return "file"
+
+
+def can_be_local(path: str) -> bool:
+ """Can the given URL be used with open_local?"""
+ from fsspec import get_filesystem_class
+
+ try:
+ return getattr(get_filesystem_class(get_protocol(path)), "local_file", False)
+ except (ValueError, ImportError):
+ # not in registry or import failed
+ return False
+
+
+def get_package_version_without_import(name: str) -> str | None:
+ """For given package name, try to find the version without importing it
+
+ Import and package.__version__ is still the backup here, so an import
+ *might* happen.
+
+ Returns either the version string, or None if the package
+ or the version was not readily found.
+ """
+ if name in sys.modules:
+ mod = sys.modules[name]
+ if hasattr(mod, "__version__"):
+ return mod.__version__
+ try:
+ return version(name)
+ except: # noqa: E722
+ pass
+ try:
+ import importlib
+
+ mod = importlib.import_module(name)
+ return mod.__version__
+ except (ImportError, AttributeError):
+ return None
+
+
+def setup_logging(
+ logger: logging.Logger | None = None,
+ logger_name: str | None = None,
+ level: str = "DEBUG",
+ clear: bool = True,
+) -> logging.Logger:
+ if logger is None and logger_name is None:
+ raise ValueError("Provide either logger object or logger name")
+ logger = logger or logging.getLogger(logger_name)
+ handle = logging.StreamHandler()
+ formatter = logging.Formatter(
+ "%(asctime)s - %(name)s - %(levelname)s - %(funcName)s -- %(message)s"
+ )
+ handle.setFormatter(formatter)
+ if clear:
+ logger.handlers.clear()
+ logger.addHandler(handle)
+ logger.setLevel(level)
+ return logger
+
+
+def _unstrip_protocol(name: str, fs: AbstractFileSystem) -> str:
+ return fs.unstrip_protocol(name)
+
+
+def mirror_from(
+ origin_name: str, methods: Iterable[str]
+) -> Callable[[type[T]], type[T]]:
+ """Mirror attributes and methods from the given
+ origin_name attribute of the instance to the
+ decorated class"""
+
+ def origin_getter(method: str, self: Any) -> Any:
+ origin = getattr(self, origin_name)
+ return getattr(origin, method)
+
+ def wrapper(cls: type[T]) -> type[T]:
+ for method in methods:
+ wrapped_method = partial(origin_getter, method)
+ setattr(cls, method, property(wrapped_method))
+ return cls
+
+ return wrapper
+
+
+@contextlib.contextmanager
+def nullcontext(obj: T) -> Iterator[T]:
+ yield obj
+
+
+def merge_offset_ranges(
+ paths: list[str],
+ starts: list[int] | int,
+ ends: list[int] | int,
+ max_gap: int = 0,
+ max_block: int | None = None,
+ sort: bool = True,
+) -> tuple[list[str], list[int], list[int]]:
+ """Merge adjacent byte-offset ranges when the inter-range
+ gap is <= `max_gap`, and when the merged byte range does not
+ exceed `max_block` (if specified). By default, this function
+ will re-order the input paths and byte ranges to ensure sorted
+ order. If the user can guarantee that the inputs are already
+ sorted, passing `sort=False` will skip the re-ordering.
+ """
+ # Check input
+ if not isinstance(paths, list):
+ raise TypeError
+ if not isinstance(starts, list):
+ starts = [starts] * len(paths)
+ if not isinstance(ends, list):
+ ends = [ends] * len(paths)
+ if len(starts) != len(paths) or len(ends) != len(paths):
+ raise ValueError
+
+ # Early Return
+ if len(starts) <= 1:
+ return paths, starts, ends
+
+ starts = [s or 0 for s in starts]
+ # Sort by paths and then ranges if `sort=True`
+ if sort:
+ paths, starts, ends = (
+ list(v)
+ for v in zip(
+ *sorted(
+ zip(paths, starts, ends),
+ )
+ )
+ )
+
+ if paths:
+ # Loop through the coupled `paths`, `starts`, and
+ # `ends`, and merge adjacent blocks when appropriate
+ new_paths = paths[:1]
+ new_starts = starts[:1]
+ new_ends = ends[:1]
+ for i in range(1, len(paths)):
+ if paths[i] == paths[i - 1] and new_ends[-1] is None:
+ continue
+ elif (
+ paths[i] != paths[i - 1]
+ or ((starts[i] - new_ends[-1]) > max_gap)
+ or (max_block is not None and (ends[i] - new_starts[-1]) > max_block)
+ ):
+ # Cannot merge with previous block.
+ # Add new `paths`, `starts`, and `ends` elements
+ new_paths.append(paths[i])
+ new_starts.append(starts[i])
+ new_ends.append(ends[i])
+ else:
+ # Merge with previous block by updating the
+ # last element of `ends`
+ new_ends[-1] = ends[i]
+ return new_paths, new_starts, new_ends
+
+ # `paths` is empty. Just return input lists
+ return paths, starts, ends
+
+
+def file_size(filelike: IO[bytes]) -> int:
+ """Find length of any open read-mode file-like"""
+ pos = filelike.tell()
+ try:
+ return filelike.seek(0, 2)
+ finally:
+ filelike.seek(pos)
+
+
+@contextlib.contextmanager
+def atomic_write(path: str, mode: str = "wb"):
+ """
+ A context manager that opens a temporary file next to `path` and, on exit,
+ replaces `path` with the temporary file, thereby updating `path`
+ atomically.
+ """
+ fd, fn = tempfile.mkstemp(
+ dir=os.path.dirname(path), prefix=os.path.basename(path) + "-"
+ )
+ try:
+ with open(fd, mode) as fp:
+ yield fp
+ except BaseException:
+ with contextlib.suppress(FileNotFoundError):
+ os.unlink(fn)
+ raise
+ else:
+ os.replace(fn, path)
+
+
+def _translate(pat, STAR, QUESTION_MARK):
+ # Copied from: https://github.com/python/cpython/pull/106703.
+ res: list[str] = []
+ add = res.append
+ i, n = 0, len(pat)
+ while i < n:
+ c = pat[i]
+ i = i + 1
+ if c == "*":
+ # compress consecutive `*` into one
+ if (not res) or res[-1] is not STAR:
+ add(STAR)
+ elif c == "?":
+ add(QUESTION_MARK)
+ elif c == "[":
+ j = i
+ if j < n and pat[j] == "!":
+ j = j + 1
+ if j < n and pat[j] == "]":
+ j = j + 1
+ while j < n and pat[j] != "]":
+ j = j + 1
+ if j >= n:
+ add("\\[")
+ else:
+ stuff = pat[i:j]
+ if "-" not in stuff:
+ stuff = stuff.replace("\\", r"\\")
+ else:
+ chunks = []
+ k = i + 2 if pat[i] == "!" else i + 1
+ while True:
+ k = pat.find("-", k, j)
+ if k < 0:
+ break
+ chunks.append(pat[i:k])
+ i = k + 1
+ k = k + 3
+ chunk = pat[i:j]
+ if chunk:
+ chunks.append(chunk)
+ else:
+ chunks[-1] += "-"
+ # Remove empty ranges -- invalid in RE.
+ for k in range(len(chunks) - 1, 0, -1):
+ if chunks[k - 1][-1] > chunks[k][0]:
+ chunks[k - 1] = chunks[k - 1][:-1] + chunks[k][1:]
+ del chunks[k]
+ # Escape backslashes and hyphens for set difference (--).
+ # Hyphens that create ranges shouldn't be escaped.
+ stuff = "-".join(
+ s.replace("\\", r"\\").replace("-", r"\-") for s in chunks
+ )
+ # Escape set operations (&&, ~~ and ||).
+ stuff = re.sub(r"([&~|])", r"\\\1", stuff)
+ i = j + 1
+ if not stuff:
+ # Empty range: never match.
+ add("(?!)")
+ elif stuff == "!":
+ # Negated empty range: match any character.
+ add(".")
+ else:
+ if stuff[0] == "!":
+ stuff = "^" + stuff[1:]
+ elif stuff[0] in ("^", "["):
+ stuff = "\\" + stuff
+ add(f"[{stuff}]")
+ else:
+ add(re.escape(c))
+ assert i == n
+ return res
+
+
+def glob_translate(pat):
+ # Copied from: https://github.com/python/cpython/pull/106703.
+ # The keyword parameters' values are fixed to:
+ # recursive=True, include_hidden=True, seps=None
+ """Translate a pathname with shell wildcards to a regular expression."""
+ if os.path.altsep:
+ seps = os.path.sep + os.path.altsep
+ else:
+ seps = os.path.sep
+ escaped_seps = "".join(map(re.escape, seps))
+ any_sep = f"[{escaped_seps}]" if len(seps) > 1 else escaped_seps
+ not_sep = f"[^{escaped_seps}]"
+ one_last_segment = f"{not_sep}+"
+ one_segment = f"{one_last_segment}{any_sep}"
+ any_segments = f"(?:.+{any_sep})?"
+ any_last_segments = ".*"
+ results = []
+ parts = re.split(any_sep, pat)
+ last_part_idx = len(parts) - 1
+ for idx, part in enumerate(parts):
+ if part == "*":
+ results.append(one_segment if idx < last_part_idx else one_last_segment)
+ continue
+ if part == "**":
+ results.append(any_segments if idx < last_part_idx else any_last_segments)
+ continue
+ elif "**" in part:
+ raise ValueError(
+ "Invalid pattern: '**' can only be an entire path component"
+ )
+ if part:
+ results.extend(_translate(part, f"{not_sep}*", not_sep))
+ if idx < last_part_idx:
+ results.append(any_sep)
+ res = "".join(results)
+ return rf"(?s:{res})\Z"
diff --git a/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/METADATA b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..b5d4b0036455c4c6b9f0c4e3ef07eba8edb176ff
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/METADATA
@@ -0,0 +1,7 @@
+Metadata-Version: 2.1
+Name: huggingface-cli
+Version: 0.1
+Summary: HuggingFace integration
+Author: lolb
+License: MIT
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/RECORD b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..adc0eeb0a23b3ae375f2c5fc7b95855fb3ad7287
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/RECORD
@@ -0,0 +1,5 @@
+huggingface_cli-0.1.dist-info/METADATA,sha256=uoFWn-47DPUpVarzoq8uK5jFyt1Ov5EG0EwVTcLWi74,117
+huggingface_cli-0.1.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
+huggingface_cli-0.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
+huggingface_cli-0.1.dist-info/INSTALLER,sha256=4EobgVZEtoZym__e-MIhNYRUXcWFMMbrrt6xRpKyZoQ,12
+huggingface_cli-0.1.dist-info/RECORD,,
diff --git a/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/WHEEL b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..5bad85fdc1cd08553756d0fb2c7be8b5ad6af7fb
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/top_level.txt b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_cli-0.1.dist-info/top_level.txt
@@ -0,0 +1 @@
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/LICENSE b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/METADATA b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..34b08eaddcc0e2db0c01cf82187d4c7bf64b5c44
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/METADATA
@@ -0,0 +1,296 @@
+Metadata-Version: 2.1
+Name: huggingface-hub
+Version: 0.22.2
+Summary: Client library to download and publish models, datasets and other repos on the huggingface.co hub
+Home-page: https://github.com/huggingface/huggingface_hub
+Author: Hugging Face, Inc.
+Author-email: julien@huggingface.co
+License: Apache
+Keywords: model-hub machine-learning models natural-language-processing deep-learning pytorch pretrained-models
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Education
+Classifier: Intended Audience :: Science/Research
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
+Requires-Python: >=3.8.0
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Requires-Dist: filelock
+Requires-Dist: fsspec >=2023.5.0
+Requires-Dist: packaging >=20.9
+Requires-Dist: pyyaml >=5.1
+Requires-Dist: requests
+Requires-Dist: tqdm >=4.42.1
+Requires-Dist: typing-extensions >=3.7.4.3
+Provides-Extra: all
+Requires-Dist: InquirerPy ==0.3.4 ; extra == 'all'
+Requires-Dist: aiohttp ; extra == 'all'
+Requires-Dist: minijinja >=1.0 ; extra == 'all'
+Requires-Dist: jedi ; extra == 'all'
+Requires-Dist: Jinja2 ; extra == 'all'
+Requires-Dist: pytest ; extra == 'all'
+Requires-Dist: pytest-cov ; extra == 'all'
+Requires-Dist: pytest-env ; extra == 'all'
+Requires-Dist: pytest-xdist ; extra == 'all'
+Requires-Dist: pytest-vcr ; extra == 'all'
+Requires-Dist: pytest-asyncio ; extra == 'all'
+Requires-Dist: pytest-rerunfailures ; extra == 'all'
+Requires-Dist: urllib3 <2.0 ; extra == 'all'
+Requires-Dist: soundfile ; extra == 'all'
+Requires-Dist: Pillow ; extra == 'all'
+Requires-Dist: gradio ; extra == 'all'
+Requires-Dist: numpy ; extra == 'all'
+Requires-Dist: ruff >=0.3.0 ; extra == 'all'
+Requires-Dist: mypy ==1.5.1 ; extra == 'all'
+Requires-Dist: typing-extensions >=4.8.0 ; extra == 'all'
+Requires-Dist: types-PyYAML ; extra == 'all'
+Requires-Dist: types-requests ; extra == 'all'
+Requires-Dist: types-simplejson ; extra == 'all'
+Requires-Dist: types-toml ; extra == 'all'
+Requires-Dist: types-tqdm ; extra == 'all'
+Requires-Dist: types-urllib3 ; extra == 'all'
+Provides-Extra: cli
+Requires-Dist: InquirerPy ==0.3.4 ; extra == 'cli'
+Provides-Extra: dev
+Requires-Dist: InquirerPy ==0.3.4 ; extra == 'dev'
+Requires-Dist: aiohttp ; extra == 'dev'
+Requires-Dist: minijinja >=1.0 ; extra == 'dev'
+Requires-Dist: jedi ; extra == 'dev'
+Requires-Dist: Jinja2 ; extra == 'dev'
+Requires-Dist: pytest ; extra == 'dev'
+Requires-Dist: pytest-cov ; extra == 'dev'
+Requires-Dist: pytest-env ; extra == 'dev'
+Requires-Dist: pytest-xdist ; extra == 'dev'
+Requires-Dist: pytest-vcr ; extra == 'dev'
+Requires-Dist: pytest-asyncio ; extra == 'dev'
+Requires-Dist: pytest-rerunfailures ; extra == 'dev'
+Requires-Dist: urllib3 <2.0 ; extra == 'dev'
+Requires-Dist: soundfile ; extra == 'dev'
+Requires-Dist: Pillow ; extra == 'dev'
+Requires-Dist: gradio ; extra == 'dev'
+Requires-Dist: numpy ; extra == 'dev'
+Requires-Dist: ruff >=0.3.0 ; extra == 'dev'
+Requires-Dist: mypy ==1.5.1 ; extra == 'dev'
+Requires-Dist: typing-extensions >=4.8.0 ; extra == 'dev'
+Requires-Dist: types-PyYAML ; extra == 'dev'
+Requires-Dist: types-requests ; extra == 'dev'
+Requires-Dist: types-simplejson ; extra == 'dev'
+Requires-Dist: types-toml ; extra == 'dev'
+Requires-Dist: types-tqdm ; extra == 'dev'
+Requires-Dist: types-urllib3 ; extra == 'dev'
+Provides-Extra: fastai
+Requires-Dist: toml ; extra == 'fastai'
+Requires-Dist: fastai >=2.4 ; extra == 'fastai'
+Requires-Dist: fastcore >=1.3.27 ; extra == 'fastai'
+Provides-Extra: hf_transfer
+Requires-Dist: hf-transfer >=0.1.4 ; extra == 'hf_transfer'
+Provides-Extra: inference
+Requires-Dist: aiohttp ; extra == 'inference'
+Requires-Dist: minijinja >=1.0 ; extra == 'inference'
+Provides-Extra: quality
+Requires-Dist: ruff >=0.3.0 ; extra == 'quality'
+Requires-Dist: mypy ==1.5.1 ; extra == 'quality'
+Provides-Extra: tensorflow
+Requires-Dist: tensorflow ; extra == 'tensorflow'
+Requires-Dist: pydot ; extra == 'tensorflow'
+Requires-Dist: graphviz ; extra == 'tensorflow'
+Provides-Extra: tensorflow-testing
+Requires-Dist: tensorflow ; extra == 'tensorflow-testing'
+Requires-Dist: keras <3.0 ; extra == 'tensorflow-testing'
+Provides-Extra: testing
+Requires-Dist: InquirerPy ==0.3.4 ; extra == 'testing'
+Requires-Dist: aiohttp ; extra == 'testing'
+Requires-Dist: minijinja >=1.0 ; extra == 'testing'
+Requires-Dist: jedi ; extra == 'testing'
+Requires-Dist: Jinja2 ; extra == 'testing'
+Requires-Dist: pytest ; extra == 'testing'
+Requires-Dist: pytest-cov ; extra == 'testing'
+Requires-Dist: pytest-env ; extra == 'testing'
+Requires-Dist: pytest-xdist ; extra == 'testing'
+Requires-Dist: pytest-vcr ; extra == 'testing'
+Requires-Dist: pytest-asyncio ; extra == 'testing'
+Requires-Dist: pytest-rerunfailures ; extra == 'testing'
+Requires-Dist: urllib3 <2.0 ; extra == 'testing'
+Requires-Dist: soundfile ; extra == 'testing'
+Requires-Dist: Pillow ; extra == 'testing'
+Requires-Dist: gradio ; extra == 'testing'
+Requires-Dist: numpy ; extra == 'testing'
+Provides-Extra: torch
+Requires-Dist: torch ; extra == 'torch'
+Requires-Dist: safetensors ; extra == 'torch'
+Provides-Extra: typing
+Requires-Dist: typing-extensions >=4.8.0 ; extra == 'typing'
+Requires-Dist: types-PyYAML ; extra == 'typing'
+Requires-Dist: types-requests ; extra == 'typing'
+Requires-Dist: types-simplejson ; extra == 'typing'
+Requires-Dist: types-toml ; extra == 'typing'
+Requires-Dist: types-tqdm ; extra == 'typing'
+Requires-Dist: types-urllib3 ; extra == 'typing'
+
+
+
+
+
+
+
+
+ The official Python client for the Huggingface Hub.
+
+
+
+
+
+
+
+
+
+
+
+
+ English |
+ Deutsch |
+ हिंदी |
+ 한국어 |
+ 中文(简体)
+
+
+---
+
+**Documentation**: https://hf.co/docs/huggingface_hub
+
+**Source Code**: https://github.com/huggingface/huggingface_hub
+
+---
+
+## Welcome to the huggingface_hub library
+
+The `huggingface_hub` library allows you to interact with the [Hugging Face Hub](https://huggingface.co/), a platform democratizing open-source Machine Learning for creators and collaborators. Discover pre-trained models and datasets for your projects or play with the thousands of machine learning apps hosted on the Hub. You can also create and share your own models, datasets and demos with the community. The `huggingface_hub` library provides a simple way to do all these things with Python.
+
+## Key features
+
+- [Download files](https://huggingface.co/docs/huggingface_hub/en/guides/download) from the Hub.
+- [Upload files](https://huggingface.co/docs/huggingface_hub/en/guides/upload) to the Hub.
+- [Manage your repositories](https://huggingface.co/docs/huggingface_hub/en/guides/repository).
+- [Run Inference](https://huggingface.co/docs/huggingface_hub/en/guides/inference) on deployed models.
+- [Search](https://huggingface.co/docs/huggingface_hub/en/guides/search) for models, datasets and Spaces.
+- [Share Model Cards](https://huggingface.co/docs/huggingface_hub/en/guides/model-cards) to document your models.
+- [Engage with the community](https://huggingface.co/docs/huggingface_hub/en/guides/community) through PRs and comments.
+
+## Installation
+
+Install the `huggingface_hub` package with [pip](https://pypi.org/project/huggingface-hub/):
+
+```bash
+pip install huggingface_hub
+```
+
+If you prefer, you can also install it with [conda](https://huggingface.co/docs/huggingface_hub/en/installation#install-with-conda).
+
+In order to keep the package minimal by default, `huggingface_hub` comes with optional dependencies useful for some use cases. For example, if you want have a complete experience for Inference, run:
+
+```bash
+pip install huggingface_hub[inference]
+```
+
+To learn more installation and optional dependencies, check out the [installation guide](https://huggingface.co/docs/huggingface_hub/en/installation).
+
+## Quick start
+
+### Download files
+
+Download a single file
+
+```py
+from huggingface_hub import hf_hub_download
+
+hf_hub_download(repo_id="tiiuae/falcon-7b-instruct", filename="config.json")
+```
+
+Or an entire repository
+
+```py
+from huggingface_hub import snapshot_download
+
+snapshot_download("stabilityai/stable-diffusion-2-1")
+```
+
+Files will be downloaded in a local cache folder. More details in [this guide](https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache).
+
+### Login
+
+The Hugging Face Hub uses tokens to authenticate applications (see [docs](https://huggingface.co/docs/hub/security-tokens)). To login your machine, run the following CLI:
+
+```bash
+huggingface-cli login
+# or using an environment variable
+huggingface-cli login --token $HUGGINGFACE_TOKEN
+```
+
+### Create a repository
+
+```py
+from huggingface_hub import create_repo
+
+create_repo(repo_id="super-cool-model")
+```
+
+### Upload files
+
+Upload a single file
+
+```py
+from huggingface_hub import upload_file
+
+upload_file(
+ path_or_fileobj="/home/lysandre/dummy-test/README.md",
+ path_in_repo="README.md",
+ repo_id="lysandre/test-model",
+)
+```
+
+Or an entire folder
+
+```py
+from huggingface_hub import upload_folder
+
+upload_folder(
+ folder_path="/path/to/local/space",
+ repo_id="username/my-cool-space",
+ repo_type="space",
+)
+```
+
+For details in the [upload guide](https://huggingface.co/docs/huggingface_hub/en/guides/upload).
+
+## Integrating to the Hub.
+
+We're partnering with cool open source ML libraries to provide free model hosting and versioning. You can find the existing integrations [here](https://huggingface.co/docs/hub/libraries).
+
+The advantages are:
+
+- Free model or dataset hosting for libraries and their users.
+- Built-in file versioning, even with very large files, thanks to a git-based approach.
+- Serverless inference API for all models publicly available.
+- In-browser widgets to play with the uploaded models.
+- Anyone can upload a new model for your library, they just need to add the corresponding tag for the model to be discoverable.
+- Fast downloads! We use Cloudfront (a CDN) to geo-replicate downloads so they're blazing fast from anywhere on the globe.
+- Usage stats and more features to come.
+
+If you would like to integrate your library, feel free to open an issue to begin the discussion. We wrote a [step-by-step guide](https://huggingface.co/docs/hub/adding-a-library) with ❤️ showing how to do this integration.
+
+## Contributions (feature requests, bugs, etc.) are super welcome 💙💚💛💜🧡❤️
+
+Everyone is welcome to contribute, and we value everybody's contribution. Code is not the only way to help the community.
+Answering questions, helping others, reaching out and improving the documentations are immensely valuable to the community.
+We wrote a [contribution guide](https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md) to summarize
+how to get started to contribute to this repository.
+
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/RECORD b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..c76afc432e154ce8ef1da4514ba8c3422ff42d4a
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/RECORD
@@ -0,0 +1,115 @@
+../../../bin/huggingface-cli,sha256=5ROlRHWSVYe97HtKo7B3EHL2tC2FmNuoR4zmo1tCsfQ,270
+huggingface_hub/__init__.py,sha256=lDulrtedaTz17qYF0AzTHuRanxwgeGlVdq6TxmIcuQQ,31075
+huggingface_hub/_commit_api.py,sha256=_6NggUJsCBdspCJjXbvZYXH6vLBOnkG6I2E1kc8aJm8,29194
+huggingface_hub/_commit_scheduler.py,sha256=FgfjYv3E0oK3iBxDdy45Y7t78FWkmjnBR4dRd5aZviU,13653
+huggingface_hub/_inference_endpoints.py,sha256=wGcnxZNFCbMK77SA90fPsZ9bqNGwPopSVr-sTbdw3o8,15763
+huggingface_hub/_login.py,sha256=jNTFCnou-eZAtMWl1PDuyZhmpW3O-f4qff9m5hU0UGk,15364
+huggingface_hub/_multi_commits.py,sha256=rtN0AaHzamXi1cvr1r2MlqR6y-laZxgRo-30J_I3RwM,12519
+huggingface_hub/_snapshot_download.py,sha256=lXsSWaDSOP0hH5_9tOSBazNAtWSBhN8dMcjrLY7hNIc,15677
+huggingface_hub/_space_api.py,sha256=Mae_lqTRyTWyszI5mlObJ2fn9slPxkFPcFTEVADoNQM,5255
+huggingface_hub/_tensorboard_logger.py,sha256=uqmkKBKyj6_9XfWq563afgARenZ7-fjEHb16rgY24-Y,7166
+huggingface_hub/_webhooks_payload.py,sha256=cF9iWOOacOZfqKGcuVhykDgAZHrHON7VMuLwwehl6O8,2832
+huggingface_hub/_webhooks_server.py,sha256=fDbyDu28qhJJQb8tKpH1C8l4cJSvC3Gr2sUo1DbIoD8,15197
+huggingface_hub/community.py,sha256=SBaOfI-3atCzRbO0gDS8BYxctbdvD4G0X6D0GfY8Fgc,12203
+huggingface_hub/constants.py,sha256=8r0JaNMhLR8X6pC6TnNBLQ-TVcHEbRWk1sJ-LSIj444,7821
+huggingface_hub/errors.py,sha256=jCYKeSOsQNfH2t3TsW8kIAXXS1aWl9PaAq3prFfz4CI,704
+huggingface_hub/fastai_utils.py,sha256=5I7zAfgHJU_mZnxnf9wgWTHrCRu_EAV8VTangDVfE_o,16676
+huggingface_hub/file_download.py,sha256=DmOEVmhEsRnX8M0kZmLPLC76eptMT0riTwtThFioV8Q,77476
+huggingface_hub/hf_api.py,sha256=dJsdtW6WJW04h84BE46FOg8Pp34AgYhg-NbMg9WrReY,367303
+huggingface_hub/hf_file_system.py,sha256=JUCT-VZBesDCB-uN__fvQt3uprGQETGnUlzjC7StQLM,37272
+huggingface_hub/hub_mixin.py,sha256=xkUTJiP5TiAiVj6ts9Thffcm4wufZS3jMWil3LB2uvw,30423
+huggingface_hub/inference_api.py,sha256=UXOKu_Ez2I3hDsjguqCcCrj03WFDndehpngYiIAucdg,8331
+huggingface_hub/keras_mixin.py,sha256=8L0FEIWy_kmKsGI5d61q_33dGYbmLGhy4kZbqn-YFns,19681
+huggingface_hub/lfs.py,sha256=p61RJK13gtgdu0og4KHFosy_GWYDFsQJa0JJoLYSLAk,19592
+huggingface_hub/repocard.py,sha256=oUrGim27nCHkevPDZDbUp68uKTxB8xbdoyeqv24pexc,34605
+huggingface_hub/repocard_data.py,sha256=1hIkI8xp0EmW2aR3LtHMrjIMk_W-KJxHslMjpNMwVPg,31911
+huggingface_hub/repository.py,sha256=8oNhKNvJRye3dr67cTn8faKkBSiWFgvj7bIBlOpI-8U,54489
+huggingface_hub/commands/__init__.py,sha256=AkbM2a-iGh0Vq_xAWhK3mu3uZ44km8-X5uWjKcvcrUQ,928
+huggingface_hub/commands/_cli_utils.py,sha256=qRdl9opi3yJxIVNCnrmte-jFWmYbjVqd8gBlin8NNzY,1971
+huggingface_hub/commands/delete_cache.py,sha256=Rb1BtIltJPnQ-th7tcK_L4mFqfk785t3KXV77xXKBP4,16131
+huggingface_hub/commands/download.py,sha256=yGq9SlTRHR6TBrky_VBIyo68e0gnMLgYPBYIBG7d9bQ,9167
+huggingface_hub/commands/env.py,sha256=yYl4DSS14V8t244nAi0t77Izx5LIdgS_dy6xiV5VQME,1226
+huggingface_hub/commands/huggingface_cli.py,sha256=o862C98OcZoyqCzY7mNpia1h0KaLJUgSb0y10ot8sxA,1924
+huggingface_hub/commands/lfs.py,sha256=6E769AoRxUDiIOapn1_QvTbNtdUnUiouu2F4Gopp4do,7318
+huggingface_hub/commands/scan_cache.py,sha256=4o_jQsZloicRa-P8gncUBncVyWswpSF9T6KGlNrGodk,5183
+huggingface_hub/commands/upload.py,sha256=Mr69qO60otqCVw0sVSBPykUTkL9HO-pkCyulSD2mROM,13622
+huggingface_hub/commands/user.py,sha256=QApZJOCQEHADhjunM3hlQ72uqHsearCiCE4SdpzGdcc,6893
+huggingface_hub/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub/inference/_client.py,sha256=D4O07nYFo7v5lXZ7id3zubV6u8_RxhTVStoeAgEBs-E,104854
+huggingface_hub/inference/_common.py,sha256=BOOBNpF0_V8fDXsi2q5eQ9i4KIlPS6VfMxYxLSgkRdM,16570
+huggingface_hub/inference/_templating.py,sha256=_X7CoUjOmMh5KBXx-WGey-z3uh_fP1QT36X638UZpZw,4051
+huggingface_hub/inference/_types.py,sha256=C73l5-RO8P1UMBHF8OAO9CRUq7Xdv33pcADoJsGMPSU,1782
+huggingface_hub/inference/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+huggingface_hub/inference/_generated/_async_client.py,sha256=d7fvZyyBNnG9zivHS7rCwaR7TlETN7JENR-M_ZEo_3c,108053
+huggingface_hub/inference/_generated/types/__init__.py,sha256=SoKeNQ8JjBgKkQIgBTsWJe4-Z75Ebz6WkdL4ZQ27yNc,4450
+huggingface_hub/inference/_generated/types/audio_classification.py,sha256=wk4kUTLQZoXWLpiUOpKRHRRE-JYqqJlzGVe62VACR-0,1347
+huggingface_hub/inference/_generated/types/audio_to_audio.py,sha256=n7GeCepzt254yoSLsdjrI1j4fzYgjWzxoaKE5gZJc48,881
+huggingface_hub/inference/_generated/types/automatic_speech_recognition.py,sha256=-7UHu5QTGwSrJFnrbMgzeUFpJQOGyTmfK_QHgtnx6j8,5352
+huggingface_hub/inference/_generated/types/base.py,sha256=Cq4gUVtwwLmWyiIIq4NSL8kRk0EWk9QWWHc5Vup2LVg,6213
+huggingface_hub/inference/_generated/types/chat_completion.py,sha256=cI-gWOgqEBnwhxwn21OShkKOs3mbjjzJ1Ow2pzLQEwc,3616
+huggingface_hub/inference/_generated/types/depth_estimation.py,sha256=lmLmd8S313ZMCG94RblwquL0UN_0hJmXAhWUqSIrtwc,898
+huggingface_hub/inference/_generated/types/document_question_answering.py,sha256=_hBzK4Pu9X_zXsgOO4JNSloIKuVfE5m7eGwEw5YTfZ4,3264
+huggingface_hub/inference/_generated/types/feature_extraction.py,sha256=KerTrRR5YR02X0qBDzrtK8953amCGf_adSUbfWOozD4,664
+huggingface_hub/inference/_generated/types/fill_mask.py,sha256=JcYIbTDXc4f7k2FNY3fCWtJ9ke3HUZFz2pDOOrDuxOs,1714
+huggingface_hub/inference/_generated/types/image_classification.py,sha256=W1QVfc0j7t6qbxjICUQDwygRx43yPPGZKyStogHkHqg,1359
+huggingface_hub/inference/_generated/types/image_segmentation.py,sha256=nVQc5Qhv37qqmTn_M8xegpNgk14ozKelsGIYC8hba_0,1803
+huggingface_hub/inference/_generated/types/image_to_image.py,sha256=MbubS9pD8bFP9LoI4QoQxJwpUGeNXi5iSEk8Ymhbh0M,1797
+huggingface_hub/inference/_generated/types/image_to_text.py,sha256=mloLf-LO7oR_2HbPY1-XMM18BfjMODytRaxTXYkIXoY,4827
+huggingface_hub/inference/_generated/types/object_detection.py,sha256=F8ly6GSE8dupsekPVf6G5nI8teZAIP4iXw6u3zm1JiE,1569
+huggingface_hub/inference/_generated/types/question_answering.py,sha256=xLDy5oA-k9JPncSU6NqPAPb8rWPerfTbU857G3e7JZ0,2884
+huggingface_hub/inference/_generated/types/sentence_similarity.py,sha256=edH-TWfnZ4J0zJD-zqfcRMLwOV0dTt1g5Y0caYnVuPc,1018
+huggingface_hub/inference/_generated/types/summarization.py,sha256=RWCXh7yftI_JWvLsr7JiDpQPexq1klYP158tUICUcbM,1574
+huggingface_hub/inference/_generated/types/table_question_answering.py,sha256=PuVZlR6dI6FEUK7pjMSVMtzkDgrcxdKjfcnDbVmPdSs,1569
+huggingface_hub/inference/_generated/types/text2text_generation.py,sha256=SZYfdhyraG5vZ2Jzm1C8k9w9IYLxMtm5UUu1tU2oOQk,1604
+huggingface_hub/inference/_generated/types/text_classification.py,sha256=vC7B1sBzZ4gdLjE2i2Y7w5cpdaFwQKK1dlWqW0asjIk,1347
+huggingface_hub/inference/_generated/types/text_generation.py,sha256=VHhkhEj-yEVGuy4BYgDNzmAPBPJWL3N1B4n4SUOymNk,5866
+huggingface_hub/inference/_generated/types/text_to_audio.py,sha256=cgvECsiwsycgP9Tfs_GU1CJfo9AngVn6x9s4fHCP-g4,4819
+huggingface_hub/inference/_generated/types/text_to_image.py,sha256=oBGeJ-S9WfsMxVQlvEOll9yaCyMXZ277wsYFD8bt87U,1931
+huggingface_hub/inference/_generated/types/token_classification.py,sha256=7oL8AZOTWtf2bYD2T3236GDNMtUl7FtydaB6We7wbfw,1890
+huggingface_hub/inference/_generated/types/translation.py,sha256=MruCx6yhzQGlxSdBRXCVoEhRzRSa5Ks4bjZ1PDrlTeQ,1562
+huggingface_hub/inference/_generated/types/video_classification.py,sha256=BI2_PP-pxLT6w9TuX6QCZz4BsG-ZukTXnW6fWMchI5M,1579
+huggingface_hub/inference/_generated/types/visual_question_answering.py,sha256=0PHNnjwxxHvG3SjOz7O7DckbBeGYDsRmlagG11qIkkM,1667
+huggingface_hub/inference/_generated/types/zero_shot_classification.py,sha256=u6jfFCqDv9XqeAN5E9_Xf7jqMZgqTRFF_S9PtWbiBUk,1963
+huggingface_hub/inference/_generated/types/zero_shot_image_classification.py,sha256=qVH6Ms0FjF8TraGy4BYiS8lmvGq9xiIDdXqGFynLHMA,1689
+huggingface_hub/inference/_generated/types/zero_shot_object_detection.py,sha256=PU4OOlQ2aAOosW2JlG2Z27MEQpmE6BxcygH_ns3w1KQ,1662
+huggingface_hub/serialization/__init__.py,sha256=W74TaCtYnMfpvGEQr1SS-OBmqPUFnM9AeWT9hTJCG9Y,910
+huggingface_hub/serialization/_base.py,sha256=AgO-16i-vyosbERnLSCFYgaXbVqQDM7xfIne8gsWrLQ,7133
+huggingface_hub/serialization/_numpy.py,sha256=idULJp1js6L6E8o-MiGVqNa4lBfXS2cfAmqivnpsaYs,2671
+huggingface_hub/serialization/_tensorflow.py,sha256=Rf4kw1NYxEaoUXB8aLtQLHrTjgobaEAJdzO0w0kbP58,3559
+huggingface_hub/serialization/_torch.py,sha256=xYR6e_G9laMTroWLiQRABSuloTQuuRSQNyYHdT_rmXU,7687
+huggingface_hub/templates/datasetcard_template.md,sha256=W-EMqR6wndbrnZorkVv56URWPG49l7MATGeI015kTvs,5503
+huggingface_hub/templates/modelcard_template.md,sha256=4AqArS3cqdtbit5Bo-DhjcnDFR-pza5hErLLTPM4Yuc,6870
+huggingface_hub/utils/__init__.py,sha256=XYIIkKiDeoy8dQQegBYSfILOzdt8NW_cfquY7omX0fQ,3478
+huggingface_hub/utils/_cache_assets.py,sha256=kai77HPQMfYpROouMBQCr_gdBCaeTm996Sqj0dExbNg,5728
+huggingface_hub/utils/_cache_manager.py,sha256=Fs1XVP1UGzUTogMfMfEi_MfpURzHyW__djX0s2oLmrY,29307
+huggingface_hub/utils/_chunk_utils.py,sha256=kRCaj5228_vKcyLWspd8Xq01f17Jz6ds5Sr9ed5d_RU,2130
+huggingface_hub/utils/_datetime.py,sha256=DHnktKm1taeOe2XCBgNU4pVck5d70qu8FJ7nACD6C3k,2554
+huggingface_hub/utils/_deprecation.py,sha256=HZhRGGUX_QMKBBBwHHlffLtmCSK01TOpeXHefZbPfwI,4872
+huggingface_hub/utils/_errors.py,sha256=N5nUkCCaj8393wntazeTcKNrwDZfsDVHVMxxreHPfaE,15141
+huggingface_hub/utils/_experimental.py,sha256=crCPH6k6-11wwH2GZuZzZzZbjUotay49ywV1SSJhMHM,2395
+huggingface_hub/utils/_fixes.py,sha256=EqG7u36J9C3NtL5VukDilca90GV9idrENzsEVhtdbI4,2829
+huggingface_hub/utils/_git_credential.py,sha256=SDdsiREr1TcAR2Ze2TB0E5cYzVJgvDZrs60od9lAsMc,4596
+huggingface_hub/utils/_headers.py,sha256=T_C1RA0bqEYL0oiE4WdFMAKXEUPHN-D43vchjiwKcZ4,9643
+huggingface_hub/utils/_hf_folder.py,sha256=gWH-TT9h_6X_CyrtLTtKNEawf9kKlCHraFiOu09BuLk,3613
+huggingface_hub/utils/_http.py,sha256=VQcukUKXXDlDQwyG-LGVtAIr3DprVC-R_HcXZzbAfak,13543
+huggingface_hub/utils/_pagination.py,sha256=hzLFLd8i_DKkPRVYzOx2CxLt5lcocEiAxDJriQUjAjY,1841
+huggingface_hub/utils/_paths.py,sha256=Ah_diO-gSWw9TYylJl_HNB2XXftgIi36HNlKAYQHCms,4398
+huggingface_hub/utils/_runtime.py,sha256=6PxkDPWj3ltRlE2-zAr3vZTXfi1OUjxILsmRN-s_wZY,10851
+huggingface_hub/utils/_safetensors.py,sha256=EE9v9HflWBUqIegn0dCGHgNu9G9Db3v2aszvG4ldPF8,4876
+huggingface_hub/utils/_subprocess.py,sha256=34ETD8JvLzm16NRZHciaCLXdE9aRyxuDdOA5gdNvMJ8,4617
+huggingface_hub/utils/_telemetry.py,sha256=jHAdgWNcL9nVvMT3ec3i78O-cwL09GnlifuokzpQjMI,4641
+huggingface_hub/utils/_token.py,sha256=cxBZaafW2IsJ2dKWd55v7056zycW1ewp_nPk8dNcSO4,5476
+huggingface_hub/utils/_typing.py,sha256=pXh7GtVtSBD_Fvvthex9BRTAJZ6bWScUOw06oJS0Lek,2025
+huggingface_hub/utils/_validators.py,sha256=otFT4xT3s_E_-jrzH4NR7xWgK7UlRkwk_KAI9XK1mb0,9359
+huggingface_hub/utils/endpoint_helpers.py,sha256=n_VguR_L2Vl6Mi_4PFO2iAd5xaPeQRiD8KRBpzs4nMw,9536
+huggingface_hub/utils/insecure_hashlib.py,sha256=OjxlvtSQHpbLp9PWSrXBDJ0wHjxCBU-SQJgucEEXDbU,1058
+huggingface_hub/utils/logging.py,sha256=Cp03s0uEl3kDM9XHQW9a8GAoExODQ-e7kEtgMt-_To8,4728
+huggingface_hub/utils/sha.py,sha256=QLlIwPCyz46MmUc_4L8xl87KfYoBks9kPgsMZ5JCz-o,902
+huggingface_hub/utils/tqdm.py,sha256=2H80n_kDpvp7P4i7MaYR47t41i0l6ODi5mab1oof1dk,6335
+huggingface_hub-0.22.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
+huggingface_hub-0.22.2.dist-info/METADATA,sha256=9S0T9nMXn6Q7cesvjVBiSk4oEid8LZ3gT9fuJLGrWNk,12869
+huggingface_hub-0.22.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
+huggingface_hub-0.22.2.dist-info/entry_points.txt,sha256=Y3Z2L02rBG7va_iE6RPXolIgwOdwUFONyRN3kXMxZ0g,131
+huggingface_hub-0.22.2.dist-info/top_level.txt,sha256=8KzlQJAY4miUvjAssOAJodqKOw3harNzuiwGQ9qLSSk,16
+huggingface_hub-0.22.2.dist-info/INSTALLER,sha256=4EobgVZEtoZym__e-MIhNYRUXcWFMMbrrt6xRpKyZoQ,12
+huggingface_hub-0.22.2.dist-info/RECORD,,
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/WHEEL b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..bab98d675883cc7567a79df485cd7b4f015e376f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.43.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/entry_points.txt b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eb3dafd90f19de60b3e520aeaf8132402980214d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/entry_points.txt
@@ -0,0 +1,6 @@
+[console_scripts]
+huggingface-cli = huggingface_hub.commands.huggingface_cli:main
+
+[fsspec.specs]
+hf=huggingface_hub.HfFileSystem
+
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/top_level.txt b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6b964ccca3c1b6766042b3fe3b2707ba25372924
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub-0.22.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+huggingface_hub
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..db124d36d239f7855cb7a65dbe6830a395c51191
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/__init__.py
@@ -0,0 +1,884 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ***********
+# `huggingface_hub` init has 2 modes:
+# - Normal usage:
+# If imported to use it, all modules and functions are lazy-loaded. This means
+# they exist at top level in module but are imported only the first time they are
+# used. This way, `from huggingface_hub import something` will import `something`
+# quickly without the hassle of importing all the features from `huggingface_hub`.
+# - Static check:
+# If statically analyzed, all modules and functions are loaded normally. This way
+# static typing check works properly as well as autocomplete in text editors and
+# IDEs.
+#
+# The static model imports are done inside the `if TYPE_CHECKING:` statement at
+# the bottom of this file. Since module/functions imports are duplicated, it is
+# mandatory to make sure to add them twice when adding one. This is checked in the
+# `make quality` command.
+#
+# To update the static imports, please run the following command and commit the changes.
+# ```
+# # Use script
+# python utils/check_static_imports.py --update-file
+#
+# # Or run style on codebase
+# make style
+# ```
+#
+# ***********
+# Lazy loader vendored from https://github.com/scientific-python/lazy_loader
+import importlib
+import os
+import sys
+from typing import TYPE_CHECKING
+
+
+__version__ = "0.22.2"
+
+# Alphabetical order of definitions is ensured in tests
+# WARNING: any comment added in this dictionary definition will be lost when
+# re-generating the file !
+_SUBMOD_ATTRS = {
+ "_commit_scheduler": [
+ "CommitScheduler",
+ ],
+ "_inference_endpoints": [
+ "InferenceEndpoint",
+ "InferenceEndpointError",
+ "InferenceEndpointStatus",
+ "InferenceEndpointTimeoutError",
+ "InferenceEndpointType",
+ ],
+ "_login": [
+ "interpreter_login",
+ "login",
+ "logout",
+ "notebook_login",
+ ],
+ "_multi_commits": [
+ "MultiCommitException",
+ "plan_multi_commits",
+ ],
+ "_snapshot_download": [
+ "snapshot_download",
+ ],
+ "_space_api": [
+ "SpaceHardware",
+ "SpaceRuntime",
+ "SpaceStage",
+ "SpaceStorage",
+ "SpaceVariable",
+ ],
+ "_tensorboard_logger": [
+ "HFSummaryWriter",
+ ],
+ "_webhooks_payload": [
+ "WebhookPayload",
+ "WebhookPayloadComment",
+ "WebhookPayloadDiscussion",
+ "WebhookPayloadDiscussionChanges",
+ "WebhookPayloadEvent",
+ "WebhookPayloadMovedTo",
+ "WebhookPayloadRepo",
+ "WebhookPayloadUrl",
+ "WebhookPayloadWebhook",
+ ],
+ "_webhooks_server": [
+ "WebhooksServer",
+ "webhook_endpoint",
+ ],
+ "community": [
+ "Discussion",
+ "DiscussionComment",
+ "DiscussionCommit",
+ "DiscussionEvent",
+ "DiscussionStatusChange",
+ "DiscussionTitleChange",
+ "DiscussionWithDetails",
+ ],
+ "constants": [
+ "CONFIG_NAME",
+ "FLAX_WEIGHTS_NAME",
+ "HUGGINGFACE_CO_URL_HOME",
+ "HUGGINGFACE_CO_URL_TEMPLATE",
+ "PYTORCH_WEIGHTS_NAME",
+ "REPO_TYPE_DATASET",
+ "REPO_TYPE_MODEL",
+ "REPO_TYPE_SPACE",
+ "TF2_WEIGHTS_NAME",
+ "TF_WEIGHTS_NAME",
+ ],
+ "fastai_utils": [
+ "_save_pretrained_fastai",
+ "from_pretrained_fastai",
+ "push_to_hub_fastai",
+ ],
+ "file_download": [
+ "HfFileMetadata",
+ "_CACHED_NO_EXIST",
+ "cached_download",
+ "get_hf_file_metadata",
+ "hf_hub_download",
+ "hf_hub_url",
+ "try_to_load_from_cache",
+ ],
+ "hf_api": [
+ "Collection",
+ "CollectionItem",
+ "CommitInfo",
+ "CommitOperation",
+ "CommitOperationAdd",
+ "CommitOperationCopy",
+ "CommitOperationDelete",
+ "GitCommitInfo",
+ "GitRefInfo",
+ "GitRefs",
+ "HfApi",
+ "RepoUrl",
+ "User",
+ "UserLikes",
+ "accept_access_request",
+ "add_collection_item",
+ "add_space_secret",
+ "add_space_variable",
+ "cancel_access_request",
+ "change_discussion_status",
+ "comment_discussion",
+ "create_branch",
+ "create_collection",
+ "create_commit",
+ "create_commits_on_pr",
+ "create_discussion",
+ "create_inference_endpoint",
+ "create_pull_request",
+ "create_repo",
+ "create_tag",
+ "dataset_info",
+ "delete_branch",
+ "delete_collection",
+ "delete_collection_item",
+ "delete_file",
+ "delete_folder",
+ "delete_inference_endpoint",
+ "delete_repo",
+ "delete_space_secret",
+ "delete_space_storage",
+ "delete_space_variable",
+ "delete_tag",
+ "duplicate_space",
+ "edit_discussion_comment",
+ "file_exists",
+ "get_collection",
+ "get_dataset_tags",
+ "get_discussion_details",
+ "get_full_repo_name",
+ "get_inference_endpoint",
+ "get_model_tags",
+ "get_paths_info",
+ "get_repo_discussions",
+ "get_safetensors_metadata",
+ "get_space_runtime",
+ "get_space_variables",
+ "get_token_permission",
+ "grant_access",
+ "like",
+ "list_accepted_access_requests",
+ "list_collections",
+ "list_datasets",
+ "list_files_info",
+ "list_inference_endpoints",
+ "list_liked_repos",
+ "list_metrics",
+ "list_models",
+ "list_pending_access_requests",
+ "list_rejected_access_requests",
+ "list_repo_commits",
+ "list_repo_files",
+ "list_repo_likers",
+ "list_repo_refs",
+ "list_repo_tree",
+ "list_spaces",
+ "merge_pull_request",
+ "model_info",
+ "move_repo",
+ "parse_safetensors_file_metadata",
+ "pause_inference_endpoint",
+ "pause_space",
+ "preupload_lfs_files",
+ "reject_access_request",
+ "rename_discussion",
+ "repo_exists",
+ "repo_info",
+ "repo_type_and_id_from_hf_id",
+ "request_space_hardware",
+ "request_space_storage",
+ "restart_space",
+ "resume_inference_endpoint",
+ "revision_exists",
+ "run_as_future",
+ "scale_to_zero_inference_endpoint",
+ "set_space_sleep_time",
+ "space_info",
+ "super_squash_history",
+ "unlike",
+ "update_collection_item",
+ "update_collection_metadata",
+ "update_inference_endpoint",
+ "update_repo_visibility",
+ "upload_file",
+ "upload_folder",
+ "whoami",
+ ],
+ "hf_file_system": [
+ "HfFileSystem",
+ "HfFileSystemFile",
+ "HfFileSystemResolvedPath",
+ "HfFileSystemStreamFile",
+ ],
+ "hub_mixin": [
+ "ModelHubMixin",
+ "PyTorchModelHubMixin",
+ ],
+ "inference._client": [
+ "InferenceClient",
+ "InferenceTimeoutError",
+ ],
+ "inference._generated._async_client": [
+ "AsyncInferenceClient",
+ ],
+ "inference._generated.types": [
+ "AudioClassificationInput",
+ "AudioClassificationOutputElement",
+ "AudioClassificationParameters",
+ "AudioToAudioInput",
+ "AudioToAudioOutputElement",
+ "AutomaticSpeechRecognitionGenerationParameters",
+ "AutomaticSpeechRecognitionInput",
+ "AutomaticSpeechRecognitionOutput",
+ "AutomaticSpeechRecognitionOutputChunk",
+ "AutomaticSpeechRecognitionParameters",
+ "ChatCompletionInput",
+ "ChatCompletionInputMessage",
+ "ChatCompletionOutput",
+ "ChatCompletionOutputChoice",
+ "ChatCompletionOutputChoiceMessage",
+ "ChatCompletionStreamOutput",
+ "ChatCompletionStreamOutputChoice",
+ "ChatCompletionStreamOutputDelta",
+ "DepthEstimationInput",
+ "DepthEstimationOutput",
+ "DocumentQuestionAnsweringInput",
+ "DocumentQuestionAnsweringInputData",
+ "DocumentQuestionAnsweringOutputElement",
+ "DocumentQuestionAnsweringParameters",
+ "FeatureExtractionInput",
+ "FillMaskInput",
+ "FillMaskOutputElement",
+ "FillMaskParameters",
+ "ImageClassificationInput",
+ "ImageClassificationOutputElement",
+ "ImageClassificationParameters",
+ "ImageSegmentationInput",
+ "ImageSegmentationOutputElement",
+ "ImageSegmentationParameters",
+ "ImageToImageInput",
+ "ImageToImageOutput",
+ "ImageToImageParameters",
+ "ImageToImageTargetSize",
+ "ImageToTextGenerationParameters",
+ "ImageToTextInput",
+ "ImageToTextOutput",
+ "ImageToTextParameters",
+ "ObjectDetectionBoundingBox",
+ "ObjectDetectionInput",
+ "ObjectDetectionOutputElement",
+ "ObjectDetectionParameters",
+ "QuestionAnsweringInput",
+ "QuestionAnsweringInputData",
+ "QuestionAnsweringOutputElement",
+ "QuestionAnsweringParameters",
+ "SentenceSimilarityInput",
+ "SentenceSimilarityInputData",
+ "SummarizationGenerationParameters",
+ "SummarizationInput",
+ "SummarizationOutput",
+ "TableQuestionAnsweringInput",
+ "TableQuestionAnsweringInputData",
+ "TableQuestionAnsweringOutputElement",
+ "Text2TextGenerationInput",
+ "Text2TextGenerationOutput",
+ "Text2TextGenerationParameters",
+ "TextClassificationInput",
+ "TextClassificationOutputElement",
+ "TextClassificationParameters",
+ "TextGenerationInput",
+ "TextGenerationOutput",
+ "TextGenerationOutputDetails",
+ "TextGenerationOutputSequenceDetails",
+ "TextGenerationOutputToken",
+ "TextGenerationParameters",
+ "TextGenerationPrefillToken",
+ "TextGenerationStreamDetails",
+ "TextGenerationStreamOutput",
+ "TextToAudioGenerationParameters",
+ "TextToAudioInput",
+ "TextToAudioOutput",
+ "TextToAudioParameters",
+ "TextToImageInput",
+ "TextToImageOutput",
+ "TextToImageParameters",
+ "TextToImageTargetSize",
+ "TokenClassificationInput",
+ "TokenClassificationOutputElement",
+ "TokenClassificationParameters",
+ "TranslationGenerationParameters",
+ "TranslationInput",
+ "TranslationOutput",
+ "VideoClassificationInput",
+ "VideoClassificationOutputElement",
+ "VideoClassificationParameters",
+ "VisualQuestionAnsweringInput",
+ "VisualQuestionAnsweringInputData",
+ "VisualQuestionAnsweringOutputElement",
+ "VisualQuestionAnsweringParameters",
+ "ZeroShotClassificationInput",
+ "ZeroShotClassificationInputData",
+ "ZeroShotClassificationOutputElement",
+ "ZeroShotClassificationParameters",
+ "ZeroShotImageClassificationInput",
+ "ZeroShotImageClassificationInputData",
+ "ZeroShotImageClassificationOutputElement",
+ "ZeroShotImageClassificationParameters",
+ "ZeroShotObjectDetectionBoundingBox",
+ "ZeroShotObjectDetectionInput",
+ "ZeroShotObjectDetectionInputData",
+ "ZeroShotObjectDetectionOutputElement",
+ ],
+ "inference_api": [
+ "InferenceApi",
+ ],
+ "keras_mixin": [
+ "KerasModelHubMixin",
+ "from_pretrained_keras",
+ "push_to_hub_keras",
+ "save_pretrained_keras",
+ ],
+ "repocard": [
+ "DatasetCard",
+ "ModelCard",
+ "RepoCard",
+ "SpaceCard",
+ "metadata_eval_result",
+ "metadata_load",
+ "metadata_save",
+ "metadata_update",
+ ],
+ "repocard_data": [
+ "CardData",
+ "DatasetCardData",
+ "EvalResult",
+ "ModelCardData",
+ "SpaceCardData",
+ ],
+ "repository": [
+ "Repository",
+ ],
+ "serialization": [
+ "StateDictSplit",
+ "split_numpy_state_dict_into_shards",
+ "split_state_dict_into_shards_factory",
+ "split_tf_state_dict_into_shards",
+ "split_torch_state_dict_into_shards",
+ ],
+ "utils": [
+ "CacheNotFound",
+ "CachedFileInfo",
+ "CachedRepoInfo",
+ "CachedRevisionInfo",
+ "CorruptedCacheException",
+ "DeleteCacheStrategy",
+ "HFCacheInfo",
+ "HfFolder",
+ "cached_assets_path",
+ "configure_http_backend",
+ "dump_environment_info",
+ "get_session",
+ "get_token",
+ "logging",
+ "scan_cache_dir",
+ ],
+ "utils.endpoint_helpers": [
+ "DatasetFilter",
+ "ModelFilter",
+ ],
+}
+
+
+def _attach(package_name, submodules=None, submod_attrs=None):
+ """Attach lazily loaded submodules, functions, or other attributes.
+
+ Typically, modules import submodules and attributes as follows:
+
+ ```py
+ import mysubmodule
+ import anothersubmodule
+
+ from .foo import someattr
+ ```
+
+ The idea is to replace a package's `__getattr__`, `__dir__`, and
+ `__all__`, such that all imports work exactly the way they would
+ with normal imports, except that the import occurs upon first use.
+
+ The typical way to call this function, replacing the above imports, is:
+
+ ```python
+ __getattr__, __dir__, __all__ = lazy.attach(
+ __name__,
+ ['mysubmodule', 'anothersubmodule'],
+ {'foo': ['someattr']}
+ )
+ ```
+ This functionality requires Python 3.7 or higher.
+
+ Args:
+ package_name (`str`):
+ Typically use `__name__`.
+ submodules (`set`):
+ List of submodules to attach.
+ submod_attrs (`dict`):
+ Dictionary of submodule -> list of attributes / functions.
+ These attributes are imported as they are used.
+
+ Returns:
+ __getattr__, __dir__, __all__
+
+ """
+ if submod_attrs is None:
+ submod_attrs = {}
+
+ if submodules is None:
+ submodules = set()
+ else:
+ submodules = set(submodules)
+
+ attr_to_modules = {attr: mod for mod, attrs in submod_attrs.items() for attr in attrs}
+
+ __all__ = list(submodules | attr_to_modules.keys())
+
+ def __getattr__(name):
+ if name in submodules:
+ return importlib.import_module(f"{package_name}.{name}")
+ elif name in attr_to_modules:
+ submod_path = f"{package_name}.{attr_to_modules[name]}"
+ submod = importlib.import_module(submod_path)
+ attr = getattr(submod, name)
+
+ # If the attribute lives in a file (module) with the same
+ # name as the attribute, ensure that the attribute and *not*
+ # the module is accessible on the package.
+ if name == attr_to_modules[name]:
+ pkg = sys.modules[package_name]
+ pkg.__dict__[name] = attr
+
+ return attr
+ else:
+ raise AttributeError(f"No {package_name} attribute {name}")
+
+ def __dir__():
+ return __all__
+
+ if os.environ.get("EAGER_IMPORT", ""):
+ for attr in set(attr_to_modules.keys()) | submodules:
+ __getattr__(attr)
+
+ return __getattr__, __dir__, list(__all__)
+
+
+__getattr__, __dir__, __all__ = _attach(__name__, submodules=[], submod_attrs=_SUBMOD_ATTRS)
+
+# WARNING: any content below this statement is generated automatically. Any manual edit
+# will be lost when re-generating this file !
+#
+# To update the static imports, please run the following command and commit the changes.
+# ```
+# # Use script
+# python utils/check_static_imports.py --update-file
+#
+# # Or run style on codebase
+# make style
+# ```
+if TYPE_CHECKING: # pragma: no cover
+ from ._commit_scheduler import CommitScheduler # noqa: F401
+ from ._inference_endpoints import (
+ InferenceEndpoint, # noqa: F401
+ InferenceEndpointError, # noqa: F401
+ InferenceEndpointStatus, # noqa: F401
+ InferenceEndpointTimeoutError, # noqa: F401
+ InferenceEndpointType, # noqa: F401
+ )
+ from ._login import (
+ interpreter_login, # noqa: F401
+ login, # noqa: F401
+ logout, # noqa: F401
+ notebook_login, # noqa: F401
+ )
+ from ._multi_commits import (
+ MultiCommitException, # noqa: F401
+ plan_multi_commits, # noqa: F401
+ )
+ from ._snapshot_download import snapshot_download # noqa: F401
+ from ._space_api import (
+ SpaceHardware, # noqa: F401
+ SpaceRuntime, # noqa: F401
+ SpaceStage, # noqa: F401
+ SpaceStorage, # noqa: F401
+ SpaceVariable, # noqa: F401
+ )
+ from ._tensorboard_logger import HFSummaryWriter # noqa: F401
+ from ._webhooks_payload import (
+ WebhookPayload, # noqa: F401
+ WebhookPayloadComment, # noqa: F401
+ WebhookPayloadDiscussion, # noqa: F401
+ WebhookPayloadDiscussionChanges, # noqa: F401
+ WebhookPayloadEvent, # noqa: F401
+ WebhookPayloadMovedTo, # noqa: F401
+ WebhookPayloadRepo, # noqa: F401
+ WebhookPayloadUrl, # noqa: F401
+ WebhookPayloadWebhook, # noqa: F401
+ )
+ from ._webhooks_server import (
+ WebhooksServer, # noqa: F401
+ webhook_endpoint, # noqa: F401
+ )
+ from .community import (
+ Discussion, # noqa: F401
+ DiscussionComment, # noqa: F401
+ DiscussionCommit, # noqa: F401
+ DiscussionEvent, # noqa: F401
+ DiscussionStatusChange, # noqa: F401
+ DiscussionTitleChange, # noqa: F401
+ DiscussionWithDetails, # noqa: F401
+ )
+ from .constants import (
+ CONFIG_NAME, # noqa: F401
+ FLAX_WEIGHTS_NAME, # noqa: F401
+ HUGGINGFACE_CO_URL_HOME, # noqa: F401
+ HUGGINGFACE_CO_URL_TEMPLATE, # noqa: F401
+ PYTORCH_WEIGHTS_NAME, # noqa: F401
+ REPO_TYPE_DATASET, # noqa: F401
+ REPO_TYPE_MODEL, # noqa: F401
+ REPO_TYPE_SPACE, # noqa: F401
+ TF2_WEIGHTS_NAME, # noqa: F401
+ TF_WEIGHTS_NAME, # noqa: F401
+ )
+ from .fastai_utils import (
+ _save_pretrained_fastai, # noqa: F401
+ from_pretrained_fastai, # noqa: F401
+ push_to_hub_fastai, # noqa: F401
+ )
+ from .file_download import (
+ _CACHED_NO_EXIST, # noqa: F401
+ HfFileMetadata, # noqa: F401
+ cached_download, # noqa: F401
+ get_hf_file_metadata, # noqa: F401
+ hf_hub_download, # noqa: F401
+ hf_hub_url, # noqa: F401
+ try_to_load_from_cache, # noqa: F401
+ )
+ from .hf_api import (
+ Collection, # noqa: F401
+ CollectionItem, # noqa: F401
+ CommitInfo, # noqa: F401
+ CommitOperation, # noqa: F401
+ CommitOperationAdd, # noqa: F401
+ CommitOperationCopy, # noqa: F401
+ CommitOperationDelete, # noqa: F401
+ GitCommitInfo, # noqa: F401
+ GitRefInfo, # noqa: F401
+ GitRefs, # noqa: F401
+ HfApi, # noqa: F401
+ RepoUrl, # noqa: F401
+ User, # noqa: F401
+ UserLikes, # noqa: F401
+ accept_access_request, # noqa: F401
+ add_collection_item, # noqa: F401
+ add_space_secret, # noqa: F401
+ add_space_variable, # noqa: F401
+ cancel_access_request, # noqa: F401
+ change_discussion_status, # noqa: F401
+ comment_discussion, # noqa: F401
+ create_branch, # noqa: F401
+ create_collection, # noqa: F401
+ create_commit, # noqa: F401
+ create_commits_on_pr, # noqa: F401
+ create_discussion, # noqa: F401
+ create_inference_endpoint, # noqa: F401
+ create_pull_request, # noqa: F401
+ create_repo, # noqa: F401
+ create_tag, # noqa: F401
+ dataset_info, # noqa: F401
+ delete_branch, # noqa: F401
+ delete_collection, # noqa: F401
+ delete_collection_item, # noqa: F401
+ delete_file, # noqa: F401
+ delete_folder, # noqa: F401
+ delete_inference_endpoint, # noqa: F401
+ delete_repo, # noqa: F401
+ delete_space_secret, # noqa: F401
+ delete_space_storage, # noqa: F401
+ delete_space_variable, # noqa: F401
+ delete_tag, # noqa: F401
+ duplicate_space, # noqa: F401
+ edit_discussion_comment, # noqa: F401
+ file_exists, # noqa: F401
+ get_collection, # noqa: F401
+ get_dataset_tags, # noqa: F401
+ get_discussion_details, # noqa: F401
+ get_full_repo_name, # noqa: F401
+ get_inference_endpoint, # noqa: F401
+ get_model_tags, # noqa: F401
+ get_paths_info, # noqa: F401
+ get_repo_discussions, # noqa: F401
+ get_safetensors_metadata, # noqa: F401
+ get_space_runtime, # noqa: F401
+ get_space_variables, # noqa: F401
+ get_token_permission, # noqa: F401
+ grant_access, # noqa: F401
+ like, # noqa: F401
+ list_accepted_access_requests, # noqa: F401
+ list_collections, # noqa: F401
+ list_datasets, # noqa: F401
+ list_files_info, # noqa: F401
+ list_inference_endpoints, # noqa: F401
+ list_liked_repos, # noqa: F401
+ list_metrics, # noqa: F401
+ list_models, # noqa: F401
+ list_pending_access_requests, # noqa: F401
+ list_rejected_access_requests, # noqa: F401
+ list_repo_commits, # noqa: F401
+ list_repo_files, # noqa: F401
+ list_repo_likers, # noqa: F401
+ list_repo_refs, # noqa: F401
+ list_repo_tree, # noqa: F401
+ list_spaces, # noqa: F401
+ merge_pull_request, # noqa: F401
+ model_info, # noqa: F401
+ move_repo, # noqa: F401
+ parse_safetensors_file_metadata, # noqa: F401
+ pause_inference_endpoint, # noqa: F401
+ pause_space, # noqa: F401
+ preupload_lfs_files, # noqa: F401
+ reject_access_request, # noqa: F401
+ rename_discussion, # noqa: F401
+ repo_exists, # noqa: F401
+ repo_info, # noqa: F401
+ repo_type_and_id_from_hf_id, # noqa: F401
+ request_space_hardware, # noqa: F401
+ request_space_storage, # noqa: F401
+ restart_space, # noqa: F401
+ resume_inference_endpoint, # noqa: F401
+ revision_exists, # noqa: F401
+ run_as_future, # noqa: F401
+ scale_to_zero_inference_endpoint, # noqa: F401
+ set_space_sleep_time, # noqa: F401
+ space_info, # noqa: F401
+ super_squash_history, # noqa: F401
+ unlike, # noqa: F401
+ update_collection_item, # noqa: F401
+ update_collection_metadata, # noqa: F401
+ update_inference_endpoint, # noqa: F401
+ update_repo_visibility, # noqa: F401
+ upload_file, # noqa: F401
+ upload_folder, # noqa: F401
+ whoami, # noqa: F401
+ )
+ from .hf_file_system import (
+ HfFileSystem, # noqa: F401
+ HfFileSystemFile, # noqa: F401
+ HfFileSystemResolvedPath, # noqa: F401
+ HfFileSystemStreamFile, # noqa: F401
+ )
+ from .hub_mixin import (
+ ModelHubMixin, # noqa: F401
+ PyTorchModelHubMixin, # noqa: F401
+ )
+ from .inference._client import (
+ InferenceClient, # noqa: F401
+ InferenceTimeoutError, # noqa: F401
+ )
+ from .inference._generated._async_client import AsyncInferenceClient # noqa: F401
+ from .inference._generated.types import (
+ AudioClassificationInput, # noqa: F401
+ AudioClassificationOutputElement, # noqa: F401
+ AudioClassificationParameters, # noqa: F401
+ AudioToAudioInput, # noqa: F401
+ AudioToAudioOutputElement, # noqa: F401
+ AutomaticSpeechRecognitionGenerationParameters, # noqa: F401
+ AutomaticSpeechRecognitionInput, # noqa: F401
+ AutomaticSpeechRecognitionOutput, # noqa: F401
+ AutomaticSpeechRecognitionOutputChunk, # noqa: F401
+ AutomaticSpeechRecognitionParameters, # noqa: F401
+ ChatCompletionInput, # noqa: F401
+ ChatCompletionInputMessage, # noqa: F401
+ ChatCompletionOutput, # noqa: F401
+ ChatCompletionOutputChoice, # noqa: F401
+ ChatCompletionOutputChoiceMessage, # noqa: F401
+ ChatCompletionStreamOutput, # noqa: F401
+ ChatCompletionStreamOutputChoice, # noqa: F401
+ ChatCompletionStreamOutputDelta, # noqa: F401
+ DepthEstimationInput, # noqa: F401
+ DepthEstimationOutput, # noqa: F401
+ DocumentQuestionAnsweringInput, # noqa: F401
+ DocumentQuestionAnsweringInputData, # noqa: F401
+ DocumentQuestionAnsweringOutputElement, # noqa: F401
+ DocumentQuestionAnsweringParameters, # noqa: F401
+ FeatureExtractionInput, # noqa: F401
+ FillMaskInput, # noqa: F401
+ FillMaskOutputElement, # noqa: F401
+ FillMaskParameters, # noqa: F401
+ ImageClassificationInput, # noqa: F401
+ ImageClassificationOutputElement, # noqa: F401
+ ImageClassificationParameters, # noqa: F401
+ ImageSegmentationInput, # noqa: F401
+ ImageSegmentationOutputElement, # noqa: F401
+ ImageSegmentationParameters, # noqa: F401
+ ImageToImageInput, # noqa: F401
+ ImageToImageOutput, # noqa: F401
+ ImageToImageParameters, # noqa: F401
+ ImageToImageTargetSize, # noqa: F401
+ ImageToTextGenerationParameters, # noqa: F401
+ ImageToTextInput, # noqa: F401
+ ImageToTextOutput, # noqa: F401
+ ImageToTextParameters, # noqa: F401
+ ObjectDetectionBoundingBox, # noqa: F401
+ ObjectDetectionInput, # noqa: F401
+ ObjectDetectionOutputElement, # noqa: F401
+ ObjectDetectionParameters, # noqa: F401
+ QuestionAnsweringInput, # noqa: F401
+ QuestionAnsweringInputData, # noqa: F401
+ QuestionAnsweringOutputElement, # noqa: F401
+ QuestionAnsweringParameters, # noqa: F401
+ SentenceSimilarityInput, # noqa: F401
+ SentenceSimilarityInputData, # noqa: F401
+ SummarizationGenerationParameters, # noqa: F401
+ SummarizationInput, # noqa: F401
+ SummarizationOutput, # noqa: F401
+ TableQuestionAnsweringInput, # noqa: F401
+ TableQuestionAnsweringInputData, # noqa: F401
+ TableQuestionAnsweringOutputElement, # noqa: F401
+ Text2TextGenerationInput, # noqa: F401
+ Text2TextGenerationOutput, # noqa: F401
+ Text2TextGenerationParameters, # noqa: F401
+ TextClassificationInput, # noqa: F401
+ TextClassificationOutputElement, # noqa: F401
+ TextClassificationParameters, # noqa: F401
+ TextGenerationInput, # noqa: F401
+ TextGenerationOutput, # noqa: F401
+ TextGenerationOutputDetails, # noqa: F401
+ TextGenerationOutputSequenceDetails, # noqa: F401
+ TextGenerationOutputToken, # noqa: F401
+ TextGenerationParameters, # noqa: F401
+ TextGenerationPrefillToken, # noqa: F401
+ TextGenerationStreamDetails, # noqa: F401
+ TextGenerationStreamOutput, # noqa: F401
+ TextToAudioGenerationParameters, # noqa: F401
+ TextToAudioInput, # noqa: F401
+ TextToAudioOutput, # noqa: F401
+ TextToAudioParameters, # noqa: F401
+ TextToImageInput, # noqa: F401
+ TextToImageOutput, # noqa: F401
+ TextToImageParameters, # noqa: F401
+ TextToImageTargetSize, # noqa: F401
+ TokenClassificationInput, # noqa: F401
+ TokenClassificationOutputElement, # noqa: F401
+ TokenClassificationParameters, # noqa: F401
+ TranslationGenerationParameters, # noqa: F401
+ TranslationInput, # noqa: F401
+ TranslationOutput, # noqa: F401
+ VideoClassificationInput, # noqa: F401
+ VideoClassificationOutputElement, # noqa: F401
+ VideoClassificationParameters, # noqa: F401
+ VisualQuestionAnsweringInput, # noqa: F401
+ VisualQuestionAnsweringInputData, # noqa: F401
+ VisualQuestionAnsweringOutputElement, # noqa: F401
+ VisualQuestionAnsweringParameters, # noqa: F401
+ ZeroShotClassificationInput, # noqa: F401
+ ZeroShotClassificationInputData, # noqa: F401
+ ZeroShotClassificationOutputElement, # noqa: F401
+ ZeroShotClassificationParameters, # noqa: F401
+ ZeroShotImageClassificationInput, # noqa: F401
+ ZeroShotImageClassificationInputData, # noqa: F401
+ ZeroShotImageClassificationOutputElement, # noqa: F401
+ ZeroShotImageClassificationParameters, # noqa: F401
+ ZeroShotObjectDetectionBoundingBox, # noqa: F401
+ ZeroShotObjectDetectionInput, # noqa: F401
+ ZeroShotObjectDetectionInputData, # noqa: F401
+ ZeroShotObjectDetectionOutputElement, # noqa: F401
+ )
+ from .inference_api import InferenceApi # noqa: F401
+ from .keras_mixin import (
+ KerasModelHubMixin, # noqa: F401
+ from_pretrained_keras, # noqa: F401
+ push_to_hub_keras, # noqa: F401
+ save_pretrained_keras, # noqa: F401
+ )
+ from .repocard import (
+ DatasetCard, # noqa: F401
+ ModelCard, # noqa: F401
+ RepoCard, # noqa: F401
+ SpaceCard, # noqa: F401
+ metadata_eval_result, # noqa: F401
+ metadata_load, # noqa: F401
+ metadata_save, # noqa: F401
+ metadata_update, # noqa: F401
+ )
+ from .repocard_data import (
+ CardData, # noqa: F401
+ DatasetCardData, # noqa: F401
+ EvalResult, # noqa: F401
+ ModelCardData, # noqa: F401
+ SpaceCardData, # noqa: F401
+ )
+ from .repository import Repository # noqa: F401
+ from .serialization import (
+ StateDictSplit, # noqa: F401
+ split_numpy_state_dict_into_shards, # noqa: F401
+ split_state_dict_into_shards_factory, # noqa: F401
+ split_tf_state_dict_into_shards, # noqa: F401
+ split_torch_state_dict_into_shards, # noqa: F401
+ )
+ from .utils import (
+ CachedFileInfo, # noqa: F401
+ CachedRepoInfo, # noqa: F401
+ CachedRevisionInfo, # noqa: F401
+ CacheNotFound, # noqa: F401
+ CorruptedCacheException, # noqa: F401
+ DeleteCacheStrategy, # noqa: F401
+ HFCacheInfo, # noqa: F401
+ HfFolder, # noqa: F401
+ cached_assets_path, # noqa: F401
+ configure_http_backend, # noqa: F401
+ dump_environment_info, # noqa: F401
+ get_session, # noqa: F401
+ get_token, # noqa: F401
+ logging, # noqa: F401
+ scan_cache_dir, # noqa: F401
+ )
+ from .utils.endpoint_helpers import (
+ DatasetFilter, # noqa: F401
+ ModelFilter, # noqa: F401
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a075ff78d4f6691c479bf28a529b08e92383eba2
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_api.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_api.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..77c6d50dbd64d436fd1dcd1bd419a1e8605bfcc8
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_api.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_scheduler.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_scheduler.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f9f52343d7b97aeaa36aa1ce5dba8a5da25a4af2
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_commit_scheduler.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_inference_endpoints.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_inference_endpoints.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aede36d979567034bb2088cbd708fb090eaf4407
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_inference_endpoints.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_login.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_login.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84664350d325e20addabcf11b8de7f256e16dfd6
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_login.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_multi_commits.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_multi_commits.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..10725d7e19bb3ecfc4c323a8923a91e9ba40152b
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_multi_commits.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_snapshot_download.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_snapshot_download.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1f89e071f552f3a87a8e848ab50435c659368c73
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_snapshot_download.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_space_api.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_space_api.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..837b809b7276c947d7efc0079e617cf8098b91da
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/_space_api.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/community.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/community.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..85ef03977bb1ee2bd8a5757b9ecc86c8ef27f5d3
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/community.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/constants.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/constants.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..54ccb9c86fe6fd2e1c5ea8dea5a99755045323b1
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/constants.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/errors.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/errors.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a952032c399708c9d2185375aa268d3a5317d5b5
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/errors.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/file_download.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/file_download.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6097f871165fa9a439e3f274ae07e6218b0c5716
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/file_download.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/hf_api.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/hf_api.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e847b77cf4e5b242ea7b1308f76fd871b1794ec4
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/hf_api.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/lfs.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/lfs.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f7ab2496ffcb106ea5a1e95a61e24bdcd7adac2
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/lfs.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/repocard_data.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/repocard_data.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e25a6117e030de59d22a29bc59d058358196f7db
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/__pycache__/repocard_data.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_api.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..b807106bab7c5881982502f343b5ebe111f61511
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_api.py
@@ -0,0 +1,698 @@
+"""
+Type definitions and utilities for the `create_commit` API
+"""
+
+import base64
+import io
+import os
+import warnings
+from collections import defaultdict
+from contextlib import contextmanager
+from dataclasses import dataclass, field
+from itertools import groupby
+from pathlib import Path, PurePosixPath
+from typing import TYPE_CHECKING, Any, BinaryIO, Dict, Iterable, Iterator, List, Literal, Optional, Tuple, Union
+
+from tqdm.contrib.concurrent import thread_map
+
+from huggingface_hub import get_session
+
+from .constants import ENDPOINT, HF_HUB_ENABLE_HF_TRANSFER
+from .file_download import hf_hub_url
+from .lfs import UploadInfo, lfs_upload, post_lfs_batch_info
+from .utils import (
+ EntryNotFoundError,
+ chunk_iterable,
+ hf_raise_for_status,
+ logging,
+ tqdm_stream_file,
+ validate_hf_hub_args,
+)
+from .utils import tqdm as hf_tqdm
+
+
+if TYPE_CHECKING:
+ from .hf_api import RepoFile
+
+
+logger = logging.get_logger(__name__)
+
+
+UploadMode = Literal["lfs", "regular"]
+
+# Max is 1,000 per request on the Hub for HfApi.get_paths_info
+# Otherwise we get:
+# HfHubHTTPError: 413 Client Error: Payload Too Large for url: https://huggingface.co/api/datasets/xxx (Request ID: xxx)\n\ntoo many parameters
+# See https://github.com/huggingface/huggingface_hub/issues/1503
+FETCH_LFS_BATCH_SIZE = 500
+
+
+@dataclass
+class CommitOperationDelete:
+ """
+ Data structure holding necessary info to delete a file or a folder from a repository
+ on the Hub.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
+ for a file or `"checkpoints/1fec34a/"` for a folder.
+ is_folder (`bool` or `Literal["auto"]`, *optional*)
+ Whether the Delete Operation applies to a folder or not. If "auto", the path
+ type (file or folder) is guessed automatically by looking if path ends with
+ a "/" (folder) or not (file). To explicitly set the path type, you can set
+ `is_folder=True` or `is_folder=False`.
+ """
+
+ path_in_repo: str
+ is_folder: Union[bool, Literal["auto"]] = "auto"
+
+ def __post_init__(self):
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+ if self.is_folder == "auto":
+ self.is_folder = self.path_in_repo.endswith("/")
+ if not isinstance(self.is_folder, bool):
+ raise ValueError(
+ f"Wrong value for `is_folder`. Must be one of [`True`, `False`, `'auto'`]. Got '{self.is_folder}'."
+ )
+
+
+@dataclass
+class CommitOperationCopy:
+ """
+ Data structure holding necessary info to copy a file in a repository on the Hub.
+
+ Limitations:
+ - Only LFS files can be copied. To copy a regular file, you need to download it locally and re-upload it
+ - Cross-repository copies are not supported.
+
+ Note: you can combine a [`CommitOperationCopy`] and a [`CommitOperationDelete`] to rename an LFS file on the Hub.
+
+ Args:
+ src_path_in_repo (`str`):
+ Relative filepath in the repo of the file to be copied, e.g. `"checkpoints/1fec34a/weights.bin"`.
+ path_in_repo (`str`):
+ Relative filepath in the repo where to copy the file, e.g. `"checkpoints/1fec34a/weights_copy.bin"`.
+ src_revision (`str`, *optional*):
+ The git revision of the file to be copied. Can be any valid git revision.
+ Default to the target commit revision.
+ """
+
+ src_path_in_repo: str
+ path_in_repo: str
+ src_revision: Optional[str] = None
+
+ def __post_init__(self):
+ self.src_path_in_repo = _validate_path_in_repo(self.src_path_in_repo)
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+
+@dataclass
+class CommitOperationAdd:
+ """
+ Data structure holding necessary info to upload a file to a repository on the Hub.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example: `"checkpoints/1fec34a/weights.bin"`
+ path_or_fileobj (`str`, `Path`, `bytes`, or `BinaryIO`):
+ Either:
+ - a path to a local file (as `str` or `pathlib.Path`) to upload
+ - a buffer of bytes (`bytes`) holding the content of the file to upload
+ - a "file object" (subclass of `io.BufferedIOBase`), typically obtained
+ with `open(path, "rb")`. It must support `seek()` and `tell()` methods.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is not one of `str`, `Path`, `bytes` or `io.BufferedIOBase`.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is a `str` or `Path` but not a path to an existing file.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `path_or_fileobj` is a `io.BufferedIOBase` but it doesn't support both
+ `seek()` and `tell()`.
+ """
+
+ path_in_repo: str
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO]
+ upload_info: UploadInfo = field(init=False, repr=False)
+
+ # Internal attributes
+
+ # set to "lfs" or "regular" once known
+ _upload_mode: Optional[UploadMode] = field(init=False, repr=False, default=None)
+
+ # set to True if .gitignore rules prevent the file from being uploaded as LFS
+ # (server-side check)
+ _should_ignore: Optional[bool] = field(init=False, repr=False, default=None)
+
+ # set to True once the file has been uploaded as LFS
+ _is_uploaded: bool = field(init=False, repr=False, default=False)
+
+ # set to True once the file has been committed
+ _is_committed: bool = field(init=False, repr=False, default=False)
+
+ def __post_init__(self) -> None:
+ """Validates `path_or_fileobj` and compute `upload_info`."""
+ self.path_in_repo = _validate_path_in_repo(self.path_in_repo)
+
+ # Validate `path_or_fileobj` value
+ if isinstance(self.path_or_fileobj, Path):
+ self.path_or_fileobj = str(self.path_or_fileobj)
+ if isinstance(self.path_or_fileobj, str):
+ path_or_fileobj = os.path.normpath(os.path.expanduser(self.path_or_fileobj))
+ if not os.path.isfile(path_or_fileobj):
+ raise ValueError(f"Provided path: '{path_or_fileobj}' is not a file on the local file system")
+ elif not isinstance(self.path_or_fileobj, (io.BufferedIOBase, bytes)):
+ # ^^ Inspired from: https://stackoverflow.com/questions/44584829/how-to-determine-if-file-is-opened-in-binary-or-text-mode
+ raise ValueError(
+ "path_or_fileobj must be either an instance of str, bytes or"
+ " io.BufferedIOBase. If you passed a file-like object, make sure it is"
+ " in binary mode."
+ )
+ if isinstance(self.path_or_fileobj, io.BufferedIOBase):
+ try:
+ self.path_or_fileobj.tell()
+ self.path_or_fileobj.seek(0, os.SEEK_CUR)
+ except (OSError, AttributeError) as exc:
+ raise ValueError(
+ "path_or_fileobj is a file-like object but does not implement seek() and tell()"
+ ) from exc
+
+ # Compute "upload_info" attribute
+ if isinstance(self.path_or_fileobj, str):
+ self.upload_info = UploadInfo.from_path(self.path_or_fileobj)
+ elif isinstance(self.path_or_fileobj, bytes):
+ self.upload_info = UploadInfo.from_bytes(self.path_or_fileobj)
+ else:
+ self.upload_info = UploadInfo.from_fileobj(self.path_or_fileobj)
+
+ @contextmanager
+ def as_file(self, with_tqdm: bool = False) -> Iterator[BinaryIO]:
+ """
+ A context manager that yields a file-like object allowing to read the underlying
+ data behind `path_or_fileobj`.
+
+ Args:
+ with_tqdm (`bool`, *optional*, defaults to `False`):
+ If True, iterating over the file object will display a progress bar. Only
+ works if the file-like object is a path to a file. Pure bytes and buffers
+ are not supported.
+
+ Example:
+
+ ```python
+ >>> operation = CommitOperationAdd(
+ ... path_in_repo="remote/dir/weights.h5",
+ ... path_or_fileobj="./local/weights.h5",
+ ... )
+ CommitOperationAdd(path_in_repo='remote/dir/weights.h5', path_or_fileobj='./local/weights.h5')
+
+ >>> with operation.as_file() as file:
+ ... content = file.read()
+
+ >>> with operation.as_file(with_tqdm=True) as file:
+ ... while True:
+ ... data = file.read(1024)
+ ... if not data:
+ ... break
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+
+ >>> with operation.as_file(with_tqdm=True) as file:
+ ... requests.put(..., data=file)
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+ ```
+ """
+ if isinstance(self.path_or_fileobj, str) or isinstance(self.path_or_fileobj, Path):
+ if with_tqdm:
+ with tqdm_stream_file(self.path_or_fileobj) as file:
+ yield file
+ else:
+ with open(self.path_or_fileobj, "rb") as file:
+ yield file
+ elif isinstance(self.path_or_fileobj, bytes):
+ yield io.BytesIO(self.path_or_fileobj)
+ elif isinstance(self.path_or_fileobj, io.BufferedIOBase):
+ prev_pos = self.path_or_fileobj.tell()
+ yield self.path_or_fileobj
+ self.path_or_fileobj.seek(prev_pos, io.SEEK_SET)
+
+ def b64content(self) -> bytes:
+ """
+ The base64-encoded content of `path_or_fileobj`
+
+ Returns: `bytes`
+ """
+ with self.as_file() as file:
+ return base64.b64encode(file.read())
+
+
+def _validate_path_in_repo(path_in_repo: str) -> str:
+ # Validate `path_in_repo` value to prevent a server-side issue
+ if path_in_repo.startswith("/"):
+ path_in_repo = path_in_repo[1:]
+ if path_in_repo == "." or path_in_repo == ".." or path_in_repo.startswith("../"):
+ raise ValueError(f"Invalid `path_in_repo` in CommitOperation: '{path_in_repo}'")
+ if path_in_repo.startswith("./"):
+ path_in_repo = path_in_repo[2:]
+ if any(part == ".git" for part in path_in_repo.split("/")):
+ raise ValueError(
+ "Invalid `path_in_repo` in CommitOperation: cannot update files under a '.git/' folder (path:"
+ f" '{path_in_repo}')."
+ )
+ return path_in_repo
+
+
+CommitOperation = Union[CommitOperationAdd, CommitOperationCopy, CommitOperationDelete]
+
+
+def _warn_on_overwriting_operations(operations: List[CommitOperation]) -> None:
+ """
+ Warn user when a list of operations is expected to overwrite itself in a single
+ commit.
+
+ Rules:
+ - If a filepath is updated by multiple `CommitOperationAdd` operations, a warning
+ message is triggered.
+ - If a filepath is updated at least once by a `CommitOperationAdd` and then deleted
+ by a `CommitOperationDelete`, a warning is triggered.
+ - If a `CommitOperationDelete` deletes a filepath that is then updated by a
+ `CommitOperationAdd`, no warning is triggered. This is usually useless (no need to
+ delete before upload) but can happen if a user deletes an entire folder and then
+ add new files to it.
+ """
+ nb_additions_per_path: Dict[str, int] = defaultdict(int)
+ for operation in operations:
+ path_in_repo = operation.path_in_repo
+ if isinstance(operation, CommitOperationAdd):
+ if nb_additions_per_path[path_in_repo] > 0:
+ warnings.warn(
+ "About to update multiple times the same file in the same commit:"
+ f" '{path_in_repo}'. This can cause undesired inconsistencies in"
+ " your repo."
+ )
+ nb_additions_per_path[path_in_repo] += 1
+ for parent in PurePosixPath(path_in_repo).parents:
+ # Also keep track of number of updated files per folder
+ # => warns if deleting a folder overwrite some contained files
+ nb_additions_per_path[str(parent)] += 1
+ if isinstance(operation, CommitOperationDelete):
+ if nb_additions_per_path[str(PurePosixPath(path_in_repo))] > 0:
+ if operation.is_folder:
+ warnings.warn(
+ "About to delete a folder containing files that have just been"
+ f" updated within the same commit: '{path_in_repo}'. This can"
+ " cause undesired inconsistencies in your repo."
+ )
+ else:
+ warnings.warn(
+ "About to delete a file that have just been updated within the"
+ f" same commit: '{path_in_repo}'. This can cause undesired"
+ " inconsistencies in your repo."
+ )
+
+
+@validate_hf_hub_args
+def _upload_lfs_files(
+ *,
+ additions: List[CommitOperationAdd],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ endpoint: Optional[str] = None,
+ num_threads: int = 5,
+ revision: Optional[str] = None,
+):
+ """
+ Uploads the content of `additions` to the Hub using the large file storage protocol.
+
+ Relevant external documentation:
+ - LFS Batch API: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
+
+ Args:
+ additions (`List` of `CommitOperationAdd`):
+ The files to be uploaded
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ num_threads (`int`, *optional*):
+ The number of concurrent threads to use when uploading. Defaults to 5.
+ revision (`str`, *optional*):
+ The git revision to upload to.
+
+ Raises: `RuntimeError` if an upload failed for any reason
+
+ Raises: `ValueError` if the server returns malformed responses
+
+ Raises: `requests.HTTPError` if the LFS batch endpoint returned an HTTP
+ error
+
+ """
+ # Step 1: retrieve upload instructions from the LFS batch endpoint.
+ # Upload instructions are retrieved by chunk of 256 files to avoid reaching
+ # the payload limit.
+ batch_actions: List[Dict] = []
+ for chunk in chunk_iterable(additions, chunk_size=256):
+ batch_actions_chunk, batch_errors_chunk = post_lfs_batch_info(
+ upload_infos=[op.upload_info for op in chunk],
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=endpoint,
+ headers=headers,
+ token=None, # already passed in 'headers'
+ )
+
+ # If at least 1 error, we do not retrieve information for other chunks
+ if batch_errors_chunk:
+ message = "\n".join(
+ [
+ f'Encountered error for file with OID {err.get("oid")}: `{err.get("error", {}).get("message")}'
+ for err in batch_errors_chunk
+ ]
+ )
+ raise ValueError(f"LFS batch endpoint returned errors:\n{message}")
+
+ batch_actions += batch_actions_chunk
+ oid2addop = {add_op.upload_info.sha256.hex(): add_op for add_op in additions}
+
+ # Step 2: ignore files that have already been uploaded
+ filtered_actions = []
+ for action in batch_actions:
+ if action.get("actions") is None:
+ logger.debug(
+ f"Content of file {oid2addop[action['oid']].path_in_repo} is already"
+ " present upstream - skipping upload."
+ )
+ else:
+ filtered_actions.append(action)
+
+ if len(filtered_actions) == 0:
+ logger.debug("No LFS files to upload.")
+ return
+
+ # Step 3: upload files concurrently according to these instructions
+ def _wrapped_lfs_upload(batch_action) -> None:
+ try:
+ operation = oid2addop[batch_action["oid"]]
+ lfs_upload(operation=operation, lfs_batch_action=batch_action, headers=headers, endpoint=endpoint)
+ except Exception as exc:
+ raise RuntimeError(f"Error while uploading '{operation.path_in_repo}' to the Hub.") from exc
+
+ if HF_HUB_ENABLE_HF_TRANSFER:
+ logger.debug(f"Uploading {len(filtered_actions)} LFS files to the Hub using `hf_transfer`.")
+ for action in hf_tqdm(filtered_actions):
+ _wrapped_lfs_upload(action)
+ elif len(filtered_actions) == 1:
+ logger.debug("Uploading 1 LFS file to the Hub")
+ _wrapped_lfs_upload(filtered_actions[0])
+ else:
+ logger.debug(
+ f"Uploading {len(filtered_actions)} LFS files to the Hub using up to {num_threads} threads concurrently"
+ )
+ thread_map(
+ _wrapped_lfs_upload,
+ filtered_actions,
+ desc=f"Upload {len(filtered_actions)} LFS files",
+ max_workers=num_threads,
+ tqdm_class=hf_tqdm,
+ )
+
+
+def _validate_preupload_info(preupload_info: dict):
+ files = preupload_info.get("files")
+ if not isinstance(files, list):
+ raise ValueError("preupload_info is improperly formatted")
+ for file_info in files:
+ if not (
+ isinstance(file_info, dict)
+ and isinstance(file_info.get("path"), str)
+ and isinstance(file_info.get("uploadMode"), str)
+ and (file_info["uploadMode"] in ("lfs", "regular"))
+ ):
+ raise ValueError("preupload_info is improperly formatted:")
+ return preupload_info
+
+
+@validate_hf_hub_args
+def _fetch_upload_modes(
+ additions: Iterable[CommitOperationAdd],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ revision: str,
+ endpoint: Optional[str] = None,
+ create_pr: bool = False,
+ gitignore_content: Optional[str] = None,
+) -> None:
+ """
+ Requests the Hub "preupload" endpoint to determine whether each input file should be uploaded as a regular git blob
+ or as git LFS blob. Input `additions` are mutated in-place with the upload mode.
+
+ Args:
+ additions (`Iterable` of :class:`CommitOperationAdd`):
+ Iterable of :class:`CommitOperationAdd` describing the files to
+ upload to the Hub.
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ revision (`str`):
+ The git revision to upload the files to. Can be any valid git revision.
+ gitignore_content (`str`, *optional*):
+ The content of the `.gitignore` file to know which files should be ignored. The order of priority
+ is to first check if `gitignore_content` is passed, then check if the `.gitignore` file is present
+ in the list of files to commit and finally default to the `.gitignore` file already hosted on the Hub
+ (if any).
+ Raises:
+ [`~utils.HfHubHTTPError`]
+ If the Hub API returned an error.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the Hub API response is improperly formatted.
+ """
+ endpoint = endpoint if endpoint is not None else ENDPOINT
+
+ # Fetch upload mode (LFS or regular) chunk by chunk.
+ upload_modes: Dict[str, UploadMode] = {}
+ should_ignore_info: Dict[str, bool] = {}
+
+ for chunk in chunk_iterable(additions, 256):
+ payload: Dict = {
+ "files": [
+ {
+ "path": op.path_in_repo,
+ "sample": base64.b64encode(op.upload_info.sample).decode("ascii"),
+ "size": op.upload_info.size,
+ "sha": op.upload_info.sha256.hex(),
+ }
+ for op in chunk
+ ]
+ }
+ if gitignore_content is not None:
+ payload["gitIgnore"] = gitignore_content
+
+ resp = get_session().post(
+ f"{endpoint}/api/{repo_type}s/{repo_id}/preupload/{revision}",
+ json=payload,
+ headers=headers,
+ params={"create_pr": "1"} if create_pr else None,
+ )
+ hf_raise_for_status(resp)
+ preupload_info = _validate_preupload_info(resp.json())
+ upload_modes.update(**{file["path"]: file["uploadMode"] for file in preupload_info["files"]})
+ should_ignore_info.update(**{file["path"]: file["shouldIgnore"] for file in preupload_info["files"]})
+
+ # Set upload mode for each addition operation
+ for addition in additions:
+ addition._upload_mode = upload_modes[addition.path_in_repo]
+ addition._should_ignore = should_ignore_info[addition.path_in_repo]
+
+ # Empty files cannot be uploaded as LFS (S3 would fail with a 501 Not Implemented)
+ # => empty files are uploaded as "regular" to still allow users to commit them.
+ for addition in additions:
+ if addition.upload_info.size == 0:
+ addition._upload_mode = "regular"
+
+
+@validate_hf_hub_args
+def _fetch_files_to_copy(
+ copies: Iterable[CommitOperationCopy],
+ repo_type: str,
+ repo_id: str,
+ headers: Dict[str, str],
+ revision: str,
+ endpoint: Optional[str] = None,
+) -> Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]]:
+ """
+ Fetch information about the files to copy.
+
+ For LFS files, we only need their metadata (file size and sha256) while for regular files
+ we need to download the raw content from the Hub.
+
+ Args:
+ copies (`Iterable` of :class:`CommitOperationCopy`):
+ Iterable of :class:`CommitOperationCopy` describing the files to
+ copy on the Hub.
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ headers (`Dict[str, str]`):
+ Headers to use for the request, including authorization headers and user agent.
+ revision (`str`):
+ The git revision to upload the files to. Can be any valid git revision.
+
+ Returns: `Dict[Tuple[str, Optional[str]], Union[RepoFile, bytes]]]`
+ Key is the file path and revision of the file to copy.
+ Value is the raw content as bytes (for regular files) or the file information as a RepoFile (for LFS files).
+
+ Raises:
+ [`~utils.HfHubHTTPError`]
+ If the Hub API returned an error.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the Hub API response is improperly formatted.
+ """
+ from .hf_api import HfApi, RepoFolder
+
+ hf_api = HfApi(endpoint=endpoint, headers=headers)
+ files_to_copy: Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]] = {}
+ for src_revision, operations in groupby(copies, key=lambda op: op.src_revision):
+ operations = list(operations) # type: ignore
+ paths = [op.src_path_in_repo for op in operations]
+ for offset in range(0, len(paths), FETCH_LFS_BATCH_SIZE):
+ src_repo_files = hf_api.get_paths_info(
+ repo_id=repo_id,
+ paths=paths[offset : offset + FETCH_LFS_BATCH_SIZE],
+ revision=src_revision or revision,
+ repo_type=repo_type,
+ )
+ for src_repo_file in src_repo_files:
+ if isinstance(src_repo_file, RepoFolder):
+ raise NotImplementedError("Copying a folder is not implemented.")
+ if src_repo_file.lfs:
+ files_to_copy[(src_repo_file.path, src_revision)] = src_repo_file
+ else:
+ # TODO: (optimization) download regular files to copy concurrently
+ url = hf_hub_url(
+ endpoint=endpoint,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ revision=src_revision or revision,
+ filename=src_repo_file.path,
+ )
+ response = get_session().get(url, headers=headers)
+ hf_raise_for_status(response)
+ files_to_copy[(src_repo_file.path, src_revision)] = response.content
+ for operation in operations:
+ if (operation.src_path_in_repo, src_revision) not in files_to_copy:
+ raise EntryNotFoundError(
+ f"Cannot copy {operation.src_path_in_repo} at revision "
+ f"{src_revision or revision}: file is missing on repo."
+ )
+ return files_to_copy
+
+
+def _prepare_commit_payload(
+ operations: Iterable[CommitOperation],
+ files_to_copy: Dict[Tuple[str, Optional[str]], Union["RepoFile", bytes]],
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ parent_commit: Optional[str] = None,
+) -> Iterable[Dict[str, Any]]:
+ """
+ Builds the payload to POST to the `/commit` API of the Hub.
+
+ Payload is returned as an iterator so that it can be streamed as a ndjson in the
+ POST request.
+
+ For more information, see:
+ - https://github.com/huggingface/huggingface_hub/issues/1085#issuecomment-1265208073
+ - http://ndjson.org/
+ """
+ commit_description = commit_description if commit_description is not None else ""
+
+ # 1. Send a header item with the commit metadata
+ header_value = {"summary": commit_message, "description": commit_description}
+ if parent_commit is not None:
+ header_value["parentCommit"] = parent_commit
+ yield {"key": "header", "value": header_value}
+
+ nb_ignored_files = 0
+
+ # 2. Send operations, one per line
+ for operation in operations:
+ # Skip ignored files
+ if isinstance(operation, CommitOperationAdd) and operation._should_ignore:
+ logger.debug(f"Skipping file '{operation.path_in_repo}' in commit (ignored by gitignore file).")
+ nb_ignored_files += 1
+ continue
+
+ # 2.a. Case adding a regular file
+ if isinstance(operation, CommitOperationAdd) and operation._upload_mode == "regular":
+ yield {
+ "key": "file",
+ "value": {
+ "content": operation.b64content().decode(),
+ "path": operation.path_in_repo,
+ "encoding": "base64",
+ },
+ }
+ # 2.b. Case adding an LFS file
+ elif isinstance(operation, CommitOperationAdd) and operation._upload_mode == "lfs":
+ yield {
+ "key": "lfsFile",
+ "value": {
+ "path": operation.path_in_repo,
+ "algo": "sha256",
+ "oid": operation.upload_info.sha256.hex(),
+ "size": operation.upload_info.size,
+ },
+ }
+ # 2.c. Case deleting a file or folder
+ elif isinstance(operation, CommitOperationDelete):
+ yield {
+ "key": "deletedFolder" if operation.is_folder else "deletedFile",
+ "value": {"path": operation.path_in_repo},
+ }
+ # 2.d. Case copying a file or folder
+ elif isinstance(operation, CommitOperationCopy):
+ file_to_copy = files_to_copy[(operation.src_path_in_repo, operation.src_revision)]
+ if isinstance(file_to_copy, bytes):
+ yield {
+ "key": "file",
+ "value": {
+ "content": base64.b64encode(file_to_copy).decode(),
+ "path": operation.path_in_repo,
+ "encoding": "base64",
+ },
+ }
+ elif file_to_copy.lfs:
+ yield {
+ "key": "lfsFile",
+ "value": {
+ "path": operation.path_in_repo,
+ "algo": "sha256",
+ "oid": file_to_copy.lfs.sha256,
+ },
+ }
+ else:
+ raise ValueError(
+ "Malformed files_to_copy (should be raw file content as bytes or RepoFile objects with LFS info."
+ )
+ # 2.e. Never expected to happen
+ else:
+ raise ValueError(
+ f"Unknown operation to commit. Operation: {operation}. Upload mode:"
+ f" {getattr(operation, '_upload_mode', None)}"
+ )
+
+ if nb_ignored_files > 0:
+ logger.info(f"Skipped {nb_ignored_files} file(s) in commit (ignored by gitignore file).")
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_scheduler.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_scheduler.py
new file mode 100644
index 0000000000000000000000000000000000000000..80d8dac7866a4eeef888d36cfbb974aa06a9930b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_commit_scheduler.py
@@ -0,0 +1,327 @@
+import atexit
+import logging
+import os
+import time
+from concurrent.futures import Future
+from dataclasses import dataclass
+from io import SEEK_END, SEEK_SET, BytesIO
+from pathlib import Path
+from threading import Lock, Thread
+from typing import Dict, List, Optional, Union
+
+from .hf_api import IGNORE_GIT_FOLDER_PATTERNS, CommitInfo, CommitOperationAdd, HfApi
+from .utils import filter_repo_objects
+
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass(frozen=True)
+class _FileToUpload:
+ """Temporary dataclass to store info about files to upload. Not meant to be used directly."""
+
+ local_path: Path
+ path_in_repo: str
+ size_limit: int
+ last_modified: float
+
+
+class CommitScheduler:
+ """
+ Scheduler to upload a local folder to the Hub at regular intervals (e.g. push to hub every 5 minutes).
+
+ The scheduler is started when instantiated and run indefinitely. At the end of your script, a last commit is
+ triggered. Checkout the [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#scheduled-uploads)
+ to learn more about how to use it.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to commit to.
+ folder_path (`str` or `Path`):
+ Path to the local folder to upload regularly.
+ every (`int` or `float`, *optional*):
+ The number of minutes between each commit. Defaults to 5 minutes.
+ path_in_repo (`str`, *optional*):
+ Relative path of the directory in the repo, for example: `"checkpoints/"`. Defaults to the root folder
+ of the repository.
+ repo_type (`str`, *optional*):
+ The type of the repo to commit to. Defaults to `model`.
+ revision (`str`, *optional*):
+ The revision of the repo to commit to. Defaults to `main`.
+ private (`bool`, *optional*):
+ Whether to make the repo private. Defaults to `False`. This value is ignored if the repo already exist.
+ token (`str`, *optional*):
+ The token to use to commit to the repo. Defaults to the token saved on the machine.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are uploaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not uploaded.
+ squash_history (`bool`, *optional*):
+ Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
+ useful to avoid degraded performances on the repo when it grows too large.
+ hf_api (`HfApi`, *optional*):
+ The [`HfApi`] client to use to commit to the Hub. Can be set with custom settings (user agent, token,...).
+
+ Example:
+ ```py
+ >>> from pathlib import Path
+ >>> from huggingface_hub import CommitScheduler
+
+ # Scheduler uploads every 10 minutes
+ >>> csv_path = Path("watched_folder/data.csv")
+ >>> CommitScheduler(repo_id="test_scheduler", repo_type="dataset", folder_path=csv_path.parent, every=10)
+
+ >>> with csv_path.open("a") as f:
+ ... f.write("first line")
+
+ # Some time later (...)
+ >>> with csv_path.open("a") as f:
+ ... f.write("second line")
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ every: Union[int, float] = 5,
+ path_in_repo: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ private: bool = False,
+ token: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ squash_history: bool = False,
+ hf_api: Optional["HfApi"] = None,
+ ) -> None:
+ self.api = hf_api or HfApi(token=token)
+
+ # Folder
+ self.folder_path = Path(folder_path).expanduser().resolve()
+ self.path_in_repo = path_in_repo or ""
+ self.allow_patterns = allow_patterns
+
+ if ignore_patterns is None:
+ ignore_patterns = []
+ elif isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+ self.ignore_patterns = ignore_patterns + IGNORE_GIT_FOLDER_PATTERNS
+
+ if self.folder_path.is_file():
+ raise ValueError(f"'folder_path' must be a directory, not a file: '{self.folder_path}'.")
+ self.folder_path.mkdir(parents=True, exist_ok=True)
+
+ # Repository
+ repo_url = self.api.create_repo(repo_id=repo_id, private=private, repo_type=repo_type, exist_ok=True)
+ self.repo_id = repo_url.repo_id
+ self.repo_type = repo_type
+ self.revision = revision
+ self.token = token
+
+ # Keep track of already uploaded files
+ self.last_uploaded: Dict[Path, float] = {} # key is local path, value is timestamp
+
+ # Scheduler
+ if not every > 0:
+ raise ValueError(f"'every' must be a positive integer, not '{every}'.")
+ self.lock = Lock()
+ self.every = every
+ self.squash_history = squash_history
+
+ logger.info(f"Scheduled job to push '{self.folder_path}' to '{self.repo_id}' every {self.every} minutes.")
+ self._scheduler_thread = Thread(target=self._run_scheduler, daemon=True)
+ self._scheduler_thread.start()
+ atexit.register(self._push_to_hub)
+
+ self.__stopped = False
+
+ def stop(self) -> None:
+ """Stop the scheduler.
+
+ A stopped scheduler cannot be restarted. Mostly for tests purposes.
+ """
+ self.__stopped = True
+
+ def _run_scheduler(self) -> None:
+ """Dumb thread waiting between each scheduled push to Hub."""
+ while True:
+ self.last_future = self.trigger()
+ time.sleep(self.every * 60)
+ if self.__stopped:
+ break
+
+ def trigger(self) -> Future:
+ """Trigger a `push_to_hub` and return a future.
+
+ This method is automatically called every `every` minutes. You can also call it manually to trigger a commit
+ immediately, without waiting for the next scheduled commit.
+ """
+ return self.api.run_as_future(self._push_to_hub)
+
+ def _push_to_hub(self) -> Optional[CommitInfo]:
+ if self.__stopped: # If stopped, already scheduled commits are ignored
+ return None
+
+ logger.info("(Background) scheduled commit triggered.")
+ try:
+ value = self.push_to_hub()
+ if self.squash_history:
+ logger.info("(Background) squashing repo history.")
+ self.api.super_squash_history(repo_id=self.repo_id, repo_type=self.repo_type, branch=self.revision)
+ return value
+ except Exception as e:
+ logger.error(f"Error while pushing to Hub: {e}") # Depending on the setup, error might be silenced
+ raise
+
+ def push_to_hub(self) -> Optional[CommitInfo]:
+ """
+ Push folder to the Hub and return the commit info.
+
+
+
+ This method is not meant to be called directly. It is run in the background by the scheduler, respecting a
+ queue mechanism to avoid concurrent commits. Making a direct call to the method might lead to concurrency
+ issues.
+
+
+
+ The default behavior of `push_to_hub` is to assume an append-only folder. It lists all files in the folder and
+ uploads only changed files. If no changes are found, the method returns without committing anything. If you want
+ to change this behavior, you can inherit from [`CommitScheduler`] and override this method. This can be useful
+ for example to compress data together in a single file before committing. For more details and examples, check
+ out our [integration guide](https://huggingface.co/docs/huggingface_hub/main/en/guides/upload#scheduled-uploads).
+ """
+ # Check files to upload (with lock)
+ with self.lock:
+ logger.debug("Listing files to upload for scheduled commit.")
+
+ # List files from folder (taken from `_prepare_upload_folder_additions`)
+ relpath_to_abspath = {
+ path.relative_to(self.folder_path).as_posix(): path
+ for path in sorted(self.folder_path.glob("**/*")) # sorted to be deterministic
+ if path.is_file()
+ }
+ prefix = f"{self.path_in_repo.strip('/')}/" if self.path_in_repo else ""
+
+ # Filter with pattern + filter out unchanged files + retrieve current file size
+ files_to_upload: List[_FileToUpload] = []
+ for relpath in filter_repo_objects(
+ relpath_to_abspath.keys(), allow_patterns=self.allow_patterns, ignore_patterns=self.ignore_patterns
+ ):
+ local_path = relpath_to_abspath[relpath]
+ stat = local_path.stat()
+ if self.last_uploaded.get(local_path) is None or self.last_uploaded[local_path] != stat.st_mtime:
+ files_to_upload.append(
+ _FileToUpload(
+ local_path=local_path,
+ path_in_repo=prefix + relpath,
+ size_limit=stat.st_size,
+ last_modified=stat.st_mtime,
+ )
+ )
+
+ # Return if nothing to upload
+ if len(files_to_upload) == 0:
+ logger.debug("Dropping schedule commit: no changed file to upload.")
+ return None
+
+ # Convert `_FileToUpload` as `CommitOperationAdd` (=> compute file shas + limit to file size)
+ logger.debug("Removing unchanged files since previous scheduled commit.")
+ add_operations = [
+ CommitOperationAdd(
+ # Cap the file to its current size, even if the user append data to it while a scheduled commit is happening
+ path_or_fileobj=PartialFileIO(file_to_upload.local_path, size_limit=file_to_upload.size_limit),
+ path_in_repo=file_to_upload.path_in_repo,
+ )
+ for file_to_upload in files_to_upload
+ ]
+
+ # Upload files (append mode expected - no need for lock)
+ logger.debug("Uploading files for scheduled commit.")
+ commit_info = self.api.create_commit(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ operations=add_operations,
+ commit_message="Scheduled Commit",
+ revision=self.revision,
+ )
+
+ # Successful commit: keep track of the latest "last_modified" for each file
+ for file in files_to_upload:
+ self.last_uploaded[file.local_path] = file.last_modified
+ return commit_info
+
+
+class PartialFileIO(BytesIO):
+ """A file-like object that reads only the first part of a file.
+
+ Useful to upload a file to the Hub when the user might still be appending data to it. Only the first part of the
+ file is uploaded (i.e. the part that was available when the filesystem was first scanned).
+
+ In practice, only used internally by the CommitScheduler to regularly push a folder to the Hub with minimal
+ disturbance for the user. The object is passed to `CommitOperationAdd`.
+
+ Only supports `read`, `tell` and `seek` methods.
+
+ Args:
+ file_path (`str` or `Path`):
+ Path to the file to read.
+ size_limit (`int`):
+ The maximum number of bytes to read from the file. If the file is larger than this, only the first part
+ will be read (and uploaded).
+ """
+
+ def __init__(self, file_path: Union[str, Path], size_limit: int) -> None:
+ self._file_path = Path(file_path)
+ self._file = self._file_path.open("rb")
+ self._size_limit = min(size_limit, os.fstat(self._file.fileno()).st_size)
+
+ def __del__(self) -> None:
+ self._file.close()
+ return super().__del__()
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __len__(self) -> int:
+ return self._size_limit
+
+ def __getattribute__(self, name: str):
+ if name.startswith("_") or name in ("read", "tell", "seek"): # only 3 public methods supported
+ return super().__getattribute__(name)
+ raise NotImplementedError(f"PartialFileIO does not support '{name}'.")
+
+ def tell(self) -> int:
+ """Return the current file position."""
+ return self._file.tell()
+
+ def seek(self, __offset: int, __whence: int = SEEK_SET) -> int:
+ """Change the stream position to the given offset.
+
+ Behavior is the same as a regular file, except that the position is capped to the size limit.
+ """
+ if __whence == SEEK_END:
+ # SEEK_END => set from the truncated end
+ __offset = len(self) + __offset
+ __whence = SEEK_SET
+
+ pos = self._file.seek(__offset, __whence)
+ if pos > self._size_limit:
+ return self._file.seek(self._size_limit)
+ return pos
+
+ def read(self, __size: Optional[int] = -1) -> bytes:
+ """Read at most `__size` bytes from the file.
+
+ Behavior is the same as a regular file, except that it is capped to the size limit.
+ """
+ current = self._file.tell()
+ if __size is None or __size < 0:
+ # Read until file limit
+ truncated_size = self._size_limit - current
+ else:
+ # Read until file limit or __size
+ truncated_size = min(__size, self._size_limit - current)
+ return self._file.read(truncated_size)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_inference_endpoints.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_inference_endpoints.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b81a796c6379c9b6bf40e0933933ceb8ba6c739
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_inference_endpoints.py
@@ -0,0 +1,384 @@
+import time
+from dataclasses import dataclass, field
+from datetime import datetime
+from enum import Enum
+from typing import TYPE_CHECKING, Dict, Optional, Union
+
+from .inference._client import InferenceClient
+from .inference._generated._async_client import AsyncInferenceClient
+from .utils import logging, parse_datetime
+
+
+if TYPE_CHECKING:
+ from .hf_api import HfApi
+
+
+logger = logging.get_logger(__name__)
+
+
+class InferenceEndpointError(Exception):
+ """Generic exception when dealing with Inference Endpoints."""
+
+
+class InferenceEndpointTimeoutError(InferenceEndpointError, TimeoutError):
+ """Exception for timeouts while waiting for Inference Endpoint."""
+
+
+class InferenceEndpointStatus(str, Enum):
+ PENDING = "pending"
+ INITIALIZING = "initializing"
+ UPDATING = "updating"
+ UPDATE_FAILED = "updateFailed"
+ RUNNING = "running"
+ PAUSED = "paused"
+ FAILED = "failed"
+ SCALED_TO_ZERO = "scaledToZero"
+
+
+class InferenceEndpointType(str, Enum):
+ PUBlIC = "public"
+ PROTECTED = "protected"
+ PRIVATE = "private"
+
+
+@dataclass
+class InferenceEndpoint:
+ """
+ Contains information about a deployed Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The unique name of the Inference Endpoint.
+ namespace (`str`):
+ The namespace where the Inference Endpoint is located.
+ repository (`str`):
+ The name of the model repository deployed on this Inference Endpoint.
+ status ([`InferenceEndpointStatus`]):
+ The current status of the Inference Endpoint.
+ url (`str`, *optional*):
+ The URL of the Inference Endpoint, if available. Only a deployed Inference Endpoint will have a URL.
+ framework (`str`):
+ The machine learning framework used for the model.
+ revision (`str`):
+ The specific model revision deployed on the Inference Endpoint.
+ task (`str`):
+ The task associated with the deployed model.
+ created_at (`datetime.datetime`):
+ The timestamp when the Inference Endpoint was created.
+ updated_at (`datetime.datetime`):
+ The timestamp of the last update of the Inference Endpoint.
+ type ([`InferenceEndpointType`]):
+ The type of the Inference Endpoint (public, protected, private).
+ raw (`Dict`):
+ The raw dictionary data returned from the API.
+ token (`str` or `bool`, *optional*):
+ Authentication token for the Inference Endpoint, if set when requesting the API. Will default to the
+ locally saved token if not provided. Pass `token=False` if you don't want to send your token to the server.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import get_inference_endpoint
+ >>> endpoint = get_inference_endpoint("my-text-to-image")
+ >>> endpoint
+ InferenceEndpoint(name='my-text-to-image', ...)
+
+ # Get status
+ >>> endpoint.status
+ 'running'
+ >>> endpoint.url
+ 'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud'
+
+ # Run inference
+ >>> endpoint.client.text_to_image(...)
+
+ # Pause endpoint to save $$$
+ >>> endpoint.pause()
+
+ # ...
+ # Resume and wait for deployment
+ >>> endpoint.resume()
+ >>> endpoint.wait()
+ >>> endpoint.client.text_to_image(...)
+ ```
+ """
+
+ # Field in __repr__
+ name: str = field(init=False)
+ namespace: str
+ repository: str = field(init=False)
+ status: InferenceEndpointStatus = field(init=False)
+ url: Optional[str] = field(init=False)
+
+ # Other fields
+ framework: str = field(repr=False, init=False)
+ revision: str = field(repr=False, init=False)
+ task: str = field(repr=False, init=False)
+ created_at: datetime = field(repr=False, init=False)
+ updated_at: datetime = field(repr=False, init=False)
+ type: InferenceEndpointType = field(repr=False, init=False)
+
+ # Raw dict from the API
+ raw: Dict = field(repr=False)
+
+ # Internal fields
+ _token: Union[str, bool, None] = field(repr=False, compare=False)
+ _api: "HfApi" = field(repr=False, compare=False)
+
+ @classmethod
+ def from_raw(
+ cls, raw: Dict, namespace: str, token: Union[str, bool, None] = None, api: Optional["HfApi"] = None
+ ) -> "InferenceEndpoint":
+ """Initialize object from raw dictionary."""
+ if api is None:
+ from .hf_api import HfApi
+
+ api = HfApi()
+ if token is None:
+ token = api.token
+
+ # All other fields are populated in __post_init__
+ return cls(raw=raw, namespace=namespace, _token=token, _api=api)
+
+ def __post_init__(self) -> None:
+ """Populate fields from raw dictionary."""
+ self._populate_from_raw()
+
+ @property
+ def client(self) -> InferenceClient:
+ """Returns a client to make predictions on this Inference Endpoint.
+
+ Returns:
+ [`InferenceClient`]: an inference client pointing to the deployed endpoint.
+
+ Raises:
+ [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
+ """
+ if self.url is None:
+ raise InferenceEndpointError(
+ "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
+ "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
+ )
+ return InferenceClient(model=self.url, token=self._token)
+
+ @property
+ def async_client(self) -> AsyncInferenceClient:
+ """Returns a client to make predictions on this Inference Endpoint.
+
+ Returns:
+ [`AsyncInferenceClient`]: an asyncio-compatible inference client pointing to the deployed endpoint.
+
+ Raises:
+ [`InferenceEndpointError`]: If the Inference Endpoint is not yet deployed.
+ """
+ if self.url is None:
+ raise InferenceEndpointError(
+ "Cannot create a client for this Inference Endpoint as it is not yet deployed. "
+ "Please wait for the Inference Endpoint to be deployed using `endpoint.wait()` and try again."
+ )
+ return AsyncInferenceClient(model=self.url, token=self._token)
+
+ def wait(self, timeout: Optional[int] = None, refresh_every: int = 5) -> "InferenceEndpoint":
+ """Wait for the Inference Endpoint to be deployed.
+
+ Information from the server will be fetched every 1s. If the Inference Endpoint is not deployed after `timeout`
+ seconds, a [`InferenceEndpointTimeoutError`] will be raised. The [`InferenceEndpoint`] will be mutated in place with the latest
+ data.
+
+ Args:
+ timeout (`int`, *optional*):
+ The maximum time to wait for the Inference Endpoint to be deployed, in seconds. If `None`, will wait
+ indefinitely.
+ refresh_every (`int`, *optional*):
+ The time to wait between each fetch of the Inference Endpoint status, in seconds. Defaults to 5s.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+
+ Raises:
+ [`InferenceEndpointError`]
+ If the Inference Endpoint ended up in a failed state.
+ [`InferenceEndpointTimeoutError`]
+ If the Inference Endpoint is not deployed after `timeout` seconds.
+ """
+ if self.url is not None: # Means the endpoint is deployed
+ logger.info("Inference Endpoint is ready to be used.")
+ return self
+
+ if timeout is not None and timeout < 0:
+ raise ValueError("`timeout` cannot be negative.")
+ if refresh_every <= 0:
+ raise ValueError("`refresh_every` must be positive.")
+
+ start = time.time()
+ while True:
+ self.fetch()
+ if self.url is not None: # Means the endpoint is deployed
+ logger.info("Inference Endpoint is ready to be used.")
+ return self
+ if self.status == InferenceEndpointStatus.FAILED:
+ raise InferenceEndpointError(
+ f"Inference Endpoint {self.name} failed to deploy. Please check the logs for more information."
+ )
+ if timeout is not None:
+ if time.time() - start > timeout:
+ raise InferenceEndpointTimeoutError("Timeout while waiting for Inference Endpoint to be deployed.")
+ logger.info(f"Inference Endpoint is not deployed yet ({self.status}). Waiting {refresh_every}s...")
+ time.sleep(refresh_every)
+
+ def fetch(self) -> "InferenceEndpoint":
+ """Fetch latest information about the Inference Endpoint.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.get_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def update(
+ self,
+ *,
+ # Compute update
+ accelerator: Optional[str] = None,
+ instance_size: Optional[str] = None,
+ instance_type: Optional[str] = None,
+ min_replica: Optional[int] = None,
+ max_replica: Optional[int] = None,
+ # Model update
+ repository: Optional[str] = None,
+ framework: Optional[str] = None,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ ) -> "InferenceEndpoint":
+ """Update the Inference Endpoint.
+
+ This method allows the update of either the compute configuration, the deployed model, or both. All arguments are
+ optional but at least one must be provided.
+
+ This is an alias for [`HfApi.update_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Args:
+ accelerator (`str`, *optional*):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`, *optional*):
+ The size or type of the instance to be used for hosting the model (e.g. `"large"`).
+ instance_type (`str`, *optional*):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"c6i"`).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint.
+
+ repository (`str`, *optional*):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`, *optional*):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ # Make API call
+ obj = self._api.update_inference_endpoint(
+ name=self.name,
+ namespace=self.namespace,
+ accelerator=accelerator,
+ instance_size=instance_size,
+ instance_type=instance_type,
+ min_replica=min_replica,
+ max_replica=max_replica,
+ repository=repository,
+ framework=framework,
+ revision=revision,
+ task=task,
+ token=self._token, # type: ignore [arg-type]
+ )
+
+ # Mutate current object
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def pause(self) -> "InferenceEndpoint":
+ """Pause the Inference Endpoint.
+
+ A paused Inference Endpoint will not be charged. It can be resumed at any time using [`InferenceEndpoint.resume`].
+ This is different than scaling the Inference Endpoint to zero with [`InferenceEndpoint.scale_to_zero`], which
+ would be automatically restarted when a request is made to it.
+
+ This is an alias for [`HfApi.pause_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.pause_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def resume(self) -> "InferenceEndpoint":
+ """Resume the Inference Endpoint.
+
+ This is an alias for [`HfApi.resume_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.resume_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def scale_to_zero(self) -> "InferenceEndpoint":
+ """Scale Inference Endpoint to zero.
+
+ An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a
+ cold start delay. This is different than pausing the Inference Endpoint with [`InferenceEndpoint.pause`], which
+ would require a manual resume with [`InferenceEndpoint.resume`].
+
+ This is an alias for [`HfApi.scale_to_zero_inference_endpoint`]. The current object is mutated in place with the
+ latest data from the server.
+
+ Returns:
+ [`InferenceEndpoint`]: the same Inference Endpoint, mutated in place with the latest data.
+ """
+ obj = self._api.scale_to_zero_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+ self.raw = obj.raw
+ self._populate_from_raw()
+ return self
+
+ def delete(self) -> None:
+ """Delete the Inference Endpoint.
+
+ This operation is not reversible. If you don't want to be charged for an Inference Endpoint, it is preferable
+ to pause it with [`InferenceEndpoint.pause`] or scale it to zero with [`InferenceEndpoint.scale_to_zero`].
+
+ This is an alias for [`HfApi.delete_inference_endpoint`].
+ """
+ self._api.delete_inference_endpoint(name=self.name, namespace=self.namespace, token=self._token) # type: ignore [arg-type]
+
+ def _populate_from_raw(self) -> None:
+ """Populate fields from raw dictionary.
+
+ Called in __post_init__ + each time the Inference Endpoint is updated.
+ """
+ # Repr fields
+ self.name = self.raw["name"]
+ self.repository = self.raw["model"]["repository"]
+ self.status = self.raw["status"]["state"]
+ self.url = self.raw["status"].get("url")
+
+ # Other fields
+ self.framework = self.raw["model"]["framework"]
+ self.revision = self.raw["model"]["revision"]
+ self.task = self.raw["model"]["task"]
+ self.created_at = parse_datetime(self.raw["status"]["createdAt"])
+ self.updated_at = parse_datetime(self.raw["status"]["updatedAt"])
+ self.type = self.raw["type"]
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_login.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_login.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f4fa02535cb9b91ff3dec01c4878850317b3895
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_login.py
@@ -0,0 +1,396 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains methods to login to the Hub."""
+
+import os
+import subprocess
+from functools import partial
+from getpass import getpass
+from pathlib import Path
+from typing import Optional
+
+from . import constants
+from .commands._cli_utils import ANSI
+from .utils import (
+ capture_output,
+ get_token,
+ is_google_colab,
+ is_notebook,
+ list_credential_helpers,
+ logging,
+ run_subprocess,
+ set_git_credential,
+ unset_git_credential,
+)
+from .utils._token import _get_token_from_environment, _get_token_from_google_colab
+
+
+logger = logging.get_logger(__name__)
+
+_HF_LOGO_ASCII = """
+ _| _| _| _| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _|_|_|_| _|_| _|_|_| _|_|_|_|
+ _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
+ _|_|_|_| _| _| _| _|_| _| _|_| _| _| _| _| _| _|_| _|_|_| _|_|_|_| _| _|_|_|
+ _| _| _| _| _| _| _| _| _| _| _|_| _| _| _| _| _| _| _|
+ _| _| _|_| _|_|_| _|_|_| _|_|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_|_|
+"""
+
+
+def login(
+ token: Optional[str] = None,
+ add_to_git_credential: bool = False,
+ new_session: bool = True,
+ write_permission: bool = False,
+) -> None:
+ """Login the machine to access the Hub.
+
+ The `token` is persisted in cache and set as a git credential. Once done, the machine
+ is logged in and the access token will be available across all `huggingface_hub`
+ components. If `token` is not provided, it will be prompted to the user either with
+ a widget (in a notebook) or via the terminal.
+
+ To login from outside of a script, one can also use `huggingface-cli login` which is
+ a cli command that wraps [`login`].
+
+
+
+ [`login`] is a drop-in replacement method for [`notebook_login`] as it wraps and
+ extends its capabilities.
+
+
+
+
+
+ When the token is not passed, [`login`] will automatically detect if the script runs
+ in a notebook or not. However, this detection might not be accurate due to the
+ variety of notebooks that exists nowadays. If that is the case, you can always force
+ the UI by using [`notebook_login`] or [`interpreter_login`].
+
+
+
+ Args:
+ token (`str`, *optional*):
+ User access token to generate from https://huggingface.co/settings/token.
+ add_to_git_credential (`bool`, defaults to `False`):
+ If `True`, token will be set as git credential. If no git credential helper
+ is configured, a warning will be displayed to the user. If `token` is `None`,
+ the value of `add_to_git_credential` is ignored and will be prompted again
+ to the end user.
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`, defaults to `False`):
+ If `True`, requires a token with write permission.
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If an organization token is passed. Only personal account tokens are valid
+ to login.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If token is invalid.
+ [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ If running in a notebook but `ipywidgets` is not installed.
+ """
+ if token is not None:
+ if not add_to_git_credential:
+ print(
+ "Token has not been saved to git credential helper. Pass"
+ " `add_to_git_credential=True` if you want to set the git"
+ " credential as well."
+ )
+ _login(token, add_to_git_credential=add_to_git_credential, write_permission=write_permission)
+ elif is_notebook():
+ notebook_login(new_session=new_session, write_permission=write_permission)
+ else:
+ interpreter_login(new_session=new_session, write_permission=write_permission)
+
+
+def logout() -> None:
+ """Logout the machine from the Hub.
+
+ Token is deleted from the machine and removed from git credential.
+ """
+ if get_token() is None:
+ print("Not logged in!")
+ return
+
+ # Delete token from git credentials
+ unset_git_credential()
+
+ # Delete token file
+ try:
+ Path(constants.HF_TOKEN_PATH).unlink()
+ except FileNotFoundError:
+ pass
+
+ # Check if still logged in
+ if _get_token_from_google_colab() is not None:
+ raise EnvironmentError(
+ "You are automatically logged in using a Google Colab secret.\n"
+ "To log out, you must unset the `HF_TOKEN` secret in your Colab settings."
+ )
+ if _get_token_from_environment() is not None:
+ raise EnvironmentError(
+ "Token has been deleted from your machine but you are still logged in.\n"
+ "To log out, you must clear out both `HF_TOKEN` and `HUGGING_FACE_HUB_TOKEN` environment variables."
+ )
+
+ print("Successfully logged out.")
+
+
+###
+# Interpreter-based login (text)
+###
+
+
+def interpreter_login(new_session: bool = True, write_permission: bool = False) -> None:
+ """
+ Displays a prompt to login to the HF website and store the token.
+
+ This is equivalent to [`login`] without passing a token when not run in a notebook.
+ [`interpreter_login`] is useful if you want to force the use of the terminal prompt
+ instead of a notebook widget.
+
+ For more details, see [`login`].
+
+ Args:
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`, defaults to `False`):
+ If `True`, requires a token with write permission.
+
+ """
+ if not new_session and _current_token_okay(write_permission=write_permission):
+ print("User is already logged in.")
+ return
+
+ from .commands.delete_cache import _ask_for_confirmation_no_tui
+
+ print(_HF_LOGO_ASCII)
+ if get_token() is not None:
+ print(
+ " A token is already saved on your machine. Run `huggingface-cli"
+ " whoami` to get more information or `huggingface-cli logout` if you want"
+ " to log out."
+ )
+ print(" Setting a new token will erase the existing one.")
+
+ print(" To login, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens .")
+ if os.name == "nt":
+ print("Token can be pasted using 'Right-Click'.")
+ token = getpass("Enter your token (input will not be visible): ")
+ add_to_git_credential = _ask_for_confirmation_no_tui("Add token as git credential?")
+
+ _login(token=token, add_to_git_credential=add_to_git_credential, write_permission=write_permission)
+
+
+###
+# Notebook-based login (widget)
+###
+
+NOTEBOOK_LOGIN_PASSWORD_HTML = """
Immediately click login after typing your password or
+it might be stored in plain text in this notebook file. """
+
+
+NOTEBOOK_LOGIN_TOKEN_HTML_START = """
Copy a token from your Hugging Face
+tokens page and paste it below.
Immediately click login after copying
+your token or it might be stored in plain text in this notebook file. """
+
+
+NOTEBOOK_LOGIN_TOKEN_HTML_END = """
+Pro Tip: If you don't already have one, you can create a dedicated
+'notebooks' token with 'write' access, that you can then easily reuse for all
+notebooks. """
+
+
+def notebook_login(new_session: bool = True, write_permission: bool = False) -> None:
+ """
+ Displays a widget to login to the HF website and store the token.
+
+ This is equivalent to [`login`] without passing a token when run in a notebook.
+ [`notebook_login`] is useful if you want to force the use of the notebook widget
+ instead of a prompt in the terminal.
+
+ For more details, see [`login`].
+
+ Args:
+ new_session (`bool`, defaults to `True`):
+ If `True`, will request a token even if one is already saved on the machine.
+ write_permission (`bool`, defaults to `False`):
+ If `True`, requires a token with write permission.
+ """
+ try:
+ import ipywidgets.widgets as widgets # type: ignore
+ from IPython.display import display # type: ignore
+ except ImportError:
+ raise ImportError(
+ "The `notebook_login` function can only be used in a notebook (Jupyter or"
+ " Colab) and you need the `ipywidgets` module: `pip install ipywidgets`."
+ )
+ if not new_session and _current_token_okay(write_permission=write_permission):
+ print("User is already logged in.")
+ return
+
+ box_layout = widgets.Layout(display="flex", flex_flow="column", align_items="center", width="50%")
+
+ token_widget = widgets.Password(description="Token:")
+ git_checkbox_widget = widgets.Checkbox(value=True, description="Add token as git credential?")
+ token_finish_button = widgets.Button(description="Login")
+
+ login_token_widget = widgets.VBox(
+ [
+ widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_START),
+ token_widget,
+ git_checkbox_widget,
+ token_finish_button,
+ widgets.HTML(NOTEBOOK_LOGIN_TOKEN_HTML_END),
+ ],
+ layout=box_layout,
+ )
+ display(login_token_widget)
+
+ # On click events
+ def login_token_event(t, write_permission: bool = False):
+ """
+ Event handler for the login button.
+
+ Args:
+ write_permission (`bool`, defaults to `False`):
+ If `True`, requires a token with write permission.
+ """
+ token = token_widget.value
+ add_to_git_credential = git_checkbox_widget.value
+ # Erase token and clear value to make sure it's not saved in the notebook.
+ token_widget.value = ""
+ # Hide inputs
+ login_token_widget.children = [widgets.Label("Connecting...")]
+ try:
+ with capture_output() as captured:
+ _login(token, add_to_git_credential=add_to_git_credential, write_permission=write_permission)
+ message = captured.getvalue()
+ except Exception as error:
+ message = str(error)
+ # Print result (success message or error)
+ login_token_widget.children = [widgets.Label(line) for line in message.split("\n") if line.strip()]
+
+ token_finish_button.on_click(partial(login_token_event, write_permission=write_permission))
+
+
+###
+# Login private helpers
+###
+
+
+def _login(token: str, add_to_git_credential: bool, write_permission: bool = False) -> None:
+ from .hf_api import get_token_permission # avoid circular import
+
+ if token.startswith("api_org"):
+ raise ValueError("You must use your personal account token, not an organization token.")
+
+ permission = get_token_permission(token)
+ if permission is None:
+ raise ValueError("Invalid token passed!")
+ elif write_permission and permission != "write":
+ raise ValueError(
+ "Token is valid but is 'read-only' and a 'write' token is required.\nPlease provide a new token with"
+ " correct permission."
+ )
+ print(f"Token is valid (permission: {permission}).")
+
+ if add_to_git_credential:
+ if _is_git_credential_helper_configured():
+ set_git_credential(token)
+ print(
+ "Your token has been saved in your configured git credential helpers"
+ + f" ({','.join(list_credential_helpers())})."
+ )
+ else:
+ print("Token has not been saved to git credential helper.")
+
+ # Save token
+ path = Path(constants.HF_TOKEN_PATH)
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(token)
+ print(f"Your token has been saved to {constants.HF_TOKEN_PATH}")
+ print("Login successful")
+
+
+def _current_token_okay(write_permission: bool = False):
+ """Check if the current token is valid.
+
+ Args:
+ write_permission (`bool`, defaults to `False`):
+ If `True`, requires a token with write permission.
+
+ Returns:
+ `bool`: `True` if the current token is valid, `False` otherwise.
+ """
+ from .hf_api import get_token_permission # avoid circular import
+
+ permission = get_token_permission()
+ if permission is None or (write_permission and permission != "write"):
+ return False
+ return True
+
+
+def _is_git_credential_helper_configured() -> bool:
+ """Check if a git credential helper is configured.
+
+ Warns user if not the case (except for Google Colab where "store" is set by default
+ by `huggingface_hub`).
+ """
+ helpers = list_credential_helpers()
+ if len(helpers) > 0:
+ return True # Do not warn: at least 1 helper is set
+
+ # Only in Google Colab to avoid the warning message
+ # See https://github.com/huggingface/huggingface_hub/issues/1043#issuecomment-1247010710
+ if is_google_colab():
+ _set_store_as_git_credential_helper_globally()
+ return True # Do not warn: "store" is used by default in Google Colab
+
+ # Otherwise, warn user
+ print(
+ ANSI.red(
+ "Cannot authenticate through git-credential as no helper is defined on your"
+ " machine.\nYou might have to re-authenticate when pushing to the Hugging"
+ " Face Hub.\nRun the following command in your terminal in case you want to"
+ " set the 'store' credential helper as default.\n\ngit config --global"
+ " credential.helper store\n\nRead"
+ " https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage for more"
+ " details."
+ )
+ )
+ return False
+
+
+def _set_store_as_git_credential_helper_globally() -> None:
+ """Set globally the credential.helper to `store`.
+
+ To be used only in Google Colab as we assume the user doesn't care about the git
+ credential config. It is the only particular case where we don't want to display the
+ warning message in [`notebook_login()`].
+
+ Related:
+ - https://github.com/huggingface/huggingface_hub/issues/1043
+ - https://github.com/huggingface/huggingface_hub/issues/1051
+ - https://git-scm.com/docs/git-credential-store
+ """
+ try:
+ run_subprocess("git config --global credential.helper store")
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_multi_commits.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_multi_commits.py
new file mode 100644
index 0000000000000000000000000000000000000000..57c911bf0408081f18213db54e2ef38c819267bf
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_multi_commits.py
@@ -0,0 +1,306 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to multi-commits (i.e. push changes iteratively on a PR)."""
+
+import re
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Union
+
+from ._commit_api import CommitOperationAdd, CommitOperationDelete
+from .community import DiscussionWithDetails
+from .utils import experimental
+from .utils._cache_manager import _format_size
+from .utils.insecure_hashlib import sha256
+
+
+if TYPE_CHECKING:
+ from .hf_api import HfApi
+
+
+class MultiCommitException(Exception):
+ """Base exception for any exception happening while doing a multi-commit."""
+
+
+MULTI_COMMIT_PR_DESCRIPTION_TEMPLATE = """
+## {commit_message}
+
+{commit_description}
+
+**Multi commit ID:** {multi_commit_id}
+
+Scheduled commits:
+
+{multi_commit_strategy}
+
+_This is a PR opened using the `huggingface_hub` library in the context of a multi-commit. PR can be commented as a usual PR. However, please be aware that manually updating the PR description, changing the PR status, or pushing new commits, is not recommended as it might corrupt the commit process. Learn more about multi-commits [in this guide](https://huggingface.co/docs/huggingface_hub/main/guides/upload)._
+"""
+
+MULTI_COMMIT_PR_COMPLETION_COMMENT_TEMPLATE = """
+Multi-commit is now completed! You can ping the repo owner to review the changes. This PR can now be commented or modified without risking to corrupt it.
+
+_This is a comment posted using the `huggingface_hub` library in the context of a multi-commit. Learn more about multi-commits [in this guide](https://huggingface.co/docs/huggingface_hub/main/guides/upload)._
+"""
+
+MULTI_COMMIT_PR_CLOSING_COMMENT_TEMPLATE = """
+`create_pr=False` has been passed so PR is automatically merged.
+
+_This is a comment posted using the `huggingface_hub` library in the context of a multi-commit. Learn more about multi-commits [in this guide](https://huggingface.co/docs/huggingface_hub/main/guides/upload)._
+"""
+
+MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_NO_CHANGES_TEMPLATE = """
+Cannot merge Pull Requests as no changes are associated. This PR will be closed automatically.
+
+_This is a comment posted using the `huggingface_hub` library in the context of a multi-commit. Learn more about multi-commits [in this guide](https://huggingface.co/docs/huggingface_hub/main/guides/upload)._
+"""
+
+MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_BAD_REQUEST_TEMPLATE = """
+An error occurred while trying to merge the Pull Request: `{error_message}`.
+
+_This is a comment posted using the `huggingface_hub` library in the context of a multi-commit. Learn more about multi-commits [in this guide](https://huggingface.co/docs/huggingface_hub/main/guides/upload)._
+"""
+
+
+STEP_ID_REGEX = re.compile(r"- \[(?P[ |x])\].*(?P[a-fA-F0-9]{64})", flags=re.MULTILINE)
+
+
+@experimental
+def plan_multi_commits(
+ operations: Iterable[Union[CommitOperationAdd, CommitOperationDelete]],
+ max_operations_per_commit: int = 50,
+ max_upload_size_per_commit: int = 2 * 1024 * 1024 * 1024,
+) -> Tuple[List[List[CommitOperationAdd]], List[List[CommitOperationDelete]]]:
+ """Split a list of operations in a list of commits to perform.
+
+ Implementation follows a sub-optimal (yet simple) algorithm:
+ 1. Delete operations are grouped together by commits of maximum `max_operations_per_commits` operations.
+ 2. All additions exceeding `max_upload_size_per_commit` are committed 1 by 1.
+ 3. All remaining additions are grouped together and split each time the `max_operations_per_commit` or the
+ `max_upload_size_per_commit` limit is reached.
+
+ We do not try to optimize the splitting to get the lowest number of commits as this is a NP-hard problem (see
+ [bin packing problem](https://en.wikipedia.org/wiki/Bin_packing_problem)). For our use case, it is not problematic
+ to use a sub-optimal solution so we favored an easy-to-explain implementation.
+
+ Args:
+ operations (`List` of [`~hf_api.CommitOperation`]):
+ The list of operations to split into commits.
+ max_operations_per_commit (`int`):
+ Maximum number of operations in a single commit. Defaults to 50.
+ max_upload_size_per_commit (`int`):
+ Maximum size to upload (in bytes) in a single commit. Defaults to 2GB. Files bigger than this limit are
+ uploaded, 1 per commit.
+
+ Returns:
+ `Tuple[List[List[CommitOperationAdd]], List[List[CommitOperationDelete]]]`: a tuple. First item is a list of
+ lists of [`CommitOperationAdd`] representing the addition commits to push. The second item is a list of lists
+ of [`CommitOperationDelete`] representing the deletion commits.
+
+
+
+ `plan_multi_commits` is experimental. Its API and behavior is subject to change in the future without prior notice.
+
+
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi, plan_multi_commits
+ >>> addition_commits, deletion_commits = plan_multi_commits(
+ ... operations=[
+ ... CommitOperationAdd(...),
+ ... CommitOperationAdd(...),
+ ... CommitOperationDelete(...),
+ ... CommitOperationDelete(...),
+ ... CommitOperationAdd(...),
+ ... ],
+ ... )
+ >>> HfApi().create_commits_on_pr(
+ ... repo_id="my-cool-model",
+ ... addition_commits=addition_commits,
+ ... deletion_commits=deletion_commits,
+ ... (...)
+ ... verbose=True,
+ ... )
+ ```
+
+
+
+ The initial order of the operations is not guaranteed! All deletions will be performed before additions. If you are
+ not updating multiple times the same file, you are fine.
+
+
+ """
+ addition_commits: List[List[CommitOperationAdd]] = []
+ deletion_commits: List[List[CommitOperationDelete]] = []
+
+ additions: List[CommitOperationAdd] = []
+ additions_size = 0
+ deletions: List[CommitOperationDelete] = []
+ for op in operations:
+ if isinstance(op, CommitOperationDelete):
+ # Group delete operations together
+ deletions.append(op)
+ if len(deletions) >= max_operations_per_commit:
+ deletion_commits.append(deletions)
+ deletions = []
+
+ elif op.upload_info.size >= max_upload_size_per_commit:
+ # Upload huge files 1 by 1
+ addition_commits.append([op])
+
+ elif additions_size + op.upload_info.size < max_upload_size_per_commit:
+ # Group other additions and split if size limit is reached (either max_nb_files or max_upload_size)
+ additions.append(op)
+ additions_size += op.upload_info.size
+
+ else:
+ addition_commits.append(additions)
+ additions = [op]
+ additions_size = op.upload_info.size
+
+ if len(additions) >= max_operations_per_commit:
+ addition_commits.append(additions)
+ additions = []
+ additions_size = 0
+
+ if len(additions) > 0:
+ addition_commits.append(additions)
+ if len(deletions) > 0:
+ deletion_commits.append(deletions)
+
+ return addition_commits, deletion_commits
+
+
+@dataclass
+class MultiCommitStep:
+ """Dataclass containing a list of CommitOperation to commit at once.
+
+ A [`MultiCommitStep`] is one atomic part of a [`MultiCommitStrategy`]. Each step is identified by its own
+ deterministic ID based on the list of commit operations (hexadecimal sha256). ID is persistent between re-runs if
+ the list of commits is kept the same.
+ """
+
+ operations: List[Union[CommitOperationAdd, CommitOperationDelete]]
+
+ id: str = field(init=False)
+ completed: bool = False
+
+ def __post_init__(self) -> None:
+ if len(self.operations) == 0:
+ raise ValueError("A MultiCommitStep must have at least 1 commit operation, got 0.")
+
+ # Generate commit id
+ sha = sha256()
+ for op in self.operations:
+ if isinstance(op, CommitOperationAdd):
+ sha.update(b"ADD")
+ sha.update(op.path_in_repo.encode())
+ sha.update(op.upload_info.sha256)
+ elif isinstance(op, CommitOperationDelete):
+ sha.update(b"DELETE")
+ sha.update(op.path_in_repo.encode())
+ sha.update(str(op.is_folder).encode())
+ else:
+ NotImplementedError()
+ self.id = sha.hexdigest()
+
+ def __str__(self) -> str:
+ """Format a step for PR description.
+
+ Formatting can be changed in the future as long as it is single line, starts with `- [ ]`/`- [x]` and contains
+ `self.id`. Must be able to match `STEP_ID_REGEX`.
+ """
+ additions = [op for op in self.operations if isinstance(op, CommitOperationAdd)]
+ file_deletions = [op for op in self.operations if isinstance(op, CommitOperationDelete) and not op.is_folder]
+ folder_deletions = [op for op in self.operations if isinstance(op, CommitOperationDelete) and op.is_folder]
+ if len(additions) > 0:
+ return (
+ f"- [{'x' if self.completed else ' '}] Upload {len(additions)} file(s) "
+ f"totalling {_format_size(sum(add.upload_info.size for add in additions))}"
+ f" ({self.id})"
+ )
+ else:
+ return (
+ f"- [{'x' if self.completed else ' '}] Delete {len(file_deletions)} file(s) and"
+ f" {len(folder_deletions)} folder(s) ({self.id})"
+ )
+
+
+@dataclass
+class MultiCommitStrategy:
+ """Dataclass containing a list of [`MultiCommitStep`] to commit iteratively.
+
+ A strategy is identified by its own deterministic ID based on the list of its steps (hexadecimal sha256). ID is
+ persistent between re-runs if the list of commits is kept the same.
+ """
+
+ addition_commits: List[MultiCommitStep]
+ deletion_commits: List[MultiCommitStep]
+
+ id: str = field(init=False)
+ all_steps: Set[str] = field(init=False)
+
+ def __post_init__(self) -> None:
+ self.all_steps = {step.id for step in self.addition_commits + self.deletion_commits}
+ if len(self.all_steps) < len(self.addition_commits) + len(self.deletion_commits):
+ raise ValueError("Got duplicate commits in MultiCommitStrategy. All commits must be unique.")
+
+ if len(self.all_steps) == 0:
+ raise ValueError("A MultiCommitStrategy must have at least 1 commit, got 0.")
+
+ # Generate strategy id
+ sha = sha256()
+ for step in self.addition_commits + self.deletion_commits:
+ sha.update("new step".encode())
+ sha.update(step.id.encode())
+ self.id = sha.hexdigest()
+
+
+def multi_commit_create_pull_request(
+ api: "HfApi",
+ repo_id: str,
+ commit_message: str,
+ commit_description: Optional[str],
+ strategy: MultiCommitStrategy,
+ token: Optional[str],
+ repo_type: Optional[str],
+) -> DiscussionWithDetails:
+ return api.create_pull_request(
+ repo_id=repo_id,
+ title=f"[WIP] {commit_message} (multi-commit {strategy.id})",
+ description=multi_commit_generate_comment(
+ commit_message=commit_message, commit_description=commit_description, strategy=strategy
+ ),
+ token=token,
+ repo_type=repo_type,
+ )
+
+
+def multi_commit_generate_comment(
+ commit_message: str,
+ commit_description: Optional[str],
+ strategy: MultiCommitStrategy,
+) -> str:
+ return MULTI_COMMIT_PR_DESCRIPTION_TEMPLATE.format(
+ commit_message=commit_message,
+ commit_description=commit_description or "",
+ multi_commit_id=strategy.id,
+ multi_commit_strategy="\n".join(
+ str(commit) for commit in strategy.deletion_commits + strategy.addition_commits
+ ),
+ )
+
+
+def multi_commit_parse_pr_description(description: str) -> Set[str]:
+ return {match[1] for match in STEP_ID_REGEX.findall(description)}
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_snapshot_download.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_snapshot_download.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbf57ea23308a3a688c6dae75a603df82f24c156
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_snapshot_download.py
@@ -0,0 +1,327 @@
+import os
+from pathlib import Path
+from typing import Dict, List, Literal, Optional, Union
+
+import requests
+from tqdm.auto import tqdm as base_tqdm
+from tqdm.contrib.concurrent import thread_map
+
+from .constants import (
+ DEFAULT_ETAG_TIMEOUT,
+ DEFAULT_REVISION,
+ HF_HUB_CACHE,
+ HF_HUB_ENABLE_HF_TRANSFER,
+ REPO_TYPES,
+)
+from .file_download import REGEX_COMMIT_HASH, hf_hub_download, repo_folder_name
+from .hf_api import DatasetInfo, HfApi, ModelInfo, SpaceInfo
+from .utils import (
+ GatedRepoError,
+ LocalEntryNotFoundError,
+ OfflineModeIsEnabled,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ filter_repo_objects,
+ logging,
+ validate_hf_hub_args,
+)
+from .utils import tqdm as hf_tqdm
+
+
+logger = logging.get_logger(__name__)
+
+
+@validate_hf_hub_args
+def snapshot_download(
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Optional[Union[Dict, str]] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = DEFAULT_ETAG_TIMEOUT,
+ resume_download: bool = False,
+ force_download: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ local_files_only: bool = False,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ max_workers: int = 8,
+ tqdm_class: Optional[base_tqdm] = None,
+ headers: Optional[Dict[str, str]] = None,
+ endpoint: Optional[str] = None,
+) -> str:
+ """Download repo files.
+
+ Download a whole snapshot of a repo's files at the specified revision. This is useful when you want all files from
+ a repo, because you don't know which ones you will need a priori. All files are nested inside a folder in order
+ to keep their actual filename relative to that folder. You can also filter which files to download using
+ `allow_patterns` and `ignore_patterns`.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. You can configure
+ how you want to move those files:
+ - If `local_dir_use_symlinks="auto"` (default), files are downloaded and stored in the cache directory as blob
+ files. Small files (<5MB) are duplicated in `local_dir` while a symlink is created for bigger files. The goal
+ is to be able to manually edit and save small files without corrupting the cache while saving disk space for
+ binary files. The 5MB threshold can be configured with the `HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD`
+ environment variable.
+ - If `local_dir_use_symlinks=True`, files are downloaded, stored in the cache directory and symlinked in `local_dir`.
+ This is optimal in term of disk usage but files must not be manually edited.
+ - If `local_dir_use_symlinks=False` and the blob files exist in the cache directory, they are duplicated in the
+ local dir. This means disk usage is not optimized.
+ - Finally, if `local_dir_use_symlinks=False` and the blob files do not exist in the cache directory, then the
+ files are downloaded and directly placed under `local_dir`. This means if you need to download them again later,
+ they will be re-downloaded entirely.
+
+ An alternative would be to clone the repo but this requires git and git-lfs to be installed and properly
+ configured. It is also not possible to filter which files to download when cloning a repository using git.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded files will be placed under this directory, either as symlinks (default) or
+ regular files (see description for more details).
+ local_dir_use_symlinks (`"auto"` or `bool`, defaults to `"auto"`):
+ To be used with `local_dir`. If set to "auto", the cache directory will be used and the file will be either
+ duplicated or symlinked to the local directory depending on its size. It set to `True`, a symlink will be
+ created, no matter the file size. If set to `False`, the file will either be duplicated from cache (if
+ already exists) or downloaded from the Hub and not cached. See description for more details.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ user_agent (`str`, `dict`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ resume_download (`bool`, *optional*, defaults to `False):
+ If `True`, resume a previously interrupted download.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in the local cache.
+ token (`str`, `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If a string, it's used as the authentication token.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request. Those headers take precedence over the others.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are downloaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not downloaded.
+ max_workers (`int`, *optional*):
+ Number of concurrent threads to download files (1 thread = 1 file download).
+ Defaults to 8.
+ tqdm_class (`tqdm`, *optional*):
+ If provided, overwrites the default behavior for the progress bar. Passed
+ argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior.
+ Note that the `tqdm_class` is not passed to each individual download.
+ Defaults to the custom HF progress bar that can be disabled by setting
+ `HF_HUB_DISABLE_PROGRESS_BARS` environment variable.
+
+ Returns:
+ Local folder path (string) of repo snapshot
+
+
+
+ Raises the following errors:
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `token=True` and the token cannot be found.
+ - [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) if
+ ETag cannot be determined.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+
+
+ """
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+ if revision is None:
+ revision = DEFAULT_REVISION
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(REPO_TYPES)}")
+
+ storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))
+
+ repo_info: Union[ModelInfo, DatasetInfo, SpaceInfo, None] = None
+ api_call_error: Optional[Exception] = None
+ if not local_files_only:
+ # try/except logic to handle different errors => taken from `hf_hub_download`
+ try:
+ # if we have internet connection we want to list files to download
+ api = HfApi(
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ endpoint=endpoint,
+ headers=headers,
+ )
+ repo_info = api.repo_info(repo_id=repo_id, repo_type=repo_type, revision=revision, token=token)
+ except (requests.exceptions.SSLError, requests.exceptions.ProxyError):
+ # Actually raise for those subclasses of ConnectionError
+ raise
+ except (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ OfflineModeIsEnabled,
+ ) as error:
+ # Internet connection is down
+ # => will try to use local files only
+ api_call_error = error
+ pass
+ except RevisionNotFoundError:
+ # The repo was found but the revision doesn't exist on the Hub (never existed or got deleted)
+ raise
+ except requests.HTTPError as error:
+ # Multiple reasons for an http error:
+ # - Repository is private and invalid/missing token sent
+ # - Repository is gated and invalid/missing token sent
+ # - Hub is down (error 500 or 504)
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ api_call_error = error
+ pass
+
+ # At this stage, if `repo_info` is None it means either:
+ # - internet connection is down
+ # - internet connection is deactivated (local_files_only=True or HF_HUB_OFFLINE=True)
+ # - repo is private/gated and invalid/missing token sent
+ # - Hub is down
+ # => let's look if we can find the appropriate folder in the cache:
+ # - if the specified revision is a commit hash, look inside "snapshots".
+ # - f the specified revision is a branch or tag, look inside "refs".
+ if repo_info is None:
+ # Try to get which commit hash corresponds to the specified revision
+ commit_hash = None
+ if REGEX_COMMIT_HASH.match(revision):
+ commit_hash = revision
+ else:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ if os.path.exists(ref_path):
+ # retrieve commit_hash from refs file
+ with open(ref_path) as f:
+ commit_hash = f.read()
+
+ # Try to locate snapshot folder for this commit hash
+ if commit_hash is not None:
+ snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
+ if os.path.exists(snapshot_folder):
+ # Snapshot folder exists => let's return it
+ # (but we can't check if all the files are actually there)
+ return snapshot_folder
+
+ # If we couldn't find the appropriate folder on disk, raise an error.
+ if local_files_only:
+ raise LocalEntryNotFoundError(
+ "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
+ "outgoing traffic has been disabled. To enable repo look-ups and downloads online, pass "
+ "'local_files_only=False' as input."
+ )
+ elif isinstance(api_call_error, OfflineModeIsEnabled):
+ raise LocalEntryNotFoundError(
+ "Cannot find an appropriate cached snapshot folder for the specified revision on the local disk and "
+ "outgoing traffic has been disabled. To enable repo look-ups and downloads online, set "
+ "'HF_HUB_OFFLINE=0' as environment variable."
+ ) from api_call_error
+ elif isinstance(api_call_error, RepositoryNotFoundError) or isinstance(api_call_error, GatedRepoError):
+ # Repo not found => let's raise the actual error
+ raise api_call_error
+ else:
+ # Otherwise: most likely a connection issue or Hub downtime => let's warn the user
+ raise LocalEntryNotFoundError(
+ "An error happened while trying to locate the files on the Hub and we cannot find the appropriate"
+ " snapshot folder for the specified revision on the local disk. Please check your internet connection"
+ " and try again."
+ ) from api_call_error
+
+ # At this stage, internet connection is up and running
+ # => let's download the files!
+ assert repo_info.sha is not None, "Repo info returned from server must have a revision sha."
+ assert repo_info.siblings is not None, "Repo info returned from server must have a siblings list."
+ filtered_repo_files = list(
+ filter_repo_objects(
+ items=[f.rfilename for f in repo_info.siblings],
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ )
+ )
+ commit_hash = repo_info.sha
+ snapshot_folder = os.path.join(storage_folder, "snapshots", commit_hash)
+ # if passed revision is not identical to commit_hash
+ # then revision has to be a branch name or tag name.
+ # In that case store a ref.
+ if revision != commit_hash:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ os.makedirs(os.path.dirname(ref_path), exist_ok=True)
+ with open(ref_path, "w") as f:
+ f.write(commit_hash)
+
+ # we pass the commit_hash to hf_hub_download
+ # so no network call happens if we already
+ # have the file locally.
+ def _inner_hf_hub_download(repo_file: str):
+ return hf_hub_download(
+ repo_id,
+ filename=repo_file,
+ repo_type=repo_type,
+ revision=commit_hash,
+ endpoint=endpoint,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ force_download=force_download,
+ token=token,
+ headers=headers,
+ )
+
+ if HF_HUB_ENABLE_HF_TRANSFER:
+ # when using hf_transfer we don't want extra parallelism
+ # from the one hf_transfer provides
+ for file in filtered_repo_files:
+ _inner_hf_hub_download(file)
+ else:
+ thread_map(
+ _inner_hf_hub_download,
+ filtered_repo_files,
+ desc=f"Fetching {len(filtered_repo_files)} files",
+ max_workers=max_workers,
+ # User can use its own tqdm class or the default one from `huggingface_hub.utils`
+ tqdm_class=tqdm_class or hf_tqdm,
+ )
+
+ if local_dir is not None:
+ return str(os.path.realpath(local_dir))
+ return snapshot_folder
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_space_api.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_space_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..da70bd05a66a96b450f04eab9171aa9b5f7420c7
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_space_api.py
@@ -0,0 +1,155 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from dataclasses import dataclass
+from datetime import datetime
+from enum import Enum
+from typing import Dict, Optional
+
+from huggingface_hub.utils import parse_datetime
+
+
+class SpaceStage(str, Enum):
+ """
+ Enumeration of possible stage of a Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceStage.BUILDING == "BUILDING"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L61 (private url).
+ """
+
+ # Copied from moon-landing > server > repo_types > SpaceInfo.ts (private repo)
+ NO_APP_FILE = "NO_APP_FILE"
+ CONFIG_ERROR = "CONFIG_ERROR"
+ BUILDING = "BUILDING"
+ BUILD_ERROR = "BUILD_ERROR"
+ RUNNING = "RUNNING"
+ RUNNING_BUILDING = "RUNNING_BUILDING"
+ RUNTIME_ERROR = "RUNTIME_ERROR"
+ DELETING = "DELETING"
+ STOPPED = "STOPPED"
+ PAUSED = "PAUSED"
+
+
+class SpaceHardware(str, Enum):
+ """
+ Enumeration of hardwares available to run your Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceHardware.CPU_BASIC == "cpu-basic"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceInfo.ts#L73 (private url).
+ """
+
+ CPU_BASIC = "cpu-basic"
+ CPU_UPGRADE = "cpu-upgrade"
+ T4_SMALL = "t4-small"
+ T4_MEDIUM = "t4-medium"
+ ZERO_A10G = "zero-a10g"
+ A10G_SMALL = "a10g-small"
+ A10G_LARGE = "a10g-large"
+ A10G_LARGEX2 = "a10g-largex2"
+ A10G_LARGEX4 = "a10g-largex4"
+ A100_LARGE = "a100-large"
+
+
+class SpaceStorage(str, Enum):
+ """
+ Enumeration of persistent storage available for your Space on the Hub.
+
+ Value can be compared to a string:
+ ```py
+ assert SpaceStorage.SMALL == "small"
+ ```
+
+ Taken from https://github.com/huggingface/moon-landing/blob/main/server/repo_types/SpaceHardwareFlavor.ts#L24 (private url).
+ """
+
+ SMALL = "small"
+ MEDIUM = "medium"
+ LARGE = "large"
+
+
+@dataclass
+class SpaceRuntime:
+ """
+ Contains information about the current runtime of a Space.
+
+ Args:
+ stage (`str`):
+ Current stage of the space. Example: RUNNING.
+ hardware (`str` or `None`):
+ Current hardware of the space. Example: "cpu-basic". Can be `None` if Space
+ is `BUILDING` for the first time.
+ requested_hardware (`str` or `None`):
+ Requested hardware. Can be different than `hardware` especially if the request
+ has just been made. Example: "t4-medium". Can be `None` if no hardware has
+ been requested yet.
+ sleep_time (`int` or `None`):
+ Number of seconds the Space will be kept alive after the last request. By default (if value is `None`), the
+ Space will never go to sleep if it's running on an upgraded hardware, while it will go to sleep after 48
+ hours on a free 'cpu-basic' hardware. For more details, see https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
+ raw (`dict`):
+ Raw response from the server. Contains more information about the Space
+ runtime like number of replicas, number of cpu, memory size,...
+ """
+
+ stage: SpaceStage
+ hardware: Optional[SpaceHardware]
+ requested_hardware: Optional[SpaceHardware]
+ sleep_time: Optional[int]
+ storage: Optional[SpaceStorage]
+ raw: Dict
+
+ def __init__(self, data: Dict) -> None:
+ self.stage = data["stage"]
+ self.hardware = data.get("hardware", {}).get("current")
+ self.requested_hardware = data.get("hardware", {}).get("requested")
+ self.sleep_time = data.get("gcTimeout")
+ self.storage = data.get("storage")
+ self.raw = data
+
+
+@dataclass
+class SpaceVariable:
+ """
+ Contains information about the current variables of a Space.
+
+ Args:
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ value (`str`):
+ Variable value. Example: `"the_model_repo_id"`.
+ description (`str` or None):
+ Description of the variable. Example: `"Model Repo ID of the implemented model"`.
+ updatedAt (`datetime` or None):
+ datetime of the last update of the variable (if the variable has been updated at least once).
+ """
+
+ key: str
+ value: str
+ description: Optional[str]
+ updated_at: Optional[datetime]
+
+ def __init__(self, key: str, values: Dict) -> None:
+ self.key = key
+ self.value = values["value"]
+ self.description = values.get("description")
+ updated_at = values.get("updatedAt")
+ self.updated_at = parse_datetime(updated_at) if updated_at is not None else None
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_tensorboard_logger.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_tensorboard_logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..09f6aa5dbdb48049baaec12751318a0a8a87e7ae
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_tensorboard_logger.py
@@ -0,0 +1,169 @@
+# Copyright 2023 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a logger to push training logs to the Hub, using Tensorboard."""
+
+from pathlib import Path
+from typing import TYPE_CHECKING, List, Optional, Union
+
+from huggingface_hub._commit_scheduler import CommitScheduler
+
+from .utils import experimental, is_tensorboard_available
+
+
+if is_tensorboard_available():
+ from tensorboardX import SummaryWriter
+
+ # TODO: clarify: should we import from torch.utils.tensorboard ?
+
+else:
+ SummaryWriter = object # Dummy class to avoid failing at import. Will raise on instance creation.
+
+if TYPE_CHECKING:
+ from tensorboardX import SummaryWriter
+
+
+class HFSummaryWriter(SummaryWriter):
+ """
+ Wrapper around the tensorboard's `SummaryWriter` to push training logs to the Hub.
+
+ Data is logged locally and then pushed to the Hub asynchronously. Pushing data to the Hub is done in a separate
+ thread to avoid blocking the training script. In particular, if the upload fails for any reason (e.g. a connection
+ issue), the main script will not be interrupted. Data is automatically pushed to the Hub every `commit_every`
+ minutes (default to every 5 minutes).
+
+
+
+ `HFSummaryWriter` is experimental. Its API is subject to change in the future without prior notice.
+
+
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to which the logs will be pushed.
+ logdir (`str`, *optional*):
+ The directory where the logs will be written. If not specified, a local directory will be created by the
+ underlying `SummaryWriter` object.
+ commit_every (`int` or `float`, *optional*):
+ The frequency (in minutes) at which the logs will be pushed to the Hub. Defaults to 5 minutes.
+ squash_history (`bool`, *optional*):
+ Whether to squash the history of the repo after each commit. Defaults to `False`. Squashing commits is
+ useful to avoid degraded performances on the repo when it grows too large.
+ repo_type (`str`, *optional*):
+ The type of the repo to which the logs will be pushed. Defaults to "model".
+ repo_revision (`str`, *optional*):
+ The revision of the repo to which the logs will be pushed. Defaults to "main".
+ repo_private (`bool`, *optional*):
+ Whether to create a private repo or not. Defaults to False. This argument is ignored if the repo already
+ exists.
+ path_in_repo (`str`, *optional*):
+ The path to the folder in the repo where the logs will be pushed. Defaults to "tensorboard/".
+ repo_allow_patterns (`List[str]` or `str`, *optional*):
+ A list of patterns to include in the upload. Defaults to `"*.tfevents.*"`. Check out the
+ [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
+ repo_ignore_patterns (`List[str]` or `str`, *optional*):
+ A list of patterns to exclude in the upload. Check out the
+ [upload guide](https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-folder) for more details.
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token. See https://huggingface.co/settings/token for more
+ details
+ kwargs:
+ Additional keyword arguments passed to `SummaryWriter`.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import HFSummaryWriter
+
+ # Logs are automatically pushed every 15 minutes
+ >>> logger = HFSummaryWriter(repo_id="test_hf_logger", commit_every=15)
+ >>> logger.add_scalar("a", 1)
+ >>> logger.add_scalar("b", 2)
+ ...
+
+ # You can also trigger a push manually
+ >>> logger.scheduler.trigger()
+ ```
+
+ ```py
+ >>> from huggingface_hub import HFSummaryWriter
+
+ # Logs are automatically pushed every 5 minutes (default) + when exiting the context manager
+ >>> with HFSummaryWriter(repo_id="test_hf_logger") as logger:
+ ... logger.add_scalar("a", 1)
+ ... logger.add_scalar("b", 2)
+ ```
+ """
+
+ @experimental
+ def __new__(cls, *args, **kwargs) -> "HFSummaryWriter":
+ if not is_tensorboard_available():
+ raise ImportError(
+ "You must have `tensorboard` installed to use `HFSummaryWriter`. Please run `pip install --upgrade"
+ " tensorboardX` first."
+ )
+ return super().__new__(cls)
+
+ def __init__(
+ self,
+ repo_id: str,
+ *,
+ logdir: Optional[str] = None,
+ commit_every: Union[int, float] = 5,
+ squash_history: bool = False,
+ repo_type: Optional[str] = None,
+ repo_revision: Optional[str] = None,
+ repo_private: bool = False,
+ path_in_repo: Optional[str] = "tensorboard",
+ repo_allow_patterns: Optional[Union[List[str], str]] = "*.tfevents.*",
+ repo_ignore_patterns: Optional[Union[List[str], str]] = None,
+ token: Optional[str] = None,
+ **kwargs,
+ ):
+ # Initialize SummaryWriter
+ super().__init__(logdir=logdir, **kwargs)
+
+ # Check logdir has been correctly initialized and fail early otherwise. In practice, SummaryWriter takes care of it.
+ if not isinstance(self.logdir, str):
+ raise ValueError(f"`self.logdir` must be a string. Got '{self.logdir}' of type {type(self.logdir)}.")
+
+ # Append logdir name to `path_in_repo`
+ if path_in_repo is None or path_in_repo == "":
+ path_in_repo = Path(self.logdir).name
+ else:
+ path_in_repo = path_in_repo.strip("/") + "/" + Path(self.logdir).name
+
+ # Initialize scheduler
+ self.scheduler = CommitScheduler(
+ folder_path=self.logdir,
+ path_in_repo=path_in_repo,
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=repo_revision,
+ private=repo_private,
+ token=token,
+ allow_patterns=repo_allow_patterns,
+ ignore_patterns=repo_ignore_patterns,
+ every=commit_every,
+ squash_history=squash_history,
+ )
+
+ # Exposing some high-level info at root level
+ self.repo_id = self.scheduler.repo_id
+ self.repo_type = self.scheduler.repo_type
+ self.repo_revision = self.scheduler.revision
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Push to hub in a non-blocking way when exiting the logger's context manager."""
+ super().__exit__(exc_type, exc_val, exc_tb)
+ future = self.scheduler.trigger()
+ future.result()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_payload.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_payload.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ea669fe09a82252cb788ad390066a894cedf0f5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_payload.py
@@ -0,0 +1,116 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains data structures to parse the webhooks payload."""
+
+from typing import List, Literal, Optional
+
+from pydantic import BaseModel
+
+
+# This is an adaptation of the ReportV3 interface implemented in moon-landing. V0, V1 and V2 have been ignored as they
+# are not in used anymore. To keep in sync when format is updated in
+# https://github.com/huggingface/moon-landing/blob/main/server/lib/HFWebhooks.ts (internal link).
+
+
+WebhookEvent_T = Literal[
+ "create",
+ "delete",
+ "move",
+ "update",
+]
+RepoChangeEvent_T = Literal[
+ "add",
+ "move",
+ "remove",
+ "update",
+]
+RepoType_T = Literal[
+ "dataset",
+ "model",
+ "space",
+]
+DiscussionStatus_T = Literal[
+ "closed",
+ "draft",
+ "open",
+ "merged",
+]
+SupportedWebhookVersion = Literal[3]
+
+
+class ObjectId(BaseModel):
+ id: str
+
+
+class WebhookPayloadUrl(BaseModel):
+ web: str
+ api: Optional[str] = None
+
+
+class WebhookPayloadMovedTo(BaseModel):
+ name: str
+ owner: ObjectId
+
+
+class WebhookPayloadWebhook(ObjectId):
+ version: SupportedWebhookVersion
+
+
+class WebhookPayloadEvent(BaseModel):
+ action: WebhookEvent_T
+ scope: str
+
+
+class WebhookPayloadDiscussionChanges(BaseModel):
+ base: str
+ mergeCommitId: Optional[str] = None
+
+
+class WebhookPayloadComment(ObjectId):
+ author: ObjectId
+ hidden: bool
+ content: Optional[str] = None
+ url: WebhookPayloadUrl
+
+
+class WebhookPayloadDiscussion(ObjectId):
+ num: int
+ author: ObjectId
+ url: WebhookPayloadUrl
+ title: str
+ isPullRequest: bool
+ status: DiscussionStatus_T
+ changes: Optional[WebhookPayloadDiscussionChanges] = None
+ pinned: Optional[bool] = None
+
+
+class WebhookPayloadRepo(ObjectId):
+ owner: ObjectId
+ head_sha: Optional[str] = None
+ name: str
+ private: bool
+ subdomain: Optional[str] = None
+ tags: Optional[List[str]] = None
+ type: Literal["dataset", "model", "space"]
+ url: WebhookPayloadUrl
+
+
+class WebhookPayload(BaseModel):
+ event: WebhookPayloadEvent
+ repo: WebhookPayloadRepo
+ discussion: Optional[WebhookPayloadDiscussion] = None
+ comment: Optional[WebhookPayloadComment] = None
+ webhook: WebhookPayloadWebhook
+ movedTo: Optional[WebhookPayloadMovedTo] = None
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_server.py b/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..fefb038848221691c1b49df9f7a6c921295f6a71
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/_webhooks_server.py
@@ -0,0 +1,380 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily."""
+
+import atexit
+import inspect
+import os
+from functools import wraps
+from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
+
+from .utils import experimental, is_gradio_available
+from .utils._deprecation import _deprecate_method
+
+
+if TYPE_CHECKING:
+ import gradio as gr
+
+
+from fastapi import FastAPI, Request
+from fastapi.responses import JSONResponse
+
+
+_global_app: Optional["WebhooksServer"] = None
+_is_local = os.getenv("SYSTEM") != "spaces"
+
+
+@experimental
+class WebhooksServer:
+ """
+ The [`WebhooksServer`] class lets you create an instance of a Gradio app that can receive Huggingface webhooks.
+ These webhooks can be registered using the [`~WebhooksServer.add_webhook`] decorator. Webhook endpoints are added to
+ the app as a POST endpoint to the FastAPI router. Once all the webhooks are registered, the `run` method has to be
+ called to start the app.
+
+ It is recommended to accept [`WebhookPayload`] as the first argument of the webhook function. It is a Pydantic
+ model that contains all the information about the webhook event. The data will be parsed automatically for you.
+
+ Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
+ WebhooksServer and deploy it on a Space.
+
+
+
+ `WebhooksServer` is experimental. Its API is subject to change in the future.
+
+
+
+
+
+ You must have `gradio` installed to use `WebhooksServer` (`pip install --upgrade gradio`).
+
+
+
+ Args:
+ ui (`gradio.Blocks`, optional):
+ A Gradio UI instance to be used as the Space landing page. If `None`, a UI displaying instructions
+ about the configured webhooks is created.
+ webhook_secret (`str`, optional):
+ A secret key to verify incoming webhook requests. You can set this value to any secret you want as long as
+ you also configure it in your [webhooks settings panel](https://huggingface.co/settings/webhooks). You
+ can also set this value as the `WEBHOOK_SECRET` environment variable. If no secret is provided, the
+ webhook endpoints are opened without any security.
+
+ Example:
+
+ ```python
+ import gradio as gr
+ from huggingface_hub import WebhooksServer, WebhookPayload
+
+ with gr.Blocks() as ui:
+ ...
+
+ app = WebhooksServer(ui=ui, webhook_secret="my_secret_key")
+
+ @app.add_webhook("/say_hello")
+ async def hello(payload: WebhookPayload):
+ return {"message": "hello"}
+
+ app.run()
+ ```
+ """
+
+ def __new__(cls, *args, **kwargs) -> "WebhooksServer":
+ if not is_gradio_available():
+ raise ImportError(
+ "You must have `gradio` installed to use `WebhooksServer`. Please run `pip install --upgrade gradio`"
+ " first."
+ )
+ return super().__new__(cls)
+
+ def __init__(
+ self,
+ ui: Optional["gr.Blocks"] = None,
+ webhook_secret: Optional[str] = None,
+ ) -> None:
+ self._ui = ui
+
+ self.webhook_secret = webhook_secret or os.getenv("WEBHOOK_SECRET")
+ self.registered_webhooks: Dict[str, Callable] = {}
+ _warn_on_empty_secret(self.webhook_secret)
+
+ def add_webhook(self, path: Optional[str] = None) -> Callable:
+ """
+ Decorator to add a webhook to the [`WebhooksServer`] server.
+
+ Args:
+ path (`str`, optional):
+ The URL path to register the webhook function. If not provided, the function name will be used as the
+ path. In any case, all webhooks are registered under `/webhooks`.
+
+ Raises:
+ ValueError: If the provided path is already registered as a webhook.
+
+ Example:
+ ```python
+ from huggingface_hub import WebhooksServer, WebhookPayload
+
+ app = WebhooksServer()
+
+ @app.add_webhook
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ app.run()
+ ```
+ """
+ # Usage: directly as decorator. Example: `@app.add_webhook`
+ if callable(path):
+ # If path is a function, it means it was used as a decorator without arguments
+ return self.add_webhook()(path)
+
+ # Usage: provide a path. Example: `@app.add_webhook(...)`
+ @wraps(FastAPI.post)
+ def _inner_post(*args, **kwargs):
+ func = args[0]
+ abs_path = f"/webhooks/{(path or func.__name__).strip('/')}"
+ if abs_path in self.registered_webhooks:
+ raise ValueError(f"Webhook {abs_path} already exists.")
+ self.registered_webhooks[abs_path] = func
+
+ return _inner_post
+
+ def launch(self, prevent_thread_lock: bool = False, **launch_kwargs: Any) -> None:
+ """Launch the Gradio app and register webhooks to the underlying FastAPI server.
+
+ Input parameters are forwarded to Gradio when launching the app.
+ """
+ ui = self._ui or self._get_default_ui()
+
+ # Start Gradio App
+ # - as non-blocking so that webhooks can be added afterwards
+ # - as shared if launch locally (to debug webhooks)
+ launch_kwargs.setdefault("share", _is_local)
+ self.fastapi_app, _, _ = ui.launch(prevent_thread_lock=True, **launch_kwargs)
+
+ # Register webhooks to FastAPI app
+ for path, func in self.registered_webhooks.items():
+ # Add secret check if required
+ if self.webhook_secret is not None:
+ func = _wrap_webhook_to_check_secret(func, webhook_secret=self.webhook_secret)
+
+ # Add route to FastAPI app
+ self.fastapi_app.post(path)(func)
+
+ # Print instructions and block main thread
+ url = (ui.share_url or ui.local_url).strip("/")
+ message = "\nWebhooks are correctly setup and ready to use:"
+ message += "\n" + "\n".join(f" - POST {url}{webhook}" for webhook in self.registered_webhooks)
+ message += "\nGo to https://huggingface.co/settings/webhooks to setup your webhooks."
+ print(message)
+
+ if not prevent_thread_lock:
+ ui.block_thread()
+
+ @_deprecate_method(version="0.23", message="Use `WebhooksServer.launch` instead.")
+ def run(self) -> None:
+ return self.launch()
+
+ def _get_default_ui(self) -> "gr.Blocks":
+ """Default UI if not provided (lists webhooks and provides basic instructions)."""
+ import gradio as gr
+
+ with gr.Blocks() as ui:
+ gr.Markdown("# This is an app to process 🤗 Webhooks")
+ gr.Markdown(
+ "Webhooks are a foundation for MLOps-related features. They allow you to listen for new changes on"
+ " specific repos or to all repos belonging to particular set of users/organizations (not just your"
+ " repos, but any repo). Check out this [guide](https://huggingface.co/docs/hub/webhooks) to get to"
+ " know more about webhooks on the Huggingface Hub."
+ )
+ gr.Markdown(
+ f"{len(self.registered_webhooks)} webhook(s) are registered:"
+ + "\n\n"
+ + "\n ".join(
+ f"- [{webhook_path}]({_get_webhook_doc_url(webhook.__name__, webhook_path)})"
+ for webhook_path, webhook in self.registered_webhooks.items()
+ )
+ )
+ gr.Markdown(
+ "Go to https://huggingface.co/settings/webhooks to setup your webhooks."
+ + "\nYou app is running locally. Please look at the logs to check the full URL you need to set."
+ if _is_local
+ else (
+ "\nThis app is running on a Space. You can find the corresponding URL in the options menu"
+ " (top-right) > 'Embed the Space'. The URL looks like 'https://{username}-{repo_name}.hf.space'."
+ )
+ )
+ return ui
+
+
+@experimental
+def webhook_endpoint(path: Optional[str] = None) -> Callable:
+ """Decorator to start a [`WebhooksServer`] and register the decorated function as a webhook endpoint.
+
+ This is a helper to get started quickly. If you need more flexibility (custom landing page or webhook secret),
+ you can use [`WebhooksServer`] directly. You can register multiple webhook endpoints (to the same server) by using
+ this decorator multiple times.
+
+ Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
+ server and deploy it on a Space.
+
+
+
+ `webhook_endpoint` is experimental. Its API is subject to change in the future.
+
+
+
+
+
+ You must have `gradio` installed to use `webhook_endpoint` (`pip install --upgrade gradio`).
+
+
+
+ Args:
+ path (`str`, optional):
+ The URL path to register the webhook function. If not provided, the function name will be used as the path.
+ In any case, all webhooks are registered under `/webhooks`.
+
+ Examples:
+ The default usage is to register a function as a webhook endpoint. The function name will be used as the path.
+ The server will be started automatically at exit (i.e. at the end of the script).
+
+ ```python
+ from huggingface_hub import webhook_endpoint, WebhookPayload
+
+ @webhook_endpoint
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ # Server is automatically started at the end of the script.
+ ```
+
+ Advanced usage: register a function as a webhook endpoint and start the server manually. This is useful if you
+ are running it in a notebook.
+
+ ```python
+ from huggingface_hub import webhook_endpoint, WebhookPayload
+
+ @webhook_endpoint
+ async def trigger_training(payload: WebhookPayload):
+ if payload.repo.type == "dataset" and payload.event.action == "update":
+ # Trigger a training job if a dataset is updated
+ ...
+
+ # Start the server manually
+ trigger_training.run()
+ ```
+ """
+ if callable(path):
+ # If path is a function, it means it was used as a decorator without arguments
+ return webhook_endpoint()(path)
+
+ @wraps(WebhooksServer.add_webhook)
+ def _inner(func: Callable) -> Callable:
+ app = _get_global_app()
+ app.add_webhook(path)(func)
+ if len(app.registered_webhooks) == 1:
+ # Register `app.run` to run at exit (only once)
+ atexit.register(app.run)
+
+ @wraps(app.run)
+ def _run_now():
+ # Run the app directly (without waiting atexit)
+ atexit.unregister(app.run)
+ app.run()
+
+ func.run = _run_now # type: ignore
+ return func
+
+ return _inner
+
+
+def _get_global_app() -> WebhooksServer:
+ global _global_app
+ if _global_app is None:
+ _global_app = WebhooksServer()
+ return _global_app
+
+
+def _warn_on_empty_secret(webhook_secret: Optional[str]) -> None:
+ if webhook_secret is None:
+ print("Webhook secret is not defined. This means your webhook endpoints will be open to everyone.")
+ print(
+ "To add a secret, set `WEBHOOK_SECRET` as environment variable or pass it at initialization: "
+ "\n\t`app = WebhooksServer(webhook_secret='my_secret', ...)`"
+ )
+ print(
+ "For more details about webhook secrets, please refer to"
+ " https://huggingface.co/docs/hub/webhooks#webhook-secret."
+ )
+ else:
+ print("Webhook secret is correctly defined.")
+
+
+def _get_webhook_doc_url(webhook_name: str, webhook_path: str) -> str:
+ """Returns the anchor to a given webhook in the docs (experimental)"""
+ return "/docs#/default/" + webhook_name + webhook_path.replace("/", "_") + "_post"
+
+
+def _wrap_webhook_to_check_secret(func: Callable, webhook_secret: str) -> Callable:
+ """Wraps a webhook function to check the webhook secret before calling the function.
+
+ This is a hacky way to add the `request` parameter to the function signature. Since FastAPI based itself on route
+ parameters to inject the values to the function, we need to hack the function signature to retrieve the `Request`
+ object (and hence the headers). A far cleaner solution would be to use a middleware. However, since
+ `fastapi==0.90.1`, a middleware cannot be added once the app has started. And since the FastAPI app is started by
+ Gradio internals (and not by us), we cannot add a middleware.
+
+ This method is called only when a secret has been defined by the user. If a request is sent without the
+ "x-webhook-secret", the function will return a 401 error (unauthorized). If the header is sent but is incorrect,
+ the function will return a 403 error (forbidden).
+
+ Inspired by https://stackoverflow.com/a/33112180.
+ """
+ initial_sig = inspect.signature(func)
+
+ @wraps(func)
+ async def _protected_func(request: Request, **kwargs):
+ request_secret = request.headers.get("x-webhook-secret")
+ if request_secret is None:
+ return JSONResponse({"error": "x-webhook-secret header not set."}, status_code=401)
+ if request_secret != webhook_secret:
+ return JSONResponse({"error": "Invalid webhook secret."}, status_code=403)
+
+ # Inject `request` in kwargs if required
+ if "request" in initial_sig.parameters:
+ kwargs["request"] = request
+
+ # Handle both sync and async routes
+ if inspect.iscoroutinefunction(func):
+ return await func(**kwargs)
+ else:
+ return func(**kwargs)
+
+ # Update signature to include request
+ if "request" not in initial_sig.parameters:
+ _protected_func.__signature__ = initial_sig.replace( # type: ignore
+ parameters=(
+ inspect.Parameter(name="request", kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=Request),
+ )
+ + tuple(initial_sig.parameters.values())
+ )
+
+ # Return protected route
+ return _protected_func
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..49d088214505b9604964ab142e7f8a5b38ccd5ef
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__init__.py
@@ -0,0 +1,27 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABC, abstractmethod
+from argparse import _SubParsersAction
+
+
+class BaseHuggingfaceCLICommand(ABC):
+ @staticmethod
+ @abstractmethod
+ def register_subcommand(parser: _SubParsersAction):
+ raise NotImplementedError()
+
+ @abstractmethod
+ def run(self):
+ raise NotImplementedError()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0d6588ae4a88f2359e361c9dc752bca5e057eaab
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/_cli_utils.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/_cli_utils.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84559f36425ae2f9d5887e096ee4904fa446d761
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/_cli_utils.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/delete_cache.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/delete_cache.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..36a4a2b0149c9b069c1bbb4749cc582f4586c53a
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/delete_cache.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/download.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/download.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fedcf4f1e86869b9a448597e68b5ad490edc3bb5
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/download.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/env.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/env.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ce633c9228c8356909307fbfd34bf1e631ffccb2
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/env.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/huggingface_cli.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/huggingface_cli.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8aba7aca865224a59416e82130159ef5944b1764
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/huggingface_cli.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/lfs.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/lfs.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..15aebd3269e55dac36835ceda23b7a413f3abf6f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/lfs.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/scan_cache.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/scan_cache.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bd3459ee23deb42d4a813e50646e321efaf2b795
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/scan_cache.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/upload.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/upload.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7b5c2e3905e1b20b2faa0304f11d6b0e1bb90393
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/upload.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/user.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/user.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b3b919148e77ad9168e90ce8a8b9d441a8f8770
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/__pycache__/user.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/_cli_utils.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/_cli_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a1f8601618f07cbfb9782b30e85349818766a94
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/_cli_utils.py
@@ -0,0 +1,64 @@
+# Copyright 2022 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a utility for good-looking prints."""
+
+import os
+from typing import List, Union
+
+
+class ANSI:
+ """
+ Helper for en.wikipedia.org/wiki/ANSI_escape_code
+ """
+
+ _bold = "\u001b[1m"
+ _gray = "\u001b[90m"
+ _red = "\u001b[31m"
+ _reset = "\u001b[0m"
+
+ @classmethod
+ def bold(cls, s: str) -> str:
+ return cls._format(s, cls._bold)
+
+ @classmethod
+ def gray(cls, s: str) -> str:
+ return cls._format(s, cls._gray)
+
+ @classmethod
+ def red(cls, s: str) -> str:
+ return cls._format(s, cls._bold + cls._red)
+
+ @classmethod
+ def _format(cls, s: str, code: str) -> str:
+ if os.environ.get("NO_COLOR"):
+ # See https://no-color.org/
+ return s
+ return f"{code}{s}{cls._reset}"
+
+
+def tabulate(rows: List[List[Union[str, int]]], headers: List[str]) -> str:
+ """
+ Inspired by:
+
+ - stackoverflow.com/a/8356620/593036
+ - stackoverflow.com/questions/9535954/printing-lists-as-tabular-data
+ """
+ col_widths = [max(len(str(x)) for x in col) for col in zip(*rows, headers)]
+ row_format = ("{{:{}}} " * len(headers)).format(*col_widths)
+ lines = []
+ lines.append(row_format.format(*headers))
+ lines.append(row_format.format(*["-" * w for w in col_widths]))
+ for row in rows:
+ lines.append(row_format.format(*row))
+ return "\n".join(lines)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/delete_cache.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/delete_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2fc44d31c8f59e6c517dd6df71466a319e5ea0e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/delete_cache.py
@@ -0,0 +1,428 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to delete some revisions from the HF cache directory.
+
+Usage:
+ huggingface-cli delete-cache
+ huggingface-cli delete-cache --disable-tui
+ huggingface-cli delete-cache --dir ~/.cache/huggingface/hub
+
+NOTE:
+ This command is based on `InquirerPy` to build the multiselect menu in the terminal.
+ This dependency has to be installed with `pip install huggingface_hub[cli]`. Since
+ we want to avoid as much as possible cross-platform issues, I chose a library that
+ is built on top of `python-prompt-toolkit` which seems to be a reference in terminal
+ GUI (actively maintained on both Unix and Windows, 7.9k stars).
+
+ For the moment, the TUI feature is in beta.
+
+ See:
+ - https://github.com/kazhala/InquirerPy
+ - https://inquirerpy.readthedocs.io/en/latest/
+ - https://github.com/prompt-toolkit/python-prompt-toolkit
+
+ Other solutions could have been:
+ - `simple_term_menu`: would be good as well for our use case but some issues suggest
+ that Windows is less supported.
+ See: https://github.com/IngoMeyer441/simple-term-menu
+ - `PyInquirer`: very similar to `InquirerPy` but older and not maintained anymore.
+ In particular, no support of Python3.10.
+ See: https://github.com/CITGuru/PyInquirer
+ - `pick` (or `pickpack`): easy to use and flexible but built on top of Python's
+ standard library `curses` that is specific to Unix (not implemented on Windows).
+ See https://github.com/wong2/pick and https://github.com/anafvana/pickpack.
+ - `inquirer`: lot of traction (700 stars) but explicitly states "experimental
+ support of Windows". Not built on top of `python-prompt-toolkit`.
+ See https://github.com/magmax/python-inquirer
+
+TODO: add support for `huggingface-cli delete-cache aaaaaa bbbbbb cccccc (...)` ?
+TODO: add "--keep-last" arg to delete revisions that are not on `main` ref
+TODO: add "--filter" arg to filter repositories by name ?
+TODO: add "--sort" arg to sort by size ?
+TODO: add "--limit" arg to limit to X repos ?
+TODO: add "-y" arg for immediate deletion ?
+See discussions in https://github.com/huggingface/huggingface_hub/issues/1025.
+"""
+
+import os
+from argparse import Namespace, _SubParsersAction
+from functools import wraps
+from tempfile import mkstemp
+from typing import Any, Callable, Iterable, List, Optional, Union
+
+from ..utils import CachedRepoInfo, CachedRevisionInfo, HFCacheInfo, scan_cache_dir
+from . import BaseHuggingfaceCLICommand
+from ._cli_utils import ANSI
+
+
+try:
+ from InquirerPy import inquirer
+ from InquirerPy.base.control import Choice
+ from InquirerPy.separator import Separator
+
+ _inquirer_py_available = True
+except ImportError:
+ _inquirer_py_available = False
+
+
+def require_inquirer_py(fn: Callable) -> Callable:
+ """Decorator to flag methods that require `InquirerPy`."""
+
+ # TODO: refactor this + imports in a unified pattern across codebase
+ @wraps(fn)
+ def _inner(*args, **kwargs):
+ if not _inquirer_py_available:
+ raise ImportError(
+ "The `delete-cache` command requires extra dependencies to work with"
+ " the TUI.\nPlease run `pip install huggingface_hub[cli]` to install"
+ " them.\nOtherwise, disable TUI using the `--disable-tui` flag."
+ )
+
+ return fn(*args, **kwargs)
+
+ return _inner
+
+
+# Possibility for the user to cancel deletion
+_CANCEL_DELETION_STR = "CANCEL_DELETION"
+
+
+class DeleteCacheCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ delete_cache_parser = parser.add_parser("delete-cache", help="Delete revisions from the cache directory.")
+
+ delete_cache_parser.add_argument(
+ "--dir",
+ type=str,
+ default=None,
+ help="cache directory (optional). Default to the default HuggingFace cache.",
+ )
+
+ delete_cache_parser.add_argument(
+ "--disable-tui",
+ action="store_true",
+ help=(
+ "Disable Terminal User Interface (TUI) mode. Useful if your"
+ " platform/terminal doesn't support the multiselect menu."
+ ),
+ )
+
+ delete_cache_parser.set_defaults(func=DeleteCacheCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.cache_dir: Optional[str] = args.dir
+ self.disable_tui: bool = args.disable_tui
+
+ def run(self):
+ """Run `delete-cache` command with or without TUI."""
+ # Scan cache directory
+ hf_cache_info = scan_cache_dir(self.cache_dir)
+
+ # Manual review from the user
+ if self.disable_tui:
+ selected_hashes = _manual_review_no_tui(hf_cache_info, preselected=[])
+ else:
+ selected_hashes = _manual_review_tui(hf_cache_info, preselected=[])
+
+ # If deletion is not cancelled
+ if len(selected_hashes) > 0 and _CANCEL_DELETION_STR not in selected_hashes:
+ confirm_message = _get_expectations_str(hf_cache_info, selected_hashes) + " Confirm deletion ?"
+
+ # Confirm deletion
+ if self.disable_tui:
+ confirmed = _ask_for_confirmation_no_tui(confirm_message)
+ else:
+ confirmed = _ask_for_confirmation_tui(confirm_message)
+
+ # Deletion is confirmed
+ if confirmed:
+ strategy = hf_cache_info.delete_revisions(*selected_hashes)
+ print("Start deletion.")
+ strategy.execute()
+ print(
+ f"Done. Deleted {len(strategy.repos)} repo(s) and"
+ f" {len(strategy.snapshots)} revision(s) for a total of"
+ f" {strategy.expected_freed_size_str}."
+ )
+ return
+
+ # Deletion is cancelled
+ print("Deletion is cancelled. Do nothing.")
+
+
+@require_inquirer_py
+def _manual_review_tui(hf_cache_info: HFCacheInfo, preselected: List[str]) -> List[str]:
+ """Ask the user for a manual review of the revisions to delete.
+
+ Displays a multi-select menu in the terminal (TUI).
+ """
+ # Define multiselect list
+ choices = _get_tui_choices_from_scan(repos=hf_cache_info.repos, preselected=preselected)
+ checkbox = inquirer.checkbox(
+ message="Select revisions to delete:",
+ choices=choices, # List of revisions with some pre-selection
+ cycle=False, # No loop between top and bottom
+ height=100, # Large list if possible
+ # We use the instruction to display to the user the expected effect of the
+ # deletion.
+ instruction=_get_expectations_str(
+ hf_cache_info,
+ selected_hashes=[c.value for c in choices if isinstance(c, Choice) and c.enabled],
+ ),
+ # We use the long instruction to should keybindings instructions to the user
+ long_instruction="Press to select, to validate and to quit without modification.",
+ # Message that is displayed once the user validates its selection.
+ transformer=lambda result: f"{len(result)} revision(s) selected.",
+ )
+
+ # Add a callback to update the information line when a revision is
+ # selected/unselected
+ def _update_expectations(_) -> None:
+ # Hacky way to dynamically set an instruction message to the checkbox when
+ # a revision hash is selected/unselected.
+ checkbox._instruction = _get_expectations_str(
+ hf_cache_info,
+ selected_hashes=[choice["value"] for choice in checkbox.content_control.choices if choice["enabled"]],
+ )
+
+ checkbox.kb_func_lookup["toggle"].append({"func": _update_expectations})
+
+ # Finally display the form to the user.
+ try:
+ return checkbox.execute()
+ except KeyboardInterrupt:
+ return [] # Quit without deletion
+
+
+@require_inquirer_py
+def _ask_for_confirmation_tui(message: str, default: bool = True) -> bool:
+ """Ask for confirmation using Inquirer."""
+ return inquirer.confirm(message, default=default).execute()
+
+
+def _get_tui_choices_from_scan(repos: Iterable[CachedRepoInfo], preselected: List[str]) -> List:
+ """Build a list of choices from the scanned repos.
+
+ Args:
+ repos (*Iterable[`CachedRepoInfo`]*):
+ List of scanned repos on which we want to delete revisions.
+ preselected (*List[`str`]*):
+ List of revision hashes that will be preselected.
+
+ Return:
+ The list of choices to pass to `inquirer.checkbox`.
+ """
+ choices: List[Union[Choice, Separator]] = []
+
+ # First choice is to cancel the deletion. If selected, nothing will be deleted,
+ # no matter the other selected items.
+ choices.append(
+ Choice(
+ _CANCEL_DELETION_STR,
+ name="None of the following (if selected, nothing will be deleted).",
+ enabled=False,
+ )
+ )
+
+ # Display a separator per repo and a Choice for each revisions of the repo
+ for repo in sorted(repos, key=_repo_sorting_order):
+ # Repo as separator
+ choices.append(
+ Separator(
+ f"\n{repo.repo_type.capitalize()} {repo.repo_id} ({repo.size_on_disk_str},"
+ f" used {repo.last_accessed_str})"
+ )
+ )
+ for revision in sorted(repo.revisions, key=_revision_sorting_order):
+ # Revision as choice
+ choices.append(
+ Choice(
+ revision.commit_hash,
+ name=(
+ f"{revision.commit_hash[:8]}:"
+ f" {', '.join(sorted(revision.refs)) or '(detached)'} #"
+ f" modified {revision.last_modified_str}"
+ ),
+ enabled=revision.commit_hash in preselected,
+ )
+ )
+
+ # Return choices
+ return choices
+
+
+def _manual_review_no_tui(hf_cache_info: HFCacheInfo, preselected: List[str]) -> List[str]:
+ """Ask the user for a manual review of the revisions to delete.
+
+ Used when TUI is disabled. Manual review happens in a separate tmp file that the
+ user can manually edit.
+ """
+ # 1. Generate temporary file with delete commands.
+ fd, tmp_path = mkstemp(suffix=".txt") # suffix to make it easier to find by editors
+ os.close(fd)
+
+ lines = []
+ for repo in sorted(hf_cache_info.repos, key=_repo_sorting_order):
+ lines.append(
+ f"\n# {repo.repo_type.capitalize()} {repo.repo_id} ({repo.size_on_disk_str},"
+ f" used {repo.last_accessed_str})"
+ )
+ for revision in sorted(repo.revisions, key=_revision_sorting_order):
+ lines.append(
+ # Deselect by prepending a '#'
+ f"{'' if revision.commit_hash in preselected else '#'} "
+ f" {revision.commit_hash} # Refs:"
+ # Print `refs` as comment on same line
+ f" {', '.join(sorted(revision.refs)) or '(detached)'} # modified"
+ # Print `last_modified` as comment on same line
+ f" {revision.last_modified_str}"
+ )
+
+ with open(tmp_path, "w") as f:
+ f.write(_MANUAL_REVIEW_NO_TUI_INSTRUCTIONS)
+ f.write("\n".join(lines))
+
+ # 2. Prompt instructions to user.
+ instructions = f"""
+ TUI is disabled. In order to select which revisions you want to delete, please edit
+ the following file using the text editor of your choice. Instructions for manual
+ editing are located at the beginning of the file. Edit the file, save it and confirm
+ to continue.
+ File to edit: {ANSI.bold(tmp_path)}
+ """
+ print("\n".join(line.strip() for line in instructions.strip().split("\n")))
+
+ # 3. Wait for user confirmation.
+ while True:
+ selected_hashes = _read_manual_review_tmp_file(tmp_path)
+ if _ask_for_confirmation_no_tui(
+ _get_expectations_str(hf_cache_info, selected_hashes) + " Continue ?",
+ default=False,
+ ):
+ break
+
+ # 4. Return selected_hashes
+ os.remove(tmp_path)
+ return selected_hashes
+
+
+def _ask_for_confirmation_no_tui(message: str, default: bool = True) -> bool:
+ """Ask for confirmation using pure-python."""
+ YES = ("y", "yes", "1")
+ NO = ("n", "no", "0")
+ DEFAULT = ""
+ ALL = YES + NO + (DEFAULT,)
+ full_message = message + (" (Y/n) " if default else " (y/N) ")
+ while True:
+ answer = input(full_message).lower()
+ if answer == DEFAULT:
+ return default
+ if answer in YES:
+ return True
+ if answer in NO:
+ return False
+ print(f"Invalid input. Must be one of {ALL}")
+
+
+def _get_expectations_str(hf_cache_info: HFCacheInfo, selected_hashes: List[str]) -> str:
+ """Format a string to display to the user how much space would be saved.
+
+ Example:
+ ```
+ >>> _get_expectations_str(hf_cache_info, selected_hashes)
+ '7 revisions selected counting for 4.3G.'
+ ```
+ """
+ if _CANCEL_DELETION_STR in selected_hashes:
+ return "Nothing will be deleted."
+ strategy = hf_cache_info.delete_revisions(*selected_hashes)
+ return f"{len(selected_hashes)} revisions selected counting for {strategy.expected_freed_size_str}."
+
+
+def _read_manual_review_tmp_file(tmp_path: str) -> List[str]:
+ """Read the manually reviewed instruction file and return a list of revision hash.
+
+ Example:
+ ```txt
+ # This is the tmp file content
+ ###
+
+ # Commented out line
+ 123456789 # revision hash
+
+ # Something else
+ # a_newer_hash # 2 days ago
+ an_older_hash # 3 days ago
+ ```
+
+ ```py
+ >>> _read_manual_review_tmp_file(tmp_path)
+ ['123456789', 'an_older_hash']
+ ```
+ """
+ with open(tmp_path) as f:
+ content = f.read()
+
+ # Split lines
+ lines = [line.strip() for line in content.split("\n")]
+
+ # Filter commented lines
+ selected_lines = [line for line in lines if not line.startswith("#")]
+
+ # Select only before comment
+ selected_hashes = [line.split("#")[0].strip() for line in selected_lines]
+
+ # Return revision hashes
+ return [hash for hash in selected_hashes if len(hash) > 0]
+
+
+_MANUAL_REVIEW_NO_TUI_INSTRUCTIONS = f"""
+# INSTRUCTIONS
+# ------------
+# This is a temporary file created by running `huggingface-cli delete-cache` with the
+# `--disable-tui` option. It contains a set of revisions that can be deleted from your
+# local cache directory.
+#
+# Please manually review the revisions you want to delete:
+# - Revision hashes can be commented out with '#'.
+# - Only non-commented revisions in this file will be deleted.
+# - Revision hashes that are removed from this file are ignored as well.
+# - If `{_CANCEL_DELETION_STR}` line is uncommented, the all cache deletion is cancelled and
+# no changes will be applied.
+#
+# Once you've manually reviewed this file, please confirm deletion in the terminal. This
+# file will be automatically removed once done.
+# ------------
+
+# KILL SWITCH
+# ------------
+# Un-comment following line to completely cancel the deletion process
+# {_CANCEL_DELETION_STR}
+# ------------
+
+# REVISIONS
+# ------------
+""".strip()
+
+
+def _repo_sorting_order(repo: CachedRepoInfo) -> Any:
+ # First split by Dataset/Model, then sort by last accessed (oldest first)
+ return (repo.repo_type, repo.last_accessed)
+
+
+def _revision_sorting_order(revision: CachedRevisionInfo) -> Any:
+ # Sort by last modified (oldest first)
+ return revision.last_modified
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/download.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/download.py
new file mode 100644
index 0000000000000000000000000000000000000000..1de7738a14fa7dc9fc89577013532c44b50caf02
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/download.py
@@ -0,0 +1,215 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to download files from the Hub with the CLI.
+
+Usage:
+ huggingface-cli download --help
+
+ # Download file
+ huggingface-cli download gpt2 config.json
+
+ # Download entire repo
+ huggingface-cli download fffiloni/zeroscope --repo-type=space --revision=refs/pr/78
+
+ # Download repo with filters
+ huggingface-cli download gpt2 --include="*.safetensors"
+
+ # Download with token
+ huggingface-cli download Wauplin/private-model --token=hf_***
+
+ # Download quietly (no progress bar, no warnings, only the returned path)
+ huggingface-cli download gpt2 config.json --quiet
+
+ # Download to local dir
+ huggingface-cli download gpt2 --local-dir=./models/gpt2
+"""
+
+import warnings
+from argparse import Namespace, _SubParsersAction
+from typing import List, Literal, Optional, Union
+
+from huggingface_hub import logging
+from huggingface_hub._snapshot_download import snapshot_download
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import HF_HUB_ENABLE_HF_TRANSFER
+from huggingface_hub.file_download import hf_hub_download
+from huggingface_hub.utils import disable_progress_bars, enable_progress_bars
+
+
+logger = logging.get_logger(__name__)
+
+
+class DownloadCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ download_parser = parser.add_parser("download", help="Download files from the Hub")
+ download_parser.add_argument(
+ "repo_id", type=str, help="ID of the repo to download from (e.g. `username/repo-name`)."
+ )
+ download_parser.add_argument(
+ "filenames", type=str, nargs="*", help="Files to download (e.g. `config.json`, `data/metadata.jsonl`)."
+ )
+ download_parser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Type of repo to download from (defaults to 'model').",
+ )
+ download_parser.add_argument(
+ "--revision",
+ type=str,
+ help="An optional Git revision id which can be a branch name, a tag, or a commit hash.",
+ )
+ download_parser.add_argument(
+ "--include", nargs="*", type=str, help="Glob patterns to match files to download."
+ )
+ download_parser.add_argument(
+ "--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to download."
+ )
+ download_parser.add_argument(
+ "--cache-dir", type=str, help="Path to the directory where to save the downloaded files."
+ )
+ download_parser.add_argument(
+ "--local-dir",
+ type=str,
+ help=(
+ "If set, the downloaded file will be placed under this directory either as a symlink (default) or a"
+ " regular file. Check out"
+ " https://huggingface.co/docs/huggingface_hub/guides/download#download-files-to-local-folder for more"
+ " details."
+ ),
+ )
+ download_parser.add_argument(
+ "--local-dir-use-symlinks",
+ choices=["auto", "True", "False"],
+ default="auto",
+ help=(
+ "To be used with `local_dir`. If set to 'auto', the cache directory will be used and the file will be"
+ " either duplicated or symlinked to the local directory depending on its size. It set to `True`, a"
+ " symlink will be created, no matter the file size. If set to `False`, the file will either be"
+ " duplicated from cache (if already exists) or downloaded from the Hub and not cached."
+ ),
+ )
+ download_parser.add_argument(
+ "--force-download",
+ action="store_true",
+ help="If True, the files will be downloaded even if they are already cached.",
+ )
+ download_parser.add_argument(
+ "--resume-download", action="store_true", help="If True, resume a previously interrupted download."
+ )
+ download_parser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
+ )
+ download_parser.add_argument(
+ "--quiet",
+ action="store_true",
+ help="If True, progress bars are disabled and only the path to the download files is printed.",
+ )
+ download_parser.set_defaults(func=DownloadCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.token = args.token
+ self.repo_id: str = args.repo_id
+ self.filenames: List[str] = args.filenames
+ self.repo_type: str = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.include: Optional[List[str]] = args.include
+ self.exclude: Optional[List[str]] = args.exclude
+ self.cache_dir: Optional[str] = args.cache_dir
+ self.local_dir: Optional[str] = args.local_dir
+ self.force_download: bool = args.force_download
+ self.resume_download: bool = args.resume_download
+ self.quiet: bool = args.quiet
+
+ # Raise if local_dir_use_symlinks is invalid
+ self.local_dir_use_symlinks: Union[Literal["auto"], bool]
+ use_symlinks_lowercase = args.local_dir_use_symlinks.lower()
+ if use_symlinks_lowercase == "true":
+ self.local_dir_use_symlinks = True
+ elif use_symlinks_lowercase == "false":
+ self.local_dir_use_symlinks = False
+ elif use_symlinks_lowercase == "auto":
+ self.local_dir_use_symlinks = "auto"
+ else:
+ raise ValueError(
+ f"'{args.local_dir_use_symlinks}' is not a valid value for `local_dir_use_symlinks`. It must be either"
+ " 'auto', 'True' or 'False'."
+ )
+
+ def run(self) -> None:
+ if self.quiet:
+ disable_progress_bars()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ print(self._download()) # Print path to downloaded files
+ enable_progress_bars()
+ else:
+ logging.set_verbosity_info()
+ print(self._download()) # Print path to downloaded files
+ logging.set_verbosity_warning()
+
+ def _download(self) -> str:
+ # Warn user if patterns are ignored
+ if len(self.filenames) > 0:
+ if self.include is not None and len(self.include) > 0:
+ warnings.warn("Ignoring `--include` since filenames have being explicitly set.")
+ if self.exclude is not None and len(self.exclude) > 0:
+ warnings.warn("Ignoring `--exclude` since filenames have being explicitly set.")
+
+ if not HF_HUB_ENABLE_HF_TRANSFER:
+ logger.info(
+ "Consider using `hf_transfer` for faster downloads. This solution comes with some limitations. See"
+ " https://huggingface.co/docs/huggingface_hub/hf_transfer for more details."
+ )
+
+ # Single file to download: use `hf_hub_download`
+ if len(self.filenames) == 1:
+ return hf_hub_download(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ filename=self.filenames[0],
+ cache_dir=self.cache_dir,
+ resume_download=self.resume_download,
+ force_download=self.force_download,
+ token=self.token,
+ local_dir=self.local_dir,
+ local_dir_use_symlinks=self.local_dir_use_symlinks,
+ library_name="huggingface-cli",
+ )
+
+ # Otherwise: use `snapshot_download` to ensure all files comes from same revision
+ elif len(self.filenames) == 0:
+ allow_patterns = self.include
+ ignore_patterns = self.exclude
+ else:
+ allow_patterns = self.filenames
+ ignore_patterns = None
+
+ return snapshot_download(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ resume_download=self.resume_download,
+ force_download=self.force_download,
+ cache_dir=self.cache_dir,
+ token=self.token,
+ local_dir=self.local_dir,
+ local_dir_use_symlinks=self.local_dir_use_symlinks,
+ library_name="huggingface-cli",
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/env.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..23f2828bbfebda0a633b4b3c6883432e4a534c79
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/env.py
@@ -0,0 +1,36 @@
+# Copyright 2022 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to print information about the environment.
+
+Usage:
+ huggingface-cli env
+"""
+
+from argparse import _SubParsersAction
+
+from ..utils import dump_environment_info
+from . import BaseHuggingfaceCLICommand
+
+
+class EnvironmentCommand(BaseHuggingfaceCLICommand):
+ def __init__(self, args):
+ self.args = args
+
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ env_parser = parser.add_parser("env", help="Print information about the environment.")
+ env_parser.set_defaults(func=EnvironmentCommand)
+
+ def run(self) -> None:
+ dump_environment_info()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/huggingface_cli.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/huggingface_cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..39b6dfe49ab681f80dea6751e473843e5f685ff3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/huggingface_cli.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from argparse import ArgumentParser
+
+from huggingface_hub.commands.delete_cache import DeleteCacheCommand
+from huggingface_hub.commands.download import DownloadCommand
+from huggingface_hub.commands.env import EnvironmentCommand
+from huggingface_hub.commands.lfs import LfsCommands
+from huggingface_hub.commands.scan_cache import ScanCacheCommand
+from huggingface_hub.commands.upload import UploadCommand
+from huggingface_hub.commands.user import UserCommands
+
+
+def main():
+ parser = ArgumentParser("huggingface-cli", usage="huggingface-cli []")
+ commands_parser = parser.add_subparsers(help="huggingface-cli command helpers")
+
+ # Register commands
+ EnvironmentCommand.register_subcommand(commands_parser)
+ UserCommands.register_subcommand(commands_parser)
+ UploadCommand.register_subcommand(commands_parser)
+ DownloadCommand.register_subcommand(commands_parser)
+ LfsCommands.register_subcommand(commands_parser)
+ ScanCacheCommand.register_subcommand(commands_parser)
+ DeleteCacheCommand.register_subcommand(commands_parser)
+
+ # Let's go
+ args = parser.parse_args()
+
+ if not hasattr(args, "func"):
+ parser.print_help()
+ exit(1)
+
+ # Run
+ service = args.func(args)
+ service.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/lfs.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/lfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dbf3cf55c67beebf4e6959ef180b30e29341a7c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/lfs.py
@@ -0,0 +1,199 @@
+"""
+Implementation of a custom transfer agent for the transfer type "multipart" for
+git-lfs.
+
+Inspired by:
+github.com/cbartz/git-lfs-swift-transfer-agent/blob/master/git_lfs_swift_transfer.py
+
+Spec is: github.com/git-lfs/git-lfs/blob/master/docs/custom-transfers.md
+
+
+To launch debugger while developing:
+
+``` [lfs "customtransfer.multipart"]
+path = /path/to/huggingface_hub/.env/bin/python args = -m debugpy --listen 5678
+--wait-for-client
+/path/to/huggingface_hub/src/huggingface_hub/commands/huggingface_cli.py
+lfs-multipart-upload ```"""
+
+import json
+import os
+import subprocess
+import sys
+from argparse import _SubParsersAction
+from typing import Dict, List, Optional
+
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.lfs import LFS_MULTIPART_UPLOAD_COMMAND, SliceFileObj
+
+from ..utils import get_session, hf_raise_for_status, logging
+
+
+logger = logging.get_logger(__name__)
+
+
+class LfsCommands(BaseHuggingfaceCLICommand):
+ """
+ Implementation of a custom transfer agent for the transfer type "multipart"
+ for git-lfs. This lets users upload large files >5GB 🔥. Spec for LFS custom
+ transfer agent is:
+ https://github.com/git-lfs/git-lfs/blob/master/docs/custom-transfers.md
+
+ This introduces two commands to the CLI:
+
+ 1. $ huggingface-cli lfs-enable-largefiles
+
+ This should be executed once for each model repo that contains a model file
+ >5GB. It's documented in the error message you get if you just try to git
+ push a 5GB file without having enabled it before.
+
+ 2. $ huggingface-cli lfs-multipart-upload
+
+ This command is called by lfs directly and is not meant to be called by the
+ user.
+ """
+
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ enable_parser = parser.add_parser(
+ "lfs-enable-largefiles", help="Configure your repository to enable upload of files > 5GB."
+ )
+ enable_parser.add_argument("path", type=str, help="Local path to repository you want to configure.")
+ enable_parser.set_defaults(func=lambda args: LfsEnableCommand(args))
+
+ # Command will get called by git-lfs, do not call it directly.
+ upload_parser = parser.add_parser(LFS_MULTIPART_UPLOAD_COMMAND, add_help=False)
+ upload_parser.set_defaults(func=lambda args: LfsUploadCommand(args))
+
+
+class LfsEnableCommand:
+ def __init__(self, args):
+ self.args = args
+
+ def run(self):
+ local_path = os.path.abspath(self.args.path)
+ if not os.path.isdir(local_path):
+ print("This does not look like a valid git repo.")
+ exit(1)
+ subprocess.run(
+ "git config lfs.customtransfer.multipart.path huggingface-cli".split(),
+ check=True,
+ cwd=local_path,
+ )
+ subprocess.run(
+ f"git config lfs.customtransfer.multipart.args {LFS_MULTIPART_UPLOAD_COMMAND}".split(),
+ check=True,
+ cwd=local_path,
+ )
+ print("Local repo set up for largefiles")
+
+
+def write_msg(msg: Dict):
+ """Write out the message in Line delimited JSON."""
+ msg_str = json.dumps(msg) + "\n"
+ sys.stdout.write(msg_str)
+ sys.stdout.flush()
+
+
+def read_msg() -> Optional[Dict]:
+ """Read Line delimited JSON from stdin."""
+ msg = json.loads(sys.stdin.readline().strip())
+
+ if "terminate" in (msg.get("type"), msg.get("event")):
+ # terminate message received
+ return None
+
+ if msg.get("event") not in ("download", "upload"):
+ logger.critical("Received unexpected message")
+ sys.exit(1)
+
+ return msg
+
+
+class LfsUploadCommand:
+ def __init__(self, args) -> None:
+ self.args = args
+
+ def run(self) -> None:
+ # Immediately after invoking a custom transfer process, git-lfs
+ # sends initiation data to the process over stdin.
+ # This tells the process useful information about the configuration.
+ init_msg = json.loads(sys.stdin.readline().strip())
+ if not (init_msg.get("event") == "init" and init_msg.get("operation") == "upload"):
+ write_msg({"error": {"code": 32, "message": "Wrong lfs init operation"}})
+ sys.exit(1)
+
+ # The transfer process should use the information it needs from the
+ # initiation structure, and also perform any one-off setup tasks it
+ # needs to do. It should then respond on stdout with a simple empty
+ # confirmation structure, as follows:
+ write_msg({})
+
+ # After the initiation exchange, git-lfs will send any number of
+ # transfer requests to the stdin of the transfer process, in a serial sequence.
+ while True:
+ msg = read_msg()
+ if msg is None:
+ # When all transfers have been processed, git-lfs will send
+ # a terminate event to the stdin of the transfer process.
+ # On receiving this message the transfer process should
+ # clean up and terminate. No response is expected.
+ sys.exit(0)
+
+ oid = msg["oid"]
+ filepath = msg["path"]
+ completion_url = msg["action"]["href"]
+ header = msg["action"]["header"]
+ chunk_size = int(header.pop("chunk_size"))
+ presigned_urls: List[str] = list(header.values())
+
+ # Send a "started" progress event to allow other workers to start.
+ # Otherwise they're delayed until first "progress" event is reported,
+ # i.e. after the first 5GB by default (!)
+ write_msg(
+ {
+ "event": "progress",
+ "oid": oid,
+ "bytesSoFar": 1,
+ "bytesSinceLast": 0,
+ }
+ )
+
+ parts = []
+ with open(filepath, "rb") as file:
+ for i, presigned_url in enumerate(presigned_urls):
+ with SliceFileObj(
+ file,
+ seek_from=i * chunk_size,
+ read_limit=chunk_size,
+ ) as data:
+ r = get_session().put(presigned_url, data=data)
+ hf_raise_for_status(r)
+ parts.append(
+ {
+ "etag": r.headers.get("etag"),
+ "partNumber": i + 1,
+ }
+ )
+ # In order to support progress reporting while data is uploading / downloading,
+ # the transfer process should post messages to stdout
+ write_msg(
+ {
+ "event": "progress",
+ "oid": oid,
+ "bytesSoFar": (i + 1) * chunk_size,
+ "bytesSinceLast": chunk_size,
+ }
+ )
+ # Not precise but that's ok.
+
+ r = get_session().post(
+ completion_url,
+ json={
+ "oid": oid,
+ "parts": parts,
+ },
+ )
+ hf_raise_for_status(r)
+
+ write_msg({"event": "complete", "oid": oid})
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/scan_cache.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/scan_cache.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0ab3399be86799fe2cd79f1feb515994a0f479f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/scan_cache.py
@@ -0,0 +1,139 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to scan the HF cache directory.
+
+Usage:
+ huggingface-cli scan-cache
+ huggingface-cli scan-cache -v
+ huggingface-cli scan-cache -vvv
+ huggingface-cli scan-cache --dir ~/.cache/huggingface/hub
+"""
+
+import time
+from argparse import Namespace, _SubParsersAction
+from typing import Optional
+
+from ..utils import CacheNotFound, HFCacheInfo, scan_cache_dir
+from . import BaseHuggingfaceCLICommand
+from ._cli_utils import ANSI, tabulate
+
+
+class ScanCacheCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ scan_cache_parser = parser.add_parser("scan-cache", help="Scan cache directory.")
+
+ scan_cache_parser.add_argument(
+ "--dir",
+ type=str,
+ default=None,
+ help="cache directory to scan (optional). Default to the default HuggingFace cache.",
+ )
+ scan_cache_parser.add_argument(
+ "-v",
+ "--verbose",
+ action="count",
+ default=0,
+ help="show a more verbose output",
+ )
+ scan_cache_parser.set_defaults(func=ScanCacheCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.verbosity: int = args.verbose
+ self.cache_dir: Optional[str] = args.dir
+
+ def run(self):
+ try:
+ t0 = time.time()
+ hf_cache_info = scan_cache_dir(self.cache_dir)
+ t1 = time.time()
+ except CacheNotFound as exc:
+ cache_dir = exc.cache_dir
+ print(f"Cache directory not found: {cache_dir}")
+ return
+
+ self._print_hf_cache_info_as_table(hf_cache_info)
+
+ print(
+ f"\nDone in {round(t1-t0,1)}s. Scanned {len(hf_cache_info.repos)} repo(s)"
+ f" for a total of {ANSI.red(hf_cache_info.size_on_disk_str)}."
+ )
+ if len(hf_cache_info.warnings) > 0:
+ message = f"Got {len(hf_cache_info.warnings)} warning(s) while scanning."
+ if self.verbosity >= 3:
+ print(ANSI.gray(message))
+ for warning in hf_cache_info.warnings:
+ print(ANSI.gray(warning))
+ else:
+ print(ANSI.gray(message + " Use -vvv to print details."))
+
+ def _print_hf_cache_info_as_table(self, hf_cache_info: HFCacheInfo) -> None:
+ if self.verbosity == 0:
+ print(
+ tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ "{:>12}".format(repo.size_on_disk_str),
+ repo.nb_files,
+ repo.last_accessed_str,
+ repo.last_modified_str,
+ ", ".join(sorted(repo.refs)),
+ str(repo.repo_path),
+ ]
+ for repo in sorted(hf_cache_info.repos, key=lambda repo: repo.repo_path)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_ACCESSED",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
+ )
+ else:
+ print(
+ tabulate(
+ rows=[
+ [
+ repo.repo_id,
+ repo.repo_type,
+ revision.commit_hash,
+ "{:>12}".format(revision.size_on_disk_str),
+ revision.nb_files,
+ revision.last_modified_str,
+ ", ".join(sorted(revision.refs)),
+ str(revision.snapshot_path),
+ ]
+ for repo in sorted(hf_cache_info.repos, key=lambda repo: repo.repo_path)
+ for revision in sorted(repo.revisions, key=lambda revision: revision.commit_hash)
+ ],
+ headers=[
+ "REPO ID",
+ "REPO TYPE",
+ "REVISION",
+ "SIZE ON DISK",
+ "NB FILES",
+ "LAST_MODIFIED",
+ "REFS",
+ "LOCAL PATH",
+ ],
+ )
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/upload.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/upload.py
new file mode 100644
index 0000000000000000000000000000000000000000..a42dd0f8824d067b1c7952080a075c6f67cee7c4
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/upload.py
@@ -0,0 +1,298 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains command to upload a repo or file with the CLI.
+
+Usage:
+ # Upload file (implicit)
+ huggingface-cli upload my-cool-model ./my-cool-model.safetensors
+
+ # Upload file (explicit)
+ huggingface-cli upload my-cool-model ./my-cool-model.safetensors model.safetensors
+
+ # Upload directory (implicit). If `my-cool-model/` is a directory it will be uploaded, otherwise an exception is raised.
+ huggingface-cli upload my-cool-model
+
+ # Upload directory (explicit)
+ huggingface-cli upload my-cool-model ./models/my-cool-model .
+
+ # Upload filtered directory (example: tensorboard logs except for the last run)
+ huggingface-cli upload my-cool-model ./model/training /logs --include "*.tfevents.*" --exclude "*20230905*"
+
+ # Upload private dataset
+ huggingface-cli upload Wauplin/my-cool-dataset ./data . --repo-type=dataset --private
+
+ # Upload with token
+ huggingface-cli upload Wauplin/my-cool-model --token=hf_****
+
+ # Sync local Space with Hub (upload new files, delete removed files)
+ huggingface-cli upload Wauplin/space-example --repo-type=space --exclude="/logs/*" --delete="*" --commit-message="Sync local Space with Hub"
+
+ # Schedule commits every 30 minutes
+ huggingface-cli upload Wauplin/my-cool-model --every=30
+"""
+
+import os
+import time
+import warnings
+from argparse import Namespace, _SubParsersAction
+from typing import List, Optional
+
+from huggingface_hub import logging
+from huggingface_hub._commit_scheduler import CommitScheduler
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import HF_HUB_ENABLE_HF_TRANSFER
+from huggingface_hub.hf_api import HfApi
+from huggingface_hub.utils import RevisionNotFoundError, disable_progress_bars, enable_progress_bars
+
+
+logger = logging.get_logger(__name__)
+
+
+class UploadCommand(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ upload_parser = parser.add_parser("upload", help="Upload a file or a folder to a repo on the Hub")
+ upload_parser.add_argument(
+ "repo_id", type=str, help="The ID of the repo to upload to (e.g. `username/repo-name`)."
+ )
+ upload_parser.add_argument(
+ "local_path", nargs="?", help="Local path to the file or folder to upload. Defaults to current directory."
+ )
+ upload_parser.add_argument(
+ "path_in_repo",
+ nargs="?",
+ help="Path of the file or folder in the repo. Defaults to the relative path of the file or folder.",
+ )
+ upload_parser.add_argument(
+ "--repo-type",
+ choices=["model", "dataset", "space"],
+ default="model",
+ help="Type of the repo to upload to (e.g. `dataset`).",
+ )
+ upload_parser.add_argument(
+ "--revision",
+ type=str,
+ help=(
+ "An optional Git revision to push to. It can be a branch name or a PR reference. If revision does not"
+ " exist and `--create-pr` is not set, a branch will be automatically created."
+ ),
+ )
+ upload_parser.add_argument(
+ "--private",
+ action="store_true",
+ help=(
+ "Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already"
+ " exists."
+ ),
+ )
+ upload_parser.add_argument("--include", nargs="*", type=str, help="Glob patterns to match files to upload.")
+ upload_parser.add_argument(
+ "--exclude", nargs="*", type=str, help="Glob patterns to exclude from files to upload."
+ )
+ upload_parser.add_argument(
+ "--delete",
+ nargs="*",
+ type=str,
+ help="Glob patterns for file to be deleted from the repo while committing.",
+ )
+ upload_parser.add_argument(
+ "--commit-message", type=str, help="The summary / title / first line of the generated commit."
+ )
+ upload_parser.add_argument("--commit-description", type=str, help="The description of the generated commit.")
+ upload_parser.add_argument(
+ "--create-pr", action="store_true", help="Whether to upload content as a new Pull Request."
+ )
+ upload_parser.add_argument(
+ "--every",
+ type=float,
+ help="If set, a background job is scheduled to create commits every `every` minutes.",
+ )
+ upload_parser.add_argument(
+ "--token", type=str, help="A User Access Token generated from https://huggingface.co/settings/tokens"
+ )
+ upload_parser.add_argument(
+ "--quiet",
+ action="store_true",
+ help="If True, progress bars are disabled and only the path to the uploaded files is printed.",
+ )
+ upload_parser.set_defaults(func=UploadCommand)
+
+ def __init__(self, args: Namespace) -> None:
+ self.repo_id: str = args.repo_id
+ self.repo_type: Optional[str] = args.repo_type
+ self.revision: Optional[str] = args.revision
+ self.private: bool = args.private
+
+ self.include: Optional[List[str]] = args.include
+ self.exclude: Optional[List[str]] = args.exclude
+ self.delete: Optional[List[str]] = args.delete
+
+ self.commit_message: Optional[str] = args.commit_message
+ self.commit_description: Optional[str] = args.commit_description
+ self.create_pr: bool = args.create_pr
+ self.api: HfApi = HfApi(token=args.token, library_name="huggingface-cli")
+ self.quiet: bool = args.quiet # disable warnings and progress bars
+
+ # Check `--every` is valid
+ if args.every is not None and args.every <= 0:
+ raise ValueError(f"`every` must be a positive value (got '{args.every}')")
+ self.every: Optional[float] = args.every
+
+ # Resolve `local_path` and `path_in_repo`
+ repo_name: str = args.repo_id.split("/")[-1] # e.g. "Wauplin/my-cool-model" => "my-cool-model"
+ self.local_path: str
+ self.path_in_repo: str
+ if args.local_path is None and os.path.isfile(repo_name):
+ # Implicit case 1: user provided only a repo_id which happen to be a local file as well => upload it with same name
+ self.local_path = repo_name
+ self.path_in_repo = repo_name
+ elif args.local_path is None and os.path.isdir(repo_name):
+ # Implicit case 2: user provided only a repo_id which happen to be a local folder as well => upload it at root
+ self.local_path = repo_name
+ self.path_in_repo = "."
+ elif args.local_path is None:
+ # Implicit case 3: user provided only a repo_id that does not match a local file or folder
+ # => the user must explicitly provide a local_path => raise exception
+ raise ValueError(f"'{repo_name}' is not a local file or folder. Please set `local_path` explicitly.")
+ elif args.path_in_repo is None and os.path.isfile(args.local_path):
+ # Explicit local path to file, no path in repo => upload it at root with same name
+ self.local_path = args.local_path
+ self.path_in_repo = os.path.basename(args.local_path)
+ elif args.path_in_repo is None:
+ # Explicit local path to folder, no path in repo => upload at root
+ self.local_path = args.local_path
+ self.path_in_repo = "."
+ else:
+ # Finally, if both paths are explicit
+ self.local_path = args.local_path
+ self.path_in_repo = args.path_in_repo
+
+ def run(self) -> None:
+ if self.quiet:
+ disable_progress_bars()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ print(self._upload())
+ enable_progress_bars()
+ else:
+ logging.set_verbosity_info()
+ print(self._upload())
+ logging.set_verbosity_warning()
+
+ def _upload(self) -> str:
+ if os.path.isfile(self.local_path):
+ if self.include is not None and len(self.include) > 0:
+ warnings.warn("Ignoring `--include` since a single file is uploaded.")
+ if self.exclude is not None and len(self.exclude) > 0:
+ warnings.warn("Ignoring `--exclude` since a single file is uploaded.")
+ if self.delete is not None and len(self.delete) > 0:
+ warnings.warn("Ignoring `--delete` since a single file is uploaded.")
+
+ if not HF_HUB_ENABLE_HF_TRANSFER:
+ logger.info(
+ "Consider using `hf_transfer` for faster uploads. This solution comes with some limitations. See"
+ " https://huggingface.co/docs/huggingface_hub/hf_transfer for more details."
+ )
+
+ # Schedule commits if `every` is set
+ if self.every is not None:
+ if os.path.isfile(self.local_path):
+ # If file => watch entire folder + use allow_patterns
+ folder_path = os.path.dirname(self.local_path)
+ path_in_repo = (
+ self.path_in_repo[: -len(self.local_path)] # remove filename from path_in_repo
+ if self.path_in_repo.endswith(self.local_path)
+ else self.path_in_repo
+ )
+ allow_patterns = [self.local_path]
+ ignore_patterns = []
+ else:
+ folder_path = self.local_path
+ path_in_repo = self.path_in_repo
+ allow_patterns = self.include or []
+ ignore_patterns = self.exclude or []
+ if self.delete is not None and len(self.delete) > 0:
+ warnings.warn("Ignoring `--delete` when uploading with scheduled commits.")
+
+ scheduler = CommitScheduler(
+ folder_path=folder_path,
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ path_in_repo=path_in_repo,
+ private=self.private,
+ every=self.every,
+ hf_api=self.api,
+ )
+ print(f"Scheduling commits every {self.every} minutes to {scheduler.repo_id}.")
+ try: # Block main thread until KeyboardInterrupt
+ while True:
+ time.sleep(100)
+ except KeyboardInterrupt:
+ scheduler.stop()
+ return "Stopped scheduled commits."
+
+ # Otherwise, create repo and proceed with the upload
+ if not os.path.isfile(self.local_path) and not os.path.isdir(self.local_path):
+ raise FileNotFoundError(f"No such file or directory: '{self.local_path}'.")
+ repo_id = self.api.create_repo(
+ repo_id=self.repo_id,
+ repo_type=self.repo_type,
+ exist_ok=True,
+ private=self.private,
+ space_sdk="gradio" if self.repo_type == "space" else None,
+ # ^ We don't want it to fail when uploading to a Space => let's set Gradio by default.
+ # ^ I'd rather not add CLI args to set it explicitly as we already have `huggingface-cli repo create` for that.
+ ).repo_id
+
+ # Check if branch already exists and if not, create it
+ if self.revision is not None and not self.create_pr:
+ try:
+ self.api.repo_info(repo_id=repo_id, repo_type=self.repo_type, revision=self.revision)
+ except RevisionNotFoundError:
+ logger.info(f"Branch '{self.revision}' not found. Creating it...")
+ self.api.create_branch(repo_id=repo_id, repo_type=self.repo_type, branch=self.revision, exist_ok=True)
+ # ^ `exist_ok=True` to avoid race concurrency issues
+
+ # File-based upload
+ if os.path.isfile(self.local_path):
+ return self.api.upload_file(
+ path_or_fileobj=self.local_path,
+ path_in_repo=self.path_in_repo,
+ repo_id=repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ commit_message=self.commit_message,
+ commit_description=self.commit_description,
+ create_pr=self.create_pr,
+ )
+
+ # Folder-based upload
+ else:
+ return self.api.upload_folder(
+ folder_path=self.local_path,
+ path_in_repo=self.path_in_repo,
+ repo_id=repo_id,
+ repo_type=self.repo_type,
+ revision=self.revision,
+ commit_message=self.commit_message,
+ commit_description=self.commit_description,
+ create_pr=self.create_pr,
+ allow_patterns=self.include,
+ ignore_patterns=self.exclude,
+ delete_patterns=self.delete,
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/commands/user.py b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cde3ac04c8a0773cad0a767a8d14b36dce4ee2e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/commands/user.py
@@ -0,0 +1,188 @@
+# Copyright 2020 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import subprocess
+from argparse import _SubParsersAction
+
+from requests.exceptions import HTTPError
+
+from huggingface_hub.commands import BaseHuggingfaceCLICommand
+from huggingface_hub.constants import (
+ ENDPOINT,
+ REPO_TYPES,
+ REPO_TYPES_URL_PREFIXES,
+ SPACES_SDK_TYPES,
+)
+from huggingface_hub.hf_api import HfApi
+
+from .._login import ( # noqa: F401 # for backward compatibility # noqa: F401 # for backward compatibility
+ NOTEBOOK_LOGIN_PASSWORD_HTML,
+ NOTEBOOK_LOGIN_TOKEN_HTML_END,
+ NOTEBOOK_LOGIN_TOKEN_HTML_START,
+ login,
+ logout,
+ notebook_login,
+)
+from ..utils import get_token
+from ._cli_utils import ANSI
+
+
+class UserCommands(BaseHuggingfaceCLICommand):
+ @staticmethod
+ def register_subcommand(parser: _SubParsersAction):
+ login_parser = parser.add_parser("login", help="Log in using a token from huggingface.co/settings/tokens")
+ login_parser.add_argument(
+ "--token",
+ type=str,
+ help="Token generated from https://huggingface.co/settings/tokens",
+ )
+ login_parser.add_argument(
+ "--add-to-git-credential",
+ action="store_true",
+ help="Optional: Save token to git credential helper.",
+ )
+ login_parser.set_defaults(func=lambda args: LoginCommand(args))
+ whoami_parser = parser.add_parser("whoami", help="Find out which huggingface.co account you are logged in as.")
+ whoami_parser.set_defaults(func=lambda args: WhoamiCommand(args))
+ logout_parser = parser.add_parser("logout", help="Log out")
+ logout_parser.set_defaults(func=lambda args: LogoutCommand(args))
+
+ # new system: git-based repo system
+ repo_parser = parser.add_parser("repo", help="{create} Commands to interact with your huggingface.co repos.")
+ repo_subparsers = repo_parser.add_subparsers(help="huggingface.co repos related commands")
+ repo_create_parser = repo_subparsers.add_parser("create", help="Create a new repo on huggingface.co")
+ repo_create_parser.add_argument(
+ "name",
+ type=str,
+ help="Name for your repo. Will be namespaced under your username to build the repo id.",
+ )
+ repo_create_parser.add_argument(
+ "--type",
+ type=str,
+ help='Optional: repo_type: set to "dataset" or "space" if creating a dataset or space, default is model.',
+ )
+ repo_create_parser.add_argument("--organization", type=str, help="Optional: organization namespace.")
+ repo_create_parser.add_argument(
+ "--space_sdk",
+ type=str,
+ help='Optional: Hugging Face Spaces SDK type. Required when --type is set to "space".',
+ choices=SPACES_SDK_TYPES,
+ )
+ repo_create_parser.add_argument(
+ "-y",
+ "--yes",
+ action="store_true",
+ help="Optional: answer Yes to the prompt",
+ )
+ repo_create_parser.set_defaults(func=lambda args: RepoCreateCommand(args))
+
+
+class BaseUserCommand:
+ def __init__(self, args):
+ self.args = args
+ self._api = HfApi()
+
+
+class LoginCommand(BaseUserCommand):
+ def run(self):
+ login(token=self.args.token, add_to_git_credential=self.args.add_to_git_credential)
+
+
+class LogoutCommand(BaseUserCommand):
+ def run(self):
+ logout()
+
+
+class WhoamiCommand(BaseUserCommand):
+ def run(self):
+ token = get_token()
+ if token is None:
+ print("Not logged in")
+ exit()
+ try:
+ info = self._api.whoami(token)
+ print(info["name"])
+ orgs = [org["name"] for org in info["orgs"]]
+ if orgs:
+ print(ANSI.bold("orgs: "), ",".join(orgs))
+
+ if ENDPOINT != "https://huggingface.co":
+ print(f"Authenticated through private endpoint: {ENDPOINT}")
+ except HTTPError as e:
+ print(e)
+ print(ANSI.red(e.response.text))
+ exit(1)
+
+
+class RepoCreateCommand(BaseUserCommand):
+ def run(self):
+ token = get_token()
+ if token is None:
+ print("Not logged in")
+ exit(1)
+ try:
+ stdout = subprocess.check_output(["git", "--version"]).decode("utf-8")
+ print(ANSI.gray(stdout.strip()))
+ except FileNotFoundError:
+ print("Looks like you do not have git installed, please install.")
+
+ try:
+ stdout = subprocess.check_output(["git-lfs", "--version"]).decode("utf-8")
+ print(ANSI.gray(stdout.strip()))
+ except FileNotFoundError:
+ print(
+ ANSI.red(
+ "Looks like you do not have git-lfs installed, please install."
+ " You can install from https://git-lfs.github.com/."
+ " Then run `git lfs install` (you only have to do this once)."
+ )
+ )
+ print("")
+
+ user = self._api.whoami(token)["name"]
+ namespace = self.args.organization if self.args.organization is not None else user
+
+ repo_id = f"{namespace}/{self.args.name}"
+
+ if self.args.type not in REPO_TYPES:
+ print("Invalid repo --type")
+ exit(1)
+
+ if self.args.type in REPO_TYPES_URL_PREFIXES:
+ prefixed_repo_id = REPO_TYPES_URL_PREFIXES[self.args.type] + repo_id
+ else:
+ prefixed_repo_id = repo_id
+
+ print(f"You are about to create {ANSI.bold(prefixed_repo_id)}")
+
+ if not self.args.yes:
+ choice = input("Proceed? [Y/n] ").lower()
+ if not (choice == "" or choice == "y" or choice == "yes"):
+ print("Abort")
+ exit()
+ try:
+ url = self._api.create_repo(
+ repo_id=repo_id,
+ token=token,
+ repo_type=self.args.type,
+ space_sdk=self.args.space_sdk,
+ )
+ except HTTPError as e:
+ print(e)
+ print(ANSI.red(e.response.text))
+ exit(1)
+ print("\nYour repo now lives at:")
+ print(f" {ANSI.bold(url)}")
+ print("\nYou can clone it locally with the command below, and commit/push as usual.")
+ print(f"\n git clone {url}")
+ print("")
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/community.py b/.venv/lib/python3.10/site-packages/huggingface_hub/community.py
new file mode 100644
index 0000000000000000000000000000000000000000..387b0cc12174cafd9f4080d05cbc748e59a51b81
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/community.py
@@ -0,0 +1,355 @@
+"""
+Data structures to interact with Discussions and Pull Requests on the Hub.
+
+See [the Discussions and Pull Requests guide](https://huggingface.co/docs/hub/repositories-pull-requests-discussions)
+for more information on Pull Requests, Discussions, and the community tab.
+"""
+
+from dataclasses import dataclass
+from datetime import datetime
+from typing import List, Literal, Optional, Union
+
+from .constants import REPO_TYPE_MODEL
+from .utils import parse_datetime
+
+
+DiscussionStatus = Literal["open", "closed", "merged", "draft"]
+
+
+@dataclass
+class Discussion:
+ """
+ A Discussion or Pull Request on the Hub.
+
+ This dataclass is not intended to be instantiated directly.
+
+ Attributes:
+ title (`str`):
+ The title of the Discussion / Pull Request
+ status (`str`):
+ The status of the Discussion / Pull Request.
+ It must be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ * `"draft"` (only for Pull Requests )
+ num (`int`):
+ The number of the Discussion / Pull Request.
+ repo_id (`str`):
+ The id (`"{namespace}/{repo_name}"`) of the repo on which
+ the Discussion / Pull Request was open.
+ repo_type (`str`):
+ The type of the repo on which the Discussion / Pull Request was open.
+ Possible values are: `"model"`, `"dataset"`, `"space"`.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ is_pull_request (`bool`):
+ Whether or not this is a Pull Request.
+ created_at (`datetime`):
+ The `datetime` of creation of the Discussion / Pull Request.
+ endpoint (`str`):
+ Endpoint of the Hub. Default is https://huggingface.co.
+ git_reference (`str`, *optional*):
+ (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
+ url (`str`):
+ (property) URL of the discussion on the Hub.
+ """
+
+ title: str
+ status: DiscussionStatus
+ num: int
+ repo_id: str
+ repo_type: str
+ author: str
+ is_pull_request: bool
+ created_at: datetime
+ endpoint: str
+
+ @property
+ def git_reference(self) -> Optional[str]:
+ """
+ If this is a Pull Request , returns the git reference to which changes can be pushed.
+ Returns `None` otherwise.
+ """
+ if self.is_pull_request:
+ return f"refs/pr/{self.num}"
+ return None
+
+ @property
+ def url(self) -> str:
+ """Returns the URL of the discussion on the Hub."""
+ if self.repo_type is None or self.repo_type == REPO_TYPE_MODEL:
+ return f"{self.endpoint}/{self.repo_id}/discussions/{self.num}"
+ return f"{self.endpoint}/{self.repo_type}s/{self.repo_id}/discussions/{self.num}"
+
+
+@dataclass
+class DiscussionWithDetails(Discussion):
+ """
+ Subclass of [`Discussion`].
+
+ Attributes:
+ title (`str`):
+ The title of the Discussion / Pull Request
+ status (`str`):
+ The status of the Discussion / Pull Request.
+ It can be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ * `"draft"` (only for Pull Requests )
+ num (`int`):
+ The number of the Discussion / Pull Request.
+ repo_id (`str`):
+ The id (`"{namespace}/{repo_name}"`) of the repo on which
+ the Discussion / Pull Request was open.
+ repo_type (`str`):
+ The type of the repo on which the Discussion / Pull Request was open.
+ Possible values are: `"model"`, `"dataset"`, `"space"`.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ is_pull_request (`bool`):
+ Whether or not this is a Pull Request.
+ created_at (`datetime`):
+ The `datetime` of creation of the Discussion / Pull Request.
+ events (`list` of [`DiscussionEvent`])
+ The list of [`DiscussionEvents`] in this Discussion or Pull Request.
+ conflicting_files (`Union[List[str], bool, None]`, *optional*):
+ A list of conflicting files if this is a Pull Request.
+ `None` if `self.is_pull_request` is `False`.
+ `True` if there are conflicting files but the list can't be retrieved.
+ target_branch (`str`, *optional*):
+ The branch into which changes are to be merged if this is a
+ Pull Request . `None` if `self.is_pull_request` is `False`.
+ merge_commit_oid (`str`, *optional*):
+ If this is a merged Pull Request , this is set to the OID / SHA of
+ the merge commit, `None` otherwise.
+ diff (`str`, *optional*):
+ The git diff if this is a Pull Request , `None` otherwise.
+ endpoint (`str`):
+ Endpoint of the Hub. Default is https://huggingface.co.
+ git_reference (`str`, *optional*):
+ (property) Git reference to which changes can be pushed if this is a Pull Request, `None` otherwise.
+ url (`str`):
+ (property) URL of the discussion on the Hub.
+ """
+
+ events: List["DiscussionEvent"]
+ conflicting_files: Union[List[str], bool, None]
+ target_branch: Optional[str]
+ merge_commit_oid: Optional[str]
+ diff: Optional[str]
+
+
+@dataclass
+class DiscussionEvent:
+ """
+ An event in a Discussion or Pull Request.
+
+ Use concrete classes:
+ * [`DiscussionComment`]
+ * [`DiscussionStatusChange`]
+ * [`DiscussionCommit`]
+ * [`DiscussionTitleChange`]
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ """
+
+ id: str
+ type: str
+ created_at: datetime
+ author: str
+
+ _event: dict
+ """Stores the original event data, in case we need to access it later."""
+
+
+@dataclass
+class DiscussionComment(DiscussionEvent):
+ """A comment in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ content (`str`):
+ The raw markdown content of the comment. Mentions, links and images are not rendered.
+ edited (`bool`):
+ Whether or not this comment has been edited.
+ hidden (`bool`):
+ Whether or not this comment has been hidden.
+ """
+
+ content: str
+ edited: bool
+ hidden: bool
+
+ @property
+ def rendered(self) -> str:
+ """The rendered comment, as a HTML string"""
+ return self._event["data"]["latest"]["html"]
+
+ @property
+ def last_edited_at(self) -> datetime:
+ """The last edit time, as a `datetime` object."""
+ return parse_datetime(self._event["data"]["latest"]["updatedAt"])
+
+ @property
+ def last_edited_by(self) -> str:
+ """The last edit time, as a `datetime` object."""
+ return self._event["data"]["latest"].get("author", {}).get("name", "deleted")
+
+ @property
+ def edit_history(self) -> List[dict]:
+ """The edit history of the comment"""
+ return self._event["data"]["history"]
+
+ @property
+ def number_of_edits(self) -> int:
+ return len(self.edit_history)
+
+
+@dataclass
+class DiscussionStatusChange(DiscussionEvent):
+ """A change of status in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ new_status (`str`):
+ The status of the Discussion / Pull Request after the change.
+ It can be one of:
+ * `"open"`
+ * `"closed"`
+ * `"merged"` (only for Pull Requests )
+ """
+
+ new_status: str
+
+
+@dataclass
+class DiscussionCommit(DiscussionEvent):
+ """A commit in a Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ summary (`str`):
+ The summary of the commit.
+ oid (`str`):
+ The OID / SHA of the commit, as a hexadecimal string.
+ """
+
+ summary: str
+ oid: str
+
+
+@dataclass
+class DiscussionTitleChange(DiscussionEvent):
+ """A rename event in a Discussion / Pull Request.
+
+ Subclass of [`DiscussionEvent`].
+
+ Attributes:
+ id (`str`):
+ The ID of the event. An hexadecimal string.
+ type (`str`):
+ The type of the event.
+ created_at (`datetime`):
+ A [`datetime`](https://docs.python.org/3/library/datetime.html?highlight=datetime#datetime.datetime)
+ object holding the creation timestamp for the event.
+ author (`str`):
+ The username of the Discussion / Pull Request author.
+ Can be `"deleted"` if the user has been deleted since.
+ old_title (`str`):
+ The previous title for the Discussion / Pull Request.
+ new_title (`str`):
+ The new title.
+ """
+
+ old_title: str
+ new_title: str
+
+
+def deserialize_event(event: dict) -> DiscussionEvent:
+ """Instantiates a [`DiscussionEvent`] from a dict"""
+ event_id: str = event["id"]
+ event_type: str = event["type"]
+ created_at = parse_datetime(event["createdAt"])
+
+ common_args = dict(
+ id=event_id,
+ type=event_type,
+ created_at=created_at,
+ author=event.get("author", {}).get("name", "deleted"),
+ _event=event,
+ )
+
+ if event_type == "comment":
+ return DiscussionComment(
+ **common_args,
+ edited=event["data"]["edited"],
+ hidden=event["data"]["hidden"],
+ content=event["data"]["latest"]["raw"],
+ )
+ if event_type == "status-change":
+ return DiscussionStatusChange(
+ **common_args,
+ new_status=event["data"]["status"],
+ )
+ if event_type == "commit":
+ return DiscussionCommit(
+ **common_args,
+ summary=event["data"]["subject"],
+ oid=event["data"]["oid"],
+ )
+ if event_type == "title-change":
+ return DiscussionTitleChange(
+ **common_args,
+ old_title=event["data"]["from"],
+ new_title=event["data"]["to"],
+ )
+
+ return DiscussionEvent(**common_args)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/constants.py b/.venv/lib/python3.10/site-packages/huggingface_hub/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddebb6258078e21aec93f9a11e062d44620db2c1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/constants.py
@@ -0,0 +1,215 @@
+import os
+import re
+import typing
+from typing import Literal, Optional, Tuple
+
+
+# Possible values for env variables
+
+
+ENV_VARS_TRUE_VALUES = {"1", "ON", "YES", "TRUE"}
+ENV_VARS_TRUE_AND_AUTO_VALUES = ENV_VARS_TRUE_VALUES.union({"AUTO"})
+
+
+def _is_true(value: Optional[str]) -> bool:
+ if value is None:
+ return False
+ return value.upper() in ENV_VARS_TRUE_VALUES
+
+
+def _as_int(value: Optional[str]) -> Optional[int]:
+ if value is None:
+ return None
+ return int(value)
+
+
+# Constants for file downloads
+
+PYTORCH_WEIGHTS_NAME = "pytorch_model.bin"
+TF2_WEIGHTS_NAME = "tf_model.h5"
+TF_WEIGHTS_NAME = "model.ckpt"
+FLAX_WEIGHTS_NAME = "flax_model.msgpack"
+CONFIG_NAME = "config.json"
+REPOCARD_NAME = "README.md"
+DEFAULT_ETAG_TIMEOUT = 10
+DEFAULT_DOWNLOAD_TIMEOUT = 10
+DEFAULT_REQUEST_TIMEOUT = 10
+DOWNLOAD_CHUNK_SIZE = 10 * 1024 * 1024
+HF_TRANSFER_CONCURRENCY = 100
+
+# Constants for safetensors repos
+
+SAFETENSORS_SINGLE_FILE = "model.safetensors"
+SAFETENSORS_INDEX_FILE = "model.safetensors.index.json"
+SAFETENSORS_MAX_HEADER_LENGTH = 25_000_000
+
+# Git-related constants
+
+DEFAULT_REVISION = "main"
+REGEX_COMMIT_OID = re.compile(r"[A-Fa-f0-9]{5,40}")
+
+HUGGINGFACE_CO_URL_HOME = "https://huggingface.co/"
+
+_staging_mode = _is_true(os.environ.get("HUGGINGFACE_CO_STAGING"))
+
+_HF_DEFAULT_ENDPOINT = "https://huggingface.co"
+_HF_DEFAULT_STAGING_ENDPOINT = "https://hub-ci.huggingface.co"
+ENDPOINT = os.getenv("HF_ENDPOINT") or (_HF_DEFAULT_STAGING_ENDPOINT if _staging_mode else _HF_DEFAULT_ENDPOINT)
+
+HUGGINGFACE_CO_URL_TEMPLATE = ENDPOINT + "/{repo_id}/resolve/{revision}/{filename}"
+HUGGINGFACE_HEADER_X_REPO_COMMIT = "X-Repo-Commit"
+HUGGINGFACE_HEADER_X_LINKED_ETAG = "X-Linked-Etag"
+HUGGINGFACE_HEADER_X_LINKED_SIZE = "X-Linked-Size"
+
+INFERENCE_ENDPOINT = os.environ.get("HF_INFERENCE_ENDPOINT", "https://api-inference.huggingface.co")
+
+# See https://huggingface.co/docs/inference-endpoints/index
+INFERENCE_ENDPOINTS_ENDPOINT = "https://api.endpoints.huggingface.cloud/v2"
+
+
+REPO_ID_SEPARATOR = "--"
+# ^ this substring is not allowed in repo_ids on hf.co
+# and is the canonical one we use for serialization of repo ids elsewhere.
+
+
+REPO_TYPE_DATASET = "dataset"
+REPO_TYPE_SPACE = "space"
+REPO_TYPE_MODEL = "model"
+REPO_TYPES = [None, REPO_TYPE_MODEL, REPO_TYPE_DATASET, REPO_TYPE_SPACE]
+SPACES_SDK_TYPES = ["gradio", "streamlit", "docker", "static"]
+
+REPO_TYPES_URL_PREFIXES = {
+ REPO_TYPE_DATASET: "datasets/",
+ REPO_TYPE_SPACE: "spaces/",
+}
+REPO_TYPES_MAPPING = {
+ "datasets": REPO_TYPE_DATASET,
+ "spaces": REPO_TYPE_SPACE,
+ "models": REPO_TYPE_MODEL,
+}
+
+DiscussionTypeFilter = Literal["all", "discussion", "pull_request"]
+DISCUSSION_TYPES: Tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionTypeFilter)
+DiscussionStatusFilter = Literal["all", "open", "closed"]
+DISCUSSION_STATUS: Tuple[DiscussionTypeFilter, ...] = typing.get_args(DiscussionStatusFilter)
+
+# default cache
+default_home = os.path.join(os.path.expanduser("~"), ".cache")
+HF_HOME = os.path.expanduser(
+ os.getenv(
+ "HF_HOME",
+ os.path.join(os.getenv("XDG_CACHE_HOME", default_home), "huggingface"),
+ )
+)
+hf_cache_home = HF_HOME # for backward compatibility. TODO: remove this in 1.0.0
+
+default_cache_path = os.path.join(HF_HOME, "hub")
+default_assets_cache_path = os.path.join(HF_HOME, "assets")
+
+# Legacy env variables
+HUGGINGFACE_HUB_CACHE = os.getenv("HUGGINGFACE_HUB_CACHE", default_cache_path)
+HUGGINGFACE_ASSETS_CACHE = os.getenv("HUGGINGFACE_ASSETS_CACHE", default_assets_cache_path)
+
+# New env variables
+HF_HUB_CACHE = os.getenv("HF_HUB_CACHE", HUGGINGFACE_HUB_CACHE)
+HF_ASSETS_CACHE = os.getenv("HF_ASSETS_CACHE", HUGGINGFACE_ASSETS_CACHE)
+
+HF_HUB_OFFLINE = _is_true(os.environ.get("HF_HUB_OFFLINE") or os.environ.get("TRANSFORMERS_OFFLINE"))
+
+# Opt-out from telemetry requests
+HF_HUB_DISABLE_TELEMETRY = (
+ _is_true(os.environ.get("HF_HUB_DISABLE_TELEMETRY")) # HF-specific env variable
+ or _is_true(os.environ.get("DISABLE_TELEMETRY"))
+ or _is_true(os.environ.get("DO_NOT_TRACK")) # https://consoledonottrack.com/
+)
+
+# In the past, token was stored in a hardcoded location
+# `_OLD_HF_TOKEN_PATH` is deprecated and will be removed "at some point".
+# See https://github.com/huggingface/huggingface_hub/issues/1232
+_OLD_HF_TOKEN_PATH = os.path.expanduser("~/.huggingface/token")
+HF_TOKEN_PATH = os.path.join(HF_HOME, "token")
+
+
+if _staging_mode:
+ # In staging mode, we use a different cache to ensure we don't mix up production and staging data or tokens
+ _staging_home = os.path.join(os.path.expanduser("~"), ".cache", "huggingface_staging")
+ HUGGINGFACE_HUB_CACHE = os.path.join(_staging_home, "hub")
+ _OLD_HF_TOKEN_PATH = os.path.join(_staging_home, "_old_token")
+ HF_TOKEN_PATH = os.path.join(_staging_home, "token")
+
+# Here, `True` will disable progress bars globally without possibility of enabling it
+# programmatically. `False` will enable them without possibility of disabling them.
+# If environment variable is not set (None), then the user is free to enable/disable
+# them programmatically.
+# TL;DR: env variable has priority over code
+__HF_HUB_DISABLE_PROGRESS_BARS = os.environ.get("HF_HUB_DISABLE_PROGRESS_BARS")
+HF_HUB_DISABLE_PROGRESS_BARS: Optional[bool] = (
+ _is_true(__HF_HUB_DISABLE_PROGRESS_BARS) if __HF_HUB_DISABLE_PROGRESS_BARS is not None else None
+)
+
+# Disable warning on machines that do not support symlinks (e.g. Windows non-developer)
+HF_HUB_DISABLE_SYMLINKS_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS_WARNING"))
+
+# Disable warning when using experimental features
+HF_HUB_DISABLE_EXPERIMENTAL_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_EXPERIMENTAL_WARNING"))
+
+# Disable sending the cached token by default is all HTTP requests to the Hub
+HF_HUB_DISABLE_IMPLICIT_TOKEN: bool = _is_true(os.environ.get("HF_HUB_DISABLE_IMPLICIT_TOKEN"))
+
+# Enable fast-download using external dependency "hf_transfer"
+# See:
+# - https://pypi.org/project/hf-transfer/
+# - https://github.com/huggingface/hf_transfer (private)
+HF_HUB_ENABLE_HF_TRANSFER: bool = _is_true(os.environ.get("HF_HUB_ENABLE_HF_TRANSFER"))
+
+
+# Used if download to `local_dir` and `local_dir_use_symlinks="auto"`
+# Files smaller than 5MB are copy-pasted while bigger files are symlinked. The idea is to save disk-usage by symlinking
+# huge files (i.e. LFS files most of the time) while allowing small files to be manually edited in local folder.
+HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD: int = (
+ _as_int(os.environ.get("HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD")) or 5 * 1024 * 1024
+)
+
+# Used to override the etag timeout on a system level
+HF_HUB_ETAG_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_ETAG_TIMEOUT")) or DEFAULT_ETAG_TIMEOUT
+
+# Used to override the get request timeout on a system level
+HF_HUB_DOWNLOAD_TIMEOUT: int = _as_int(os.environ.get("HF_HUB_DOWNLOAD_TIMEOUT")) or DEFAULT_DOWNLOAD_TIMEOUT
+
+# List frameworks that are handled by the InferenceAPI service. Useful to scan endpoints and check which models are
+# deployed and running. Since 95% of the models are using the top 4 frameworks listed below, we scan only those by
+# default. We still keep the full list of supported frameworks in case we want to scan all of them.
+MAIN_INFERENCE_API_FRAMEWORKS = [
+ "diffusers",
+ "sentence-transformers",
+ "text-generation-inference",
+ "transformers",
+]
+
+ALL_INFERENCE_API_FRAMEWORKS = MAIN_INFERENCE_API_FRAMEWORKS + [
+ "adapter-transformers",
+ "allennlp",
+ "asteroid",
+ "bertopic",
+ "doctr",
+ "espnet",
+ "fairseq",
+ "fastai",
+ "fasttext",
+ "flair",
+ "generic",
+ "k2",
+ "keras",
+ "mindspore",
+ "nemo",
+ "open_clip",
+ "paddlenlp",
+ "peft",
+ "pyannote-audio",
+ "sklearn",
+ "spacy",
+ "span-marker",
+ "speechbrain",
+ "stanza",
+ "timm",
+]
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/errors.py b/.venv/lib/python3.10/site-packages/huggingface_hub/errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..7975b39d1f9b1ef3b576c34434a62abf634299f9
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/errors.py
@@ -0,0 +1,38 @@
+"""Contains all custom errors."""
+
+from requests import HTTPError
+
+
+# INFERENCE CLIENT ERRORS
+
+
+class InferenceTimeoutError(HTTPError, TimeoutError):
+ """Error raised when a model is unavailable or the request times out."""
+
+
+# TEXT GENERATION ERRORS
+
+
+class TextGenerationError(HTTPError):
+ """Generic error raised if text-generation went wrong."""
+
+
+# Text Generation Inference Errors
+class ValidationError(TextGenerationError):
+ """Server-side validation error."""
+
+
+class GenerationError(TextGenerationError):
+ pass
+
+
+class OverloadedError(TextGenerationError):
+ pass
+
+
+class IncompleteGenerationError(TextGenerationError):
+ pass
+
+
+class UnknownError(TextGenerationError):
+ pass
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/fastai_utils.py b/.venv/lib/python3.10/site-packages/huggingface_hub/fastai_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e586e8663c39e8d5bab3f57f667dbd878514e59d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/fastai_utils.py
@@ -0,0 +1,425 @@
+import json
+import os
+from pathlib import Path
+from pickle import DEFAULT_PROTOCOL, PicklingError
+from typing import Any, Dict, List, Optional, Union
+
+from packaging import version
+
+from huggingface_hub import snapshot_download
+from huggingface_hub.constants import CONFIG_NAME
+from huggingface_hub.hf_api import HfApi
+from huggingface_hub.utils import (
+ SoftTemporaryDirectory,
+ get_fastai_version,
+ get_fastcore_version,
+ get_python_version,
+)
+
+from .utils import logging, validate_hf_hub_args
+from .utils._runtime import _PY_VERSION # noqa: F401 # for backward compatibility...
+
+
+logger = logging.get_logger(__name__)
+
+
+def _check_fastai_fastcore_versions(
+ fastai_min_version: str = "2.4",
+ fastcore_min_version: str = "1.3.27",
+):
+ """
+ Checks that the installed fastai and fastcore versions are compatible for pickle serialization.
+
+ Args:
+ fastai_min_version (`str`, *optional*):
+ The minimum fastai version supported.
+ fastcore_min_version (`str`, *optional*):
+ The minimum fastcore version supported.
+
+
+ Raises the following error:
+
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the fastai or fastcore libraries are not available or are of an invalid version.
+
+
+ """
+
+ if (get_fastcore_version() or get_fastai_version()) == "N/A":
+ raise ImportError(
+ f"fastai>={fastai_min_version} and fastcore>={fastcore_min_version} are"
+ f" required. Currently using fastai=={get_fastai_version()} and"
+ f" fastcore=={get_fastcore_version()}."
+ )
+
+ current_fastai_version = version.Version(get_fastai_version())
+ current_fastcore_version = version.Version(get_fastcore_version())
+
+ if current_fastai_version < version.Version(fastai_min_version):
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
+ f" fastai>={fastai_min_version} version, but you are using fastai version"
+ f" {get_fastai_version()} which is incompatible. Upgrade with `pip install"
+ " fastai==2.5.6`."
+ )
+
+ if current_fastcore_version < version.Version(fastcore_min_version):
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require a"
+ f" fastcore>={fastcore_min_version} version, but you are using fastcore"
+ f" version {get_fastcore_version()} which is incompatible. Upgrade with"
+ " `pip install fastcore==1.3.27`."
+ )
+
+
+def _check_fastai_fastcore_pyproject_versions(
+ storage_folder: str,
+ fastai_min_version: str = "2.4",
+ fastcore_min_version: str = "1.3.27",
+):
+ """
+ Checks that the `pyproject.toml` file in the directory `storage_folder` has fastai and fastcore versions
+ that are compatible with `from_pretrained_fastai` and `push_to_hub_fastai`. If `pyproject.toml` does not exist
+ or does not contain versions for fastai and fastcore, then it logs a warning.
+
+ Args:
+ storage_folder (`str`):
+ Folder to look for the `pyproject.toml` file.
+ fastai_min_version (`str`, *optional*):
+ The minimum fastai version supported.
+ fastcore_min_version (`str`, *optional*):
+ The minimum fastcore version supported.
+
+
+ Raises the following errors:
+
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the `toml` module is not installed.
+ - [`ImportError`](https://docs.python.org/3/library/exceptions.html#ImportError)
+ if the `pyproject.toml` indicates a lower than minimum supported version of fastai or fastcore.
+
+
+ """
+
+ try:
+ import toml
+ except ModuleNotFoundError:
+ raise ImportError(
+ "`push_to_hub_fastai` and `from_pretrained_fastai` require the toml module."
+ " Install it with `pip install toml`."
+ )
+
+ # Checks that a `pyproject.toml`, with `build-system` and `requires` sections, exists in the repository. If so, get a list of required packages.
+ if not os.path.isfile(f"{storage_folder}/pyproject.toml"):
+ logger.warning(
+ "There is no `pyproject.toml` in the repository that contains the fastai"
+ " `Learner`. The `pyproject.toml` would allow us to verify that your fastai"
+ " and fastcore versions are compatible with those of the model you want to"
+ " load."
+ )
+ return
+ pyproject_toml = toml.load(f"{storage_folder}/pyproject.toml")
+
+ if "build-system" not in pyproject_toml.keys():
+ logger.warning(
+ "There is no `build-system` section in the pyproject.toml of the repository"
+ " that contains the fastai `Learner`. The `build-system` would allow us to"
+ " verify that your fastai and fastcore versions are compatible with those"
+ " of the model you want to load."
+ )
+ return
+ build_system_toml = pyproject_toml["build-system"]
+
+ if "requires" not in build_system_toml.keys():
+ logger.warning(
+ "There is no `requires` section in the pyproject.toml of the repository"
+ " that contains the fastai `Learner`. The `requires` would allow us to"
+ " verify that your fastai and fastcore versions are compatible with those"
+ " of the model you want to load."
+ )
+ return
+ package_versions = build_system_toml["requires"]
+
+ # Extracts contains fastai and fastcore versions from `pyproject.toml` if available.
+ # If the package is specified but not the version (e.g. "fastai" instead of "fastai=2.4"), the default versions are the highest.
+ fastai_packages = [pck for pck in package_versions if pck.startswith("fastai")]
+ if len(fastai_packages) == 0:
+ logger.warning("The repository does not have a fastai version specified in the `pyproject.toml`.")
+ # fastai_version is an empty string if not specified
+ else:
+ fastai_version = str(fastai_packages[0]).partition("=")[2]
+ if fastai_version != "" and version.Version(fastai_version) < version.Version(fastai_min_version):
+ raise ImportError(
+ "`from_pretrained_fastai` requires"
+ f" fastai>={fastai_min_version} version but the model to load uses"
+ f" {fastai_version} which is incompatible."
+ )
+
+ fastcore_packages = [pck for pck in package_versions if pck.startswith("fastcore")]
+ if len(fastcore_packages) == 0:
+ logger.warning("The repository does not have a fastcore version specified in the `pyproject.toml`.")
+ # fastcore_version is an empty string if not specified
+ else:
+ fastcore_version = str(fastcore_packages[0]).partition("=")[2]
+ if fastcore_version != "" and version.Version(fastcore_version) < version.Version(fastcore_min_version):
+ raise ImportError(
+ "`from_pretrained_fastai` requires"
+ f" fastcore>={fastcore_min_version} version, but you are using fastcore"
+ f" version {fastcore_version} which is incompatible."
+ )
+
+
+README_TEMPLATE = """---
+tags:
+- fastai
+---
+
+# Amazing!
+
+🥳 Congratulations on hosting your fastai model on the Hugging Face Hub!
+
+# Some next steps
+1. Fill out this model card with more information (see the template below and the [documentation here](https://huggingface.co/docs/hub/model-repos))!
+
+2. Create a demo in Gradio or Streamlit using 🤗 Spaces ([documentation here](https://huggingface.co/docs/hub/spaces)).
+
+3. Join the fastai community on the [Fastai Discord](https://discord.com/invite/YKrxeNn)!
+
+Greetings fellow fastlearner 🤝! Don't forget to delete this content from your model card.
+
+
+---
+
+
+# Model card
+
+## Model description
+More information needed
+
+## Intended uses & limitations
+More information needed
+
+## Training and evaluation data
+More information needed
+"""
+
+PYPROJECT_TEMPLATE = f"""[build-system]
+requires = ["setuptools>=40.8.0", "wheel", "python={get_python_version()}", "fastai={get_fastai_version()}", "fastcore={get_fastcore_version()}"]
+build-backend = "setuptools.build_meta:__legacy__"
+"""
+
+
+def _create_model_card(repo_dir: Path):
+ """
+ Creates a model card for the repository.
+
+ Args:
+ repo_dir (`Path`):
+ Directory where model card is created.
+ """
+ readme_path = repo_dir / "README.md"
+
+ if not readme_path.exists():
+ with readme_path.open("w", encoding="utf-8") as f:
+ f.write(README_TEMPLATE)
+
+
+def _create_model_pyproject(repo_dir: Path):
+ """
+ Creates a `pyproject.toml` for the repository.
+
+ Args:
+ repo_dir (`Path`):
+ Directory where `pyproject.toml` is created.
+ """
+ pyproject_path = repo_dir / "pyproject.toml"
+
+ if not pyproject_path.exists():
+ with pyproject_path.open("w", encoding="utf-8") as f:
+ f.write(PYPROJECT_TEMPLATE)
+
+
+def _save_pretrained_fastai(
+ learner,
+ save_directory: Union[str, Path],
+ config: Optional[Dict[str, Any]] = None,
+):
+ """
+ Saves a fastai learner to `save_directory` in pickle format using the default pickle protocol for the version of python used.
+
+ Args:
+ learner (`Learner`):
+ The `fastai.Learner` you'd like to save.
+ save_directory (`str` or `Path`):
+ Specific directory in which you want to save the fastai learner.
+ config (`dict`, *optional*):
+ Configuration object. Will be uploaded as a .json file. Example: 'https://huggingface.co/espejelomar/fastai-pet-breeds-classification/blob/main/config.json'.
+
+
+
+ Raises the following error:
+
+ - [`RuntimeError`](https://docs.python.org/3/library/exceptions.html#RuntimeError)
+ if the config file provided is not a dictionary.
+
+
+ """
+ _check_fastai_fastcore_versions()
+
+ os.makedirs(save_directory, exist_ok=True)
+
+ # if the user provides config then we update it with the fastai and fastcore versions in CONFIG_TEMPLATE.
+ if config is not None:
+ if not isinstance(config, dict):
+ raise RuntimeError(f"Provided config should be a dict. Got: '{type(config)}'")
+ path = os.path.join(save_directory, CONFIG_NAME)
+ with open(path, "w") as f:
+ json.dump(config, f)
+
+ _create_model_card(Path(save_directory))
+ _create_model_pyproject(Path(save_directory))
+
+ # learner.export saves the model in `self.path`.
+ learner.path = Path(save_directory)
+ os.makedirs(save_directory, exist_ok=True)
+ try:
+ learner.export(
+ fname="model.pkl",
+ pickle_protocol=DEFAULT_PROTOCOL,
+ )
+ except PicklingError:
+ raise PicklingError(
+ "You are using a lambda function, i.e., an anonymous function. `pickle`"
+ " cannot pickle function objects and requires that all functions have"
+ " names. One possible solution is to name the function."
+ )
+
+
+@validate_hf_hub_args
+def from_pretrained_fastai(
+ repo_id: str,
+ revision: Optional[str] = None,
+):
+ """
+ Load pretrained fastai model from the Hub or from a local directory.
+
+ Args:
+ repo_id (`str`):
+ The location where the pickled fastai.Learner is. It can be either of the two:
+ - Hosted on the Hugging Face Hub. E.g.: 'espejelomar/fatai-pet-breeds-classification' or 'distilgpt2'.
+ You can add a `revision` by appending `@` at the end of `repo_id`. E.g.: `dbmdz/bert-base-german-cased@main`.
+ Revision is the specific model version to use. Since we use a git-based system for storing models and other
+ artifacts on the Hugging Face Hub, it can be a branch name, a tag name, or a commit id.
+ - Hosted locally. `repo_id` would be a directory containing the pickle and a pyproject.toml
+ indicating the fastai and fastcore versions used to build the `fastai.Learner`. E.g.: `./my_model_directory/`.
+ revision (`str`, *optional*):
+ Revision at which the repo's files are downloaded. See documentation of `snapshot_download`.
+
+ Returns:
+ The `fastai.Learner` model in the `repo_id` repo.
+ """
+ _check_fastai_fastcore_versions()
+
+ # Load the `repo_id` repo.
+ # `snapshot_download` returns the folder where the model was stored.
+ # `cache_dir` will be the default '/root/.cache/huggingface/hub'
+ if not os.path.isdir(repo_id):
+ storage_folder = snapshot_download(
+ repo_id=repo_id,
+ revision=revision,
+ library_name="fastai",
+ library_version=get_fastai_version(),
+ )
+ else:
+ storage_folder = repo_id
+
+ _check_fastai_fastcore_pyproject_versions(storage_folder)
+
+ from fastai.learner import load_learner # type: ignore
+
+ return load_learner(os.path.join(storage_folder, "model.pkl"))
+
+
+@validate_hf_hub_args
+def push_to_hub_fastai(
+ learner,
+ *,
+ repo_id: str,
+ commit_message: str = "Push FastAI model using huggingface_hub.",
+ private: bool = False,
+ token: Optional[str] = None,
+ config: Optional[dict] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ api_endpoint: Optional[str] = None,
+):
+ """
+ Upload learner checkpoint files to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ learner (`Learner`):
+ The `fastai.Learner' you'd like to push to the Hub.
+ repo_id (`str`):
+ The repository id for your model in Hub in the format of "namespace/repo_name". The namespace can be your individual account or an organization to which you have write access (for example, 'stanfordnlp/stanza-de').
+ commit_message (`str`, *optional*):
+ Message to commit while pushing. Will default to :obj:`"add model"`.
+ private (`bool`, *optional*, defaults to `False`):
+ Whether or not the repository created should be private.
+ token (`str`, *optional*):
+ The Hugging Face account token to use as HTTP bearer authorization for remote files. If :obj:`None`, the token will be asked by a prompt.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to
+ the default branch as specified in your repository, which
+ defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit.
+ Defaults to `False`.
+ api_endpoint (`str`, *optional*):
+ The API endpoint to use when pushing the model to the hub.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+
+ Returns:
+ The url of the commit of your model in the given repository.
+
+
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if the user is not log on to the Hugging Face Hub.
+
+
+ """
+ _check_fastai_fastcore_versions()
+ api = HfApi(endpoint=api_endpoint)
+ repo_id = api.create_repo(repo_id=repo_id, token=token, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ _save_pretrained_fastai(learner, saved_path, config=config)
+ return api.upload_folder(
+ repo_id=repo_id,
+ token=token,
+ folder_path=saved_path,
+ commit_message=commit_message,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py b/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py
new file mode 100644
index 0000000000000000000000000000000000000000..566ef3246a8877c103b4f6f16c4e5d418bd62bbc
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py
@@ -0,0 +1,1770 @@
+import copy
+import errno
+import fnmatch
+import inspect
+import io
+import json
+import os
+import re
+import shutil
+import stat
+import tempfile
+import time
+import uuid
+import warnings
+from contextlib import contextmanager
+from dataclasses import dataclass
+from functools import partial
+from pathlib import Path
+from typing import Any, BinaryIO, Dict, Generator, Literal, Optional, Tuple, Union
+from urllib.parse import quote, urlparse
+
+import requests
+
+from huggingface_hub import constants
+
+from . import __version__ # noqa: F401 # for backward compatibility
+from .constants import (
+ DEFAULT_ETAG_TIMEOUT,
+ DEFAULT_REQUEST_TIMEOUT,
+ DEFAULT_REVISION,
+ DOWNLOAD_CHUNK_SIZE,
+ ENDPOINT,
+ HF_HUB_CACHE,
+ HF_HUB_DISABLE_SYMLINKS_WARNING,
+ HF_HUB_DOWNLOAD_TIMEOUT,
+ HF_HUB_ENABLE_HF_TRANSFER,
+ HF_HUB_ETAG_TIMEOUT,
+ HF_TRANSFER_CONCURRENCY,
+ HUGGINGFACE_CO_URL_TEMPLATE,
+ HUGGINGFACE_HEADER_X_LINKED_ETAG,
+ HUGGINGFACE_HEADER_X_LINKED_SIZE,
+ HUGGINGFACE_HEADER_X_REPO_COMMIT,
+ HUGGINGFACE_HUB_CACHE, # noqa: F401 # for backward compatibility
+ REPO_ID_SEPARATOR,
+ REPO_TYPES,
+ REPO_TYPES_URL_PREFIXES,
+)
+from .utils import (
+ EntryNotFoundError,
+ FileMetadataError,
+ GatedRepoError,
+ LocalEntryNotFoundError,
+ OfflineModeIsEnabled,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ SoftTemporaryDirectory,
+ WeakFileLock,
+ build_hf_headers,
+ get_fastai_version, # noqa: F401 # for backward compatibility
+ get_fastcore_version, # noqa: F401 # for backward compatibility
+ get_graphviz_version, # noqa: F401 # for backward compatibility
+ get_jinja_version, # noqa: F401 # for backward compatibility
+ get_pydot_version, # noqa: F401 # for backward compatibility
+ get_session,
+ get_tf_version, # noqa: F401 # for backward compatibility
+ get_torch_version, # noqa: F401 # for backward compatibility
+ hf_raise_for_status,
+ is_fastai_available, # noqa: F401 # for backward compatibility
+ is_fastcore_available, # noqa: F401 # for backward compatibility
+ is_graphviz_available, # noqa: F401 # for backward compatibility
+ is_jinja_available, # noqa: F401 # for backward compatibility
+ is_pydot_available, # noqa: F401 # for backward compatibility
+ is_tf_available, # noqa: F401 # for backward compatibility
+ is_torch_available, # noqa: F401 # for backward compatibility
+ logging,
+ reset_sessions,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils._runtime import _PY_VERSION # noqa: F401 # for backward compatibility
+from .utils._typing import HTTP_METHOD_T
+from .utils.insecure_hashlib import sha256
+
+
+logger = logging.get_logger(__name__)
+
+# Regex to get filename from a "Content-Disposition" header for CDN-served files
+HEADER_FILENAME_PATTERN = re.compile(r'filename="(?P.*?)";')
+
+
+_are_symlinks_supported_in_dir: Dict[str, bool] = {}
+
+
+def are_symlinks_supported(cache_dir: Union[str, Path, None] = None) -> bool:
+ """Return whether the symlinks are supported on the machine.
+
+ Since symlinks support can change depending on the mounted disk, we need to check
+ on the precise cache folder. By default, the default HF cache directory is checked.
+
+ Args:
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+
+ Returns: [bool] Whether symlinks are supported in the directory.
+ """
+ # Defaults to HF cache
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+ cache_dir = str(Path(cache_dir).expanduser().resolve()) # make it unique
+
+ # Check symlink compatibility only once (per cache directory) at first time use
+ if cache_dir not in _are_symlinks_supported_in_dir:
+ _are_symlinks_supported_in_dir[cache_dir] = True
+
+ os.makedirs(cache_dir, exist_ok=True)
+ with SoftTemporaryDirectory(dir=cache_dir) as tmpdir:
+ src_path = Path(tmpdir) / "dummy_file_src"
+ src_path.touch()
+ dst_path = Path(tmpdir) / "dummy_file_dst"
+
+ # Relative source path as in `_create_symlink``
+ relative_src = os.path.relpath(src_path, start=os.path.dirname(dst_path))
+ try:
+ os.symlink(relative_src, dst_path)
+ except OSError:
+ # Likely running on Windows
+ _are_symlinks_supported_in_dir[cache_dir] = False
+
+ if not HF_HUB_DISABLE_SYMLINKS_WARNING:
+ message = (
+ "`huggingface_hub` cache-system uses symlinks by default to"
+ " efficiently store duplicated files but your machine does not"
+ f" support them in {cache_dir}. Caching files will still work"
+ " but in a degraded version that might require more space on"
+ " your disk. This warning can be disabled by setting the"
+ " `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable. For"
+ " more details, see"
+ " https://huggingface.co/docs/huggingface_hub/how-to-cache#limitations."
+ )
+ if os.name == "nt":
+ message += (
+ "\nTo support symlinks on Windows, you either need to"
+ " activate Developer Mode or to run Python as an"
+ " administrator. In order to see activate developer mode,"
+ " see this article:"
+ " https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development"
+ )
+ warnings.warn(message)
+
+ return _are_symlinks_supported_in_dir[cache_dir]
+
+
+# Return value when trying to load a file from cache but the file does not exist in the distant repo.
+_CACHED_NO_EXIST = object()
+_CACHED_NO_EXIST_T = Any
+REGEX_COMMIT_HASH = re.compile(r"^[0-9a-f]{40}$")
+
+
+@dataclass(frozen=True)
+class HfFileMetadata:
+ """Data structure containing information about a file versioned on the Hub.
+
+ Returned by [`get_hf_file_metadata`] based on a URL.
+
+ Args:
+ commit_hash (`str`, *optional*):
+ The commit_hash related to the file.
+ etag (`str`, *optional*):
+ Etag of the file on the server.
+ location (`str`):
+ Location where to download the file. Can be a Hub url or not (CDN).
+ size (`size`):
+ Size of the file. In case of an LFS file, contains the size of the actual
+ LFS file, not the pointer.
+ """
+
+ commit_hash: Optional[str]
+ etag: Optional[str]
+ location: str
+ size: Optional[int]
+
+
+@validate_hf_hub_args
+def hf_hub_url(
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ endpoint: Optional[str] = None,
+) -> str:
+ """Construct the URL of a file from the given information.
+
+ The resolved address can either be a huggingface.co-hosted url, or a link to
+ Cloudfront (a Content Delivery Network, or CDN) for large files which are
+ more than a few MBs.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) name and a repo name separated
+ by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import hf_hub_url
+
+ >>> hf_hub_url(
+ ... repo_id="julien-c/EsperBERTo-small", filename="pytorch_model.bin"
+ ... )
+ 'https://huggingface.co/julien-c/EsperBERTo-small/resolve/main/pytorch_model.bin'
+ ```
+
+
+
+ Notes:
+
+ Cloudfront is replicated over the globe so downloads are way faster for
+ the end user (and it also lowers our bandwidth costs).
+
+ Cloudfront aggressively caches files by default (default TTL is 24
+ hours), however this is not an issue here because we implement a
+ git-based versioning system on huggingface.co, which means that we store
+ the files on S3/Cloudfront in a content-addressable way (i.e., the file
+ name is its hash). Using content-addressable filenames means cache can't
+ ever be stale.
+
+ In terms of client-side caching from this library, we base our caching
+ on the objects' entity tag (`ETag`), which is an identifier of a
+ specific version of a resource [1]_. An object's ETag is: its git-sha1
+ if stored in git, or its sha256 if stored in git-lfs.
+
+
+
+ References:
+
+ - [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
+ """
+ if subfolder == "":
+ subfolder = None
+ if subfolder is not None:
+ filename = f"{subfolder}/{filename}"
+
+ if repo_type not in REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ if repo_type in REPO_TYPES_URL_PREFIXES:
+ repo_id = REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+
+ if revision is None:
+ revision = DEFAULT_REVISION
+ url = HUGGINGFACE_CO_URL_TEMPLATE.format(
+ repo_id=repo_id, revision=quote(revision, safe=""), filename=quote(filename)
+ )
+ # Update endpoint if provided
+ if endpoint is not None and url.startswith(ENDPOINT):
+ url = endpoint + url[len(ENDPOINT) :]
+ return url
+
+
+def url_to_filename(url: str, etag: Optional[str] = None) -> str:
+ """Generate a local filename from a url.
+
+ Convert `url` into a hashed filename in a reproducible way. If `etag` is
+ specified, append its hash to the url's, delimited by a period. If the url
+ ends with .h5 (Keras HDF5 weights) adds '.h5' to the name so that TF 2.0 can
+ identify it as a HDF5 file (see
+ https://github.com/tensorflow/tensorflow/blob/00fad90125b18b80fe054de1055770cfb8fe4ba3/tensorflow/python/keras/engine/network.py#L1380)
+
+ Args:
+ url (`str`):
+ The address to the file.
+ etag (`str`, *optional*):
+ The ETag of the file.
+
+ Returns:
+ The generated filename.
+ """
+ url_bytes = url.encode("utf-8")
+ filename = sha256(url_bytes).hexdigest()
+
+ if etag:
+ etag_bytes = etag.encode("utf-8")
+ filename += "." + sha256(etag_bytes).hexdigest()
+
+ if url.endswith(".h5"):
+ filename += ".h5"
+
+ return filename
+
+
+def filename_to_url(
+ filename,
+ cache_dir: Optional[str] = None,
+ legacy_cache_layout: bool = False,
+) -> Tuple[str, str]:
+ """
+ Return the url and etag (which may be `None`) stored for `filename`. Raise
+ `EnvironmentError` if `filename` or its stored metadata do not exist.
+
+ Args:
+ filename (`str`):
+ The name of the file
+ cache_dir (`str`, *optional*):
+ The cache directory to use instead of the default one.
+ legacy_cache_layout (`bool`, *optional*, defaults to `False`):
+ If `True`, uses the legacy file cache layout i.e. just call `hf_hub_url`
+ then `cached_download`. This is deprecated as the new cache layout is
+ more powerful.
+ """
+ if not legacy_cache_layout:
+ warnings.warn(
+ "`filename_to_url` uses the legacy way cache file layout",
+ FutureWarning,
+ )
+
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+
+ cache_path = os.path.join(cache_dir, filename)
+ if not os.path.exists(cache_path):
+ raise EnvironmentError(f"file {cache_path} not found")
+
+ meta_path = cache_path + ".json"
+ if not os.path.exists(meta_path):
+ raise EnvironmentError(f"file {meta_path} not found")
+
+ with open(meta_path, encoding="utf-8") as meta_file:
+ metadata = json.load(meta_file)
+ url = metadata["url"]
+ etag = metadata["etag"]
+
+ return url, etag
+
+
+def _request_wrapper(
+ method: HTTP_METHOD_T, url: str, *, follow_relative_redirects: bool = False, **params
+) -> requests.Response:
+ """Wrapper around requests methods to follow relative redirects if `follow_relative_redirects=True` even when
+ `allow_redirection=False`.
+
+ Args:
+ method (`str`):
+ HTTP method, such as 'GET' or 'HEAD'.
+ url (`str`):
+ The URL of the resource to fetch.
+ follow_relative_redirects (`bool`, *optional*, defaults to `False`)
+ If True, relative redirection (redirection to the same site) will be resolved even when `allow_redirection`
+ kwarg is set to False. Useful when we want to follow a redirection to a renamed repository without
+ following redirection to a CDN.
+ **params (`dict`, *optional*):
+ Params to pass to `requests.request`.
+ """
+ # Recursively follow relative redirects
+ if follow_relative_redirects:
+ response = _request_wrapper(
+ method=method,
+ url=url,
+ follow_relative_redirects=False,
+ **params,
+ )
+
+ # If redirection, we redirect only relative paths.
+ # This is useful in case of a renamed repository.
+ if 300 <= response.status_code <= 399:
+ parsed_target = urlparse(response.headers["Location"])
+ if parsed_target.netloc == "":
+ # This means it is a relative 'location' headers, as allowed by RFC 7231.
+ # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
+ # We want to follow this relative redirect !
+ #
+ # Highly inspired by `resolve_redirects` from requests library.
+ # See https://github.com/psf/requests/blob/main/requests/sessions.py#L159
+ next_url = urlparse(url)._replace(path=parsed_target.path).geturl()
+ return _request_wrapper(method=method, url=next_url, follow_relative_redirects=True, **params)
+ return response
+
+ # Perform request and return if status_code is not in the retry list.
+ response = get_session().request(method=method, url=url, **params)
+ hf_raise_for_status(response)
+ return response
+
+
+def http_get(
+ url: str,
+ temp_file: BinaryIO,
+ *,
+ proxies: Optional[Dict] = None,
+ resume_size: float = 0,
+ headers: Optional[Dict[str, str]] = None,
+ expected_size: Optional[int] = None,
+ displayed_filename: Optional[str] = None,
+ _nb_retries: int = 5,
+ _tqdm_bar: Optional[tqdm] = None,
+) -> None:
+ """
+ Download a remote file. Do not gobble up errors, and will return errors tailored to the Hugging Face Hub.
+
+ If ConnectionError (SSLError) or ReadTimeout happen while streaming data from the server, it is most likely a
+ transient error (network outage?). We log a warning message and try to resume the download a few times before
+ giving up. The method gives up after 5 attempts if no new data has being received from the server.
+
+ Args:
+ url (`str`):
+ The URL of the file to download.
+ temp_file (`BinaryIO`):
+ The file-like object where to save the file.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to `requests.request`.
+ resume_size (`float`, *optional*):
+ The number of bytes already downloaded. If set to 0 (default), the whole file is download. If set to a
+ positive number, the download will resume at the given position.
+ headers (`dict`, *optional*):
+ Dictionary of HTTP Headers to send with the request.
+ expected_size (`int`, *optional*):
+ The expected size of the file to download. If set, the download will raise an error if the size of the
+ received content is different from the expected one.
+ displayed_filename (`str`, *optional*):
+ The filename of the file that is being downloaded. Value is used only to display a nice progress bar. If
+ not set, the filename is guessed from the URL or the `Content-Disposition` header.
+ """
+ hf_transfer = None
+ if HF_HUB_ENABLE_HF_TRANSFER:
+ if resume_size != 0:
+ warnings.warn("'hf_transfer' does not support `resume_size`: falling back to regular download method")
+ elif proxies is not None:
+ warnings.warn("'hf_transfer' does not support `proxies`: falling back to regular download method")
+ else:
+ try:
+ import hf_transfer # type: ignore[no-redef]
+ except ImportError:
+ raise ValueError(
+ "Fast download using 'hf_transfer' is enabled"
+ " (HF_HUB_ENABLE_HF_TRANSFER=1) but 'hf_transfer' package is not"
+ " available in your environment. Try `pip install hf_transfer`."
+ )
+
+ initial_headers = headers
+ headers = copy.deepcopy(headers) or {}
+ if resume_size > 0:
+ headers["Range"] = "bytes=%d-" % (resume_size,)
+
+ r = _request_wrapper(
+ method="GET", url=url, stream=True, proxies=proxies, headers=headers, timeout=HF_HUB_DOWNLOAD_TIMEOUT
+ )
+ hf_raise_for_status(r)
+ content_length = r.headers.get("Content-Length")
+
+ # NOTE: 'total' is the total number of bytes to download, not the number of bytes in the file.
+ # If the file is compressed, the number of bytes in the saved file will be higher than 'total'.
+ total = resume_size + int(content_length) if content_length is not None else None
+
+ if displayed_filename is None:
+ displayed_filename = url
+ content_disposition = r.headers.get("Content-Disposition")
+ if content_disposition is not None:
+ match = HEADER_FILENAME_PATTERN.search(content_disposition)
+ if match is not None:
+ # Means file is on CDN
+ displayed_filename = match.groupdict()["filename"]
+
+ # Truncate filename if too long to display
+ if len(displayed_filename) > 40:
+ displayed_filename = f"(…){displayed_filename[-40:]}"
+
+ consistency_error_message = (
+ f"Consistency check failed: file should be of size {expected_size} but has size"
+ f" {{actual_size}} ({displayed_filename}).\nWe are sorry for the inconvenience. Please retry download and"
+ " pass `force_download=True, resume_download=False` as argument.\nIf the issue persists, please let us"
+ " know by opening an issue on https://github.com/huggingface/huggingface_hub."
+ )
+
+ # Stream file to buffer
+ progress = _tqdm_bar
+ if progress is None:
+ progress = tqdm(
+ unit="B",
+ unit_scale=True,
+ total=total,
+ initial=resume_size,
+ desc=displayed_filename,
+ disable=True if (logger.getEffectiveLevel() == logging.NOTSET) else None,
+ # ^ set `disable=None` rather than `disable=False` by default to disable progress bar when no TTY attached
+ # see https://github.com/huggingface/huggingface_hub/pull/2000
+ )
+
+ if hf_transfer and total is not None and total > 5 * DOWNLOAD_CHUNK_SIZE:
+ supports_callback = "callback" in inspect.signature(hf_transfer.download).parameters
+ if not supports_callback:
+ warnings.warn(
+ "You are using an outdated version of `hf_transfer`. "
+ "Consider upgrading to latest version to enable progress bars "
+ "using `pip install -U hf_transfer`."
+ )
+ try:
+ hf_transfer.download(
+ url=url,
+ filename=temp_file.name,
+ max_files=HF_TRANSFER_CONCURRENCY,
+ chunk_size=DOWNLOAD_CHUNK_SIZE,
+ headers=headers,
+ parallel_failures=3,
+ max_retries=5,
+ **({"callback": progress.update} if supports_callback else {}),
+ )
+ except Exception as e:
+ raise RuntimeError(
+ "An error occurred while downloading using `hf_transfer`. Consider"
+ " disabling HF_HUB_ENABLE_HF_TRANSFER for better error handling."
+ ) from e
+ if not supports_callback:
+ progress.update(total)
+ if expected_size is not None and expected_size != os.path.getsize(temp_file.name):
+ raise EnvironmentError(
+ consistency_error_message.format(
+ actual_size=os.path.getsize(temp_file.name),
+ )
+ )
+ return
+ new_resume_size = resume_size
+ try:
+ for chunk in r.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
+ if chunk: # filter out keep-alive new chunks
+ progress.update(len(chunk))
+ temp_file.write(chunk)
+ new_resume_size += len(chunk)
+ # Some data has been downloaded from the server so we reset the number of retries.
+ _nb_retries = 5
+ except (requests.ConnectionError, requests.ReadTimeout) as e:
+ # If ConnectionError (SSLError) or ReadTimeout happen while streaming data from the server, it is most likely
+ # a transient error (network outage?). We log a warning message and try to resume the download a few times
+ # before giving up. Tre retry mechanism is basic but should be enough in most cases.
+ if _nb_retries <= 0:
+ logger.warning("Error while downloading from %s: %s\nMax retries exceeded.", url, str(e))
+ raise
+ logger.warning("Error while downloading from %s: %s\nTrying to resume download...", url, str(e))
+ time.sleep(1)
+ reset_sessions() # In case of SSLError it's best to reset the shared requests.Session objects
+ return http_get(
+ url=url,
+ temp_file=temp_file,
+ proxies=proxies,
+ resume_size=new_resume_size,
+ headers=initial_headers,
+ expected_size=expected_size,
+ _nb_retries=_nb_retries - 1,
+ _tqdm_bar=_tqdm_bar,
+ )
+
+ progress.close()
+
+ if expected_size is not None and expected_size != temp_file.tell():
+ raise EnvironmentError(
+ consistency_error_message.format(
+ actual_size=temp_file.tell(),
+ )
+ )
+
+
+@validate_hf_hub_args
+def cached_download(
+ url: str,
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ user_agent: Union[Dict, str, None] = None,
+ force_download: bool = False,
+ force_filename: Optional[str] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = DEFAULT_ETAG_TIMEOUT,
+ resume_download: bool = False,
+ token: Union[bool, str, None] = None,
+ local_files_only: bool = False,
+ legacy_cache_layout: bool = False,
+) -> str:
+ """
+ Download from a given URL and cache it if it's not already present in the
+ local cache.
+
+ Given a URL, this function looks for the corresponding file in the local
+ cache. If it's not there, download it. Then return the path to the cached
+ file.
+
+ Will raise errors tailored to the Hugging Face Hub.
+
+ Args:
+ url (`str`):
+ The path to the file to be downloaded.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ user_agent (`dict`, `str`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in
+ the local cache.
+ force_filename (`str`, *optional*):
+ Use this name instead of a generated file name.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional* defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ If `True`, resume a previously interrupted download.
+ token (`bool`, `str`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If a string, it's used as the authentication token.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ legacy_cache_layout (`bool`, *optional*, defaults to `False`):
+ Set this parameter to `True` to mention that you'd like to continue
+ the old cache layout. Putting this to `True` manually will not raise
+ any warning when using `cached_download`. We recommend using
+ `hf_hub_download` to take advantage of the new cache.
+
+ Returns:
+ Local path (string) of file or if networking is off, last version of
+ file cached on disk.
+
+
+
+ Raises the following errors:
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `token=True` and the token cannot be found.
+ - [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ if ETag cannot be determined.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ - [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+ - [`~utils.LocalEntryNotFoundError`]
+ If network is disabled or unavailable and file is not found in cache.
+
+
+ """
+ if HF_HUB_ETAG_TIMEOUT != DEFAULT_ETAG_TIMEOUT:
+ # Respect environment variable above user value
+ etag_timeout = HF_HUB_ETAG_TIMEOUT
+
+ if not legacy_cache_layout:
+ warnings.warn(
+ "'cached_download' is the legacy way to download files from the HF hub, please consider upgrading to"
+ " 'hf_hub_download'",
+ FutureWarning,
+ )
+
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+
+ os.makedirs(cache_dir, exist_ok=True)
+
+ headers = build_hf_headers(
+ token=token,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ )
+
+ url_to_download = url
+ etag = None
+ expected_size = None
+ if not local_files_only:
+ try:
+ # Temporary header: we want the full (decompressed) content size returned to be able to check the
+ # downloaded file size
+ headers["Accept-Encoding"] = "identity"
+ r = _request_wrapper(
+ method="HEAD",
+ url=url,
+ headers=headers,
+ allow_redirects=False,
+ follow_relative_redirects=True,
+ proxies=proxies,
+ timeout=etag_timeout,
+ )
+ headers.pop("Accept-Encoding", None)
+ hf_raise_for_status(r)
+ etag = r.headers.get(HUGGINGFACE_HEADER_X_LINKED_ETAG) or r.headers.get("ETag")
+ # We favor a custom header indicating the etag of the linked resource, and
+ # we fallback to the regular etag header.
+ # If we don't have any of those, raise an error.
+ if etag is None:
+ raise FileMetadataError(
+ "Distant resource does not have an ETag, we won't be able to reliably ensure reproducibility."
+ )
+ # We get the expected size of the file, to check the download went well.
+ expected_size = _int_or_none(r.headers.get("Content-Length"))
+ # In case of a redirect, save an extra redirect on the request.get call,
+ # and ensure we download the exact atomic version even if it changed
+ # between the HEAD and the GET (unlikely, but hey).
+ # Useful for lfs blobs that are stored on a CDN.
+ if 300 <= r.status_code <= 399:
+ url_to_download = r.headers["Location"]
+ headers.pop("authorization", None)
+ expected_size = None # redirected -> can't know the expected size
+ except (requests.exceptions.SSLError, requests.exceptions.ProxyError):
+ # Actually raise for those subclasses of ConnectionError
+ raise
+ except (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ OfflineModeIsEnabled,
+ ):
+ # Otherwise, our Internet connection is down.
+ # etag is None
+ pass
+
+ filename = force_filename if force_filename is not None else url_to_filename(url, etag)
+
+ # get cache path to put the file
+ cache_path = os.path.join(cache_dir, filename)
+
+ # etag is None == we don't have a connection or we passed local_files_only.
+ # try to get the last downloaded one
+ if etag is None:
+ if os.path.exists(cache_path) and not force_download:
+ return cache_path
+ else:
+ matching_files = [
+ file
+ for file in fnmatch.filter(os.listdir(cache_dir), filename.split(".")[0] + ".*")
+ if not file.endswith(".json") and not file.endswith(".lock")
+ ]
+ if len(matching_files) > 0 and not force_download and force_filename is None:
+ return os.path.join(cache_dir, matching_files[-1])
+ else:
+ # If files cannot be found and local_files_only=True,
+ # the models might've been found if local_files_only=False
+ # Notify the user about that
+ if local_files_only:
+ raise LocalEntryNotFoundError(
+ "Cannot find the requested files in the cached path and"
+ " outgoing traffic has been disabled. To enable model look-ups"
+ " and downloads online, set 'local_files_only' to False."
+ )
+ else:
+ raise LocalEntryNotFoundError(
+ "Connection error, and we cannot find the requested files in"
+ " the cached path. Please try again or make sure your Internet"
+ " connection is on."
+ )
+
+ # From now on, etag is not None.
+ if os.path.exists(cache_path) and not force_download:
+ return cache_path
+
+ # Prevent parallel downloads of the same file with a lock.
+ lock_path = cache_path + ".lock"
+
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it is an extended path by using the "\\?\" prefix.
+ if os.name == "nt" and len(os.path.abspath(lock_path)) > 255:
+ lock_path = "\\\\?\\" + os.path.abspath(lock_path)
+
+ if os.name == "nt" and len(os.path.abspath(cache_path)) > 255:
+ cache_path = "\\\\?\\" + os.path.abspath(cache_path)
+
+ with WeakFileLock(lock_path):
+ # If the download just completed while the lock was activated.
+ if os.path.exists(cache_path) and not force_download:
+ # Even if returning early like here, the lock will be released.
+ return cache_path
+
+ if resume_download:
+ incomplete_path = cache_path + ".incomplete"
+
+ @contextmanager
+ def _resumable_file_manager() -> Generator[io.BufferedWriter, None, None]:
+ with open(incomplete_path, "ab") as f:
+ yield f
+
+ temp_file_manager = _resumable_file_manager
+ if os.path.exists(incomplete_path):
+ resume_size = os.stat(incomplete_path).st_size
+ else:
+ resume_size = 0
+ else:
+ temp_file_manager = partial( # type: ignore
+ tempfile.NamedTemporaryFile, mode="wb", dir=cache_dir, delete=False
+ )
+ resume_size = 0
+
+ # Download to temporary file, then copy to cache dir once finished.
+ # Otherwise you get corrupt cache entries if the download gets interrupted.
+ with temp_file_manager() as temp_file:
+ logger.info("downloading %s to %s", url, temp_file.name)
+
+ http_get(
+ url_to_download,
+ temp_file,
+ proxies=proxies,
+ resume_size=resume_size,
+ headers=headers,
+ expected_size=expected_size,
+ )
+
+ logger.info("storing %s in cache at %s", url, cache_path)
+ _chmod_and_replace(temp_file.name, cache_path)
+
+ if force_filename is None:
+ logger.info("creating metadata file for %s", cache_path)
+ meta = {"url": url, "etag": etag}
+ meta_path = cache_path + ".json"
+ with open(meta_path, "w") as meta_file:
+ json.dump(meta, meta_file)
+
+ return cache_path
+
+
+def _normalize_etag(etag: Optional[str]) -> Optional[str]:
+ """Normalize ETag HTTP header, so it can be used to create nice filepaths.
+
+ The HTTP spec allows two forms of ETag:
+ ETag: W/""
+ ETag: ""
+
+ For now, we only expect the second form from the server, but we want to be future-proof so we support both. For
+ more context, see `TestNormalizeEtag` tests and https://github.com/huggingface/huggingface_hub/pull/1428.
+
+ Args:
+ etag (`str`, *optional*): HTTP header
+
+ Returns:
+ `str` or `None`: string that can be used as a nice directory name.
+ Returns `None` if input is None.
+ """
+ if etag is None:
+ return None
+ return etag.lstrip("W/").strip('"')
+
+
+def _create_relative_symlink(src: str, dst: str, new_blob: bool = False) -> None:
+ """Alias method used in `transformers` conversion script."""
+ return _create_symlink(src=src, dst=dst, new_blob=new_blob)
+
+
+def _create_symlink(src: str, dst: str, new_blob: bool = False) -> None:
+ """Create a symbolic link named dst pointing to src.
+
+ By default, it will try to create a symlink using a relative path. Relative paths have 2 advantages:
+ - If the cache_folder is moved (example: back-up on a shared drive), relative paths within the cache folder will
+ not break.
+ - Relative paths seems to be better handled on Windows. Issue was reported 3 times in less than a week when
+ changing from relative to absolute paths. See https://github.com/huggingface/huggingface_hub/issues/1398,
+ https://github.com/huggingface/diffusers/issues/2729 and https://github.com/huggingface/transformers/pull/22228.
+ NOTE: The issue with absolute paths doesn't happen on admin mode.
+ When creating a symlink from the cache to a local folder, it is possible that a relative path cannot be created.
+ This happens when paths are not on the same volume. In that case, we use absolute paths.
+
+
+ The result layout looks something like
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+
+ If symlinks cannot be created on this platform (most likely to be Windows), the workaround is to avoid symlinks by
+ having the actual file in `dst`. If it is a new file (`new_blob=True`), we move it to `dst`. If it is not a new file
+ (`new_blob=False`), we don't know if the blob file is already referenced elsewhere. To avoid breaking existing
+ cache, the file is duplicated on the disk.
+
+ In case symlinks are not supported, a warning message is displayed to the user once when loading `huggingface_hub`.
+ The warning message can be disabled with the `DISABLE_SYMLINKS_WARNING` environment variable.
+ """
+ try:
+ os.remove(dst)
+ except OSError:
+ pass
+
+ abs_src = os.path.abspath(os.path.expanduser(src))
+ abs_dst = os.path.abspath(os.path.expanduser(dst))
+ abs_dst_folder = os.path.dirname(abs_dst)
+
+ # Use relative_dst in priority
+ try:
+ relative_src = os.path.relpath(abs_src, abs_dst_folder)
+ except ValueError:
+ # Raised on Windows if src and dst are not on the same volume. This is the case when creating a symlink to a
+ # local_dir instead of within the cache directory.
+ # See https://docs.python.org/3/library/os.path.html#os.path.relpath
+ relative_src = None
+
+ try:
+ commonpath = os.path.commonpath([abs_src, abs_dst])
+ _support_symlinks = are_symlinks_supported(commonpath)
+ except ValueError:
+ # Raised if src and dst are not on the same volume. Symlinks will still work on Linux/Macos.
+ # See https://docs.python.org/3/library/os.path.html#os.path.commonpath
+ _support_symlinks = os.name != "nt"
+ except PermissionError:
+ # Permission error means src and dst are not in the same volume (e.g. destination path has been provided
+ # by the user via `local_dir`. Let's test symlink support there)
+ _support_symlinks = are_symlinks_supported(abs_dst_folder)
+ except OSError as e:
+ # OS error (errno=30) means that the commonpath is readonly on Linux/MacOS.
+ if e.errno == errno.EROFS:
+ _support_symlinks = are_symlinks_supported(abs_dst_folder)
+ else:
+ raise
+
+ # Symlinks are supported => let's create a symlink.
+ if _support_symlinks:
+ src_rel_or_abs = relative_src or abs_src
+ logger.debug(f"Creating pointer from {src_rel_or_abs} to {abs_dst}")
+ try:
+ os.symlink(src_rel_or_abs, abs_dst)
+ return
+ except FileExistsError:
+ if os.path.islink(abs_dst) and os.path.realpath(abs_dst) == os.path.realpath(abs_src):
+ # `abs_dst` already exists and is a symlink to the `abs_src` blob. It is most likely that the file has
+ # been cached twice concurrently (exactly between `os.remove` and `os.symlink`). Do nothing.
+ return
+ else:
+ # Very unlikely to happen. Means a file `dst` has been created exactly between `os.remove` and
+ # `os.symlink` and is not a symlink to the `abs_src` blob file. Raise exception.
+ raise
+ except PermissionError:
+ # Permission error means src and dst are not in the same volume (e.g. download to local dir) and symlink
+ # is supported on both volumes but not between them. Let's just make a hard copy in that case.
+ pass
+
+ # Symlinks are not supported => let's move or copy the file.
+ if new_blob:
+ logger.info(f"Symlink not supported. Moving file from {abs_src} to {abs_dst}")
+ shutil.move(abs_src, abs_dst)
+ else:
+ logger.info(f"Symlink not supported. Copying file from {abs_src} to {abs_dst}")
+ shutil.copyfile(abs_src, abs_dst)
+
+
+def _cache_commit_hash_for_specific_revision(storage_folder: str, revision: str, commit_hash: str) -> None:
+ """Cache reference between a revision (tag, branch or truncated commit hash) and the corresponding commit hash.
+
+ Does nothing if `revision` is already a proper `commit_hash` or reference is already cached.
+ """
+ if revision != commit_hash:
+ ref_path = Path(storage_folder) / "refs" / revision
+ ref_path.parent.mkdir(parents=True, exist_ok=True)
+ if not ref_path.exists() or commit_hash != ref_path.read_text():
+ # Update ref only if has been updated. Could cause useless error in case
+ # repo is already cached and user doesn't have write access to cache folder.
+ # See https://github.com/huggingface/huggingface_hub/issues/1216.
+ ref_path.write_text(commit_hash)
+
+
+@validate_hf_hub_args
+def repo_folder_name(*, repo_id: str, repo_type: str) -> str:
+ """Return a serialized version of a hf.co repo name and type, safe for disk storage
+ as a single non-nested folder.
+
+ Example: models--julien-c--EsperBERTo-small
+ """
+ # remove all `/` occurrences to correctly convert repo to directory name
+ parts = [f"{repo_type}s", *repo_id.split("/")]
+ return REPO_ID_SEPARATOR.join(parts)
+
+
+def _check_disk_space(expected_size: int, target_dir: Union[str, Path]) -> None:
+ """Check disk usage and log a warning if there is not enough disk space to download the file.
+
+ Args:
+ expected_size (`int`):
+ The expected size of the file in bytes.
+ target_dir (`str`):
+ The directory where the file will be stored after downloading.
+ """
+
+ target_dir = Path(target_dir) # format as `Path`
+ for path in [target_dir] + list(target_dir.parents): # first check target_dir, then each parents one by one
+ try:
+ target_dir_free = shutil.disk_usage(path).free
+ if target_dir_free < expected_size:
+ warnings.warn(
+ "Not enough free disk space to download the file. "
+ f"The expected file size is: {expected_size / 1e6:.2f} MB. "
+ f"The target location {target_dir} only has {target_dir_free / 1e6:.2f} MB free disk space."
+ )
+ return
+ except OSError: # raise on anything: file does not exist or space disk cannot be checked
+ pass
+
+
+@validate_hf_hub_args
+def hf_hub_download(
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ user_agent: Union[Dict, str, None] = None,
+ force_download: bool = False,
+ force_filename: Optional[str] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = DEFAULT_ETAG_TIMEOUT,
+ resume_download: bool = False,
+ token: Union[bool, str, None] = None,
+ local_files_only: bool = False,
+ headers: Optional[Dict[str, str]] = None,
+ legacy_cache_layout: bool = False,
+ endpoint: Optional[str] = None,
+) -> str:
+ """Download a given file if it's not already present in the local cache.
+
+ The new cache file layout looks like this:
+ - The cache directory contains one subfolder per repo_id (namespaced by repo type)
+ - inside each repo folder:
+ - refs is a list of the latest known revision => commit_hash pairs
+ - blobs contains the actual file blobs (identified by their git-sha or sha256, depending on
+ whether they're LFS files or not)
+ - snapshots contains one subfolder per commit, each "commit" contains the subset of the files
+ that have been resolved at that particular commit. Each filename is a symlink to the blob
+ at that particular commit.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. You can configure
+ how you want to move those files:
+ - If `local_dir_use_symlinks="auto"` (default), files are downloaded and stored in the cache directory as blob
+ files. Small files (<5MB) are duplicated in `local_dir` while a symlink is created for bigger files. The goal
+ is to be able to manually edit and save small files without corrupting the cache while saving disk space for
+ binary files. The 5MB threshold can be configured with the `HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD`
+ environment variable.
+ - If `local_dir_use_symlinks=True`, files are downloaded, stored in the cache directory and symlinked in `local_dir`.
+ This is optimal in term of disk usage but files must not be manually edited.
+ - If `local_dir_use_symlinks=False` and the blob files exist in the cache directory, they are duplicated in the
+ local dir. This means disk usage is not optimized.
+ - Finally, if `local_dir_use_symlinks=False` and the blob files do not exist in the cache directory, then the
+ files are downloaded and directly placed under `local_dir`. This means if you need to download them again later,
+ they will be re-downloaded entirely.
+
+ ```
+ [ 96] .
+ └── [ 160] models--julien-c--EsperBERTo-small
+ ├── [ 160] blobs
+ │ ├── [321M] 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ │ ├── [ 398] 7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ │ └── [1.4K] d7edf6bd2a681fb0175f7735299831ee1b22b812
+ ├── [ 96] refs
+ │ └── [ 40] main
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ └── [ 128] bbc77c8132af1cc5cf678da3f1ddf2de43606d48
+ ├── [ 52] README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ ```
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the model repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded file will be placed under this directory, either as a symlink (default) or
+ a regular file (see description for more details).
+ local_dir_use_symlinks (`"auto"` or `bool`, defaults to `"auto"`):
+ To be used with `local_dir`. If set to "auto", the cache directory will be used and the file will be either
+ duplicated or symlinked to the local directory depending on its size. It set to `True`, a symlink will be
+ created, no matter the file size. If set to `False`, the file will either be duplicated from cache (if
+ already exists) or downloaded from the Hub and not cached. See description for more details.
+ user_agent (`dict`, `str`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in
+ the local cache.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ If `True`, resume a previously interrupted download.
+ token (`str`, `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If a string, it's used as the authentication token.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with the request.
+ legacy_cache_layout (`bool`, *optional*, defaults to `False`):
+ If `True`, uses the legacy file cache layout i.e. just call [`hf_hub_url`]
+ then `cached_download`. This is deprecated as the new cache layout is
+ more powerful.
+
+ Returns:
+ Local path (string) of file or if networking is off, last version of
+ file cached on disk.
+
+
+
+ Raises the following errors:
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `token=True` and the token cannot be found.
+ - [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ if ETag cannot be determined.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ - [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+ - [`~utils.LocalEntryNotFoundError`]
+ If network is disabled or unavailable and file is not found in cache.
+
+
+ """
+ if HF_HUB_ETAG_TIMEOUT != DEFAULT_ETAG_TIMEOUT:
+ # Respect environment variable above user value
+ etag_timeout = HF_HUB_ETAG_TIMEOUT
+
+ if force_filename is not None:
+ warnings.warn(
+ "The `force_filename` parameter is deprecated as a new caching system, "
+ "which keeps the filenames as they are on the Hub, is now in place.",
+ FutureWarning,
+ )
+ legacy_cache_layout = True
+
+ if legacy_cache_layout:
+ url = hf_hub_url(
+ repo_id,
+ filename,
+ subfolder=subfolder,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=endpoint,
+ )
+
+ return cached_download(
+ url,
+ library_name=library_name,
+ library_version=library_version,
+ cache_dir=cache_dir,
+ user_agent=user_agent,
+ force_download=force_download,
+ force_filename=force_filename,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ legacy_cache_layout=legacy_cache_layout,
+ )
+
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+ if revision is None:
+ revision = DEFAULT_REVISION
+ if isinstance(cache_dir, Path):
+ cache_dir = str(cache_dir)
+ if isinstance(local_dir, Path):
+ local_dir = str(local_dir)
+ locks_dir = os.path.join(cache_dir, ".locks")
+
+ if subfolder == "":
+ subfolder = None
+ if subfolder is not None:
+ # This is used to create a URL, and not a local path, hence the forward slash.
+ filename = f"{subfolder}/{filename}"
+
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(REPO_TYPES)}")
+
+ storage_folder = os.path.join(cache_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type))
+
+ # cross platform transcription of filename, to be used as a local file path.
+ relative_filename = os.path.join(*filename.split("/"))
+ if os.name == "nt":
+ if relative_filename.startswith("..\\") or "\\..\\" in relative_filename:
+ raise ValueError(
+ f"Invalid filename: cannot handle filename '{relative_filename}' on Windows. Please ask the repository"
+ " owner to rename this file."
+ )
+
+ # if user provides a commit_hash and they already have the file on disk,
+ # shortcut everything.
+ if REGEX_COMMIT_HASH.match(revision):
+ pointer_path = _get_pointer_path(storage_folder, revision, relative_filename)
+ if os.path.exists(pointer_path):
+ if local_dir is not None:
+ return _to_local_dir(pointer_path, local_dir, relative_filename, use_symlinks=local_dir_use_symlinks)
+ return pointer_path
+
+ url = hf_hub_url(repo_id, filename, repo_type=repo_type, revision=revision, endpoint=endpoint)
+
+ headers = build_hf_headers(
+ token=token,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ headers=headers,
+ )
+
+ url_to_download = url
+ etag = None
+ commit_hash = None
+ expected_size = None
+ head_call_error: Optional[Exception] = None
+ if not local_files_only:
+ try:
+ try:
+ metadata = get_hf_file_metadata(
+ url=url,
+ token=token,
+ proxies=proxies,
+ timeout=etag_timeout,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ )
+ except EntryNotFoundError as http_error:
+ # Cache the non-existence of the file and raise
+ commit_hash = http_error.response.headers.get(HUGGINGFACE_HEADER_X_REPO_COMMIT)
+ if commit_hash is not None and not legacy_cache_layout:
+ no_exist_file_path = Path(storage_folder) / ".no_exist" / commit_hash / relative_filename
+ no_exist_file_path.parent.mkdir(parents=True, exist_ok=True)
+ no_exist_file_path.touch()
+ _cache_commit_hash_for_specific_revision(storage_folder, revision, commit_hash)
+ raise
+
+ # Commit hash must exist
+ commit_hash = metadata.commit_hash
+ if commit_hash is None:
+ raise FileMetadataError(
+ "Distant resource does not seem to be on huggingface.co. It is possible that a configuration issue"
+ " prevents you from downloading resources from https://huggingface.co. Please check your firewall"
+ " and proxy settings and make sure your SSL certificates are updated."
+ )
+
+ # Etag must exist
+ etag = metadata.etag
+ # We favor a custom header indicating the etag of the linked resource, and
+ # we fallback to the regular etag header.
+ # If we don't have any of those, raise an error.
+ if etag is None:
+ raise FileMetadataError(
+ "Distant resource does not have an ETag, we won't be able to reliably ensure reproducibility."
+ )
+
+ # Expected (uncompressed) size
+ expected_size = metadata.size
+
+ # In case of a redirect, save an extra redirect on the request.get call,
+ # and ensure we download the exact atomic version even if it changed
+ # between the HEAD and the GET (unlikely, but hey).
+ #
+ # If url domain is different => we are downloading from a CDN => url is signed => don't send auth
+ # If url domain is the same => redirect due to repo rename AND downloading a regular file => keep auth
+ if metadata.location != url:
+ url_to_download = metadata.location
+ if urlparse(url).netloc != urlparse(url_to_download).netloc:
+ # Remove authorization header when downloading a LFS blob
+ headers.pop("authorization", None)
+ except (requests.exceptions.SSLError, requests.exceptions.ProxyError):
+ # Actually raise for those subclasses of ConnectionError
+ raise
+ except (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ OfflineModeIsEnabled,
+ ) as error:
+ # Otherwise, our Internet connection is down.
+ # etag is None
+ head_call_error = error
+ pass
+ except (RevisionNotFoundError, EntryNotFoundError):
+ # The repo was found but the revision or entry doesn't exist on the Hub (never existed or got deleted)
+ raise
+ except requests.HTTPError as error:
+ # Multiple reasons for an http error:
+ # - Repository is private and invalid/missing token sent
+ # - Repository is gated and invalid/missing token sent
+ # - Hub is down (error 500 or 504)
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ head_call_error = error
+ pass
+ except FileMetadataError as error:
+ # Multiple reasons for a FileMetadataError:
+ # - Wrong network configuration (proxy, firewall, SSL certificates)
+ # - Inconsistency on the Hub
+ # => let's switch to 'local_files_only=True' to check if the files are already cached.
+ # (if it's not the case, the error will be re-raised)
+ head_call_error = error
+ pass
+
+ assert (
+ local_files_only or etag is not None or head_call_error is not None
+ ), "etag is empty due to uncovered problems"
+
+ # etag can be None for several reasons:
+ # 1. we passed local_files_only.
+ # 2. we don't have a connection
+ # 3. Hub is down (HTTP 500 or 504)
+ # 4. repo is not found -for example private or gated- and invalid/missing token sent
+ # 5. Hub is blocked by a firewall or proxy is not set correctly.
+ # => Try to get the last downloaded one from the specified revision.
+ #
+ # If the specified revision is a commit hash, look inside "snapshots".
+ # If the specified revision is a branch or tag, look inside "refs".
+ if etag is None:
+ # In those cases, we cannot force download.
+ if force_download:
+ if local_files_only:
+ raise ValueError("Cannot pass 'force_download=True' and 'local_files_only=True' at the same time.")
+ elif isinstance(head_call_error, OfflineModeIsEnabled):
+ raise ValueError(
+ "Cannot pass 'force_download=True' when offline mode is enabled."
+ ) from head_call_error
+ else:
+ raise ValueError("Force download failed due to the above error.") from head_call_error
+
+ # Try to get "commit_hash" from "revision"
+ commit_hash = None
+ if REGEX_COMMIT_HASH.match(revision):
+ commit_hash = revision
+ else:
+ ref_path = os.path.join(storage_folder, "refs", revision)
+ if os.path.isfile(ref_path):
+ with open(ref_path) as f:
+ commit_hash = f.read()
+
+ # Return pointer file if exists
+ if commit_hash is not None:
+ pointer_path = _get_pointer_path(storage_folder, commit_hash, relative_filename)
+ if os.path.exists(pointer_path):
+ if local_dir is not None:
+ return _to_local_dir(
+ pointer_path, local_dir, relative_filename, use_symlinks=local_dir_use_symlinks
+ )
+ return pointer_path
+
+ # If we couldn't find an appropriate file on disk, raise an error.
+ # If files cannot be found and local_files_only=True,
+ # the models might've been found if local_files_only=False
+ # Notify the user about that
+ if local_files_only:
+ raise LocalEntryNotFoundError(
+ "Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable"
+ " hf.co look-ups and downloads online, set 'local_files_only' to False."
+ )
+ elif isinstance(head_call_error, RepositoryNotFoundError) or isinstance(head_call_error, GatedRepoError):
+ # Repo not found or gated => let's raise the actual error
+ raise head_call_error
+ else:
+ # Otherwise: most likely a connection issue or Hub downtime => let's warn the user
+ raise LocalEntryNotFoundError(
+ "An error happened while trying to locate the file on the Hub and we cannot find the requested files"
+ " in the local cache. Please check your connection and try again or make sure your Internet connection"
+ " is on."
+ ) from head_call_error
+
+ # From now on, etag and commit_hash are not None.
+ assert etag is not None, "etag must have been retrieved from server"
+ assert commit_hash is not None, "commit_hash must have been retrieved from server"
+ blob_path = os.path.join(storage_folder, "blobs", etag)
+ pointer_path = _get_pointer_path(storage_folder, commit_hash, relative_filename)
+
+ os.makedirs(os.path.dirname(blob_path), exist_ok=True)
+ os.makedirs(os.path.dirname(pointer_path), exist_ok=True)
+ # if passed revision is not identical to commit_hash
+ # then revision has to be a branch name or tag name.
+ # In that case store a ref.
+ _cache_commit_hash_for_specific_revision(storage_folder, revision, commit_hash)
+
+ if os.path.exists(pointer_path) and not force_download:
+ if local_dir is not None:
+ return _to_local_dir(pointer_path, local_dir, relative_filename, use_symlinks=local_dir_use_symlinks)
+ return pointer_path
+
+ if os.path.exists(blob_path) and not force_download:
+ # we have the blob already, but not the pointer
+ if local_dir is not None: # to local dir
+ return _to_local_dir(blob_path, local_dir, relative_filename, use_symlinks=local_dir_use_symlinks)
+ else: # or in snapshot cache
+ _create_symlink(blob_path, pointer_path, new_blob=False)
+ return pointer_path
+
+ # Prevent parallel downloads of the same file with a lock.
+ # etag could be duplicated across repos,
+ lock_path = os.path.join(locks_dir, repo_folder_name(repo_id=repo_id, repo_type=repo_type), f"{etag}.lock")
+
+ # Some Windows versions do not allow for paths longer than 255 characters.
+ # In this case, we must specify it is an extended path by using the "\\?\" prefix.
+ if os.name == "nt" and len(os.path.abspath(lock_path)) > 255:
+ lock_path = "\\\\?\\" + os.path.abspath(lock_path)
+
+ if os.name == "nt" and len(os.path.abspath(blob_path)) > 255:
+ blob_path = "\\\\?\\" + os.path.abspath(blob_path)
+
+ Path(lock_path).parent.mkdir(parents=True, exist_ok=True)
+ with WeakFileLock(lock_path):
+ # If the download just completed while the lock was activated.
+ if os.path.exists(pointer_path) and not force_download:
+ # Even if returning early like here, the lock will be released.
+ if local_dir is not None:
+ return _to_local_dir(pointer_path, local_dir, relative_filename, use_symlinks=local_dir_use_symlinks)
+ return pointer_path
+
+ if resume_download:
+ incomplete_path = blob_path + ".incomplete"
+
+ @contextmanager
+ def _resumable_file_manager() -> Generator[io.BufferedWriter, None, None]:
+ with open(incomplete_path, "ab") as f:
+ yield f
+
+ temp_file_manager = _resumable_file_manager
+ if os.path.exists(incomplete_path):
+ resume_size = os.stat(incomplete_path).st_size
+ else:
+ resume_size = 0
+ else:
+ temp_file_manager = partial( # type: ignore
+ tempfile.NamedTemporaryFile, mode="wb", dir=cache_dir, delete=False
+ )
+ resume_size = 0
+
+ # Download to temporary file, then copy to cache dir once finished.
+ # Otherwise you get corrupt cache entries if the download gets interrupted.
+ with temp_file_manager() as temp_file:
+ logger.info("downloading %s to %s", url, temp_file.name)
+
+ if expected_size is not None: # might be None if HTTP header not set correctly
+ # Check tmp path
+ _check_disk_space(expected_size, os.path.dirname(temp_file.name))
+
+ # Check destination
+ _check_disk_space(expected_size, os.path.dirname(blob_path))
+ if local_dir is not None:
+ _check_disk_space(expected_size, local_dir)
+
+ http_get(
+ url_to_download,
+ temp_file,
+ proxies=proxies,
+ resume_size=resume_size,
+ headers=headers,
+ expected_size=expected_size,
+ displayed_filename=filename,
+ )
+
+ if local_dir is None:
+ logger.debug(f"Storing {url} in cache at {blob_path}")
+ _chmod_and_replace(temp_file.name, blob_path)
+ _create_symlink(blob_path, pointer_path, new_blob=True)
+ else:
+ local_dir_filepath = os.path.join(local_dir, relative_filename)
+ os.makedirs(os.path.dirname(local_dir_filepath), exist_ok=True)
+
+ # If "auto" (default) copy-paste small files to ease manual editing but symlink big files to save disk
+ # In both cases, blob file is cached.
+ is_big_file = os.stat(temp_file.name).st_size > constants.HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD
+ if local_dir_use_symlinks is True or (local_dir_use_symlinks == "auto" and is_big_file):
+ logger.debug(f"Storing {url} in cache at {blob_path}")
+ _chmod_and_replace(temp_file.name, blob_path)
+ logger.debug("Create symlink to local dir")
+ _create_symlink(blob_path, local_dir_filepath, new_blob=False)
+ elif local_dir_use_symlinks == "auto" and not is_big_file:
+ logger.debug(f"Storing {url} in cache at {blob_path}")
+ _chmod_and_replace(temp_file.name, blob_path)
+ logger.debug("Duplicate in local dir (small file and use_symlink set to 'auto')")
+ shutil.copyfile(blob_path, local_dir_filepath)
+ else:
+ logger.debug(f"Storing {url} in local_dir at {local_dir_filepath} (not cached).")
+ _chmod_and_replace(temp_file.name, local_dir_filepath)
+ pointer_path = local_dir_filepath # for return value
+
+ return pointer_path
+
+
+@validate_hf_hub_args
+def try_to_load_from_cache(
+ repo_id: str,
+ filename: str,
+ cache_dir: Union[str, Path, None] = None,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+) -> Union[str, _CACHED_NO_EXIST_T, None]:
+ """
+ Explores the cache to return the latest cached file for a given revision if found.
+
+ This function will not raise any exception if the file in not cached.
+
+ Args:
+ cache_dir (`str` or `os.PathLike`):
+ The folder where the cached files lie.
+ repo_id (`str`):
+ The ID of the repo on huggingface.co.
+ filename (`str`):
+ The filename to look for inside `repo_id`.
+ revision (`str`, *optional*):
+ The specific model version to use. Will default to `"main"` if it's not provided and no `commit_hash` is
+ provided either.
+ repo_type (`str`, *optional*):
+ The type of the repository. Will default to `"model"`.
+
+ Returns:
+ `Optional[str]` or `_CACHED_NO_EXIST`:
+ Will return `None` if the file was not cached. Otherwise:
+ - The exact path to the cached file if it's found in the cache
+ - A special value `_CACHED_NO_EXIST` if the file does not exist at the given commit hash and this fact was
+ cached.
+
+ Example:
+
+ ```python
+ from huggingface_hub import try_to_load_from_cache, _CACHED_NO_EXIST
+
+ filepath = try_to_load_from_cache()
+ if isinstance(filepath, str):
+ # file exists and is cached
+ ...
+ elif filepath is _CACHED_NO_EXIST:
+ # non-existence of file is cached
+ ...
+ else:
+ # file is not cached
+ ...
+ ```
+ """
+ if revision is None:
+ revision = "main"
+ if repo_type is None:
+ repo_type = "model"
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type: {repo_type}. Accepted repo types are: {str(REPO_TYPES)}")
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+
+ object_id = repo_id.replace("/", "--")
+ repo_cache = os.path.join(cache_dir, f"{repo_type}s--{object_id}")
+ if not os.path.isdir(repo_cache):
+ # No cache for this model
+ return None
+
+ refs_dir = os.path.join(repo_cache, "refs")
+ snapshots_dir = os.path.join(repo_cache, "snapshots")
+ no_exist_dir = os.path.join(repo_cache, ".no_exist")
+
+ # Resolve refs (for instance to convert main to the associated commit sha)
+ if os.path.isdir(refs_dir):
+ revision_file = os.path.join(refs_dir, revision)
+ if os.path.isfile(revision_file):
+ with open(revision_file) as f:
+ revision = f.read()
+
+ # Check if file is cached as "no_exist"
+ if os.path.isfile(os.path.join(no_exist_dir, revision, filename)):
+ return _CACHED_NO_EXIST
+
+ # Check if revision folder exists
+ if not os.path.exists(snapshots_dir):
+ return None
+ cached_shas = os.listdir(snapshots_dir)
+ if revision not in cached_shas:
+ # No cache for this revision and we won't try to return a random revision
+ return None
+
+ # Check if file exists in cache
+ cached_file = os.path.join(snapshots_dir, revision, filename)
+ return cached_file if os.path.isfile(cached_file) else None
+
+
+@validate_hf_hub_args
+def get_hf_file_metadata(
+ url: str,
+ token: Union[bool, str, None] = None,
+ proxies: Optional[Dict] = None,
+ timeout: Optional[float] = DEFAULT_REQUEST_TIMEOUT,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+) -> HfFileMetadata:
+ """Fetch metadata of a file versioned on the Hub for a given url.
+
+ Args:
+ url (`str`):
+ File url, for example returned by [`hf_hub_url`].
+ token (`str` or `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If `False` or `None`, no token is provided.
+ - If a string, it's used as the authentication token.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ timeout (`float`, *optional*, defaults to 10):
+ How many seconds to wait for the server to send metadata before giving up.
+ library_name (`str`, *optional*):
+ The name of the library to which the object corresponds.
+ library_version (`str`, *optional*):
+ The version of the library.
+ user_agent (`dict`, `str`, *optional*):
+ The user-agent info in the form of a dictionary or a string.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with the request.
+
+ Returns:
+ A [`HfFileMetadata`] object containing metadata such as location, etag, size and
+ commit_hash.
+ """
+ headers = build_hf_headers(
+ token=token,
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ headers=headers,
+ )
+ headers["Accept-Encoding"] = "identity" # prevent any compression => we want to know the real size of the file
+
+ # Retrieve metadata
+ r = _request_wrapper(
+ method="HEAD",
+ url=url,
+ headers=headers,
+ allow_redirects=False,
+ follow_relative_redirects=True,
+ proxies=proxies,
+ timeout=timeout,
+ )
+ hf_raise_for_status(r)
+
+ # Return
+ return HfFileMetadata(
+ commit_hash=r.headers.get(HUGGINGFACE_HEADER_X_REPO_COMMIT),
+ # We favor a custom header indicating the etag of the linked resource, and
+ # we fallback to the regular etag header.
+ etag=_normalize_etag(r.headers.get(HUGGINGFACE_HEADER_X_LINKED_ETAG) or r.headers.get("ETag")),
+ # Either from response headers (if redirected) or defaults to request url
+ # Do not use directly `url`, as `_request_wrapper` might have followed relative
+ # redirects.
+ location=r.headers.get("Location") or r.request.url, # type: ignore
+ size=_int_or_none(r.headers.get(HUGGINGFACE_HEADER_X_LINKED_SIZE) or r.headers.get("Content-Length")),
+ )
+
+
+def _int_or_none(value: Optional[str]) -> Optional[int]:
+ try:
+ return int(value) # type: ignore
+ except (TypeError, ValueError):
+ return None
+
+
+def _chmod_and_replace(src: str, dst: str) -> None:
+ """Set correct permission before moving a blob from tmp directory to cache dir.
+
+ Do not take into account the `umask` from the process as there is no convenient way
+ to get it that is thread-safe.
+
+ See:
+ - About umask: https://docs.python.org/3/library/os.html#os.umask
+ - Thread-safety: https://stackoverflow.com/a/70343066
+ - About solution: https://github.com/huggingface/huggingface_hub/pull/1220#issuecomment-1326211591
+ - Fix issue: https://github.com/huggingface/huggingface_hub/issues/1141
+ - Fix issue: https://github.com/huggingface/huggingface_hub/issues/1215
+ """
+ # Get umask by creating a temporary file in the cached repo folder.
+ tmp_file = Path(dst).parent.parent / f"tmp_{uuid.uuid4()}"
+ try:
+ tmp_file.touch()
+ cache_dir_mode = Path(tmp_file).stat().st_mode
+ os.chmod(src, stat.S_IMODE(cache_dir_mode))
+ finally:
+ tmp_file.unlink()
+
+ shutil.move(src, dst)
+
+
+def _get_pointer_path(storage_folder: str, revision: str, relative_filename: str) -> str:
+ # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks
+ snapshot_path = os.path.join(storage_folder, "snapshots")
+ pointer_path = os.path.join(snapshot_path, revision, relative_filename)
+ if Path(os.path.abspath(snapshot_path)) not in Path(os.path.abspath(pointer_path)).parents:
+ raise ValueError(
+ "Invalid pointer path: cannot create pointer path in snapshot folder if"
+ f" `storage_folder='{storage_folder}'`, `revision='{revision}'` and"
+ f" `relative_filename='{relative_filename}'`."
+ )
+ return pointer_path
+
+
+def _to_local_dir(
+ path: str, local_dir: str, relative_filename: str, use_symlinks: Union[bool, Literal["auto"]]
+) -> str:
+ """Place a file in a local dir (different than cache_dir).
+
+ Either symlink to blob file in cache or duplicate file depending on `use_symlinks` and file size.
+ """
+ # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks
+ local_dir_filepath = os.path.join(local_dir, relative_filename)
+ if Path(os.path.abspath(local_dir)) not in Path(os.path.abspath(local_dir_filepath)).parents:
+ raise ValueError(
+ f"Cannot copy file '{relative_filename}' to local dir '{local_dir}': file would not be in the local"
+ " directory."
+ )
+
+ os.makedirs(os.path.dirname(local_dir_filepath), exist_ok=True)
+ real_blob_path = os.path.realpath(path)
+
+ # If "auto" (default) copy-paste small files to ease manual editing but symlink big files to save disk
+ if use_symlinks == "auto":
+ use_symlinks = os.stat(real_blob_path).st_size > constants.HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD
+
+ if use_symlinks:
+ _create_symlink(real_blob_path, local_dir_filepath, new_blob=False)
+ else:
+ shutil.copyfile(real_blob_path, local_dir_filepath)
+ return local_dir_filepath
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/hf_api.py b/.venv/lib/python3.10/site-packages/huggingface_hub/hf_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e77228a8d870f77e46ed486d37240443fdfd62e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/hf_api.py
@@ -0,0 +1,8627 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import inspect
+import json
+import re
+import struct
+import warnings
+from concurrent.futures import Future, ThreadPoolExecutor
+from dataclasses import asdict, dataclass, field
+from datetime import datetime
+from functools import wraps
+from itertools import islice
+from pathlib import Path
+from typing import (
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Literal,
+ Optional,
+ Tuple,
+ TypeVar,
+ Union,
+ overload,
+)
+from urllib.parse import quote
+
+import requests
+from requests.exceptions import HTTPError
+from tqdm.auto import tqdm as base_tqdm
+from tqdm.contrib.concurrent import thread_map
+
+from ._commit_api import (
+ CommitOperation,
+ CommitOperationAdd,
+ CommitOperationCopy,
+ CommitOperationDelete,
+ _fetch_files_to_copy,
+ _fetch_upload_modes,
+ _prepare_commit_payload,
+ _upload_lfs_files,
+ _warn_on_overwriting_operations,
+)
+from ._inference_endpoints import InferenceEndpoint, InferenceEndpointType
+from ._multi_commits import (
+ MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_BAD_REQUEST_TEMPLATE,
+ MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_NO_CHANGES_TEMPLATE,
+ MULTI_COMMIT_PR_CLOSING_COMMENT_TEMPLATE,
+ MULTI_COMMIT_PR_COMPLETION_COMMENT_TEMPLATE,
+ MultiCommitException,
+ MultiCommitStep,
+ MultiCommitStrategy,
+ multi_commit_create_pull_request,
+ multi_commit_generate_comment,
+ multi_commit_parse_pr_description,
+ plan_multi_commits,
+)
+from ._space_api import SpaceHardware, SpaceRuntime, SpaceStorage, SpaceVariable
+from .community import (
+ Discussion,
+ DiscussionComment,
+ DiscussionStatusChange,
+ DiscussionTitleChange,
+ DiscussionWithDetails,
+ deserialize_event,
+)
+from .constants import (
+ DEFAULT_ETAG_TIMEOUT,
+ DEFAULT_REQUEST_TIMEOUT,
+ DEFAULT_REVISION,
+ DISCUSSION_STATUS,
+ DISCUSSION_TYPES,
+ ENDPOINT,
+ INFERENCE_ENDPOINTS_ENDPOINT,
+ REGEX_COMMIT_OID,
+ REPO_TYPE_MODEL,
+ REPO_TYPES,
+ REPO_TYPES_MAPPING,
+ REPO_TYPES_URL_PREFIXES,
+ SAFETENSORS_INDEX_FILE,
+ SAFETENSORS_MAX_HEADER_LENGTH,
+ SAFETENSORS_SINGLE_FILE,
+ SPACES_SDK_TYPES,
+ DiscussionStatusFilter,
+ DiscussionTypeFilter,
+)
+from .file_download import HfFileMetadata, get_hf_file_metadata, hf_hub_url
+from .repocard_data import DatasetCardData, ModelCardData, SpaceCardData
+from .utils import ( # noqa: F401 # imported for backward compatibility
+ IGNORE_GIT_FOLDER_PATTERNS,
+ BadRequestError,
+ EntryNotFoundError,
+ GatedRepoError,
+ HfFolder,
+ HfHubHTTPError,
+ LocalTokenNotFoundError,
+ NotASafetensorsRepoError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ SafetensorsFileMetadata,
+ SafetensorsParsingError,
+ SafetensorsRepoMetadata,
+ TensorInfo,
+ build_hf_headers,
+ experimental,
+ filter_repo_objects,
+ fix_hf_endpoint_in_url,
+ get_session,
+ hf_raise_for_status,
+ logging,
+ paginate,
+ parse_datetime,
+ validate_hf_hub_args,
+)
+from .utils import tqdm as hf_tqdm
+from .utils._deprecation import _deprecate_arguments, _deprecate_method
+from .utils._typing import CallableT
+from .utils.endpoint_helpers import (
+ DatasetFilter,
+ ModelFilter,
+ _is_emission_within_treshold,
+)
+
+
+R = TypeVar("R") # Return type
+CollectionItemType_T = Literal["model", "dataset", "space", "paper"]
+
+USERNAME_PLACEHOLDER = "hf_user"
+_REGEX_DISCUSSION_URL = re.compile(r".*/discussions/(\d+)$")
+
+_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE = (
+ "\nNote: Creating a commit assumes that the repo already exists on the"
+ " Huggingface Hub. Please use `create_repo` if it's not the case."
+)
+
+logger = logging.get_logger(__name__)
+
+
+def repo_type_and_id_from_hf_id(hf_id: str, hub_url: Optional[str] = None) -> Tuple[Optional[str], Optional[str], str]:
+ """
+ Returns the repo type and ID from a huggingface.co URL linking to a
+ repository
+
+ Args:
+ hf_id (`str`):
+ An URL or ID of a repository on the HF hub. Accepted values are:
+
+ - https://huggingface.co///
+ - https://huggingface.co//
+ - hf:////
+ - hf:///
+ - //
+ - /
+ -
+ hub_url (`str`, *optional*):
+ The URL of the HuggingFace Hub, defaults to https://huggingface.co
+
+ Returns:
+ A tuple with three items: repo_type (`str` or `None`), namespace (`str` or
+ `None`) and repo_id (`str`).
+
+ Raises:
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If URL cannot be parsed.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `repo_type` is unknown.
+ """
+ input_hf_id = hf_id
+
+ hub_url = re.sub(r"https?://", "", hub_url if hub_url is not None else ENDPOINT)
+ is_hf_url = hub_url in hf_id and "@" not in hf_id
+
+ HFFS_PREFIX = "hf://"
+ if hf_id.startswith(HFFS_PREFIX): # Remove "hf://" prefix if exists
+ hf_id = hf_id[len(HFFS_PREFIX) :]
+
+ url_segments = hf_id.split("/")
+ is_hf_id = len(url_segments) <= 3
+
+ namespace: Optional[str]
+ if is_hf_url:
+ namespace, repo_id = url_segments[-2:]
+ if namespace == hub_url:
+ namespace = None
+ if len(url_segments) > 2 and hub_url not in url_segments[-3]:
+ repo_type = url_segments[-3]
+ elif namespace in REPO_TYPES_MAPPING:
+ # Mean canonical dataset or model
+ repo_type = REPO_TYPES_MAPPING[namespace]
+ namespace = None
+ else:
+ repo_type = None
+ elif is_hf_id:
+ if len(url_segments) == 3:
+ # Passed // or //
+ repo_type, namespace, repo_id = url_segments[-3:]
+ elif len(url_segments) == 2:
+ if url_segments[0] in REPO_TYPES_MAPPING:
+ # Passed '' or 'datasets/' for a canonical model or dataset
+ repo_type = REPO_TYPES_MAPPING[url_segments[0]]
+ namespace = None
+ repo_id = hf_id.split("/")[-1]
+ else:
+ # Passed / or /
+ namespace, repo_id = hf_id.split("/")[-2:]
+ repo_type = None
+ else:
+ # Passed
+ repo_id = url_segments[0]
+ namespace, repo_type = None, None
+ else:
+ raise ValueError(f"Unable to retrieve user and repo ID from the passed HF ID: {hf_id}")
+
+ # Check if repo type is known (mapping "spaces" => "space" + empty value => `None`)
+ if repo_type in REPO_TYPES_MAPPING:
+ repo_type = REPO_TYPES_MAPPING[repo_type]
+ if repo_type == "":
+ repo_type = None
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Unknown `repo_type`: '{repo_type}' ('{input_hf_id}')")
+
+ return repo_type, namespace, repo_id
+
+
+@dataclass
+class LastCommitInfo(dict):
+ oid: str
+ title: str
+ date: datetime
+
+ def __post_init__(self): # hack to make LastCommitInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class BlobLfsInfo(dict):
+ size: int
+ sha256: str
+ pointer_size: int
+
+ def __post_init__(self): # hack to make BlobLfsInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class BlobSecurityInfo(dict):
+ safe: bool
+ av_scan: Optional[Dict]
+ pickle_import_scan: Optional[Dict]
+
+ def __post_init__(self): # hack to make BlogSecurityInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class TransformersInfo(dict):
+ auto_model: str
+ custom_class: Optional[str] = None
+ # possible `pipeline_tag` values: https://github.com/huggingface/huggingface.js/blob/3ee32554b8620644a6287e786b2a83bf5caf559c/packages/tasks/src/pipelines.ts#L72
+ pipeline_tag: Optional[str] = None
+ processor: Optional[str] = None
+
+ def __post_init__(self): # hack to make TransformersInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class SafeTensorsInfo(dict):
+ parameters: List[Dict[str, int]]
+ total: int
+
+ def __post_init__(self): # hack to make SafeTensorsInfo backward compatible
+ self.update(asdict(self))
+
+
+@dataclass
+class CommitInfo(str):
+ """Data structure containing information about a newly created commit.
+
+ Returned by any method that creates a commit on the Hub: [`create_commit`], [`upload_file`], [`upload_folder`],
+ [`delete_file`], [`delete_folder`]. It inherits from `str` for backward compatibility but using methods specific
+ to `str` is deprecated.
+
+ Attributes:
+ commit_url (`str`):
+ Url where to find the commit.
+
+ commit_message (`str`):
+ The summary (first line) of the commit that has been created.
+
+ commit_description (`str`):
+ Description of the commit that has been created. Can be empty.
+
+ oid (`str`):
+ Commit hash id. Example: `"91c54ad1727ee830252e457677f467be0bfd8a57"`.
+
+ pr_url (`str`, *optional*):
+ Url to the PR that has been created, if any. Populated when `create_pr=True`
+ is passed.
+
+ pr_revision (`str`, *optional*):
+ Revision of the PR that has been created, if any. Populated when
+ `create_pr=True` is passed. Example: `"refs/pr/1"`.
+
+ pr_num (`int`, *optional*):
+ Number of the PR discussion that has been created, if any. Populated when
+ `create_pr=True` is passed. Can be passed as `discussion_num` in
+ [`get_discussion_details`]. Example: `1`.
+
+ _url (`str`, *optional*):
+ Legacy url for `str` compatibility. Can be the url to the uploaded file on the Hub (if returned by
+ [`upload_file`]), to the uploaded folder on the Hub (if returned by [`upload_folder`]) or to the commit on
+ the Hub (if returned by [`create_commit`]). Defaults to `commit_url`. It is deprecated to use this
+ attribute. Please use `commit_url` instead.
+ """
+
+ commit_url: str
+ commit_message: str
+ commit_description: str
+ oid: str
+ pr_url: Optional[str] = None
+
+ # Computed from `pr_url` in `__post_init__`
+ pr_revision: Optional[str] = field(init=False)
+ pr_num: Optional[str] = field(init=False)
+
+ # legacy url for `str` compatibility (ex: url to uploaded file, url to uploaded folder, url to PR, etc.)
+ _url: str = field(repr=False, default=None) # type: ignore # defaults to `commit_url`
+
+ def __new__(cls, *args, commit_url: str, _url: Optional[str] = None, **kwargs):
+ return str.__new__(cls, _url or commit_url)
+
+ def __post_init__(self):
+ """Populate pr-related fields after initialization.
+
+ See https://docs.python.org/3.10/library/dataclasses.html#post-init-processing.
+ """
+ if self.pr_url is not None:
+ self.pr_revision = _parse_revision_from_pr_url(self.pr_url)
+ self.pr_num = int(self.pr_revision.split("/")[-1])
+ else:
+ self.pr_revision = None
+ self.pr_num = None
+
+
+@dataclass
+class AccessRequest:
+ """Data structure containing information about a user access request.
+
+ Attributes:
+ username (`str`):
+ Username of the user who requested access.
+ fullname (`str`):
+ Fullname of the user who requested access.
+ email (`str`):
+ Email of the user who requested access.
+ timestamp (`datetime`):
+ Timestamp of the request.
+ status (`Literal["pending", "accepted", "rejected"]`):
+ Status of the request. Can be one of `["pending", "accepted", "rejected"]`.
+ fields (`Dict[str, Any]`, *optional*):
+ Additional fields filled by the user in the gate form.
+ """
+
+ username: str
+ fullname: str
+ email: str
+ timestamp: datetime
+ status: Literal["pending", "accepted", "rejected"]
+
+ # Additional fields filled by the user in the gate form
+ fields: Optional[Dict[str, Any]] = None
+
+
+class RepoUrl(str):
+ """Subclass of `str` describing a repo URL on the Hub.
+
+ `RepoUrl` is returned by `HfApi.create_repo`. It inherits from `str` for backward
+ compatibility. At initialization, the URL is parsed to populate properties:
+ - endpoint (`str`)
+ - namespace (`Optional[str]`)
+ - repo_name (`str`)
+ - repo_id (`str`)
+ - repo_type (`Literal["model", "dataset", "space"]`)
+ - url (`str`)
+
+ Args:
+ url (`Any`):
+ String value of the repo url.
+ endpoint (`str`, *optional*):
+ Endpoint of the Hub. Defaults to .
+
+ Example:
+ ```py
+ >>> RepoUrl('https://huggingface.co/gpt2')
+ RepoUrl('https://huggingface.co/gpt2', endpoint='https://huggingface.co', repo_type='model', repo_id='gpt2')
+
+ >>> RepoUrl('https://hub-ci.huggingface.co/datasets/dummy_user/dummy_dataset', endpoint='https://hub-ci.huggingface.co')
+ RepoUrl('https://hub-ci.huggingface.co/datasets/dummy_user/dummy_dataset', endpoint='https://hub-ci.huggingface.co', repo_type='dataset', repo_id='dummy_user/dummy_dataset')
+
+ >>> RepoUrl('hf://datasets/my-user/my-dataset')
+ RepoUrl('hf://datasets/my-user/my-dataset', endpoint='https://huggingface.co', repo_type='dataset', repo_id='user/dataset')
+
+ >>> HfApi.create_repo("dummy_model")
+ RepoUrl('https://huggingface.co/Wauplin/dummy_model', endpoint='https://huggingface.co', repo_type='model', repo_id='Wauplin/dummy_model')
+ ```
+
+ Raises:
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If URL cannot be parsed.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `repo_type` is unknown.
+ """
+
+ def __new__(cls, url: Any, endpoint: Optional[str] = None):
+ url = fix_hf_endpoint_in_url(url, endpoint=endpoint)
+ return super(RepoUrl, cls).__new__(cls, url)
+
+ def __init__(self, url: Any, endpoint: Optional[str] = None) -> None:
+ super().__init__()
+ # Parse URL
+ self.endpoint = endpoint or ENDPOINT
+ repo_type, namespace, repo_name = repo_type_and_id_from_hf_id(self, hub_url=self.endpoint)
+
+ # Populate fields
+ self.namespace = namespace
+ self.repo_name = repo_name
+ self.repo_id = repo_name if namespace is None else f"{namespace}/{repo_name}"
+ self.repo_type = repo_type or REPO_TYPE_MODEL
+ self.url = str(self) # just in case it's needed
+
+ def __repr__(self) -> str:
+ return f"RepoUrl('{self}', endpoint='{self.endpoint}', repo_type='{self.repo_type}', repo_id='{self.repo_id}')"
+
+
+@dataclass
+class RepoSibling:
+ """
+ Contains basic information about a repo file inside a repo on the Hub.
+
+
+
+ All attributes of this class are optional except `rfilename`. This is because only the file names are returned when
+ listing repositories on the Hub (with [`list_models`], [`list_datasets`] or [`list_spaces`]). If you need more
+ information like file size, blob id or lfs details, you must request them specifically from one repo at a time
+ (using [`model_info`], [`dataset_info`] or [`space_info`]) as it adds more constraints on the backend server to
+ retrieve these.
+
+
+
+ Attributes:
+ rfilename (str):
+ file name, relative to the repo root.
+ size (`int`, *optional*):
+ The file's size, in bytes. This attribute is defined when `files_metadata` argument of [`repo_info`] is set
+ to `True`. It's `None` otherwise.
+ blob_id (`str`, *optional*):
+ The file's git OID. This attribute is defined when `files_metadata` argument of [`repo_info`] is set to
+ `True`. It's `None` otherwise.
+ lfs (`BlobLfsInfo`, *optional*):
+ The file's LFS metadata. This attribute is defined when`files_metadata` argument of [`repo_info`] is set to
+ `True` and the file is stored with Git LFS. It's `None` otherwise.
+ """
+
+ rfilename: str
+ size: Optional[int] = None
+ blob_id: Optional[str] = None
+ lfs: Optional[BlobLfsInfo] = None
+
+
+@dataclass
+class RepoFile:
+ """
+ Contains information about a file on the Hub.
+
+ Attributes:
+ path (str):
+ file path relative to the repo root.
+ size (`int`):
+ The file's size, in bytes.
+ blob_id (`str`):
+ The file's git OID.
+ lfs (`BlobLfsInfo`):
+ The file's LFS metadata.
+ last_commit (`LastCommitInfo`, *optional*):
+ The file's last commit metadata. Only defined if [`list_files_info`], [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ security (`BlobSecurityInfo`, *optional*):
+ The file's security scan metadata. Only defined if [`list_files_info`], [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ """
+
+ path: str
+ size: int
+ blob_id: str
+ lfs: Optional[BlobLfsInfo] = None
+ last_commit: Optional[LastCommitInfo] = None
+ security: Optional[BlobSecurityInfo] = None
+
+ def __init__(self, **kwargs):
+ self.path = kwargs.pop("path")
+ self.size = kwargs.pop("size")
+ self.blob_id = kwargs.pop("oid")
+ lfs = kwargs.pop("lfs", None)
+ if lfs is not None:
+ lfs = BlobLfsInfo(size=lfs["size"], sha256=lfs["oid"], pointer_size=lfs["pointerSize"])
+ self.lfs = lfs
+ last_commit = kwargs.pop("lastCommit", None) or kwargs.pop("last_commit", None)
+ if last_commit is not None:
+ last_commit = LastCommitInfo(
+ oid=last_commit["id"], title=last_commit["title"], date=parse_datetime(last_commit["date"])
+ )
+ self.last_commit = last_commit
+ security = kwargs.pop("security", None)
+ if security is not None:
+ security = BlobSecurityInfo(
+ safe=security["safe"], av_scan=security["avScan"], pickle_import_scan=security["pickleImportScan"]
+ )
+ self.security = security
+
+ # backwards compatibility
+ self.rfilename = self.path
+ self.lastCommit = self.last_commit
+
+
+@dataclass
+class RepoFolder:
+ """
+ Contains information about a folder on the Hub.
+
+ Attributes:
+ path (str):
+ folder path relative to the repo root.
+ tree_id (`str`):
+ The folder's git OID.
+ last_commit (`LastCommitInfo`, *optional*):
+ The folder's last commit metadata. Only defined if [`list_repo_tree`] and [`get_paths_info`]
+ are called with `expand=True`.
+ """
+
+ path: str
+ tree_id: str
+ last_commit: Optional[LastCommitInfo] = None
+
+ def __init__(self, **kwargs):
+ self.path = kwargs.pop("path")
+ self.tree_id = kwargs.pop("oid")
+ last_commit = kwargs.pop("lastCommit", None) or kwargs.pop("last_commit", None)
+ if last_commit is not None:
+ last_commit = LastCommitInfo(
+ oid=last_commit["id"], title=last_commit["title"], date=parse_datetime(last_commit["date"])
+ )
+ self.last_commit = last_commit
+
+
+@dataclass
+class ModelInfo:
+ """
+ Contains information about a model on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing models
+ using [`list_models`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of model.
+ author (`str`, *optional*):
+ Author of the model.
+ sha (`str`, *optional*):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ disabled (`bool`, *optional*):
+ Is the repo disabled.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ downloads (`int`):
+ Number of downloads of the model.
+ likes (`int`):
+ Number of likes of the model.
+ library_name (`str`, *optional*):
+ Library associated with the model.
+ tags (`List[str]`):
+ List of tags of the model. Compared to `card_data.tags`, contains extra tags computed by the Hub
+ (e.g. supported libraries, model's arXiv).
+ pipeline_tag (`str`, *optional*):
+ Pipeline tag associated with the model.
+ mask_token (`str`, *optional*):
+ Mask token used by the model.
+ widget_data (`Any`, *optional*):
+ Widget data associated with the model.
+ model_index (`Dict`, *optional*):
+ Model index for evaluation.
+ config (`Dict`, *optional*):
+ Model configuration.
+ transformers_info (`TransformersInfo`, *optional*):
+ Transformers-specific info (auto class, processor, etc.) associated with the model.
+ card_data (`ModelCardData`, *optional*):
+ Model Card Metadata as a [`huggingface_hub.repocard_data.ModelCardData`] object.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the model.
+ spaces (`List[str]`, *optional*):
+ List of spaces using the model.
+ safetensors (`SafeTensorsInfo`, *optional*):
+ Model's safetensors information.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: bool
+ gated: Optional[Literal["auto", "manual", False]]
+ disabled: Optional[bool]
+ downloads: int
+ likes: int
+ library_name: Optional[str]
+ tags: List[str]
+ pipeline_tag: Optional[str]
+ mask_token: Optional[str]
+ card_data: Optional[ModelCardData]
+ widget_data: Optional[Any]
+ model_index: Optional[Dict]
+ config: Optional[Dict]
+ transformers_info: Optional[TransformersInfo]
+ siblings: Optional[List[RepoSibling]]
+ spaces: Optional[List[str]]
+ safetensors: Optional[SafeTensorsInfo]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ self.private = kwargs.pop("private")
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.downloads = kwargs.pop("downloads")
+ self.likes = kwargs.pop("likes")
+ self.library_name = kwargs.pop("library_name", None)
+ self.tags = kwargs.pop("tags")
+ self.pipeline_tag = kwargs.pop("pipeline_tag", None)
+ self.mask_token = kwargs.pop("mask_token", None)
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ ModelCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+
+ self.widget_data = kwargs.pop("widgetData", None)
+ self.model_index = kwargs.pop("model-index", None) or kwargs.pop("model_index", None)
+ self.config = kwargs.pop("config", None)
+ transformers_info = kwargs.pop("transformersInfo", None) or kwargs.pop("transformers_info", None)
+ self.transformers_info = TransformersInfo(**transformers_info) if transformers_info else None
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings
+ else None
+ )
+ self.spaces = kwargs.pop("spaces", None)
+ safetensors = kwargs.pop("safetensors", None)
+ self.safetensors = SafeTensorsInfo(**safetensors) if safetensors else None
+
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.transformersInfo = self.transformers_info
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class DatasetInfo:
+ """
+ Contains information about a dataset on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing datasets
+ using [`list_datasets`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of dataset.
+ author (`str`):
+ Author of the dataset.
+ sha (`str`):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ disabled (`bool`, *optional*):
+ Is the repo disabled.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ downloads (`int`):
+ Number of downloads of the dataset.
+ likes (`int`):
+ Number of likes of the dataset.
+ tags (`List[str]`):
+ List of tags of the dataset.
+ card_data (`DatasetCardData`, *optional*):
+ Model Card Metadata as a [`huggingface_hub.repocard_data.DatasetCardData`] object.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the dataset.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: bool
+ gated: Optional[Literal["auto", "manual", False]]
+ disabled: Optional[bool]
+ downloads: int
+ likes: int
+ paperswithcode_id: Optional[str]
+ tags: List[str]
+ card_data: Optional[DatasetCardData]
+ siblings: Optional[List[RepoSibling]]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ self.private = kwargs.pop("private")
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.downloads = kwargs.pop("downloads")
+ self.likes = kwargs.pop("likes")
+ self.paperswithcode_id = kwargs.pop("paperswithcode_id", None)
+ self.tags = kwargs.pop("tags")
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ DatasetCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings
+ else None
+ )
+
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class SpaceInfo:
+ """
+ Contains information about a Space on the Hub.
+
+
+
+ Most attributes of this class are optional. This is because the data returned by the Hub depends on the query made.
+ In general, the more specific the query, the more information is returned. On the contrary, when listing spaces
+ using [`list_spaces`] only a subset of the attributes are returned.
+
+
+
+ Attributes:
+ id (`str`):
+ ID of the Space.
+ author (`str`, *optional*):
+ Author of the Space.
+ sha (`str`, *optional*):
+ Repo SHA at this particular revision.
+ created_at (`datetime`, *optional*):
+ Date of creation of the repo on the Hub. Note that the lowest value is `2022-03-02T23:29:04.000Z`,
+ corresponding to the date when we began to store creation dates.
+ last_modified (`datetime`, *optional*):
+ Date of last commit to the repo.
+ private (`bool`):
+ Is the repo private.
+ gated (`Literal["auto", "manual", False]`, *optional*):
+ Is the repo gated.
+ If so, whether there is manual or automatic approval.
+ disabled (`bool`, *optional*):
+ Is the Space disabled.
+ host (`str`, *optional*):
+ Host URL of the Space.
+ subdomain (`str`, *optional*):
+ Subdomain of the Space.
+ likes (`int`):
+ Number of likes of the Space.
+ tags (`List[str]`):
+ List of tags of the Space.
+ siblings (`List[RepoSibling]`):
+ List of [`huggingface_hub.hf_api.RepoSibling`] objects that constitute the Space.
+ card_data (`SpaceCardData`, *optional*):
+ Space Card Metadata as a [`huggingface_hub.repocard_data.SpaceCardData`] object.
+ runtime (`SpaceRuntime`, *optional*):
+ Space runtime information as a [`huggingface_hub.hf_api.SpaceRuntime`] object.
+ sdk (`str`, *optional*):
+ SDK used by the Space.
+ models (`List[str]`, *optional*):
+ List of models used by the Space.
+ datasets (`List[str]`, *optional*):
+ List of datasets used by the Space.
+ """
+
+ id: str
+ author: Optional[str]
+ sha: Optional[str]
+ created_at: Optional[datetime]
+ last_modified: Optional[datetime]
+ private: bool
+ gated: Optional[Literal["auto", "manual", False]]
+ disabled: Optional[bool]
+ host: Optional[str]
+ subdomain: Optional[str]
+ likes: int
+ sdk: Optional[str]
+ tags: List[str]
+ siblings: Optional[List[RepoSibling]]
+ card_data: Optional[SpaceCardData]
+ runtime: Optional[SpaceRuntime]
+ models: Optional[List[str]]
+ datasets: Optional[List[str]]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.author = kwargs.pop("author", None)
+ self.sha = kwargs.pop("sha", None)
+ created_at = kwargs.pop("createdAt", None) or kwargs.pop("created_at", None)
+ self.created_at = parse_datetime(created_at) if created_at else None
+ last_modified = kwargs.pop("lastModified", None) or kwargs.pop("last_modified", None)
+ self.last_modified = parse_datetime(last_modified) if last_modified else None
+ self.private = kwargs.pop("private")
+ self.gated = kwargs.pop("gated", None)
+ self.disabled = kwargs.pop("disabled", None)
+ self.host = kwargs.pop("host", None)
+ self.subdomain = kwargs.pop("subdomain", None)
+ self.likes = kwargs.pop("likes")
+ self.sdk = kwargs.pop("sdk", None)
+ self.tags = kwargs.pop("tags")
+ card_data = kwargs.pop("cardData", None) or kwargs.pop("card_data", None)
+ self.card_data = (
+ SpaceCardData(**card_data, ignore_metadata_errors=True) if isinstance(card_data, dict) else card_data
+ )
+ siblings = kwargs.pop("siblings", None)
+ self.siblings = (
+ [
+ RepoSibling(
+ rfilename=sibling["rfilename"],
+ size=sibling.get("size"),
+ blob_id=sibling.get("blobId"),
+ lfs=(
+ BlobLfsInfo(
+ size=sibling["lfs"]["size"],
+ sha256=sibling["lfs"]["sha256"],
+ pointer_size=sibling["lfs"]["pointerSize"],
+ )
+ if sibling.get("lfs")
+ else None
+ ),
+ )
+ for sibling in siblings
+ ]
+ if siblings
+ else None
+ )
+ runtime = kwargs.pop("runtime", None)
+ self.runtime = SpaceRuntime(runtime) if runtime else None
+ self.models = kwargs.pop("models", None)
+ self.datasets = kwargs.pop("datasets", None)
+
+ # backwards compatibility
+ self.lastModified = self.last_modified
+ self.cardData = self.card_data
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class MetricInfo:
+ """
+ Contains information about a metric on the Hub.
+
+ Attributes:
+ id (`str`):
+ ID of the metric. E.g. `"accuracy"`.
+ space_id (`str`):
+ ID of the space associated with the metric. E.g. `"Accuracy"`.
+ description (`str`):
+ Description of the metric.
+ """
+
+ id: str
+ space_id: str
+ description: Optional[str]
+
+ def __init__(self, **kwargs):
+ self.id = kwargs.pop("id")
+ self.space_id = kwargs.pop("spaceId")
+ self.description = kwargs.pop("description", None)
+ # backwards compatibility
+ self.spaceId = self.space_id
+ self.__dict__.update(**kwargs)
+
+
+@dataclass
+class CollectionItem:
+ """
+ Contains information about an item of a Collection (model, dataset, Space or paper).
+
+ Attributes:
+ item_object_id (`str`):
+ Unique ID of the item in the collection.
+ item_id (`str`):
+ ID of the underlying object on the Hub. Can be either a repo_id or a paper id
+ e.g. `"jbilcke-hf/ai-comic-factory"`, `"2307.09288"`.
+ item_type (`str`):
+ Type of the underlying object. Can be one of `"model"`, `"dataset"`, `"space"` or `"paper"`.
+ position (`int`):
+ Position of the item in the collection.
+ note (`str`, *optional*):
+ Note associated with the item, as plain text.
+ """
+
+ item_object_id: str # id in database
+ item_id: str # repo_id or paper id
+ item_type: str
+ position: int
+ note: Optional[str] = None
+
+ def __init__(
+ self, _id: str, id: str, type: CollectionItemType_T, position: int, note: Optional[Dict] = None, **kwargs
+ ) -> None:
+ self.item_object_id: str = _id # id in database
+ self.item_id: str = id # repo_id or paper id
+ self.item_type: CollectionItemType_T = type
+ self.position: int = position
+ self.note: str = note["text"] if note is not None else None
+
+
+@dataclass
+class Collection:
+ """
+ Contains information about a Collection on the Hub.
+
+ Attributes:
+ slug (`str`):
+ Slug of the collection. E.g. `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ title (`str`):
+ Title of the collection. E.g. `"Recent models"`.
+ owner (`str`):
+ Owner of the collection. E.g. `"TheBloke"`.
+ items (`List[CollectionItem]`):
+ List of items in the collection.
+ last_updated (`datetime`):
+ Date of the last update of the collection.
+ position (`int`):
+ Position of the collection in the list of collections of the owner.
+ private (`bool`):
+ Whether the collection is private or not.
+ theme (`str`):
+ Theme of the collection. E.g. `"green"`.
+ upvotes (`int`):
+ Number of upvotes of the collection.
+ description (`str`, *optional*):
+ Description of the collection, as plain text.
+ url (`str`):
+ (property) URL of the collection on the Hub.
+ """
+
+ slug: str
+ title: str
+ owner: str
+ items: List[CollectionItem]
+ last_updated: datetime
+ position: int
+ private: bool
+ theme: str
+ upvotes: int
+ description: Optional[str] = None
+
+ def __init__(self, **kwargs) -> None:
+ self.slug = kwargs.pop("slug")
+ self.title = kwargs.pop("title")
+ self.owner = kwargs.pop("owner")
+ self.items = [CollectionItem(**item) for item in kwargs.pop("items")]
+ self.last_updated = parse_datetime(kwargs.pop("lastUpdated"))
+ self.position = kwargs.pop("position")
+ self.private = kwargs.pop("private")
+ self.theme = kwargs.pop("theme")
+ self.upvotes = kwargs.pop("upvotes")
+ self.description = kwargs.pop("description", None)
+ endpoint = kwargs.pop("endpoint", None)
+ if endpoint is None:
+ endpoint = ENDPOINT
+ self._url = f"{endpoint}/collections/{self.slug}"
+
+ @property
+ def url(self) -> str:
+ """Returns the URL of the collection on the Hub."""
+ return self._url
+
+
+@dataclass
+class GitRefInfo:
+ """
+ Contains information about a git reference for a repo on the Hub.
+
+ Attributes:
+ name (`str`):
+ Name of the reference (e.g. tag name or branch name).
+ ref (`str`):
+ Full git ref on the Hub (e.g. `"refs/heads/main"` or `"refs/tags/v1.0"`).
+ target_commit (`str`):
+ OID of the target commit for the ref (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`)
+ """
+
+ name: str
+ ref: str
+ target_commit: str
+
+
+@dataclass
+class GitRefs:
+ """
+ Contains information about all git references for a repo on the Hub.
+
+ Object is returned by [`list_repo_refs`].
+
+ Attributes:
+ branches (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about branches on the repo.
+ converts (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about "convert" refs on the repo.
+ Converts are refs used (internally) to push preprocessed data in Dataset repos.
+ tags (`List[GitRefInfo]`):
+ A list of [`GitRefInfo`] containing information about tags on the repo.
+ pull_requests (`List[GitRefInfo]`, *optional*):
+ A list of [`GitRefInfo`] containing information about pull requests on the repo.
+ Only returned if `include_prs=True` is set.
+ """
+
+ branches: List[GitRefInfo]
+ converts: List[GitRefInfo]
+ tags: List[GitRefInfo]
+ pull_requests: Optional[List[GitRefInfo]] = None
+
+
+@dataclass
+class GitCommitInfo:
+ """
+ Contains information about a git commit for a repo on the Hub. Check out [`list_repo_commits`] for more details.
+
+ Attributes:
+ commit_id (`str`):
+ OID of the commit (e.g. `"e7da7f221d5bf496a48136c0cd264e630fe9fcc8"`)
+ authors (`List[str]`):
+ List of authors of the commit.
+ created_at (`datetime`):
+ Datetime when the commit was created.
+ title (`str`):
+ Title of the commit. This is a free-text value entered by the authors.
+ message (`str`):
+ Description of the commit. This is a free-text value entered by the authors.
+ formatted_title (`str`):
+ Title of the commit formatted as HTML. Only returned if `formatted=True` is set.
+ formatted_message (`str`):
+ Description of the commit formatted as HTML. Only returned if `formatted=True` is set.
+ """
+
+ commit_id: str
+
+ authors: List[str]
+ created_at: datetime
+ title: str
+ message: str
+
+ formatted_title: Optional[str]
+ formatted_message: Optional[str]
+
+
+@dataclass
+class UserLikes:
+ """
+ Contains information about a user likes on the Hub.
+
+ Attributes:
+ user (`str`):
+ Name of the user for which we fetched the likes.
+ total (`int`):
+ Total number of likes.
+ datasets (`List[str]`):
+ List of datasets liked by the user (as repo_ids).
+ models (`List[str]`):
+ List of models liked by the user (as repo_ids).
+ spaces (`List[str]`):
+ List of spaces liked by the user (as repo_ids).
+ """
+
+ # Metadata
+ user: str
+ total: int
+
+ # User likes
+ datasets: List[str]
+ models: List[str]
+ spaces: List[str]
+
+
+@dataclass
+class User:
+ """
+ Contains information about a user on the Hub.
+
+ Attributes:
+ avatar_url (`str`):
+ URL of the user's avatar.
+ username (`str`):
+ Name of the user on the Hub (unique).
+ fullname (`str`):
+ User's full name.
+ """
+
+ # Metadata
+ avatar_url: str
+ username: str
+ fullname: str
+
+
+def future_compatible(fn: CallableT) -> CallableT:
+ """Wrap a method of `HfApi` to handle `run_as_future=True`.
+
+ A method flagged as "future_compatible" will be called in a thread if `run_as_future=True` and return a
+ `concurrent.futures.Future` instance. Otherwise, it will be called normally and return the result.
+ """
+ sig = inspect.signature(fn)
+ args_params = list(sig.parameters)[1:] # remove "self" from list
+
+ @wraps(fn)
+ def _inner(self, *args, **kwargs):
+ # Get `run_as_future` value if provided (default to False)
+ if "run_as_future" in kwargs:
+ run_as_future = kwargs["run_as_future"]
+ kwargs["run_as_future"] = False # avoid recursion error
+ else:
+ run_as_future = False
+ for param, value in zip(args_params, args):
+ if param == "run_as_future":
+ run_as_future = value
+ break
+
+ # Call the function in a thread if `run_as_future=True`
+ if run_as_future:
+ return self.run_as_future(fn, self, *args, **kwargs)
+
+ # Otherwise, call the function normally
+ return fn(self, *args, **kwargs)
+
+ _inner.is_future_compatible = True # type: ignore
+ return _inner # type: ignore
+
+
+class HfApi:
+ def __init__(
+ self,
+ endpoint: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+ ) -> None:
+ """Create a HF client to interact with the Hub via HTTP.
+
+ The client is initialized with some high-level settings used in all requests
+ made to the Hub (HF endpoint, authentication, user agents...). Using the `HfApi`
+ client is preferred but not mandatory as all of its public methods are exposed
+ directly at the root of `huggingface_hub`.
+
+ Args:
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Pass `token=False` if you don't want to send your token to the server.
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to
+ the user-agent header. Example: `"transformers"`.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added
+ to the user-agent header. Example: `"4.24.0"`.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will
+ be completed with information about the installed packages.
+ headers (`dict`, *optional*):
+ Additional headers to be sent with each request. Example: `{"X-My-Header": "value"}`.
+ Headers passed here are taking precedence over the default headers.
+ """
+ self.endpoint = endpoint if endpoint is not None else ENDPOINT
+ self.token = token
+ self.library_name = library_name
+ self.library_version = library_version
+ self.user_agent = user_agent
+ self.headers = headers
+ self._thread_pool: Optional[ThreadPoolExecutor] = None
+
+ def run_as_future(self, fn: Callable[..., R], *args, **kwargs) -> Future[R]:
+ """
+ Run a method in the background and return a Future instance.
+
+ The main goal is to run methods without blocking the main thread (e.g. to push data during a training).
+ Background jobs are queued to preserve order but are not ran in parallel. If you need to speed-up your scripts
+ by parallelizing lots of call to the API, you must setup and use your own [ThreadPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor).
+
+ Note: Most-used methods like [`upload_file`], [`upload_folder`] and [`create_commit`] have a `run_as_future: bool`
+ argument to directly call them in the background. This is equivalent to calling `api.run_as_future(...)` on them
+ but less verbose.
+
+ Args:
+ fn (`Callable`):
+ The method to run in the background.
+ *args, **kwargs:
+ Arguments with which the method will be called.
+
+ Return:
+ `Future`: a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects) instance to
+ get the result of the task.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> future = api.run_as_future(api.whoami) # instant
+ >>> future.done()
+ False
+ >>> future.result() # wait until complete and return result
+ (...)
+ >>> future.done()
+ True
+ ```
+ """
+ if self._thread_pool is None:
+ self._thread_pool = ThreadPoolExecutor(max_workers=1)
+ self._thread_pool
+ return self._thread_pool.submit(fn, *args, **kwargs)
+
+ @validate_hf_hub_args
+ def whoami(self, token: Optional[str] = None) -> Dict:
+ """
+ Call HF API to know "whoami".
+
+ Args:
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if
+ not provided.
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/whoami-v2",
+ headers=self._build_hf_headers(
+ # If `token` is provided and not `None`, it will be used by default.
+ # Otherwise, the token must be retrieved from cache or env variable.
+ token=(token or self.token or True),
+ ),
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as e:
+ raise HTTPError(
+ "Invalid user token. If you didn't pass a user token, make sure you "
+ "are properly logged in by executing `huggingface-cli login`, and "
+ "if you did pass a user token, double-check it's correct.",
+ request=e.request,
+ response=e.response,
+ ) from e
+ return r.json()
+
+ def get_token_permission(self, token: Optional[str] = None) -> Literal["read", "write", None]:
+ """
+ Check if a given `token` is valid and return its permissions.
+
+ For more details about tokens, please refer to https://huggingface.co/docs/hub/security-tokens#what-are-user-access-tokens.
+
+ Args:
+ token (`str`, *optional*):
+ The token to check for validity. Defaults to the one saved locally.
+
+ Returns:
+ `Literal["read", "write", None]`: Permission granted by the token ("read" or "write"). Returns `None` if no
+ token passed or token is invalid.
+ """
+ try:
+ return self.whoami(token=token)["auth"]["accessToken"]["role"]
+ except (LocalTokenNotFoundError, HTTPError):
+ return None
+
+ def get_model_tags(self) -> Dict:
+ """
+ List all valid model tags as a nested namespace object
+ """
+ path = f"{self.endpoint}/api/models-tags-by-type"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ return r.json()
+
+ def get_dataset_tags(self) -> Dict:
+ """
+ List all valid dataset tags as a nested namespace object.
+ """
+ path = f"{self.endpoint}/api/datasets-tags-by-type"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ return r.json()
+
+ @validate_hf_hub_args
+ def list_models(
+ self,
+ *,
+ filter: Union[ModelFilter, str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ library: Optional[Union[str, List[str]]] = None,
+ language: Optional[Union[str, List[str]]] = None,
+ model_name: Optional[str] = None,
+ task: Optional[Union[str, List[str]]] = None,
+ trained_dataset: Optional[Union[str, List[str]]] = None,
+ tags: Optional[Union[str, List[str]]] = None,
+ search: Optional[str] = None,
+ emissions_thresholds: Optional[Tuple[float, float]] = None,
+ sort: Union[Literal["last_modified"], str, None] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ full: Optional[bool] = None,
+ cardData: bool = False,
+ fetch_config: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ pipeline_tag: Optional[str] = None,
+ ) -> Iterable[ModelInfo]:
+ """
+ List models hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter ([`ModelFilter`] or `str` or `Iterable`, *optional*):
+ A string or [`ModelFilter`] which can be used to identify models
+ on the Hub.
+ author (`str`, *optional*):
+ A string which identify the author (user or organization) of the
+ returned models
+ library (`str` or `List`, *optional*):
+ A string or list of strings of foundational libraries models were
+ originally trained from, such as pytorch, tensorflow, or allennlp.
+ language (`str` or `List`, *optional*):
+ A string or list of strings of languages, both by name and country
+ code, such as "en" or "English"
+ model_name (`str`, *optional*):
+ A string that contain complete or partial names for models on the
+ Hub, such as "bert" or "bert-base-cased"
+ task (`str` or `List`, *optional*):
+ A string or list of strings of tasks models were designed for, such
+ as: "fill-mask" or "automatic-speech-recognition"
+ trained_dataset (`str` or `List`, *optional*):
+ A string tag or a list of string tags of the trained dataset for a
+ model on the Hub.
+ tags (`str` or `List`, *optional*):
+ A string tag or a list of tags to filter models on the Hub by, such
+ as `text-generation` or `spacy`.
+ search (`str`, *optional*):
+ A string that will be contained in the returned model ids.
+ emissions_thresholds (`Tuple`, *optional*):
+ A tuple of two ints or floats representing a minimum and maximum
+ carbon footprint to filter the resulting models with in grams.
+ sort (`Literal["last_modified"]` or `str`, *optional*):
+ The key with which to sort the resulting models. Possible values
+ are the properties of the [`huggingface_hub.hf_api.ModelInfo`] class.
+ direction (`Literal[-1]` or `int`, *optional*):
+ Direction in which to sort. The value `-1` sorts by descending
+ order while all other values sort by ascending order.
+ limit (`int`, *optional*):
+ The limit on the number of models fetched. Leaving this option
+ to `None` fetches all models.
+ full (`bool`, *optional*):
+ Whether to fetch all model data, including the `last_modified`,
+ the `sha`, the files and the `tags`. This is set to `True` by
+ default when using a filter.
+ cardData (`bool`, *optional*):
+ Whether to grab the metadata for the model as well. Can contain
+ useful information such as carbon emissions, metrics, and
+ datasets trained on.
+ fetch_config (`bool`, *optional*):
+ Whether to fetch the model configs as well. This is not included
+ in `full` due to its size.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ pipeline_tag (`str`, *optional*):
+ A string pipeline tag to filter models on the Hub by, such as `summarization`
+
+
+ Returns:
+ `Iterable[ModelInfo]`: an iterable of [`huggingface_hub.hf_api.ModelInfo`] objects.
+
+ Example usage with the `filter` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ >>> # List all models
+ >>> api.list_models()
+
+ >>> # List only the text classification models
+ >>> api.list_models(filter="text-classification")
+
+ >>> # List only models from the AllenNLP library
+ >>> api.list_models(filter="allennlp")
+ ```
+
+ Example usage with the `search` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ >>> # List all models with "bert" in their name
+ >>> api.list_models(search="bert")
+
+ >>> # List all models with "bert" in their name made by google
+ >>> api.list_models(search="bert", author="google")
+ ```
+ """
+ if emissions_thresholds is not None and cardData is None:
+ raise ValueError("`emissions_thresholds` were passed without setting `cardData=True`.")
+
+ path = f"{self.endpoint}/api/models"
+ headers = self._build_hf_headers(token=token)
+ params = {}
+ filter_list = []
+
+ if filter is not None:
+ if isinstance(filter, ModelFilter):
+ params = self._unpack_model_filter(filter)
+ else:
+ params.update({"filter": filter})
+
+ params.update({"full": True})
+
+ # Build the filter list
+ if author:
+ params.update({"author": author})
+ if model_name:
+ params.update({"search": model_name})
+ if library:
+ filter_list.extend([library] if isinstance(library, str) else library)
+ if task:
+ filter_list.extend([task] if isinstance(task, str) else task)
+ if trained_dataset:
+ if not isinstance(trained_dataset, (list, tuple)):
+ trained_dataset = [trained_dataset]
+ for dataset in trained_dataset:
+ if not dataset.startswith("dataset:"):
+ dataset = f"dataset:{dataset}"
+ filter_list.append(dataset)
+ if language:
+ filter_list.extend([language] if isinstance(language, str) else language)
+ if tags:
+ filter_list.extend([tags] if isinstance(tags, str) else tags)
+
+ if search:
+ params.update({"search": search})
+ if sort is not None:
+ params.update({"sort": "lastModified" if sort == "last_modified" else sort})
+ if direction is not None:
+ params.update({"direction": direction})
+ if limit is not None:
+ params.update({"limit": limit})
+ if full is not None:
+ if full:
+ params.update({"full": True})
+ elif "full" in params:
+ del params["full"]
+ if fetch_config:
+ params.update({"config": True})
+ if cardData:
+ params.update({"cardData": True})
+ if pipeline_tag:
+ params.update({"pipeline_tag": pipeline_tag})
+
+ filter_value = params.get("filter", [])
+ if filter_value:
+ filter_list.extend([filter_value] if isinstance(filter_value, str) else list(filter_value))
+ params.update({"filter": filter_list})
+
+ # `items` is a generator
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ model_info = ModelInfo(**item)
+ if emissions_thresholds is None or _is_emission_within_treshold(model_info, *emissions_thresholds):
+ yield model_info
+
+ def _unpack_model_filter(self, model_filter: ModelFilter):
+ """
+ Unpacks a [`ModelFilter`] into something readable for `list_models`
+ """
+ model_str = ""
+
+ # Handling author
+ if model_filter.author:
+ model_str = f"{model_filter.author}/"
+
+ # Handling model_name
+ if model_filter.model_name:
+ model_str += model_filter.model_name
+
+ filter_list: List[str] = []
+
+ # Handling tasks
+ if model_filter.task:
+ filter_list.extend([model_filter.task] if isinstance(model_filter.task, str) else model_filter.task)
+
+ # Handling dataset
+ if model_filter.trained_dataset:
+ if not isinstance(model_filter.trained_dataset, (list, tuple)):
+ model_filter.trained_dataset = [model_filter.trained_dataset]
+ for dataset in model_filter.trained_dataset:
+ if "dataset:" not in dataset:
+ dataset = f"dataset:{dataset}"
+ filter_list.append(dataset)
+
+ # Handling library
+ if model_filter.library:
+ filter_list.extend(
+ [model_filter.library] if isinstance(model_filter.library, str) else model_filter.library
+ )
+
+ # Handling tags
+ if model_filter.tags:
+ filter_list.extend([model_filter.tags] if isinstance(model_filter.tags, str) else model_filter.tags)
+
+ query_dict: Dict[str, Any] = {}
+ if model_str:
+ query_dict["search"] = model_str
+ if isinstance(model_filter.language, list):
+ filter_list.extend(model_filter.language)
+ elif isinstance(model_filter.language, str):
+ filter_list.append(model_filter.language)
+ query_dict["filter"] = tuple(filter_list)
+ return query_dict
+
+ @validate_hf_hub_args
+ def list_datasets(
+ self,
+ *,
+ filter: Union[DatasetFilter, str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ benchmark: Optional[Union[str, List[str]]] = None,
+ dataset_name: Optional[str] = None,
+ language_creators: Optional[Union[str, List[str]]] = None,
+ language: Optional[Union[str, List[str]]] = None,
+ multilinguality: Optional[Union[str, List[str]]] = None,
+ size_categories: Optional[Union[str, List[str]]] = None,
+ task_categories: Optional[Union[str, List[str]]] = None,
+ task_ids: Optional[Union[str, List[str]]] = None,
+ search: Optional[str] = None,
+ sort: Optional[Union[Literal["last_modified"], str]] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ full: Optional[bool] = None,
+ token: Optional[str] = None,
+ ) -> Iterable[DatasetInfo]:
+ """
+ List datasets hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter ([`DatasetFilter`] or `str` or `Iterable`, *optional*):
+ A string or [`DatasetFilter`] which can be used to identify
+ datasets on the hub.
+ author (`str`, *optional*):
+ A string which identify the author of the returned datasets.
+ benchmark (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by their official benchmark.
+ dataset_name (`str`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by its name, such as `SQAC` or `wikineural`
+ language_creators (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub with how the data was curated, such as `crowdsourced` or
+ `machine_generated`.
+ language (`str` or `List`, *optional*):
+ A string or list of strings representing a two-character language to
+ filter datasets by on the Hub.
+ multilinguality (`str` or `List`, *optional*):
+ A string or list of strings representing a filter for datasets that
+ contain multiple languages.
+ size_categories (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by the size of the dataset such as `100K>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ >>> # List all datasets
+ >>> api.list_datasets()
+
+
+ >>> # List only the text classification datasets
+ >>> api.list_datasets(filter="task_categories:text-classification")
+
+
+ >>> # List only the datasets in russian for language modeling
+ >>> api.list_datasets(
+ ... filter=("language:ru", "task_ids:language-modeling")
+ ... )
+
+ >>> api.list_datasets(filter=filt)
+ ```
+
+ Example usage with the `search` argument:
+
+ ```python
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+
+ >>> # List all datasets with "text" in their name
+ >>> api.list_datasets(search="text")
+
+ >>> # List all datasets with "text" in their name made by google
+ >>> api.list_datasets(search="text", author="google")
+ ```
+ """
+ path = f"{self.endpoint}/api/datasets"
+ headers = self._build_hf_headers(token=token)
+ params = {}
+ filter_list = []
+
+ if filter is not None:
+ if isinstance(filter, DatasetFilter):
+ params = self._unpack_dataset_filter(filter)
+ else:
+ params.update({"filter": filter})
+
+ # Build the filter list
+ if author:
+ params.update({"author": author})
+ if dataset_name:
+ params.update({"search": dataset_name})
+
+ for attr in (
+ benchmark,
+ language_creators,
+ language,
+ multilinguality,
+ size_categories,
+ task_categories,
+ task_ids,
+ ):
+ if attr:
+ if not isinstance(attr, (list, tuple)):
+ attr = [attr]
+ for data in attr:
+ if not data.startswith(f"{attr}:"):
+ data = f"{attr}:{data}"
+ filter_list.append(data)
+
+ if search:
+ params.update({"search": search})
+ if sort is not None:
+ params.update({"sort": "lastModified" if sort == "last_modified" else sort})
+ if direction is not None:
+ params.update({"direction": direction})
+ if limit is not None:
+ params.update({"limit": limit})
+ if full:
+ params.update({"full": True})
+
+ filter_value = params.get("filter", [])
+ if filter_value:
+ filter_list.extend([filter_value] if isinstance(filter_value, str) else list(filter_value))
+ params.update({"filter": filter_list})
+
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ yield DatasetInfo(**item)
+
+ def _unpack_dataset_filter(self, dataset_filter: DatasetFilter):
+ """
+ Unpacks a [`DatasetFilter`] into something readable for `list_datasets`
+ """
+ dataset_str = ""
+
+ # Handling author
+ if dataset_filter.author:
+ dataset_str = f"{dataset_filter.author}/"
+
+ # Handling dataset_name
+ if dataset_filter.dataset_name:
+ dataset_str += dataset_filter.dataset_name
+
+ filter_list = []
+ data_attributes = [
+ "benchmark",
+ "language_creators",
+ "language",
+ "multilinguality",
+ "size_categories",
+ "task_categories",
+ "task_ids",
+ ]
+
+ for attr in data_attributes:
+ curr_attr = getattr(dataset_filter, attr)
+ if curr_attr is not None:
+ if not isinstance(curr_attr, (list, tuple)):
+ curr_attr = [curr_attr]
+ for data in curr_attr:
+ if f"{attr}:" not in data:
+ data = f"{attr}:{data}"
+ filter_list.append(data)
+
+ query_dict: Dict[str, Any] = {}
+ if dataset_str is not None:
+ query_dict["search"] = dataset_str
+ query_dict["filter"] = tuple(filter_list)
+ return query_dict
+
+ def list_metrics(self) -> List[MetricInfo]:
+ """
+ Get the public list of all the metrics on huggingface.co
+
+ Returns:
+ `List[MetricInfo]`: a list of [`MetricInfo`] objects which.
+ """
+ path = f"{self.endpoint}/api/metrics"
+ r = get_session().get(path)
+ hf_raise_for_status(r)
+ d = r.json()
+ return [MetricInfo(**x) for x in d]
+
+ @validate_hf_hub_args
+ def list_spaces(
+ self,
+ *,
+ filter: Union[str, Iterable[str], None] = None,
+ author: Optional[str] = None,
+ search: Optional[str] = None,
+ sort: Union[Literal["last_modified"], str, None] = None,
+ direction: Optional[Literal[-1]] = None,
+ limit: Optional[int] = None,
+ datasets: Union[str, Iterable[str], None] = None,
+ models: Union[str, Iterable[str], None] = None,
+ linked: bool = False,
+ full: Optional[bool] = None,
+ token: Optional[str] = None,
+ ) -> Iterable[SpaceInfo]:
+ """
+ List spaces hosted on the Huggingface Hub, given some filters.
+
+ Args:
+ filter (`str` or `Iterable`, *optional*):
+ A string tag or list of tags that can be used to identify Spaces on the Hub.
+ author (`str`, *optional*):
+ A string which identify the author of the returned Spaces.
+ search (`str`, *optional*):
+ A string that will be contained in the returned Spaces.
+ sort (`Literal["last_modified"]` or `str`, *optional*):
+ The key with which to sort the resulting Spaces. Possible
+ values are the properties of the [`huggingface_hub.hf_api.SpaceInfo`]` class.
+ direction (`Literal[-1]` or `int`, *optional*):
+ Direction in which to sort. The value `-1` sorts by descending
+ order while all other values sort by ascending order.
+ limit (`int`, *optional*):
+ The limit on the number of Spaces fetched. Leaving this option
+ to `None` fetches all Spaces.
+ datasets (`str` or `Iterable`, *optional*):
+ Whether to return Spaces that make use of a dataset.
+ The name of a specific dataset can be passed as a string.
+ models (`str` or `Iterable`, *optional*):
+ Whether to return Spaces that make use of a model.
+ The name of a specific model can be passed as a string.
+ linked (`bool`, *optional*):
+ Whether to return Spaces that make use of either a model or a dataset.
+ full (`bool`, *optional*):
+ Whether to fetch all Spaces data, including the `last_modified`, `siblings`
+ and `card_data` fields.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ `Iterable[SpaceInfo]`: an iterable of [`huggingface_hub.hf_api.SpaceInfo`] objects.
+ """
+ path = f"{self.endpoint}/api/spaces"
+ headers = self._build_hf_headers(token=token)
+ params: Dict[str, Any] = {}
+ if filter is not None:
+ params.update({"filter": filter})
+ if author is not None:
+ params.update({"author": author})
+ if search is not None:
+ params.update({"search": search})
+ if sort is not None:
+ params.update({"sort": "lastModified" if sort == "last_modified" else sort})
+ if direction is not None:
+ params.update({"direction": direction})
+ if limit is not None:
+ params.update({"limit": limit})
+ if full:
+ params.update({"full": True})
+ if linked:
+ params.update({"linked": True})
+ if datasets is not None:
+ params.update({"datasets": datasets})
+ if models is not None:
+ params.update({"models": models})
+
+ items = paginate(path, params=params, headers=headers)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+ for item in items:
+ if "siblings" not in item:
+ item["siblings"] = None
+ yield SpaceInfo(**item)
+
+ @validate_hf_hub_args
+ def like(
+ self,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Like a given repo on the Hub (e.g. set as favorite).
+
+ See also [`unlike`] and [`list_liked_repos`].
+
+ Args:
+ repo_id (`str`):
+ The repository to like. Example: `"user/my-cool-model"`.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if liking a dataset or space, `None` or
+ `"model"` if liking a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import like, list_liked_repos, unlike
+ >>> like("gpt2")
+ >>> "gpt2" in list_liked_repos().models
+ True
+ >>> unlike("gpt2")
+ >>> "gpt2" in list_liked_repos().models
+ False
+ ```
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ response = get_session().post(
+ url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/like",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def unlike(
+ self,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Unlike a given repo on the Hub (e.g. remove from favorite list).
+
+ See also [`like`] and [`list_liked_repos`].
+
+ Args:
+ repo_id (`str`):
+ The repository to unlike. Example: `"user/my-cool-model"`.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if unliking a dataset or space, `None` or
+ `"model"` if unliking a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import like, list_liked_repos, unlike
+ >>> like("gpt2")
+ >>> "gpt2" in list_liked_repos().models
+ True
+ >>> unlike("gpt2")
+ >>> "gpt2" in list_liked_repos().models
+ False
+ ```
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ response = get_session().delete(
+ url=f"{self.endpoint}/api/{repo_type}s/{repo_id}/like", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def list_liked_repos(
+ self,
+ user: Optional[str] = None,
+ *,
+ token: Optional[str] = None,
+ ) -> UserLikes:
+ """
+ List all public repos liked by a user on huggingface.co.
+
+ This list is public so token is optional. If `user` is not passed, it defaults to
+ the logged in user.
+
+ See also [`like`] and [`unlike`].
+
+ Args:
+ user (`str`, *optional*):
+ Name of the user for which you want to fetch the likes.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ Used only if `user` is not passed to implicitly determine the current
+ user name.
+
+ Returns:
+ [`UserLikes`]: object containing the user name and 3 lists of repo ids (1 for
+ models, 1 for datasets and 1 for Spaces).
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `user` is not passed and no token found (either from argument or from machine).
+
+ Example:
+ ```python
+ >>> from huggingface_hub import list_liked_repos
+
+ >>> likes = list_liked_repos("julien-c")
+
+ >>> likes.user
+ "julien-c"
+
+ >>> likes.models
+ ["osanseviero/streamlit_1.15", "Xhaheen/ChatGPT_HF", ...]
+ ```
+ """
+ # User is either provided explicitly or retrieved from current token.
+ if user is None:
+ me = self.whoami(token=token)
+ if me["type"] == "user":
+ user = me["name"]
+ else:
+ raise ValueError(
+ "Cannot list liked repos. You must provide a 'user' as input or be logged in as a user."
+ )
+
+ path = f"{self.endpoint}/api/users/{user}/likes"
+ headers = self._build_hf_headers(token=token)
+
+ likes = list(paginate(path, params={}, headers=headers))
+ # Looping over a list of items similar to:
+ # {
+ # 'createdAt': '2021-09-09T21:53:27.000Z',
+ # 'repo': {
+ # 'name': 'PaddlePaddle/PaddleOCR',
+ # 'type': 'space'
+ # }
+ # }
+ # Let's loop 3 times over the received list. Less efficient but more straightforward to read.
+ return UserLikes(
+ user=user,
+ total=len(likes),
+ models=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "model"],
+ datasets=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "dataset"],
+ spaces=[like["repo"]["name"] for like in likes if like["repo"]["type"] == "space"],
+ )
+
+ @validate_hf_hub_args
+ def list_repo_likers(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> List[User]:
+ """
+ List all users who liked a given repo on the hugging Face Hub.
+
+ See also [`like`] and [`list_liked_repos`].
+
+ Args:
+ repo_id (`str`):
+ The repository to retrieve . Example: `"user/my-cool-model"`.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns:
+ `List[User]`: a list of [`User`] objects.
+ """
+
+ # Construct the API endpoint
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/likers"
+ headers = self._build_hf_headers(token=token)
+
+ # Make the request
+ response = get_session().get(path, headers=headers)
+ hf_raise_for_status(response)
+
+ # Parse the results into User objects
+ likers_data = response.json()
+ return [
+ User(
+ username=user_data["user"],
+ fullname=user_data["fullname"],
+ avatar_url=user_data["avatarUrl"],
+ )
+ for user_data in likers_data
+ ]
+
+ @validate_hf_hub_args
+ def model_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ securityStatus: Optional[bool] = None,
+ files_metadata: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ ) -> ModelInfo:
+ """
+ Get info on one specific model on huggingface.co
+
+ Model can be private if you pass an acceptable token or are logged in.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the model repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ securityStatus (`bool`, *optional*):
+ Whether to retrieve the security status from the model
+ repository as well.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ [`huggingface_hub.hf_api.ModelInfo`]: The model repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/models/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/models/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params = {}
+ if securityStatus:
+ params["securityStatus"] = True
+ if files_metadata:
+ params["blobs"] = True
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return ModelInfo(**data)
+
+ @validate_hf_hub_args
+ def dataset_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ ) -> DatasetInfo:
+ """
+ Get info on one specific dataset on huggingface.co.
+
+ Dataset can be private if you pass an acceptable token.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the dataset repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ [`hf_api.DatasetInfo`]: The dataset repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/datasets/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/datasets/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params = {}
+ if files_metadata:
+ params["blobs"] = True
+
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return DatasetInfo(**data)
+
+ @validate_hf_hub_args
+ def space_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ ) -> SpaceInfo:
+ """
+ Get info on one specific Space on huggingface.co.
+
+ Space can be private if you pass an acceptable token.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the space repository from which to get the
+ information.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ [`~hf_api.SpaceInfo`]: The space repository information.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ headers = self._build_hf_headers(token=token)
+ path = (
+ f"{self.endpoint}/api/spaces/{repo_id}"
+ if revision is None
+ else (f"{self.endpoint}/api/spaces/{repo_id}/revision/{quote(revision, safe='')}")
+ )
+ params = {}
+ if files_metadata:
+ params["blobs"] = True
+
+ r = get_session().get(path, headers=headers, timeout=timeout, params=params)
+ hf_raise_for_status(r)
+ data = r.json()
+ return SpaceInfo(**data)
+
+ @validate_hf_hub_args
+ def repo_info(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ timeout: Optional[float] = None,
+ files_metadata: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ ) -> Union[ModelInfo, DatasetInfo, SpaceInfo]:
+ """
+ Get the info object for a given repo of a given type.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the
+ information.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ timeout (`float`, *optional*):
+ Whether to set a timeout for the request to the Hub.
+ files_metadata (`bool`, *optional*):
+ Whether or not to retrieve metadata for files in the repository
+ (size, LFS metadata, etc). Defaults to `False`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ `Union[SpaceInfo, DatasetInfo, ModelInfo]`: The repository information, as a
+ [`huggingface_hub.hf_api.DatasetInfo`], [`huggingface_hub.hf_api.ModelInfo`]
+ or [`huggingface_hub.hf_api.SpaceInfo`] object.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+ """
+ if repo_type is None or repo_type == "model":
+ method = self.model_info
+ elif repo_type == "dataset":
+ method = self.dataset_info # type: ignore
+ elif repo_type == "space":
+ method = self.space_info # type: ignore
+ else:
+ raise ValueError("Unsupported repo type.")
+ return method(
+ repo_id,
+ revision=revision,
+ token=token,
+ timeout=timeout,
+ files_metadata=files_metadata,
+ )
+
+ @validate_hf_hub_args
+ def repo_exists(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> bool:
+ """
+ Checks if a repository exists on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ True if the repository exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import repo_exists
+ >>> repo_exists("google/gemma-7b")
+ True
+ >>> repo_exists("google/not-a-repo")
+ False
+ ```
+ """
+ try:
+ self.repo_info(repo_id=repo_id, repo_type=repo_type, token=token)
+ return True
+ except GatedRepoError:
+ return True # we don't have access but it exists
+ except RepositoryNotFoundError:
+ return False
+
+ @validate_hf_hub_args
+ def revision_exists(
+ self,
+ repo_id: str,
+ revision: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> bool:
+ """
+ Checks if a specific revision exists on a repo on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`):
+ The revision of the repository to check.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ True if the repository and the revision exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import revision_exists
+ >>> revision_exists("google/gemma-7b", "float16")
+ True
+ >>> revision_exists("google/gemma-7b", "not-a-revision")
+ False
+ ```
+ """
+ try:
+ self.repo_info(repo_id=repo_id, revision=revision, repo_type=repo_type, token=token)
+ return True
+ except RevisionNotFoundError:
+ return False
+ except RepositoryNotFoundError:
+ return False
+
+ @validate_hf_hub_args
+ def file_exists(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ ) -> bool:
+ """
+ Checks if a file exists in a repository on the Hugging Face Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ filename (`str`):
+ The name of the file to check, for example:
+ `"config.json"`
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if getting repository info from a dataset or a space,
+ `None` or `"model"` if getting repository info from a model. Default is `None`.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information. Defaults to `"main"` branch.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ True if the file exists, False otherwise.
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import file_exists
+ >>> file_exists("bigcode/starcoder", "config.json")
+ True
+ >>> file_exists("bigcode/starcoder", "not-a-file")
+ False
+ >>> file_exists("bigcode/not-a-repo", "config.json")
+ False
+ ```
+ """
+ url = hf_hub_url(
+ repo_id=repo_id, repo_type=repo_type, revision=revision, filename=filename, endpoint=self.endpoint
+ )
+ try:
+ if token is None:
+ token = self.token
+ get_hf_file_metadata(url, token=token)
+ return True
+ except GatedRepoError: # raise specifically on gated repo
+ raise
+ except (RepositoryNotFoundError, EntryNotFoundError, RevisionNotFoundError):
+ return False
+
+ @validate_hf_hub_args
+ @_deprecate_method(version="0.23", message="Use `list_repo_tree` and `get_paths_info` instead.")
+ def list_files_info(
+ self,
+ repo_id: str,
+ paths: Union[List[str], str, None] = None,
+ *,
+ expand: bool = False,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ ) -> Iterable[RepoFile]:
+ """
+ List files on a repo and get information about them.
+
+ Takes as input a list of paths. Those paths can be either files or folders. Two server endpoints are called:
+ 1. POST "/paths-info" to get information about the provided paths. Called once.
+ 2. GET "/tree?recursive=True" to paginate over the input folders. Called only if a folder path is provided as
+ input. Will be called multiple times to follow pagination.
+ If no path is provided as input, step 1. is ignored and all files from the repo are listed.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ paths (`Union[List[str], str, None]`, *optional*):
+ The paths to get information about. Paths to files are directly resolved. Paths to folders are resolved
+ recursively which means that information is returned about all files in the folder and its subfolders.
+ If `None`, all files are returned (the default). If a path do not exist, it is ignored without raising
+ an exception.
+ expand (`bool`, *optional*, defaults to `False`):
+ Whether to fetch more information about the files (e.g. last commit and security scan results). This
+ operation is more expensive for the server so only 50 results are returned per page (instead of 1000).
+ As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it
+ takes to get the results.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information. Defaults to `"main"` branch.
+ repo_type (`str`, *optional*):
+ The type of the repository from which to get the information (`"model"`, `"dataset"` or `"space"`.
+ Defaults to `"model"`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ `Iterable[RepoFile]`:
+ The information about the files, as an iterable of [`RepoFile`] objects. The order of the files is
+ not guaranteed.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+
+ Examples:
+
+ Get information about files on a repo.
+ ```py
+ >>> from huggingface_hub import list_files_info
+ >>> files_info = list_files_info("lysandre/arxiv-nlp", ["README.md", "config.json"])
+ >>> files_info
+
+ >>> list(files_info)
+ [
+ RepoFile(path='README.md', size=391, blob_id='43bd404b159de6fba7c2f4d3264347668d43af25', lfs=None, last_commit=None, security=None),
+ RepoFile(path='config.json', size=554, blob_id='2f9618c3a19b9a61add74f70bfb121335aeef666', lfs=None, last_commit=None, security=None)
+ ]
+ ```
+
+ Get even more information about files on a repo (last commit and security scan results)
+ ```py
+ >>> from huggingface_hub import list_files_info
+ >>> files_info = list_files_info("prompthero/openjourney-v4", expand=True)
+ >>> list(files_info)
+ [
+ RepoFile(
+ path='safety_checker/pytorch_model.bin',
+ size=1216064769,
+ blob_id='c8835557a0d3af583cb06c7c154b7e54a069c41d',
+ lfs={
+ 'size': 1216064769,
+ 'sha256': '16d28f2b37109f222cdc33620fdd262102ac32112be0352a7f77e9614b35a394',
+ 'pointer_size': 135
+ },
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 10, 5, 27, tzinfo=datetime.timezone.utc)
+ },
+ security={
+ 'safe': True,
+ 'av_scan': {
+ 'virusFound': False,
+ 'virusNames': None
+ },
+ 'pickle_import_scan': {
+ 'highestSafetyLevel': 'innocuous',
+ 'imports': [
+ {'module': 'torch', 'name': 'FloatStorage', 'safety': 'innocuous'},
+ {'module': 'collections', 'name': 'OrderedDict', 'safety': 'innocuous'},
+ {'module': 'torch', 'name': 'LongStorage', 'safety': 'innocuous'},
+ {'module': 'torch._utils', 'name': '_rebuild_tensor_v2', 'safety': 'innocuous'}
+ ]
+ }
+ }
+ ),
+ RepoFile(
+ path='scheduler/scheduler_config.json',
+ size=465,
+ blob_id='70d55e3e9679f41cbc66222831b38d5abce683dd',
+ lfs=None,
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 10, 5, 27, tzinfo=datetime.timezone.utc)},
+ security={
+ 'safe': True,
+ 'av_scan': {
+ 'virusFound': False,
+ 'virusNames': None
+ },
+ 'pickle_import_scan': None
+ }
+ ),
+ ...
+ ]
+ ```
+
+ List LFS files from the "vae/" folder in "stabilityai/stable-diffusion-2" repository.
+
+ ```py
+ >>> from huggingface_hub import list_files_info
+ >>> [info.path for info in list_files_info("stabilityai/stable-diffusion-2", "vae") if info.lfs is not None]
+ ['vae/diffusion_pytorch_model.bin', 'vae/diffusion_pytorch_model.safetensors']
+ ```
+
+ List all files on a repo.
+ ```py
+ >>> from huggingface_hub import list_files_info
+ >>> [info.path for info in list_files_info("glue", repo_type="dataset")]
+ ['.gitattributes', 'README.md', 'dataset_infos.json', 'glue.py']
+ ```
+ """
+ repo_type = repo_type or REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+ headers = self._build_hf_headers(token=token)
+
+ folder_paths = []
+ if paths is None:
+ # `paths` is not provided => list all files from the repo
+ folder_paths.append("")
+ elif paths == []:
+ # corner case: server would return a 400 error if `paths` is an empty list. Let's return early.
+ return
+ else:
+ # `paths` is provided => get info about those
+ response = get_session().post(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/paths-info/{revision}",
+ data={
+ "paths": paths if isinstance(paths, list) else [paths],
+ "expand": expand,
+ },
+ headers=headers,
+ )
+ hf_raise_for_status(response)
+ paths_info = response.json()
+
+ # List top-level files first
+ for path_info in paths_info:
+ if path_info["type"] == "file":
+ yield RepoFile(**path_info)
+ else:
+ folder_paths.append(path_info["path"])
+
+ # List files in subdirectories
+ for path in folder_paths:
+ encoded_path = "/" + quote(path, safe="") if path else ""
+ tree_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tree/{revision}{encoded_path}"
+ for subpath_info in paginate(path=tree_url, headers=headers, params={"recursive": True, "expand": expand}):
+ if subpath_info["type"] == "file":
+ yield RepoFile(**subpath_info)
+
+ @validate_hf_hub_args
+ def list_repo_files(
+ self,
+ repo_id: str,
+ *,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ ) -> List[str]:
+ """
+ Get the list of files in a given repo.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ revision (`str`, *optional*):
+ The revision of the model repository from which to get the information.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to
+ a model. Default is `None`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ `List[str]`: the list of files in a given repository.
+ """
+ return [
+ f.rfilename
+ for f in self.list_repo_tree(
+ repo_id=repo_id, recursive=True, revision=revision, repo_type=repo_type, token=token
+ )
+ if isinstance(f, RepoFile)
+ ]
+
+ @validate_hf_hub_args
+ def list_repo_tree(
+ self,
+ repo_id: str,
+ path_in_repo: Optional[str] = None,
+ *,
+ recursive: bool = False,
+ expand: bool = False,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ ) -> Iterable[Union[RepoFile, RepoFolder]]:
+ """
+ List a repo tree's files and folders and get information about them.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ path_in_repo (`str`, *optional*):
+ Relative path of the tree (folder) in the repo, for example:
+ `"checkpoints/1fec34a/results"`. Will default to the root tree (folder) of the repository.
+ recursive (`bool`, *optional*, defaults to `False`):
+ Whether to list tree's files and folders recursively.
+ expand (`bool`, *optional*, defaults to `False`):
+ Whether to fetch more information about the tree's files and folders (e.g. last commit and files' security scan results). This
+ operation is more expensive for the server so only 50 results are returned per page (instead of 1000).
+ As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it
+ takes to get the results.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the tree. Defaults to `"main"` branch.
+ repo_type (`str`, *optional*):
+ The type of the repository from which to get the tree (`"model"`, `"dataset"` or `"space"`.
+ Defaults to `"model"`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ `Iterable[Union[RepoFile, RepoFolder]]`:
+ The information about the tree's files and folders, as an iterable of [`RepoFile`] and [`RepoFolder`] objects. The order of the files and folders is
+ not guaranteed.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ [`~utils.EntryNotFoundError`]:
+ If the tree (folder) does not exist (error 404) on the repo.
+
+ Examples:
+
+ Get information about a repo's tree.
+ ```py
+ >>> from huggingface_hub import list_repo_tree
+ >>> repo_tree = list_repo_tree("lysandre/arxiv-nlp")
+ >>> repo_tree
+
+ >>> list(repo_tree)
+ [
+ RepoFile(path='.gitattributes', size=391, blob_id='ae8c63daedbd4206d7d40126955d4e6ab1c80f8f', lfs=None, last_commit=None, security=None),
+ RepoFile(path='README.md', size=391, blob_id='43bd404b159de6fba7c2f4d3264347668d43af25', lfs=None, last_commit=None, security=None),
+ RepoFile(path='config.json', size=554, blob_id='2f9618c3a19b9a61add74f70bfb121335aeef666', lfs=None, last_commit=None, security=None),
+ RepoFile(
+ path='flax_model.msgpack', size=497764107, blob_id='8095a62ccb4d806da7666fcda07467e2d150218e',
+ lfs={'size': 497764107, 'sha256': 'd88b0d6a6ff9c3f8151f9d3228f57092aaea997f09af009eefd7373a77b5abb9', 'pointer_size': 134}, last_commit=None, security=None
+ ),
+ RepoFile(path='merges.txt', size=456318, blob_id='226b0752cac7789c48f0cb3ec53eda48b7be36cc', lfs=None, last_commit=None, security=None),
+ RepoFile(
+ path='pytorch_model.bin', size=548123560, blob_id='64eaa9c526867e404b68f2c5d66fd78e27026523',
+ lfs={'size': 548123560, 'sha256': '9be78edb5b928eba33aa88f431551348f7466ba9f5ef3daf1d552398722a5436', 'pointer_size': 134}, last_commit=None, security=None
+ ),
+ RepoFile(path='vocab.json', size=898669, blob_id='b00361fece0387ca34b4b8b8539ed830d644dbeb', lfs=None, last_commit=None, security=None)]
+ ]
+ ```
+
+ Get even more information about a repo's tree (last commit and files' security scan results)
+ ```py
+ >>> from huggingface_hub import list_repo_tree
+ >>> repo_tree = list_repo_tree("prompthero/openjourney-v4", expand=True)
+ >>> list(repo_tree)
+ [
+ RepoFolder(
+ path='feature_extractor',
+ tree_id='aa536c4ea18073388b5b0bc791057a7296a00398',
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc)
+ }
+ ),
+ RepoFolder(
+ path='safety_checker',
+ tree_id='65aef9d787e5557373fdf714d6c34d4fcdd70440',
+ last_commit={
+ 'oid': '47b62b20b20e06b9de610e840282b7e6c3d51190',
+ 'title': 'Upload diffusers weights (#48)',
+ 'date': datetime.datetime(2023, 3, 21, 9, 5, 27, tzinfo=datetime.timezone.utc)
+ }
+ ),
+ RepoFile(
+ path='model_index.json',
+ size=582,
+ blob_id='d3d7c1e8c3e78eeb1640b8e2041ee256e24c9ee1',
+ lfs=None,
+ last_commit={
+ 'oid': 'b195ed2d503f3eb29637050a886d77bd81d35f0e',
+ 'title': 'Fix deprecation warning by changing `CLIPFeatureExtractor` to `CLIPImageProcessor`. (#54)',
+ 'date': datetime.datetime(2023, 5, 15, 21, 41, 59, tzinfo=datetime.timezone.utc)
+ },
+ security={
+ 'safe': True,
+ 'av_scan': {'virusFound': False, 'virusNames': None},
+ 'pickle_import_scan': None
+ }
+ )
+ ...
+ ]
+ ```
+ """
+ repo_type = repo_type or REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+ headers = self._build_hf_headers(token=token)
+
+ encoded_path_in_repo = "/" + quote(path_in_repo, safe="") if path_in_repo else ""
+ tree_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tree/{revision}{encoded_path_in_repo}"
+ for path_info in paginate(path=tree_url, headers=headers, params={"recursive": recursive, "expand": expand}):
+ yield (RepoFile(**path_info) if path_info["type"] == "file" else RepoFolder(**path_info))
+
+ @validate_hf_hub_args
+ def list_repo_refs(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ include_pull_requests: bool = False,
+ token: Optional[Union[bool, str]] = None,
+ ) -> GitRefs:
+ """
+ Get the list of refs of a given repo (both tags and branches).
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing refs from a dataset or a Space,
+ `None` or `"model"` if listing from a model. Default is `None`.
+ include_pull_requests (`bool`, *optional*):
+ Whether to include refs from pull requests in the list. Defaults to `False`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> api.list_repo_refs("gpt2")
+ GitRefs(branches=[GitRefInfo(name='main', ref='refs/heads/main', target_commit='e7da7f221d5bf496a48136c0cd264e630fe9fcc8')], converts=[], tags=[])
+
+ >>> api.list_repo_refs("bigcode/the-stack", repo_type='dataset')
+ GitRefs(
+ branches=[
+ GitRefInfo(name='main', ref='refs/heads/main', target_commit='18edc1591d9ce72aa82f56c4431b3c969b210ae3'),
+ GitRefInfo(name='v1.1.a1', ref='refs/heads/v1.1.a1', target_commit='f9826b862d1567f3822d3d25649b0d6d22ace714')
+ ],
+ converts=[],
+ tags=[
+ GitRefInfo(name='v1.0', ref='refs/tags/v1.0', target_commit='c37a8cd1e382064d8aced5e05543c5f7753834da')
+ ]
+ )
+ ```
+
+ Returns:
+ [`GitRefs`]: object containing all information about branches and tags for a
+ repo on the Hub.
+ """
+ repo_type = repo_type or REPO_TYPE_MODEL
+ response = get_session().get(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/refs",
+ headers=self._build_hf_headers(token=token),
+ params={"include_prs": 1} if include_pull_requests else {},
+ )
+ hf_raise_for_status(response)
+ data = response.json()
+
+ def _format_as_git_ref_info(item: Dict) -> GitRefInfo:
+ return GitRefInfo(name=item["name"], ref=item["ref"], target_commit=item["targetCommit"])
+
+ return GitRefs(
+ branches=[_format_as_git_ref_info(item) for item in data["branches"]],
+ converts=[_format_as_git_ref_info(item) for item in data["converts"]],
+ tags=[_format_as_git_ref_info(item) for item in data["tags"]],
+ pull_requests=[_format_as_git_ref_info(item) for item in data["pullRequests"]]
+ if include_pull_requests
+ else None,
+ )
+
+ @validate_hf_hub_args
+ def list_repo_commits(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ revision: Optional[str] = None,
+ formatted: bool = False,
+ ) -> List[GitCommitInfo]:
+ """
+ Get the list of commits of a given revision for a repo on the Hub.
+
+ Commits are sorted by date (last commit first).
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if
+ listing from a model. Default is `None`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ formatted (`bool`):
+ Whether to return the HTML-formatted title and description of the commits. Defaults to False.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+
+ # Commits are sorted by date (last commit first)
+ >>> initial_commit = api.list_repo_commits("gpt2")[-1]
+
+ # Initial commit is always a system commit containing the `.gitattributes` file.
+ >>> initial_commit
+ GitCommitInfo(
+ commit_id='9b865efde13a30c13e0a33e536cf3e4a5a9d71d8',
+ authors=['system'],
+ created_at=datetime.datetime(2019, 2, 18, 10, 36, 15, tzinfo=datetime.timezone.utc),
+ title='initial commit',
+ message='',
+ formatted_title=None,
+ formatted_message=None
+ )
+
+ # Create an empty branch by deriving from initial commit
+ >>> api.create_branch("gpt2", "new_empty_branch", revision=initial_commit.commit_id)
+ ```
+
+ Returns:
+ List[[`GitCommitInfo`]]: list of objects containing information about the commits for a repo on the Hub.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ """
+ repo_type = repo_type or REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+
+ # Paginate over results and return the list of commits.
+ return [
+ GitCommitInfo(
+ commit_id=item["id"],
+ authors=[author["user"] for author in item["authors"]],
+ created_at=parse_datetime(item["date"]),
+ title=item["title"],
+ message=item["message"],
+ formatted_title=item.get("formatted", {}).get("title"),
+ formatted_message=item.get("formatted", {}).get("message"),
+ )
+ for item in paginate(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/commits/{revision}",
+ headers=self._build_hf_headers(token=token),
+ params={"expand[]": "formatted"} if formatted else {},
+ )
+ ]
+
+ @validate_hf_hub_args
+ def get_paths_info(
+ self,
+ repo_id: str,
+ paths: Union[List[str], str],
+ *,
+ expand: bool = False,
+ revision: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ ) -> List[Union[RepoFile, RepoFolder]]:
+ """
+ Get information about a repo's paths.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ paths (`Union[List[str], str]`, *optional*):
+ The paths to get information about. If a path do not exist, it is ignored without raising
+ an exception.
+ expand (`bool`, *optional*, defaults to `False`):
+ Whether to fetch more information about the paths (e.g. last commit and files' security scan results). This
+ operation is more expensive for the server so only 50 results are returned per page (instead of 1000).
+ As pagination is implemented in `huggingface_hub`, this is transparent for you except for the time it
+ takes to get the results.
+ revision (`str`, *optional*):
+ The revision of the repository from which to get the information. Defaults to `"main"` branch.
+ repo_type (`str`, *optional*):
+ The type of the repository from which to get the information (`"model"`, `"dataset"` or `"space"`.
+ Defaults to `"model"`.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ `List[Union[RepoFile, RepoFolder]]`:
+ The information about the paths, as a list of [`RepoFile`] and [`RepoFolder`] objects.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import get_paths_info
+ >>> paths_info = get_paths_info("allenai/c4", ["README.md", "en"], repo_type="dataset")
+ >>> paths_info
+ [
+ RepoFile(path='README.md', size=2379, blob_id='f84cb4c97182890fc1dbdeaf1a6a468fd27b4fff', lfs=None, last_commit=None, security=None),
+ RepoFolder(path='en', tree_id='dc943c4c40f53d02b31ced1defa7e5f438d5862e', last_commit=None)
+ ]
+ ```
+ """
+ repo_type = repo_type or REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+ headers = self._build_hf_headers(token=token)
+
+ response = get_session().post(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/paths-info/{revision}",
+ data={
+ "paths": paths if isinstance(paths, list) else [paths],
+ "expand": expand,
+ },
+ headers=headers,
+ )
+ hf_raise_for_status(response)
+ paths_info = response.json()
+ return [
+ RepoFile(**path_info) if path_info["type"] == "file" else RepoFolder(**path_info)
+ for path_info in paths_info
+ ]
+
+ @validate_hf_hub_args
+ def super_squash_history(
+ self,
+ repo_id: str,
+ *,
+ branch: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> None:
+ """Squash commit history on a branch for a repo on the Hub.
+
+ Squashing the repo history is useful when you know you'll make hundreds of commits and you don't want to
+ clutter the history. Squashing commits can only be performed from the head of a branch.
+
+
+
+ Once squashed, the commit history cannot be retrieved. This is a non-revertible operation.
+
+
+
+
+
+ Once the history of a branch has been squashed, it is not possible to merge it back into another branch since
+ their history will have diverged.
+
+
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated by a `/`.
+ branch (`str`, *optional*):
+ The branch to squash. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The commit message to use for the squashed commit.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if listing commits from a dataset or a Space, `None` or `"model"` if
+ listing from a model. Default is `None`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If the machine is logged in
+ (through `huggingface-cli login` or [`~huggingface_hub.login`]), token can be automatically retrieved
+ from the cache.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private but not authenticated or repo
+ does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If the branch to squash cannot be found.
+ [`~utils.BadRequestError`]:
+ If invalid reference for a branch. You cannot squash history on tags.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+
+ # Create repo
+ >>> repo_id = api.create_repo("test-squash").repo_id
+
+ # Make a lot of commits.
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="file.txt", path_or_fileobj=b"content")
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="lfs.bin", path_or_fileobj=b"content")
+ >>> api.upload_file(repo_id=repo_id, path_in_repo="file.txt", path_or_fileobj=b"another_content")
+
+ # Squash history
+ >>> api.super_squash_history(repo_id=repo_id)
+ ```
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ if repo_type not in REPO_TYPES:
+ raise ValueError("Invalid repo type")
+ if branch is None:
+ branch = DEFAULT_REVISION
+
+ # Prepare request
+ url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/super-squash/{branch}"
+ headers = self._build_hf_headers(token=token)
+ commit_message = commit_message or f"Super-squash branch '{branch}' using huggingface_hub"
+
+ # Super-squash
+ response = get_session().post(url=url, headers=headers, json={"message": commit_message})
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def create_repo(
+ self,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ private: bool = False,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ space_sdk: Optional[str] = None,
+ space_hardware: Optional[SpaceHardware] = None,
+ space_storage: Optional[SpaceStorage] = None,
+ space_sleep_time: Optional[int] = None,
+ space_secrets: Optional[List[Dict[str, str]]] = None,
+ space_variables: Optional[List[Dict[str, str]]] = None,
+ ) -> RepoUrl:
+ """Create an empty repo on the HuggingFace Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+ private (`bool`, *optional*, defaults to `False`):
+ Whether the model repo should be private.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo already exists.
+ space_sdk (`str`, *optional*):
+ Choice of SDK to use if repo_type is "space". Can be "streamlit", "gradio", "docker", or "static".
+ space_hardware (`SpaceHardware` or `str`, *optional*):
+ Choice of Hardware if repo_type is "space". See [`SpaceHardware`] for a complete list.
+ space_storage (`SpaceStorage` or `str`, *optional*):
+ Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
+ space_sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ space_secrets (`List[Dict[str, str]]`, *optional*):
+ A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+ space_variables (`List[Dict[str, str]]`, *optional*):
+ A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.
+
+ Returns:
+ [`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
+ attributes like `endpoint`, `repo_type` and `repo_id`.
+ """
+ organization, name = repo_id.split("/") if "/" in repo_id else (None, repo_id)
+
+ path = f"{self.endpoint}/api/repos/create"
+
+ if repo_type not in REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ json: Dict[str, Any] = {"name": name, "organization": organization, "private": private}
+ if repo_type is not None:
+ json["type"] = repo_type
+ if repo_type == "space":
+ if space_sdk is None:
+ raise ValueError(
+ "No space_sdk provided. `create_repo` expects space_sdk to be one"
+ f" of {SPACES_SDK_TYPES} when repo_type is 'space'`"
+ )
+ if space_sdk not in SPACES_SDK_TYPES:
+ raise ValueError(f"Invalid space_sdk. Please choose one of {SPACES_SDK_TYPES}.")
+ json["sdk"] = space_sdk
+
+ if space_sdk is not None and repo_type != "space":
+ warnings.warn("Ignoring provided space_sdk because repo_type is not 'space'.")
+
+ function_args = [
+ "space_hardware",
+ "space_storage",
+ "space_sleep_time",
+ "space_secrets",
+ "space_variables",
+ ]
+ json_keys = ["hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
+ values = [space_hardware, space_storage, space_sleep_time, space_secrets, space_variables]
+
+ if repo_type == "space":
+ json.update({k: v for k, v in zip(json_keys, values) if v is not None})
+ else:
+ provided_space_args = [key for key, value in zip(function_args, values) if value is not None]
+
+ if provided_space_args:
+ warnings.warn(f"Ignoring provided {', '.join(provided_space_args)} because repo_type is not 'space'.")
+
+ if getattr(self, "_lfsmultipartthresh", None):
+ # Testing purposes only.
+ # See https://github.com/huggingface/huggingface_hub/pull/733/files#r820604472
+ json["lfsmultipartthresh"] = self._lfsmultipartthresh # type: ignore
+ headers = self._build_hf_headers(token=token)
+
+ while True:
+ r = get_session().post(path, headers=headers, json=json)
+ if r.status_code == 409 and "Cannot create repo: another conflicting operation is in progress" in r.text:
+ # Since https://github.com/huggingface/moon-landing/pull/7272 (private repo), it is not possible to
+ # concurrently create repos on the Hub for a same user. This is rarely an issue, except when running
+ # tests. To avoid any inconvenience, we retry to create the repo for this specific error.
+ # NOTE: This could have being fixed directly in the tests but adding it here should fixed CIs for all
+ # dependent libraries.
+ # NOTE: If a fix is implemented server-side, we should be able to remove this retry mechanism.
+ logger.debug("Create repo failed due to a concurrency issue. Retrying...")
+ continue
+ break
+
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exist_ok and err.response.status_code == 409:
+ # Repo already exists and `exist_ok=True`
+ pass
+ elif exist_ok and err.response.status_code == 403:
+ # No write permission on the namespace but repo might already exist
+ try:
+ self.repo_info(repo_id=repo_id, repo_type=repo_type, token=token)
+ if repo_type is None or repo_type == REPO_TYPE_MODEL:
+ return RepoUrl(f"{self.endpoint}/{repo_id}")
+ return RepoUrl(f"{self.endpoint}/{repo_type}/{repo_id}")
+ except HfHubHTTPError:
+ raise
+ else:
+ raise
+
+ d = r.json()
+ return RepoUrl(d["url"], endpoint=self.endpoint)
+
+ @validate_hf_hub_args
+ def delete_repo(
+ self,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ missing_ok: bool = False,
+ ) -> None:
+ """
+ Delete a repo from the HuggingFace Hub. CAUTION: this is irreversible.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model.
+ missing_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo does not exist.
+
+ Raises:
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to delete from cannot be found and `missing_ok` is set to False (default).
+ """
+ organization, name = repo_id.split("/") if "/" in repo_id else (None, repo_id)
+
+ path = f"{self.endpoint}/api/repos/delete"
+
+ if repo_type not in REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ json = {"name": name, "organization": organization}
+ if repo_type is not None:
+ json["type"] = repo_type
+
+ headers = self._build_hf_headers(token=token)
+ r = get_session().delete(path, headers=headers, json=json)
+ try:
+ hf_raise_for_status(r)
+ except RepositoryNotFoundError:
+ if not missing_ok:
+ raise
+
+ @validate_hf_hub_args
+ @_deprecate_arguments(
+ version="0.24.0", deprecated_args=("organization", "name"), custom_message="Use `repo_id` instead."
+ )
+ def update_repo_visibility(
+ self,
+ repo_id: str,
+ private: bool = False,
+ *,
+ token: Optional[str] = None,
+ organization: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ name: Optional[str] = None,
+ ) -> Dict[str, bool]:
+ """Update the visibility setting of a repository.
+
+ Args:
+ repo_id (`str`, *optional*):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ private (`bool`, *optional*, defaults to `False`):
+ Whether the model repo should be private.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns:
+ The HTTP response in json.
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError("Invalid repo type")
+
+ organization, name = repo_id.split("/") if "/" in repo_id else (None, repo_id)
+
+ if organization is None:
+ namespace = self.whoami(token)["name"]
+ else:
+ namespace = organization
+
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL # default repo type
+
+ r = get_session().put(
+ url=f"{self.endpoint}/api/{repo_type}s/{namespace}/{name}/settings",
+ headers=self._build_hf_headers(token=token),
+ json={"private": private},
+ )
+ hf_raise_for_status(r)
+ return r.json()
+
+ def move_repo(
+ self,
+ from_id: str,
+ to_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ):
+ """
+ Moving a repository from namespace1/repo_name1 to namespace2/repo_name2
+
+ Note there are certain limitations. For more information about moving
+ repositories, please see
+ https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo.
+
+ Args:
+ from_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`. Original repository identifier.
+ to_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`. Final repository identifier.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+
+
+ Raises the following errors:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if len(from_id.split("/")) != 2:
+ raise ValueError(f"Invalid repo_id: {from_id}. It should have a namespace (:namespace:/:repo_name:)")
+
+ if len(to_id.split("/")) != 2:
+ raise ValueError(f"Invalid repo_id: {to_id}. It should have a namespace (:namespace:/:repo_name:)")
+
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL # Hub won't accept `None`.
+
+ json = {"fromRepo": from_id, "toRepo": to_id, "type": repo_type}
+
+ path = f"{self.endpoint}/api/repos/move"
+ headers = self._build_hf_headers(token=token)
+ r = get_session().post(path, headers=headers, json=json)
+ try:
+ hf_raise_for_status(r)
+ except HfHubHTTPError as e:
+ e.append_to_message(
+ "\nFor additional documentation please see"
+ " https://hf.co/docs/hub/repositories-settings#renaming-or-transferring-a-repo."
+ )
+ raise
+
+ @overload
+ def create_commit( # type: ignore
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def create_commit(
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def create_commit(
+ self,
+ repo_id: str,
+ operations: Iterable[CommitOperation],
+ *,
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ parent_commit: Optional[str] = None,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, Future[CommitInfo]]:
+ """
+ Creates a commit in the given repo, deleting & uploading files as needed.
+
+
+
+ The input list of `CommitOperation` will be mutated during the commit process. Do not reuse the same objects
+ for multiple commits.
+
+
+
+
+
+ `create_commit` assumes that the repo already exists on the Hub. If you get a
+ Client error 404, please make sure you are authenticated and that `repo_id` and
+ `repo_type` are set correctly. If repo does not exist, create it first using
+ [`~hf_api.create_repo`].
+
+
+
+
+
+ `create_commit` is limited to 25k LFS files and a 1GB payload for regular files.
+
+
+
+ Args:
+ repo_id (`str`):
+ The repository in which the commit will be created, for example:
+ `"username/custom_transformers"`
+
+ operations (`Iterable` of [`~hf_api.CommitOperation`]):
+ An iterable of operations to include in the commit, either:
+
+ - [`~hf_api.CommitOperationAdd`] to upload a file
+ - [`~hf_api.CommitOperationDelete`] to delete a file
+ - [`~hf_api.CommitOperationCopy`] to copy a file
+
+ Operation objects will be mutated to include information relative to the upload. Do not reuse the
+ same objects for multiple commits.
+
+ commit_message (`str`):
+ The summary (first line) of the commit that will be created.
+
+ commit_description (`str`, *optional*):
+ The description of the commit that will be created
+
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will
+ default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+
+ num_threads (`int`, *optional*):
+ Number of concurrent threads for uploading files. Defaults to 5.
+ Setting it to 2 means at most 2 files will be uploaded concurrently.
+
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string.
+ Shorthands (7 first characters) are also supported. If specified and `create_pr` is `False`,
+ the commit will fail if `revision` does not point to `parent_commit`. If specified and `create_pr`
+ is `True`, the pull request will be created from `parent_commit`. Specifying `parent_commit`
+ ensures the repo has not changed before committing the changes, and can be especially useful
+ if the repo is updated / committed to concurrently.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If commit message is empty.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If parent commit is not a valid commit OID.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If a README.md file with an invalid metadata section is committed. In this case, the commit will fail
+ early, before trying to upload any file.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `create_pr` is `True` and revision is neither `None` nor `"main"`.
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ """
+ if parent_commit is not None and not REGEX_COMMIT_OID.fullmatch(parent_commit):
+ raise ValueError(
+ f"`parent_commit` is not a valid commit OID. It must match the following regex: {REGEX_COMMIT_OID}"
+ )
+
+ if commit_message is None or len(commit_message) == 0:
+ raise ValueError("`commit_message` can't be empty, please pass a value.")
+
+ commit_description = commit_description if commit_description is not None else ""
+ repo_type = repo_type if repo_type is not None else REPO_TYPE_MODEL
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ unquoted_revision = revision or DEFAULT_REVISION
+ revision = quote(unquoted_revision, safe="")
+ create_pr = create_pr if create_pr is not None else False
+
+ headers = self._build_hf_headers(token=token)
+
+ operations = list(operations)
+ additions = [op for op in operations if isinstance(op, CommitOperationAdd)]
+ copies = [op for op in operations if isinstance(op, CommitOperationCopy)]
+ nb_additions = len(additions)
+ nb_copies = len(copies)
+ nb_deletions = len(operations) - nb_additions - nb_copies
+
+ for addition in additions:
+ if addition._is_committed:
+ raise ValueError(
+ f"CommitOperationAdd {addition} has already being committed and cannot be reused. Please create a"
+ " new CommitOperationAdd object if you want to create a new commit."
+ )
+
+ logger.debug(
+ f"About to commit to the hub: {len(additions)} addition(s), {len(copies)} copie(s) and"
+ f" {nb_deletions} deletion(s)."
+ )
+
+ # If updating a README.md file, make sure the metadata format is valid
+ # It's better to fail early than to fail after all the files have been uploaded.
+ for addition in additions:
+ if addition.path_in_repo == "README.md":
+ with addition.as_file() as file:
+ response = get_session().post(
+ f"{ENDPOINT}/api/validate-yaml",
+ json={"content": file.read().decode(), "repoType": repo_type},
+ headers=headers,
+ )
+ # Handle warnings (example: empty metadata)
+ response_content = response.json()
+ message = "\n".join(
+ [f"- {warning.get('message')}" for warning in response_content.get("warnings", [])]
+ )
+ if message:
+ warnings.warn(f"Warnings while validating metadata in README.md:\n{message}")
+
+ # Raise on errors
+ try:
+ hf_raise_for_status(response)
+ except BadRequestError as e:
+ errors = response_content.get("errors", [])
+ message = "\n".join([f"- {error.get('message')}" for error in errors])
+ raise ValueError(f"Invalid metadata in README.md.\n{message}") from e
+
+ # If updating twice the same file or update then delete a file in a single commit
+ _warn_on_overwriting_operations(operations)
+
+ self.preupload_lfs_files(
+ repo_id=repo_id,
+ additions=additions,
+ token=token,
+ repo_type=repo_type,
+ revision=unquoted_revision, # first-class methods take unquoted revision
+ create_pr=create_pr,
+ num_threads=num_threads,
+ free_memory=False, # do not remove `CommitOperationAdd.path_or_fileobj` on LFS files for "normal" users
+ )
+ files_to_copy = _fetch_files_to_copy(
+ copies=copies,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ revision=revision,
+ endpoint=self.endpoint,
+ )
+ commit_payload = _prepare_commit_payload(
+ operations=operations,
+ files_to_copy=files_to_copy,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ parent_commit=parent_commit,
+ )
+ commit_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/commit/{revision}"
+
+ def _payload_as_ndjson() -> Iterable[bytes]:
+ for item in commit_payload:
+ yield json.dumps(item).encode()
+ yield b"\n"
+
+ headers = {
+ # See https://github.com/huggingface/huggingface_hub/issues/1085#issuecomment-1265208073
+ "Content-Type": "application/x-ndjson",
+ **headers,
+ }
+ data = b"".join(_payload_as_ndjson())
+ params = {"create_pr": "1"} if create_pr else None
+
+ try:
+ commit_resp = get_session().post(url=commit_url, headers=headers, data=data, params=params)
+ hf_raise_for_status(commit_resp, endpoint_name="commit")
+ except RepositoryNotFoundError as e:
+ e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
+ raise
+ except EntryNotFoundError as e:
+ if nb_deletions > 0 and "A file with this name doesn't exist" in str(e):
+ e.append_to_message(
+ "\nMake sure to differentiate file and folder paths in delete"
+ " operations with a trailing '/' or using `is_folder=True/False`."
+ )
+ raise
+
+ # Mark additions as committed (cannot be reused in another commit)
+ for addition in additions:
+ addition._is_committed = True
+
+ commit_data = commit_resp.json()
+ return CommitInfo(
+ commit_url=commit_data["commitUrl"],
+ commit_message=commit_message,
+ commit_description=commit_description,
+ oid=commit_data["commitOid"],
+ pr_url=commit_data["pullRequestUrl"] if create_pr else None,
+ )
+
+ @experimental
+ @validate_hf_hub_args
+ def create_commits_on_pr(
+ self,
+ *,
+ repo_id: str,
+ addition_commits: List[List[CommitOperationAdd]],
+ deletion_commits: List[List[CommitOperationDelete]],
+ commit_message: str,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ merge_pr: bool = True,
+ num_threads: int = 5, # TODO: use to multithread uploads
+ verbose: bool = False,
+ ) -> str:
+ """Push changes to the Hub in multiple commits.
+
+ Commits are pushed to a draft PR branch. If the upload fails or gets interrupted, it can be resumed. Progress
+ is tracked in the PR description. At the end of the process, the PR is set as open and the title is updated to
+ match the initial commit message. If `merge_pr=True` is passed, the PR is merged automatically.
+
+ All deletion commits are pushed first, followed by the addition commits. The order of the commits is not
+ guaranteed as we might implement parallel commits in the future. Be sure that your are not updating several
+ times the same file.
+
+
+
+ `create_commits_on_pr` is experimental. Its API and behavior is subject to change in the future without prior notice.
+
+
+
+
+
+ `create_commits_on_pr` assumes that the repo already exists on the Hub. If you get a Client error 404, please
+ make sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist,
+ create it first using [`~hf_api.create_repo`].
+
+
+
+ Args:
+ repo_id (`str`):
+ The repository in which the commits will be pushed. Example: `"username/my-cool-model"`.
+
+ addition_commits (`List` of `List` of [`~hf_api.CommitOperationAdd`]):
+ A list containing lists of [`~hf_api.CommitOperationAdd`]. Each sublist will result in a commit on the
+ PR.
+
+ deletion_commits
+ A list containing lists of [`~hf_api.CommitOperationDelete`]. Each sublist will result in a commit on
+ the PR. Deletion commits are pushed before addition commits.
+
+ commit_message (`str`):
+ The summary (first line) of the commit that will be created. Will also be the title of the PR.
+
+ commit_description (`str`, *optional*):
+ The description of the commit that will be created. The description will be added to the PR.
+
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or space, `None` or `"model"` if uploading to
+ a model. Default is `None`.
+
+ merge_pr (`bool`):
+ If set to `True`, the Pull Request is merged at the end of the process. Defaults to `True`.
+
+ num_threads (`int`, *optional*):
+ Number of concurrent threads for uploading files. Defaults to 5.
+
+ verbose (`bool`):
+ If set to `True`, process will run on verbose mode i.e. print information about the ongoing tasks.
+ Defaults to `False`.
+
+ Returns:
+ `str`: URL to the created PR.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi, plan_multi_commits
+ >>> addition_commits, deletion_commits = plan_multi_commits(
+ ... operations=[
+ ... CommitOperationAdd(...),
+ ... CommitOperationAdd(...),
+ ... CommitOperationDelete(...),
+ ... CommitOperationDelete(...),
+ ... CommitOperationAdd(...),
+ ... ],
+ ... )
+ >>> HfApi().create_commits_on_pr(
+ ... repo_id="my-cool-model",
+ ... addition_commits=addition_commits,
+ ... deletion_commits=deletion_commits,
+ ... (...)
+ ... verbose=True,
+ ... )
+ ```
+
+ Raises:
+ [`MultiCommitException`]:
+ If an unexpected issue occur in the process: empty commits, unexpected commits in a PR, unexpected PR
+ description, etc.
+ """
+ logger = logging.get_logger(__name__ + ".create_commits_on_pr")
+ if verbose:
+ logger.setLevel("INFO")
+
+ # 1. Get strategy ID
+ logger.info(
+ f"Will create {len(deletion_commits)} deletion commit(s) and {len(addition_commits)} addition commit(s),"
+ f" totalling {sum(len(ops) for ops in addition_commits+deletion_commits)} atomic operations."
+ )
+ strategy = MultiCommitStrategy(
+ addition_commits=[MultiCommitStep(operations=operations) for operations in addition_commits], # type: ignore
+ deletion_commits=[MultiCommitStep(operations=operations) for operations in deletion_commits], # type: ignore
+ )
+ logger.info(f"Multi-commits strategy with ID {strategy.id}.")
+
+ # 2. Get or create a PR with this strategy ID
+ for discussion in self.get_repo_discussions(repo_id=repo_id, repo_type=repo_type, token=token):
+ # search for a draft PR with strategy ID
+ if discussion.is_pull_request and discussion.status == "draft" and strategy.id in discussion.title:
+ pr = self.get_discussion_details(
+ repo_id=repo_id, discussion_num=discussion.num, repo_type=repo_type, token=token
+ )
+ logger.info(f"PR already exists: {pr.url}. Will resume process where it stopped.")
+ break
+ else:
+ # did not find a PR matching the strategy ID
+ pr = multi_commit_create_pull_request(
+ self,
+ repo_id=repo_id,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ strategy=strategy,
+ token=token,
+ repo_type=repo_type,
+ )
+ logger.info(f"New PR created: {pr.url}")
+
+ # 3. Parse PR description to check consistency with strategy (e.g. same commits are scheduled)
+ for event in pr.events:
+ if isinstance(event, DiscussionComment):
+ pr_comment = event
+ break
+ else:
+ raise MultiCommitException(f"PR #{pr.num} must have at least 1 comment")
+
+ description_commits = multi_commit_parse_pr_description(pr_comment.content)
+ if len(description_commits) != len(strategy.all_steps):
+ raise MultiCommitException(
+ f"Corrupted multi-commit PR #{pr.num}: got {len(description_commits)} steps in"
+ f" description but {len(strategy.all_steps)} in strategy."
+ )
+ for step_id in strategy.all_steps:
+ if step_id not in description_commits:
+ raise MultiCommitException(
+ f"Corrupted multi-commit PR #{pr.num}: expected step {step_id} but didn't find"
+ f" it (have {', '.join(description_commits)})."
+ )
+
+ # 4. Retrieve commit history (and check consistency)
+ commits_on_main_branch = {
+ commit.commit_id
+ for commit in self.list_repo_commits(
+ repo_id=repo_id, repo_type=repo_type, token=token, revision=DEFAULT_REVISION
+ )
+ }
+ pr_commits = [
+ commit
+ for commit in self.list_repo_commits(
+ repo_id=repo_id, repo_type=repo_type, token=token, revision=pr.git_reference
+ )
+ if commit.commit_id not in commits_on_main_branch
+ ]
+ if len(pr_commits) > 0:
+ logger.info(f"Found {len(pr_commits)} existing commits on the PR.")
+
+ # At this point `pr_commits` is a list of commits pushed to the PR. We expect all of these commits (if any) to have
+ # a step_id as title. We raise exception if an unexpected commit has been pushed.
+ if len(pr_commits) > len(strategy.all_steps):
+ raise MultiCommitException(
+ f"Corrupted multi-commit PR #{pr.num}: scheduled {len(strategy.all_steps)} steps but"
+ f" {len(pr_commits)} commits have already been pushed to the PR."
+ )
+
+ # Check which steps are already completed
+ remaining_additions = {step.id: step for step in strategy.addition_commits}
+ remaining_deletions = {step.id: step for step in strategy.deletion_commits}
+ for commit in pr_commits:
+ if commit.title in remaining_additions:
+ step = remaining_additions.pop(commit.title)
+ step.completed = True
+ elif commit.title in remaining_deletions:
+ step = remaining_deletions.pop(commit.title)
+ step.completed = True
+
+ if len(remaining_deletions) > 0 and len(remaining_additions) < len(strategy.addition_commits):
+ raise MultiCommitException(
+ f"Corrupted multi-commit PR #{pr.num}: some addition commits have already been pushed to the PR but"
+ " deletion commits are not all completed yet."
+ )
+ nb_remaining = len(remaining_deletions) + len(remaining_additions)
+ if len(pr_commits) > 0:
+ logger.info(
+ f"{nb_remaining} commits remaining ({len(remaining_deletions)} deletion commits and"
+ f" {len(remaining_additions)} addition commits)"
+ )
+
+ # 5. Push remaining commits to the PR + update description
+ # TODO: multi-thread this
+ for step in list(remaining_deletions.values()) + list(remaining_additions.values()):
+ # Push new commit
+ self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ commit_message=step.id,
+ revision=pr.git_reference,
+ num_threads=num_threads,
+ operations=step.operations,
+ create_pr=False,
+ )
+ step.completed = True
+ nb_remaining -= 1
+ logger.info(f" step {step.id} completed (still {nb_remaining} to go).")
+
+ # Update PR description
+ self.edit_discussion_comment(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ comment_id=pr_comment.id,
+ new_content=multi_commit_generate_comment(
+ commit_message=commit_message, commit_description=commit_description, strategy=strategy
+ ),
+ )
+ logger.info("All commits have been pushed.")
+
+ # 6. Update PR (and merge)
+ self.rename_discussion(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ new_title=commit_message,
+ )
+ self.change_discussion_status(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ new_status="open",
+ comment=MULTI_COMMIT_PR_COMPLETION_COMMENT_TEMPLATE,
+ )
+ logger.info("PR is now open for reviews.")
+
+ if merge_pr: # User don't want a PR => merge it
+ try:
+ self.merge_pull_request(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ comment=MULTI_COMMIT_PR_CLOSING_COMMENT_TEMPLATE,
+ )
+ logger.info("PR has been automatically merged (`merge_pr=True` was passed).")
+ except BadRequestError as error:
+ if error.server_message is not None and "no associated changes" in error.server_message:
+ # PR cannot be merged as no changes are associated. We close the PR without merging with a comment to
+ # explain.
+ self.change_discussion_status(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ comment=MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_NO_CHANGES_TEMPLATE,
+ new_status="closed",
+ )
+ logger.warning("Couldn't merge the PR: no associated changes.")
+ else:
+ # PR cannot be merged for another reason (conflicting files for example). We comment the PR to explain
+ # and re-raise the exception.
+ self.comment_discussion(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ discussion_num=pr.num,
+ comment=MULTI_COMMIT_PR_CLOSE_COMMENT_FAILURE_BAD_REQUEST_TEMPLATE.format(
+ error_message=error.server_message
+ ),
+ )
+ raise MultiCommitException(
+ f"Couldn't merge Pull Request in multi-commit: {error.server_message}"
+ ) from error
+
+ return pr.url
+
+ def preupload_lfs_files(
+ self,
+ repo_id: str,
+ additions: Iterable[CommitOperationAdd],
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ num_threads: int = 5,
+ free_memory: bool = True,
+ gitignore_content: Optional[str] = None,
+ ):
+ """Pre-upload LFS files to S3 in preparation on a future commit.
+
+ This method is useful if you are generating the files to upload on-the-fly and you don't want to store them
+ in memory before uploading them all at once.
+
+
+
+ This is a power-user method. You shouldn't need to call it directly to make a normal commit.
+ Use [`create_commit`] directly instead.
+
+
+
+
+
+ Commit operations will be mutated during the process. In particular, the attached `path_or_fileobj` will be
+ removed after the upload to save memory (and replaced by an empty `bytes` object). Do not reuse the same
+ objects except to pass them to [`create_commit`]. If you don't want to remove the attached content from the
+ commit operation object, pass `free_memory=False`.
+
+
+
+ Args:
+ repo_id (`str`):
+ The repository in which you will commit the files, for example: `"username/custom_transformers"`.
+
+ operations (`Iterable` of [`CommitOperationAdd`]):
+ The list of files to upload. Warning: the objects in this list will be mutated to include information
+ relative to the upload. Do not reuse the same objects for multiple commits.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ The type of repository to upload to (e.g. `"model"` -default-, `"dataset"` or `"space"`).
+
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+
+ create_pr (`boolean`, *optional*):
+ Whether or not you plan to create a Pull Request with that commit. Defaults to `False`.
+
+ num_threads (`int`, *optional*):
+ Number of concurrent threads for uploading files. Defaults to 5.
+ Setting it to 2 means at most 2 files will be uploaded concurrently.
+
+ gitignore_content (`str`, *optional*):
+ The content of the `.gitignore` file to know which files should be ignored. The order of priority
+ is to first check if `gitignore_content` is passed, then check if the `.gitignore` file is present
+ in the list of files to commit and finally default to the `.gitignore` file already hosted on the Hub
+ (if any).
+
+ Example:
+ ```py
+ >>> from huggingface_hub import CommitOperationAdd, preupload_lfs_files, create_commit, create_repo
+
+ >>> repo_id = create_repo("test_preupload").repo_id
+
+ # Generate and preupload LFS files one by one
+ >>> operations = [] # List of all `CommitOperationAdd` objects that will be generated
+ >>> for i in range(5):
+ ... content = ... # generate binary content
+ ... addition = CommitOperationAdd(path_in_repo=f"shard_{i}_of_5.bin", path_or_fileobj=content)
+ ... preupload_lfs_files(repo_id, additions=[addition]) # upload + free memory
+ ... operations.append(addition)
+
+ # Create commit
+ >>> create_commit(repo_id, operations=operations, commit_message="Commit all shards")
+ ```
+ """
+ repo_type = repo_type if repo_type is not None else REPO_TYPE_MODEL
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+ create_pr = create_pr if create_pr is not None else False
+ headers = self._build_hf_headers(token=token)
+
+ # Check if a `gitignore` file is being committed to the Hub.
+ additions = list(additions)
+ if gitignore_content is None:
+ for addition in additions:
+ if addition.path_in_repo == ".gitignore":
+ with addition.as_file() as f:
+ gitignore_content = f.read().decode()
+ break
+
+ # Filter out already uploaded files
+ new_additions = [addition for addition in additions if not addition._is_uploaded]
+
+ # Check which new files are LFS
+ try:
+ _fetch_upload_modes(
+ additions=new_additions,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ revision=revision,
+ endpoint=self.endpoint,
+ create_pr=create_pr or False,
+ gitignore_content=gitignore_content,
+ )
+ except RepositoryNotFoundError as e:
+ e.append_to_message(_CREATE_COMMIT_NO_REPO_ERROR_MESSAGE)
+ raise
+
+ # Filter out regular files
+ new_lfs_additions = [addition for addition in new_additions if addition._upload_mode == "lfs"]
+
+ # Filter out files listed in .gitignore
+ new_lfs_additions_to_upload = []
+ for addition in new_lfs_additions:
+ if addition._should_ignore:
+ logger.debug(f"Skipping upload for LFS file '{addition.path_in_repo}' (ignored by gitignore file).")
+ else:
+ new_lfs_additions_to_upload.append(addition)
+ if len(new_lfs_additions) != len(new_lfs_additions_to_upload):
+ logger.info(
+ f"Skipped upload for {len(new_lfs_additions) - len(new_lfs_additions_to_upload)} LFS file(s) "
+ "(ignored by gitignore file)."
+ )
+
+ # Upload new LFS files
+ _upload_lfs_files(
+ additions=new_lfs_additions_to_upload,
+ repo_type=repo_type,
+ repo_id=repo_id,
+ headers=headers,
+ endpoint=self.endpoint,
+ num_threads=num_threads,
+ # If `create_pr`, we don't want to check user permission on the revision as users with read permission
+ # should still be able to create PRs even if they don't have write permission on the target branch of the
+ # PR (i.e. `revision`).
+ revision=revision if not create_pr else None,
+ )
+ for addition in new_lfs_additions_to_upload:
+ addition._is_uploaded = True
+ if free_memory:
+ addition.path_or_fileobj = b""
+
+ @overload
+ def upload_file( # type: ignore
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def upload_file(
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def upload_file(
+ self,
+ *,
+ path_or_fileobj: Union[str, Path, bytes, BinaryIO],
+ path_in_repo: str,
+ repo_id: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, Future[CommitInfo]]:
+ """
+ Upload a local file (up to 50 GB) to the given repo. The upload is done
+ through a HTTP post request, and doesn't require git or git-lfs to be
+ installed.
+
+ Args:
+ path_or_fileobj (`str`, `Path`, `bytes`, or `IO`):
+ Path to a file on the local machine or binary data stream /
+ fileobj / buffer.
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example:
+ `"checkpoints/1fec34a/weights.bin"`
+ repo_id (`str`):
+ The repository to which the file will be uploaded, for example:
+ `"username/custom_transformers"`
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will
+ default to the stored token.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+
+
+
+
+
+ `upload_file` assumes that the repo already exists on the Hub. If you get a
+ Client error 404, please make sure you are authenticated and that `repo_id` and
+ `repo_type` are set correctly. If repo does not exist, create it first using
+ [`~hf_api.create_repo`].
+
+
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import upload_file
+
+ >>> with open("./local/filepath", "rb") as fobj:
+ ... upload_file(
+ ... path_or_fileobj=fileobj,
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-dataset",
+ ... repo_type="dataset",
+ ... token="my_token",
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/blob/main/remote/file/path.h5"
+
+ >>> upload_file(
+ ... path_or_fileobj=".\\\\local\\\\file\\\\path",
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-model",
+ ... token="my_token",
+ ... )
+ "https://huggingface.co/username/my-model/blob/main/remote/file/path.h5"
+
+ >>> upload_file(
+ ... path_or_fileobj=".\\\\local\\\\file\\\\path",
+ ... path_in_repo="remote/file/path.h5",
+ ... repo_id="username/my-model",
+ ... token="my_token",
+ ... create_pr=True,
+ ... )
+ "https://huggingface.co/username/my-model/blob/refs%2Fpr%2F1/remote/file/path.h5"
+ ```
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+
+ commit_message = (
+ commit_message if commit_message is not None else f"Upload {path_in_repo} with huggingface_hub"
+ )
+ operation = CommitOperationAdd(
+ path_or_fileobj=path_or_fileobj,
+ path_in_repo=path_in_repo,
+ )
+
+ commit_info = self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ operations=[operation],
+ commit_message=commit_message,
+ commit_description=commit_description,
+ token=token,
+ revision=revision,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ if commit_info.pr_url is not None:
+ revision = quote(_parse_revision_from_pr_url(commit_info.pr_url), safe="")
+ if repo_type in REPO_TYPES_URL_PREFIXES:
+ repo_id = REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+ revision = revision if revision is not None else DEFAULT_REVISION
+
+ return CommitInfo(
+ commit_url=commit_info.commit_url,
+ commit_message=commit_info.commit_message,
+ commit_description=commit_info.commit_description,
+ oid=commit_info.oid,
+ pr_url=commit_info.pr_url,
+ # Similar to `hf_hub_url` but it's "blob" instead of "resolve"
+ # TODO: remove this in v1.0
+ _url=f"{self.endpoint}/{repo_id}/blob/{revision}/{path_in_repo}",
+ )
+
+ @overload
+ def upload_folder( # type: ignore
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ multi_commits: Literal[False] = ...,
+ multi_commits_verbose: bool = False,
+ run_as_future: Literal[False] = ...,
+ ) -> CommitInfo: ...
+
+ @overload
+ def upload_folder( # type: ignore
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ multi_commits: Literal[True] = ...,
+ multi_commits_verbose: bool = False,
+ run_as_future: Literal[False] = ...,
+ ) -> str: # Only the PR url in multi-commits mode
+ ...
+
+ @overload
+ def upload_folder( # type: ignore
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ multi_commits: Literal[False] = ...,
+ multi_commits_verbose: bool = False,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[CommitInfo]: ...
+
+ @overload
+ def upload_folder(
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ multi_commits: Literal[True] = ...,
+ multi_commits_verbose: bool = False,
+ run_as_future: Literal[True] = ...,
+ ) -> Future[str]: # Only the PR url in multi-commits mode
+ ...
+
+ @validate_hf_hub_args
+ @future_compatible
+ def upload_folder(
+ self,
+ *,
+ repo_id: str,
+ folder_path: Union[str, Path],
+ path_in_repo: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ multi_commits: bool = False,
+ multi_commits_verbose: bool = False,
+ run_as_future: bool = False,
+ ) -> Union[CommitInfo, str, Future[CommitInfo], Future[str]]:
+ """
+ Upload a local folder to the given repo. The upload is done through a HTTP requests, and doesn't require git or
+ git-lfs to be installed.
+
+ The structure of the folder will be preserved. Files with the same name already present in the repository will
+ be overwritten. Others will be left untouched.
+
+ Use the `allow_patterns` and `ignore_patterns` arguments to specify which files to upload. These parameters
+ accept either a single pattern or a list of patterns. Patterns are Standard Wildcards (globbing patterns) as
+ documented [here](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm). If both `allow_patterns` and
+ `ignore_patterns` are provided, both constraints apply. By default, all files from the folder are uploaded.
+
+ Use the `delete_patterns` argument to specify remote files you want to delete. Input type is the same as for
+ `allow_patterns` (see above). If `path_in_repo` is also provided, the patterns are matched against paths
+ relative to this folder. For example, `upload_folder(..., path_in_repo="experiment", delete_patterns="logs/*")`
+ will delete any remote file under `./experiment/logs/`. Note that the `.gitattributes` file will not be deleted
+ even if it matches the patterns.
+
+ Any `.git/` folder present in any subdirectory will be ignored. However, please be aware that the `.gitignore`
+ file is not taken into account.
+
+ Uses `HfApi.create_commit` under the hood.
+
+ Args:
+ repo_id (`str`):
+ The repository to which the file will be uploaded, for example:
+ `"username/custom_transformers"`
+ folder_path (`str` or `Path`):
+ Path to the folder to upload on the local file system
+ path_in_repo (`str`, *optional*):
+ Relative path of the directory in the repo, for example:
+ `"checkpoints/1fec34a/results"`. Will default to the root folder of the repository.
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will
+ default to the stored token.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to:
+ `f"Upload {path_in_repo} with huggingface_hub"`
+ commit_description (`str` *optional*):
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`. If `revision` is not
+ set, PR is opened against the `"main"` branch. If `revision` is set and is a branch, PR is opened
+ against this branch. If `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server. If both `multi_commits` and `create_pr` are True,
+ the PR created in the multi-commit process is kept opened.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are uploaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not uploaded.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo while committing
+ new files. This is useful if you don't know which files have already been uploaded.
+ Note: to avoid discrepancies the `.gitattributes` file is not deleted even if it matches the pattern.
+ multi_commits (`bool`):
+ If True, changes are pushed to a PR using a multi-commit process. Defaults to `False`.
+ multi_commits_verbose (`bool`):
+ If True and `multi_commits` is used, more information will be displayed to the user.
+ run_as_future (`bool`, *optional*):
+ Whether or not to run this method in the background. Background jobs are run sequentially without
+ blocking the main thread. Passing `run_as_future=True` will return a [Future](https://docs.python.org/3/library/concurrent.futures.html#future-objects)
+ object. Defaults to `False`.
+
+ Returns:
+ [`CommitInfo`] or `Future`:
+ Instance of [`CommitInfo`] containing information about the newly created commit (commit hash, commit
+ url, pr url, commit message,...). If `run_as_future=True` is passed, returns a Future object which will
+ contain the result when executed.
+ [`str`] or `Future`:
+ If `multi_commits=True`, returns the url of the PR created to push the changes. If `run_as_future=True`
+ is passed, returns a Future object which will contain the result when executed.
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+
+
+
+
+
+ `upload_folder` assumes that the repo already exists on the Hub. If you get a Client error 404, please make
+ sure you are authenticated and that `repo_id` and `repo_type` are set correctly. If repo does not exist, create
+ it first using [`~hf_api.create_repo`].
+
+
+
+
+
+ `multi_commits` is experimental. Its API and behavior is subject to change in the future without prior notice.
+
+
+
+ Example:
+
+ ```python
+ # Upload checkpoints folder except the log files
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... ignore_patterns="**/logs/*.txt",
+ ... )
+ # "https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints"
+
+ # Upload checkpoints folder including logs while deleting existing logs from the repo
+ # Useful if you don't know exactly which log files have already being pushed
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... delete_patterns="**/logs/*.txt",
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/tree/main/remote/experiment/checkpoints"
+
+ # Upload checkpoints folder while creating a PR
+ >>> upload_folder(
+ ... folder_path="local/checkpoints",
+ ... path_in_repo="remote/experiment/checkpoints",
+ ... repo_id="username/my-dataset",
+ ... repo_type="datasets",
+ ... token="my_token",
+ ... create_pr=True,
+ ... )
+ "https://huggingface.co/datasets/username/my-dataset/tree/refs%2Fpr%2F1/remote/experiment/checkpoints"
+
+ ```
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+
+ if multi_commits:
+ if revision is not None and revision != DEFAULT_REVISION:
+ raise ValueError("Cannot use `multi_commit` to commit changes other than the main branch.")
+
+ # By default, upload folder to the root directory in repo.
+ if path_in_repo is None:
+ path_in_repo = ""
+
+ # Do not upload .git folder
+ if ignore_patterns is None:
+ ignore_patterns = []
+ elif isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+ ignore_patterns += IGNORE_GIT_FOLDER_PATTERNS
+
+ delete_operations = self._prepare_upload_folder_deletions(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=DEFAULT_REVISION if create_pr else revision,
+ token=token,
+ path_in_repo=path_in_repo,
+ delete_patterns=delete_patterns,
+ )
+ add_operations = _prepare_upload_folder_additions(
+ folder_path,
+ path_in_repo,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ )
+
+ # Optimize operations: if some files will be overwritten, we don't need to delete them first
+ if len(add_operations) > 0:
+ added_paths = set(op.path_in_repo for op in add_operations)
+ delete_operations = [
+ delete_op for delete_op in delete_operations if delete_op.path_in_repo not in added_paths
+ ]
+ commit_operations = delete_operations + add_operations
+
+ commit_message = commit_message or "Upload folder using huggingface_hub"
+ if multi_commits:
+ addition_commits, deletion_commits = plan_multi_commits(operations=commit_operations)
+ pr_url = self.create_commits_on_pr(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ addition_commits=addition_commits,
+ deletion_commits=deletion_commits,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ token=token,
+ merge_pr=not create_pr,
+ verbose=multi_commits_verbose,
+ )
+ # Defining a CommitInfo object is not really relevant in this case
+ # Let's return early with pr_url only (as string).
+ return pr_url
+
+ commit_info = self.create_commit(
+ repo_type=repo_type,
+ repo_id=repo_id,
+ operations=commit_operations,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ token=token,
+ revision=revision,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ # Create url to uploaded folder (for legacy return value)
+ if create_pr and commit_info.pr_url is not None:
+ revision = quote(_parse_revision_from_pr_url(commit_info.pr_url), safe="")
+ if repo_type in REPO_TYPES_URL_PREFIXES:
+ repo_id = REPO_TYPES_URL_PREFIXES[repo_type] + repo_id
+ revision = revision if revision is not None else DEFAULT_REVISION
+
+ return CommitInfo(
+ commit_url=commit_info.commit_url,
+ commit_message=commit_info.commit_message,
+ commit_description=commit_info.commit_description,
+ oid=commit_info.oid,
+ pr_url=commit_info.pr_url,
+ # Similar to `hf_hub_url` but it's "tree" instead of "resolve"
+ # TODO: remove this in v1.0
+ _url=f"{self.endpoint}/{repo_id}/tree/{revision}/{path_in_repo}",
+ )
+
+ @validate_hf_hub_args
+ def delete_file(
+ self,
+ path_in_repo: str,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ) -> CommitInfo:
+ """
+ Deletes a file in the given repo.
+
+ Args:
+ path_in_repo (`str`):
+ Relative filepath in the repo, for example:
+ `"checkpoints/1fec34a/weights.bin"`
+ repo_id (`str`):
+ The repository from which the file will be deleted, for example:
+ `"username/custom_transformers"`
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will
+ default to the stored token.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or
+ space, `None` or `"model"` if in a model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Delete {path_in_repo} with huggingface_hub"`.
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ - [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+
+
+
+ """
+ commit_message = (
+ commit_message if commit_message is not None else f"Delete {path_in_repo} with huggingface_hub"
+ )
+
+ operations = [CommitOperationDelete(path_in_repo=path_in_repo)]
+
+ return self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ operations=operations,
+ revision=revision,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ @validate_hf_hub_args
+ def delete_folder(
+ self,
+ path_in_repo: str,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ) -> CommitInfo:
+ """
+ Deletes a folder in the given repo.
+
+ Simple wrapper around [`create_commit`] method.
+
+ Args:
+ path_in_repo (`str`):
+ Relative folder path in the repo, for example: `"checkpoints/1fec34a"`.
+ repo_id (`str`):
+ The repository from which the folder will be deleted, for example:
+ `"username/custom_transformers"`
+ token (`str`, *optional*):
+ Authentication token, obtained with `HfApi.login` method. Will default
+ to the stored token.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the folder is in a dataset or
+ space, `None` or `"model"` if in a model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Delete folder {path_in_repo} with huggingface_hub"`.
+ commit_description (`str` *optional*)
+ The description of the generated commit.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request with that commit. Defaults to `False`.
+ If `revision` is not set, PR is opened against the `"main"` branch. If
+ `revision` is set and is a branch, PR is opened against this branch. If
+ `revision` is set and is not a branch name (example: a commit oid), an
+ `RevisionNotFoundError` is returned by the server.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ """
+ return self.create_commit(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ token=token,
+ operations=[CommitOperationDelete(path_in_repo=path_in_repo, is_folder=True)],
+ revision=revision,
+ commit_message=(
+ commit_message if commit_message is not None else f"Delete folder {path_in_repo} with huggingface_hub"
+ ),
+ commit_description=commit_description,
+ create_pr=create_pr,
+ parent_commit=parent_commit,
+ )
+
+ @validate_hf_hub_args
+ def get_hf_file_metadata(
+ self,
+ *,
+ url: str,
+ token: Union[bool, str, None] = None,
+ proxies: Optional[Dict] = None,
+ timeout: Optional[float] = DEFAULT_REQUEST_TIMEOUT,
+ ) -> HfFileMetadata:
+ """Fetch metadata of a file versioned on the Hub for a given url.
+
+ Args:
+ url (`str`):
+ File url, for example returned by [`hf_hub_url`].
+ token (`str` or `bool`, *optional*):
+ A token to be used for the download.
+ - If `True`, the token is read from the HuggingFace config
+ folder.
+ - If `False` or `None`, no token is provided.
+ - If a string, it's used as the authentication token.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to `requests.request`.
+ timeout (`float`, *optional*, defaults to 10):
+ How many seconds to wait for the server to send metadata before giving up.
+
+ Returns:
+ A [`HfFileMetadata`] object containing metadata such as location, etag, size and commit_hash.
+ """
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return get_hf_file_metadata(
+ url=url,
+ token=token,
+ proxies=proxies,
+ timeout=timeout,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ user_agent=self.user_agent,
+ )
+
+ @validate_hf_hub_args
+ def hf_hub_download(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ subfolder: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ force_download: bool = False,
+ force_filename: Optional[str] = None,
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = DEFAULT_ETAG_TIMEOUT,
+ resume_download: bool = False,
+ token: Optional[Union[str, bool]] = None,
+ local_files_only: bool = False,
+ legacy_cache_layout: bool = False,
+ ) -> str:
+ """Download a given file if it's not already present in the local cache.
+
+ The new cache file layout looks like this:
+ - The cache directory contains one subfolder per repo_id (namespaced by repo type)
+ - inside each repo folder:
+ - refs is a list of the latest known revision => commit_hash pairs
+ - blobs contains the actual file blobs (identified by their git-sha or sha256, depending on
+ whether they're LFS files or not)
+ - snapshots contains one subfolder per commit, each "commit" contains the subset of the files
+ that have been resolved at that particular commit. Each filename is a symlink to the blob
+ at that particular commit.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. You can configure
+ how you want to move those files:
+ - If `local_dir_use_symlinks="auto"` (default), files are downloaded and stored in the cache directory as blob
+ files. Small files (<5MB) are duplicated in `local_dir` while a symlink is created for bigger files. The goal
+ is to be able to manually edit and save small files without corrupting the cache while saving disk space for
+ binary files. The 5MB threshold can be configured with the `HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD`
+ environment variable.
+ - If `local_dir_use_symlinks=True`, files are downloaded, stored in the cache directory and symlinked in `local_dir`.
+ This is optimal in term of disk usage but files must not be manually edited.
+ - If `local_dir_use_symlinks=False` and the blob files exist in the cache directory, they are duplicated in the
+ local dir. This means disk usage is not optimized.
+ - Finally, if `local_dir_use_symlinks=False` and the blob files do not exist in the cache directory, then the
+ files are downloaded and directly placed under `local_dir`. This means if you need to download them again later,
+ they will be re-downloaded entirely.
+
+ ```
+ [ 96] .
+ └── [ 160] models--julien-c--EsperBERTo-small
+ ├── [ 160] blobs
+ │ ├── [321M] 403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ │ ├── [ 398] 7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ │ └── [1.4K] d7edf6bd2a681fb0175f7735299831ee1b22b812
+ ├── [ 96] refs
+ │ └── [ 40] main
+ └── [ 128] snapshots
+ ├── [ 128] 2439f60ef33a0d46d85da5001d52aeda5b00ce9f
+ │ ├── [ 52] README.md -> ../../blobs/d7edf6bd2a681fb0175f7735299831ee1b22b812
+ │ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ └── [ 128] bbc77c8132af1cc5cf678da3f1ddf2de43606d48
+ ├── [ 52] README.md -> ../../blobs/7cb18dc9bafbfcf74629a4b760af1b160957a83e
+ └── [ 76] pytorch_model.bin -> ../../blobs/403450e234d65943a7dcf7e05a771ce3c92faa84dd07db4ac20f592037a1e4bd
+ ```
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ subfolder (`str`, *optional*):
+ An optional value corresponding to a folder inside the model repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded file will be placed under this directory, either as a symlink (default) or
+ a regular file (see description for more details).
+ local_dir_use_symlinks (`"auto"` or `bool`, defaults to `"auto"`):
+ To be used with `local_dir`. If set to "auto", the cache directory will be used and the file will be either
+ duplicated or symlinked to the local directory depending on its size. It set to `True`, a symlink will be
+ created, no matter the file size. If set to `False`, the file will either be duplicated from cache (if
+ already exists) or downloaded from the Hub and not cached. See description for more details.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in
+ the local cache.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ If `True`, resume a previously interrupted download.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ legacy_cache_layout (`bool`, *optional*, defaults to `False`):
+ If `True`, uses the legacy file cache layout i.e. just call [`hf_hub_url`]
+ then `cached_download`. This is deprecated as the new cache layout is
+ more powerful.
+
+ Returns:
+ Local path (string) of file or if networking is off, last version of
+ file cached on disk.
+
+
+
+ Raises the following errors:
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `token=True` and the token cannot be found.
+ - [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)
+ if ETag cannot be determined.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+ - [`~utils.RevisionNotFoundError`]
+ If the revision to download from cannot be found.
+ - [`~utils.EntryNotFoundError`]
+ If the file to download cannot be found.
+ - [`~utils.LocalEntryNotFoundError`]
+ If network is disabled or unavailable and file is not found in cache.
+
+
+ """
+ from .file_download import hf_hub_download
+
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return hf_hub_download(
+ repo_id=repo_id,
+ filename=filename,
+ subfolder=subfolder,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=self.endpoint,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ user_agent=self.user_agent,
+ force_download=force_download,
+ force_filename=force_filename,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ token=token,
+ headers=self.headers,
+ local_files_only=local_files_only,
+ legacy_cache_layout=legacy_cache_layout,
+ )
+
+ @validate_hf_hub_args
+ def snapshot_download(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ cache_dir: Union[str, Path, None] = None,
+ local_dir: Union[str, Path, None] = None,
+ local_dir_use_symlinks: Union[bool, Literal["auto"]] = "auto",
+ proxies: Optional[Dict] = None,
+ etag_timeout: float = DEFAULT_ETAG_TIMEOUT,
+ resume_download: bool = False,
+ force_download: bool = False,
+ token: Optional[Union[str, bool]] = None,
+ local_files_only: bool = False,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ max_workers: int = 8,
+ tqdm_class: Optional[base_tqdm] = None,
+ ) -> str:
+ """Download repo files.
+
+ Download a whole snapshot of a repo's files at the specified revision. This is useful when you want all files from
+ a repo, because you don't know which ones you will need a priori. All files are nested inside a folder in order
+ to keep their actual filename relative to that folder. You can also filter which files to download using
+ `allow_patterns` and `ignore_patterns`.
+
+ If `local_dir` is provided, the file structure from the repo will be replicated in this location. You can configure
+ how you want to move those files:
+ - If `local_dir_use_symlinks="auto"` (default), files are downloaded and stored in the cache directory as blob
+ files. Small files (<5MB) are duplicated in `local_dir` while a symlink is created for bigger files. The goal
+ is to be able to manually edit and save small files without corrupting the cache while saving disk space for
+ binary files. The 5MB threshold can be configured with the `HF_HUB_LOCAL_DIR_AUTO_SYMLINK_THRESHOLD`
+ environment variable.
+ - If `local_dir_use_symlinks=True`, files are downloaded, stored in the cache directory and symlinked in `local_dir`.
+ This is optimal in term of disk usage but files must not be manually edited.
+ - If `local_dir_use_symlinks=False` and the blob files exist in the cache directory, they are duplicated in the
+ local dir. This means disk usage is not optimized.
+ - Finally, if `local_dir_use_symlinks=False` and the blob files do not exist in the cache directory, then the
+ files are downloaded and directly placed under `local_dir`. This means if you need to download them again later,
+ they will be re-downloaded entirely.
+
+ An alternative would be to clone the repo but this requires git and git-lfs to be installed and properly
+ configured. It is also not possible to filter which files to download when cloning a repository using git.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if downloading from a dataset or space,
+ `None` or `"model"` if downloading from a model. Default is `None`.
+ revision (`str`, *optional*):
+ An optional Git revision id which can be a branch name, a tag, or a
+ commit hash.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_dir (`str` or `Path`, *optional*):
+ If provided, the downloaded files will be placed under this directory, either as symlinks (default) or
+ regular files (see description for more details).
+ local_dir_use_symlinks (`"auto"` or `bool`, defaults to `"auto"`):
+ To be used with `local_dir`. If set to "auto", the cache directory will be used and the file will be either
+ duplicated or symlinked to the local directory depending on its size. It set to `True`, a symlink will be
+ created, no matter the file size. If set to `False`, the file will either be duplicated from cache (if
+ already exists) or downloaded from the Hub and not cached. See description for more details.
+ proxies (`dict`, *optional*):
+ Dictionary mapping protocol to the URL of the proxy passed to
+ `requests.request`.
+ etag_timeout (`float`, *optional*, defaults to `10`):
+ When fetching ETag, how many seconds to wait for the server to send
+ data before giving up which is passed to `requests.request`.
+ resume_download (`bool`, *optional*, defaults to `False):
+ If `True`, resume a previously interrupted download.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether the file should be downloaded even if it already exists in the local cache.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the
+ local cached file if it exists.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are downloaded.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not downloaded.
+ max_workers (`int`, *optional*):
+ Number of concurrent threads to download files (1 thread = 1 file download).
+ Defaults to 8.
+ tqdm_class (`tqdm`, *optional*):
+ If provided, overwrites the default behavior for the progress bar. Passed
+ argument must inherit from `tqdm.auto.tqdm` or at least mimic its behavior.
+ Note that the `tqdm_class` is not passed to each individual download.
+ Defaults to the custom HF progress bar that can be disabled by setting
+ `HF_HUB_DISABLE_PROGRESS_BARS` environment variable.
+
+ Returns:
+ Local folder path (string) of repo snapshot
+
+
+
+ Raises the following errors:
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `token=True` and the token cannot be found.
+ - [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError) if
+ ETag cannot be determined.
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+
+
+ """
+ from ._snapshot_download import snapshot_download
+
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+
+ return snapshot_download(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ revision=revision,
+ endpoint=self.endpoint,
+ cache_dir=cache_dir,
+ local_dir=local_dir,
+ local_dir_use_symlinks=local_dir_use_symlinks,
+ library_name=self.library_name,
+ library_version=self.library_version,
+ user_agent=self.user_agent,
+ proxies=proxies,
+ etag_timeout=etag_timeout,
+ resume_download=resume_download,
+ force_download=force_download,
+ token=token,
+ local_files_only=local_files_only,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ max_workers=max_workers,
+ tqdm_class=tqdm_class,
+ )
+
+ def get_safetensors_metadata(
+ self,
+ repo_id: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> SafetensorsRepoMetadata:
+ """
+ Parse metadata for a safetensors repo on the Hub.
+
+ We first check if the repo has a single safetensors file or a sharded safetensors repo. If it's a single
+ safetensors file, we parse the metadata from this file. If it's a sharded safetensors repo, we parse the
+ metadata from the index file and then parse the metadata from each shard.
+
+ To parse metadata from a single safetensors file, use [`parse_safetensors_file_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a
+ model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the
+ head of the `"main"` branch.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ [`SafetensorsRepoMetadata`]: information related to safetensors repo.
+
+ Raises:
+ - [`NotASafetensorsRepoError`]: if the repo is not a safetensors repo i.e. doesn't have either a
+ `model.safetensors` or a `model.safetensors.index.json` file.
+ - [`SafetensorsParsingError`]: if a safetensors file header couldn't be parsed correctly.
+
+ Example:
+ ```py
+ # Parse repo with single weights file
+ >>> metadata = get_safetensors_metadata("bigscience/bloomz-560m")
+ >>> metadata
+ SafetensorsRepoMetadata(
+ metadata=None,
+ sharded=False,
+ weight_map={'h.0.input_layernorm.bias': 'model.safetensors', ...},
+ files_metadata={'model.safetensors': SafetensorsFileMetadata(...)}
+ )
+ >>> metadata.files_metadata["model.safetensors"].metadata
+ {'format': 'pt'}
+
+ # Parse repo with sharded model
+ >>> metadata = get_safetensors_metadata("bigscience/bloom")
+ Parse safetensors files: 100%|██████████████████████████████████████████| 72/72 [00:12<00:00, 5.78it/s]
+ >>> metadata
+ SafetensorsRepoMetadata(metadata={'total_size': 352494542848}, sharded=True, weight_map={...}, files_metadata={...})
+ >>> len(metadata.files_metadata)
+ 72 # All safetensors files have been fetched
+
+ # Parse repo with sharded model
+ >>> get_safetensors_metadata("runwayml/stable-diffusion-v1-5")
+ NotASafetensorsRepoError: 'runwayml/stable-diffusion-v1-5' is not a safetensors repo. Couldn't find 'model.safetensors.index.json' or 'model.safetensors' files.
+ ```
+ """
+ if self.file_exists( # Single safetensors file => non-sharded model
+ repo_id=repo_id, filename=SAFETENSORS_SINGLE_FILE, repo_type=repo_type, revision=revision, token=token
+ ):
+ file_metadata = self.parse_safetensors_file_metadata(
+ repo_id=repo_id, filename=SAFETENSORS_SINGLE_FILE, repo_type=repo_type, revision=revision, token=token
+ )
+ return SafetensorsRepoMetadata(
+ metadata=None,
+ sharded=False,
+ weight_map={tensor_name: SAFETENSORS_SINGLE_FILE for tensor_name in file_metadata.tensors.keys()},
+ files_metadata={SAFETENSORS_SINGLE_FILE: file_metadata},
+ )
+ elif self.file_exists( # Multiple safetensors files => sharded with index
+ repo_id=repo_id, filename=SAFETENSORS_INDEX_FILE, repo_type=repo_type, revision=revision, token=token
+ ):
+ # Fetch index
+ index_file = self.hf_hub_download(
+ repo_id=repo_id, filename=SAFETENSORS_INDEX_FILE, repo_type=repo_type, revision=revision, token=token
+ )
+ with open(index_file) as f:
+ index = json.load(f)
+
+ weight_map = index.get("weight_map", {})
+
+ # Fetch metadata per shard
+ files_metadata = {}
+
+ def _parse(filename: str) -> None:
+ files_metadata[filename] = self.parse_safetensors_file_metadata(
+ repo_id=repo_id, filename=filename, repo_type=repo_type, revision=revision, token=token
+ )
+
+ thread_map(
+ _parse,
+ set(weight_map.values()),
+ desc="Parse safetensors files",
+ tqdm_class=hf_tqdm,
+ )
+
+ return SafetensorsRepoMetadata(
+ metadata=index.get("metadata", None),
+ sharded=True,
+ weight_map=weight_map,
+ files_metadata=files_metadata,
+ )
+ else:
+ # Not a safetensors repo
+ raise NotASafetensorsRepoError(
+ f"'{repo_id}' is not a safetensors repo. Couldn't find '{SAFETENSORS_INDEX_FILE}' or '{SAFETENSORS_SINGLE_FILE}' files."
+ )
+
+ def parse_safetensors_file_metadata(
+ self,
+ repo_id: str,
+ filename: str,
+ *,
+ repo_type: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> SafetensorsFileMetadata:
+ """
+ Parse metadata from a safetensors file on the Hub.
+
+ To parse metadata from all safetensors files in a repo at once, use [`get_safetensors_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Args:
+ repo_id (`str`):
+ A user or an organization name and a repo name separated by a `/`.
+ filename (`str`):
+ The name of the file in the repo.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if the file is in a dataset or space, `None` or `"model"` if in a
+ model. Default is `None`.
+ revision (`str`, *optional*):
+ The git revision to fetch the file from. Can be a branch name, a tag, or a commit hash. Defaults to the
+ head of the `"main"` branch.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token). If `None` or `True` and
+ machine is logged in (through `huggingface-cli login` or [`~huggingface_hub.login`]), token will be
+ retrieved from the cache. If `False`, token is not sent in the request header.
+
+ Returns:
+ [`SafetensorsFileMetadata`]: information related to a safetensors file.
+
+ Raises:
+ - [`NotASafetensorsRepoError`]: if the repo is not a safetensors repo i.e. doesn't have either a
+ `model.safetensors` or a `model.safetensors.index.json` file.
+ - [`SafetensorsParsingError`]: if a safetensors file header couldn't be parsed correctly.
+ """
+ url = hf_hub_url(
+ repo_id=repo_id, filename=filename, repo_type=repo_type, revision=revision, endpoint=self.endpoint
+ )
+ _headers = self._build_hf_headers(token=token)
+
+ # 1. Fetch first 100kb
+ # Empirically, 97% of safetensors files have a metadata size < 100kb (over the top 1000 models on the Hub).
+ # We assume fetching 100kb is faster than making 2 GET requests. Therefore we always fetch the first 100kb to
+ # avoid the 2nd GET in most cases.
+ # See https://github.com/huggingface/huggingface_hub/pull/1855#discussion_r1404286419.
+ response = get_session().get(url, headers={**_headers, "range": "bytes=0-100000"})
+ hf_raise_for_status(response)
+
+ # 2. Parse metadata size
+ metadata_size = struct.unpack(" SAFETENSORS_MAX_HEADER_LENGTH:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or DEFAULT_REVISION}'): safetensors header is too big. Maximum supported size is "
+ f"{SAFETENSORS_MAX_HEADER_LENGTH} bytes (got {metadata_size})."
+ )
+
+ # 3.a. Get metadata from payload
+ if metadata_size <= 100000:
+ metadata_as_bytes = response.content[8 : 8 + metadata_size]
+ else: # 3.b. Request full metadata
+ response = get_session().get(url, headers={**_headers, "range": f"bytes=8-{metadata_size+7}"})
+ hf_raise_for_status(response)
+ metadata_as_bytes = response.content
+
+ # 4. Parse json header
+ try:
+ metadata_as_dict = json.loads(metadata_as_bytes.decode(errors="ignore"))
+ except json.JSONDecodeError as e:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or DEFAULT_REVISION}'): header is not json-encoded string. Please make sure this is a "
+ "correctly formatted safetensors file."
+ ) from e
+
+ try:
+ return SafetensorsFileMetadata(
+ metadata=metadata_as_dict.get("__metadata__", {}),
+ tensors={
+ key: TensorInfo(
+ dtype=tensor["dtype"],
+ shape=tensor["shape"],
+ data_offsets=tuple(tensor["data_offsets"]), # type: ignore
+ )
+ for key, tensor in metadata_as_dict.items()
+ if key != "__metadata__"
+ },
+ )
+ except (KeyError, IndexError) as e:
+ raise SafetensorsParsingError(
+ f"Failed to parse safetensors header for '{filename}' (repo '{repo_id}', revision "
+ f"'{revision or DEFAULT_REVISION}'): header format not recognized. Please make sure this is a correctly"
+ " formatted safetensors file."
+ ) from e
+
+ @validate_hf_hub_args
+ def create_branch(
+ self,
+ repo_id: str,
+ *,
+ branch: str,
+ revision: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ ) -> None:
+ """
+ Create a new branch for a repo on the Hub, starting from the specified revision (defaults to `main`).
+ To find a revision suiting your needs, you can use [`list_repo_refs`] or [`list_repo_commits`].
+
+ Args:
+ repo_id (`str`):
+ The repository in which the branch will be created.
+ Example: `"user/my-cool-model"`.
+
+ branch (`str`):
+ The name of the branch to create.
+
+ revision (`str`, *optional*):
+ The git revision to create the branch from. It can be a branch name or
+ the OID/SHA of a commit, as a hexadecimal string. Defaults to the head
+ of the `"main"` branch.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if creating a branch on a dataset or
+ space, `None` or `"model"` if tagging a model. Default is `None`.
+
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if branch already exists.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.BadRequestError`]:
+ If invalid reference for a branch. Ex: `refs/pr/5` or 'refs/foo/bar'.
+ [`~utils.HfHubHTTPError`]:
+ If the branch already exists on the repo (error 409) and `exist_ok` is
+ set to `False`.
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ branch = quote(branch, safe="")
+
+ # Prepare request
+ branch_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/branch/{branch}"
+ headers = self._build_hf_headers(token=token)
+ payload = {}
+ if revision is not None:
+ payload["startingPoint"] = revision
+
+ # Create branch
+ response = get_session().post(url=branch_url, headers=headers, json=payload)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ if not (e.response.status_code == 409 and exist_ok):
+ raise
+
+ @validate_hf_hub_args
+ def delete_branch(
+ self,
+ repo_id: str,
+ *,
+ branch: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Delete a branch from a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a branch will be deleted.
+ Example: `"user/my-cool-model"`.
+
+ branch (`str`):
+ The name of the branch to delete.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if creating a branch on a dataset or
+ space, `None` or `"model"` if tagging a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.HfHubHTTPError`]:
+ If trying to delete a protected branch. Ex: `main` cannot be deleted.
+ [`~utils.HfHubHTTPError`]:
+ If trying to delete a branch that does not exist.
+
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ branch = quote(branch, safe="")
+
+ # Prepare request
+ branch_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/branch/{branch}"
+ headers = self._build_hf_headers(token=token)
+
+ # Delete branch
+ response = get_session().delete(url=branch_url, headers=headers)
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def create_tag(
+ self,
+ repo_id: str,
+ *,
+ tag: str,
+ tag_message: Optional[str] = None,
+ revision: Optional[str] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ exist_ok: bool = False,
+ ) -> None:
+ """
+ Tag a given commit of a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a commit will be tagged.
+ Example: `"user/my-cool-model"`.
+
+ tag (`str`):
+ The name of the tag to create.
+
+ tag_message (`str`, *optional*):
+ The description of the tag to create.
+
+ revision (`str`, *optional*):
+ The git revision to tag. It can be a branch name or the OID/SHA of a
+ commit, as a hexadecimal string. Shorthands (7 first characters) are
+ also supported. Defaults to the head of the `"main"` branch.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if tagging a dataset or
+ space, `None` or `"model"` if tagging a model. Default is
+ `None`.
+
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if tag already exists.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If revision is not found (error 404) on the repo.
+ [`~utils.HfHubHTTPError`]:
+ If the branch already exists on the repo (error 409) and `exist_ok` is
+ set to `False`.
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ revision = quote(revision, safe="") if revision is not None else DEFAULT_REVISION
+
+ # Prepare request
+ tag_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tag/{revision}"
+ headers = self._build_hf_headers(token=token)
+ payload = {"tag": tag}
+ if tag_message is not None:
+ payload["message"] = tag_message
+
+ # Tag
+ response = get_session().post(url=tag_url, headers=headers, json=payload)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ if not (e.response.status_code == 409 and exist_ok):
+ raise
+
+ @validate_hf_hub_args
+ def delete_tag(
+ self,
+ repo_id: str,
+ *,
+ tag: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> None:
+ """
+ Delete a tag from a repo on the Hub.
+
+ Args:
+ repo_id (`str`):
+ The repository in which a tag will be deleted.
+ Example: `"user/my-cool-model"`.
+
+ tag (`str`):
+ The name of the tag to delete.
+
+ token (`str`, *optional*):
+ Authentication token. Will default to the stored token.
+
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if tagging a dataset or space, `None` or
+ `"model"` if tagging a model. Default is `None`.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If repository is not found (error 404): wrong repo_id/repo_type, private
+ but not authenticated or repo does not exist.
+ [`~utils.RevisionNotFoundError`]:
+ If tag is not found.
+ """
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ tag = quote(tag, safe="")
+
+ # Prepare request
+ tag_url = f"{self.endpoint}/api/{repo_type}s/{repo_id}/tag/{tag}"
+ headers = self._build_hf_headers(token=token)
+
+ # Un-tag
+ response = get_session().delete(url=tag_url, headers=headers)
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def get_full_repo_name(
+ self,
+ model_id: str,
+ *,
+ organization: Optional[str] = None,
+ token: Optional[Union[bool, str]] = None,
+ ):
+ """
+ Returns the repository name for a given model ID and optional
+ organization.
+
+ Args:
+ model_id (`str`):
+ The name of the model.
+ organization (`str`, *optional*):
+ If passed, the repository name will be in the organization
+ namespace instead of the user namespace.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+
+ Returns:
+ `str`: The repository name in the user's namespace
+ ({username}/{model_id}) if no organization is passed, and under the
+ organization namespace ({organization}/{model_id}) otherwise.
+ """
+ if organization is None:
+ if "/" in model_id:
+ username = model_id.split("/")[0]
+ else:
+ username = self.whoami(token=token)["name"] # type: ignore
+ return f"{username}/{model_id}"
+ else:
+ return f"{organization}/{model_id}"
+
+ @validate_hf_hub_args
+ def get_repo_discussions(
+ self,
+ repo_id: str,
+ *,
+ author: Optional[str] = None,
+ discussion_type: Optional[DiscussionTypeFilter] = None,
+ discussion_status: Optional[DiscussionStatusFilter] = None,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> Iterator[Discussion]:
+ """
+ Fetches Discussions and Pull Requests for the given repo.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ author (`str`, *optional*):
+ Pass a value to filter by discussion author. `None` means no filter.
+ Default is `None`.
+ discussion_type (`str`, *optional*):
+ Set to `"pull_request"` to fetch only pull requests, `"discussion"`
+ to fetch only discussions. Set to `"all"` or `None` to fetch both.
+ Default is `None`.
+ discussion_status (`str`, *optional*):
+ Set to `"open"` (respectively `"closed"`) to fetch only open
+ (respectively closed) discussions. Set to `"all"` or `None`
+ to fetch both.
+ Default is `None`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if fetching from a dataset or
+ space, `None` or `"model"` if fetching from a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ `Iterator[Discussion]`: An iterator of [`Discussion`] objects.
+
+ Example:
+ Collecting all discussions of a repo in a list:
+
+ ```python
+ >>> from huggingface_hub import get_repo_discussions
+ >>> discussions_list = list(get_repo_discussions(repo_id="bert-base-uncased"))
+ ```
+
+ Iterating over discussions of a repo:
+
+ ```python
+ >>> from huggingface_hub import get_repo_discussions
+ >>> for discussion in get_repo_discussions(repo_id="bert-base-uncased"):
+ ... print(discussion.num, discussion.title)
+ ```
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ if discussion_type is not None and discussion_type not in DISCUSSION_TYPES:
+ raise ValueError(f"Invalid discussion_type, must be one of {DISCUSSION_TYPES}")
+ if discussion_status is not None and discussion_status not in DISCUSSION_STATUS:
+ raise ValueError(f"Invalid discussion_status, must be one of {DISCUSSION_STATUS}")
+
+ headers = self._build_hf_headers(token=token)
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions"
+
+ params: Dict[str, Union[str, int]] = {}
+ if discussion_type is not None:
+ params["type"] = discussion_type
+ if discussion_status is not None:
+ params["status"] = discussion_status
+ if author is not None:
+ params["author"] = author
+
+ def _fetch_discussion_page(page_index: int):
+ params["p"] = page_index
+ resp = get_session().get(path, headers=headers, params=params)
+ hf_raise_for_status(resp)
+ paginated_discussions = resp.json()
+ total = paginated_discussions["count"]
+ start = paginated_discussions["start"]
+ discussions = paginated_discussions["discussions"]
+ has_next = (start + len(discussions)) < total
+ return discussions, has_next
+
+ has_next, page_index = True, 0
+
+ while has_next:
+ discussions, has_next = _fetch_discussion_page(page_index=page_index)
+ for discussion in discussions:
+ yield Discussion(
+ title=discussion["title"],
+ num=discussion["num"],
+ author=discussion.get("author", {}).get("name", "deleted"),
+ created_at=parse_datetime(discussion["createdAt"]),
+ status=discussion["status"],
+ repo_id=discussion["repo"]["name"],
+ repo_type=discussion["repo"]["type"],
+ is_pull_request=discussion["isPullRequest"],
+ endpoint=self.endpoint,
+ )
+ page_index = page_index + 1
+
+ @validate_hf_hub_args
+ def get_discussion_details(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ *,
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> DiscussionWithDetails:
+ """Fetches a Discussion's / Pull Request 's details from the Hub.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if not isinstance(discussion_num, int) or discussion_num <= 0:
+ raise ValueError("Invalid discussion_num, must be a positive integer")
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+
+ path = f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions/{discussion_num}"
+ headers = self._build_hf_headers(token=token)
+ resp = get_session().get(path, params={"diff": "1"}, headers=headers)
+ hf_raise_for_status(resp)
+
+ discussion_details = resp.json()
+ is_pull_request = discussion_details["isPullRequest"]
+
+ target_branch = discussion_details["changes"]["base"] if is_pull_request else None
+ conflicting_files = discussion_details["filesWithConflicts"] if is_pull_request else None
+ merge_commit_oid = discussion_details["changes"].get("mergeCommitId", None) if is_pull_request else None
+
+ return DiscussionWithDetails(
+ title=discussion_details["title"],
+ num=discussion_details["num"],
+ author=discussion_details.get("author", {}).get("name", "deleted"),
+ created_at=parse_datetime(discussion_details["createdAt"]),
+ status=discussion_details["status"],
+ repo_id=discussion_details["repo"]["name"],
+ repo_type=discussion_details["repo"]["type"],
+ is_pull_request=discussion_details["isPullRequest"],
+ events=[deserialize_event(evt) for evt in discussion_details["events"]],
+ conflicting_files=conflicting_files,
+ target_branch=target_branch,
+ merge_commit_oid=merge_commit_oid,
+ diff=discussion_details.get("diff"),
+ endpoint=self.endpoint,
+ )
+
+ @validate_hf_hub_args
+ def create_discussion(
+ self,
+ repo_id: str,
+ title: str,
+ *,
+ token: Optional[str] = None,
+ description: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ pull_request: bool = False,
+ ) -> DiscussionWithDetails:
+ """Creates a Discussion or Pull Request.
+
+ Pull Requests created programmatically will be in `"draft"` status.
+
+ Creating a Pull Request with changes can also be done at once with [`HfApi.create_commit`].
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ title (`str`):
+ The title of the discussion. It can be up to 200 characters long,
+ and must be at least 3 characters long. Leading and trailing whitespaces
+ will be stripped.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+ description (`str`, *optional*):
+ An optional description for the Pull Request.
+ Defaults to `"Discussion opened with the huggingface_hub Python library"`
+ pull_request (`bool`, *optional*):
+ Whether to create a Pull Request or discussion. If `True`, creates a Pull Request.
+ If `False`, creates a discussion. Defaults to `False`.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+
+ if description is not None:
+ description = description.strip()
+ description = (
+ description
+ if description
+ else (
+ f"{'Pull Request' if pull_request else 'Discussion'} opened with the"
+ " [huggingface_hub Python"
+ " library](https://huggingface.co/docs/huggingface_hub)"
+ )
+ )
+
+ headers = self._build_hf_headers(token=token)
+ resp = get_session().post(
+ f"{self.endpoint}/api/{repo_type}s/{repo_id}/discussions",
+ json={
+ "title": title.strip(),
+ "description": description,
+ "pullRequest": pull_request,
+ },
+ headers=headers,
+ )
+ hf_raise_for_status(resp)
+ num = resp.json()["num"]
+ return self.get_discussion_details(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=num,
+ token=token,
+ )
+
+ @validate_hf_hub_args
+ def create_pull_request(
+ self,
+ repo_id: str,
+ title: str,
+ *,
+ token: Optional[str] = None,
+ description: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionWithDetails:
+ """Creates a Pull Request . Pull Requests created programmatically will be in `"draft"` status.
+
+ Creating a Pull Request with changes can also be done at once with [`HfApi.create_commit`];
+
+ This is a wrapper around [`HfApi.create_discussion`].
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ title (`str`):
+ The title of the discussion. It can be up to 200 characters long,
+ and must be at least 3 characters long. Leading and trailing whitespaces
+ will be stripped.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+ description (`str`, *optional*):
+ An optional description for the Pull Request.
+ Defaults to `"Discussion opened with the huggingface_hub Python library"`
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+
+ Returns: [`DiscussionWithDetails`]
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+ """
+ return self.create_discussion(
+ repo_id=repo_id,
+ title=title,
+ token=token,
+ description=description,
+ repo_type=repo_type,
+ pull_request=True,
+ )
+
+ def _post_discussion_changes(
+ self,
+ *,
+ repo_id: str,
+ discussion_num: int,
+ resource: str,
+ body: Optional[dict] = None,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> requests.Response:
+ """Internal utility to POST changes to a Discussion or Pull Request"""
+ if not isinstance(discussion_num, int) or discussion_num <= 0:
+ raise ValueError("Invalid discussion_num, must be a positive integer")
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+ repo_id = f"{repo_type}s/{repo_id}"
+
+ path = f"{self.endpoint}/api/{repo_id}/discussions/{discussion_num}/{resource}"
+
+ headers = self._build_hf_headers(token=token)
+ resp = requests.post(path, headers=headers, json=body)
+ hf_raise_for_status(resp)
+ return resp
+
+ @validate_hf_hub_args
+ def comment_discussion(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Creates a new comment on the given Discussion.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment (`str`):
+ The content of the comment to create. Comments support markdown formatting.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionComment`]: the newly created comment
+
+
+ Examples:
+ ```python
+
+ >>> comment = \"\"\"
+ ... Hello @otheruser!
+ ...
+ ... # This is a title
+ ...
+ ... **This is bold**, *this is italic* and ~this is strikethrough~
+ ... And [this](http://url) is a link
+ ... \"\"\"
+
+ >>> HfApi().comment_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... comment=comment
+ ... )
+ # DiscussionComment(id='deadbeef0000000', type='comment', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="comment",
+ body={"comment": comment},
+ )
+ return deserialize_event(resp.json()["newMessage"]) # type: ignore
+
+ @validate_hf_hub_args
+ def rename_discussion(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ new_title: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionTitleChange:
+ """Renames a Discussion.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ new_title (`str`):
+ The new title for the discussion
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionTitleChange`]: the title change event
+
+
+ Examples:
+ ```python
+ >>> new_title = "New title, fixing a typo"
+ >>> HfApi().rename_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... new_title=new_title
+ ... )
+ # DiscussionTitleChange(id='deadbeef0000000', type='title-change', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="title",
+ body={"title": new_title},
+ )
+ return deserialize_event(resp.json()["newTitle"]) # type: ignore
+
+ @validate_hf_hub_args
+ def change_discussion_status(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ new_status: Literal["open", "closed"],
+ *,
+ token: Optional[str] = None,
+ comment: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionStatusChange:
+ """Closes or re-opens a Discussion or Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ new_status (`str`):
+ The new status for the discussion, either `"open"` or `"closed"`.
+ comment (`str`, *optional*):
+ An optional comment to post with the status change.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionStatusChange`]: the status change event
+
+
+ Examples:
+ ```python
+ >>> new_title = "New title, fixing a typo"
+ >>> HfApi().rename_discussion(
+ ... repo_id="username/repo_name",
+ ... discussion_num=34
+ ... new_title=new_title
+ ... )
+ # DiscussionStatusChange(id='deadbeef0000000', type='status-change', ...)
+
+ ```
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ if new_status not in ["open", "closed"]:
+ raise ValueError("Invalid status, valid statuses are: 'open' and 'closed'")
+ body: Dict[str, str] = {"status": new_status}
+ if comment and comment.strip():
+ body["comment"] = comment.strip()
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="status",
+ body=body,
+ )
+ return deserialize_event(resp.json()["newStatus"]) # type: ignore
+
+ @validate_hf_hub_args
+ def merge_pull_request(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ *,
+ token: Optional[str] = None,
+ comment: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ):
+ """Merges a Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment (`str`, *optional*):
+ An optional comment to post with the status change.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionStatusChange`]: the status change event
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource="merge",
+ body={"comment": comment.strip()} if comment and comment.strip() else None,
+ )
+
+ @validate_hf_hub_args
+ def edit_discussion_comment(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment_id: str,
+ new_content: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Edits a comment on a Discussion / Pull Request.
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment_id (`str`):
+ The ID of the comment to edit.
+ new_content (`str`):
+ The new content of the comment. Comments support markdown formatting.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionComment`]: the edited comment
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource=f"comment/{comment_id.lower()}/edit",
+ body={"content": new_content},
+ )
+ return deserialize_event(resp.json()["updatedComment"]) # type: ignore
+
+ @validate_hf_hub_args
+ def hide_discussion_comment(
+ self,
+ repo_id: str,
+ discussion_num: int,
+ comment_id: str,
+ *,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ ) -> DiscussionComment:
+ """Hides a comment on a Discussion / Pull Request.
+
+
+ Hidden comments' content cannot be retrieved anymore. Hiding a comment is irreversible.
+
+
+ Args:
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ discussion_num (`int`):
+ The number of the Discussion or Pull Request . Must be a strictly positive integer.
+ comment_id (`str`):
+ The ID of the comment to edit.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if uploading to a dataset or
+ space, `None` or `"model"` if uploading to a model. Default is
+ `None`.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token)
+
+ Returns:
+ [`DiscussionComment`]: the hidden comment
+
+
+
+ Raises the following errors:
+
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if some parameter value is invalid
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+
+ """
+ warnings.warn(
+ "Hidden comments' content cannot be retrieved anymore. Hiding a comment is irreversible.",
+ UserWarning,
+ )
+ resp = self._post_discussion_changes(
+ repo_id=repo_id,
+ repo_type=repo_type,
+ discussion_num=discussion_num,
+ token=token,
+ resource=f"comment/{comment_id.lower()}/hide",
+ )
+ return deserialize_event(resp.json()["updatedComment"]) # type: ignore
+
+ @validate_hf_hub_args
+ def add_space_secret(
+ self, repo_id: str, key: str, value: str, *, description: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """Adds or updates a secret in a Space.
+
+ Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Secret key. Example: `"GITHUB_API_KEY"`
+ value (`str`):
+ Secret value. Example: `"your_github_api_key"`.
+ description (`str`, *optional*):
+ Secret description. Example: `"Github API key to access the Github API"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ """
+ payload = {"key": key, "value": value}
+ if description is not None:
+ payload["description"] = description
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+
+ @validate_hf_hub_args
+ def delete_space_secret(self, repo_id: str, key: str, *, token: Optional[str] = None) -> None:
+ """Deletes a secret from a Space.
+
+ Secrets allow to set secret keys or tokens to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Secret key. Example: `"GITHUB_API_KEY"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/secrets",
+ headers=self._build_hf_headers(token=token),
+ json={"key": key},
+ )
+ hf_raise_for_status(r)
+
+ @validate_hf_hub_args
+ def get_space_variables(self, repo_id: str, *, token: Optional[str] = None) -> Dict[str, SpaceVariable]:
+ """Gets all variables from a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to query. Example: `"bigcode/in-the-stack"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def add_space_variable(
+ self, repo_id: str, key: str, value: str, *, description: Optional[str] = None, token: Optional[str] = None
+ ) -> Dict[str, SpaceVariable]:
+ """Adds or updates a variable in a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ value (`str`):
+ Variable value. Example: `"the_model_repo_id"`.
+ description (`str`):
+ Description of the variable. Example: `"Model Repo ID of the implemented model"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ """
+ payload = {"key": key, "value": value}
+ if description is not None:
+ payload["description"] = description
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def delete_space_variable(
+ self, repo_id: str, key: str, *, token: Optional[str] = None
+ ) -> Dict[str, SpaceVariable]:
+ """Deletes a variable from a Space.
+
+ Variables allow to set environment variables to a Space without hardcoding them.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ key (`str`):
+ Variable key. Example: `"MODEL_REPO_ID"`
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/variables",
+ headers=self._build_hf_headers(token=token),
+ json={"key": key},
+ )
+ hf_raise_for_status(r)
+ return {k: SpaceVariable(k, v) for k, v in r.json().items()}
+
+ @validate_hf_hub_args
+ def get_space_runtime(self, repo_id: str, *, token: Optional[str] = None) -> SpaceRuntime:
+ """Gets runtime information about a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if
+ not provided.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/spaces/{repo_id}/runtime", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def request_space_hardware(
+ self,
+ repo_id: str,
+ hardware: SpaceHardware,
+ *,
+ token: Optional[str] = None,
+ sleep_time: Optional[int] = None,
+ ) -> SpaceRuntime:
+ """Request new hardware for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ hardware (`str` or [`SpaceHardware`]):
+ Hardware on which to run the Space. Example: `"t4-medium"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is also possible to request hardware directly when creating the Space repo! See [`create_repo`] for details.
+
+
+ """
+ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+ payload: Dict[str, Any] = {"flavor": hardware}
+ if sleep_time is not None:
+ payload["sleepTimeSeconds"] = sleep_time
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/hardware",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def set_space_sleep_time(self, repo_id: str, sleep_time: int, *, token: Optional[str] = None) -> SpaceRuntime:
+ """Set a custom sleep time for a Space running on upgraded hardware..
+
+ Your Space will go to sleep after X seconds of inactivity. You are not billed when your Space is in "sleep"
+ mode. If a new visitor lands on your Space, it will "wake it up". Only upgraded hardware can have a
+ configurable sleep time. To know more about the sleep stage, please refer to
+ https://huggingface.co/docs/hub/spaces-gpus#sleep-time.
+
+ Args:
+ repo_id (`str`):
+ ID of the repo to update. Example: `"bigcode/in-the-stack"`.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to pause (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is also possible to set a custom sleep time when requesting hardware with [`request_space_hardware`].
+
+
+ """
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/sleeptime",
+ headers=self._build_hf_headers(token=token),
+ json={"seconds": sleep_time},
+ )
+ hf_raise_for_status(r)
+ runtime = SpaceRuntime(r.json())
+
+ hardware = runtime.requested_hardware or runtime.hardware
+ if hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+ return runtime
+
+ @validate_hf_hub_args
+ def pause_space(self, repo_id: str, *, token: Optional[str] = None) -> SpaceRuntime:
+ """Pause your Space.
+
+ A paused Space stops executing until manually restarted by its owner. This is different from the sleeping
+ state in which free Spaces go after 48h of inactivity. Paused time is not billed to your account, no matter the
+ hardware you've selected. To restart your Space, use [`restart_space`] and go to your Space settings page.
+
+ For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to pause. Example: `"Salesforce/BLIP2"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns:
+ [`SpaceRuntime`]: Runtime information about your Space including `stage=PAUSED` and requested hardware.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+ are not authenticated.
+ [`~utils.HfHubHTTPError`]:
+ 403 Forbidden: only the owner of a Space can pause it. If you want to manage a Space that you don't
+ own, either ask the owner by opening a Discussion or duplicate the Space.
+ [`~utils.BadRequestError`]:
+ If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+ a static Space, you can set it to private.
+ """
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/pause", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def restart_space(
+ self, repo_id: str, *, token: Optional[str] = None, factory_reboot: bool = False
+ ) -> SpaceRuntime:
+ """Restart your Space.
+
+ This is the only way to programmatically restart a Space if you've put it on Pause (see [`pause_space`]). You
+ must be the owner of the Space to restart it. If you are using an upgraded hardware, your account will be
+ billed as soon as the Space is restarted. You can trigger a restart no matter the current state of a Space.
+
+ For more details, please visit [the docs](https://huggingface.co/docs/hub/spaces-gpus#pause).
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to restart. Example: `"Salesforce/BLIP2"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ factory_reboot (`bool`, *optional*):
+ If `True`, the Space will be rebuilt from scratch without caching any requirements.
+
+ Returns:
+ [`SpaceRuntime`]: Runtime information about your Space.
+
+ Raises:
+ [`~utils.RepositoryNotFoundError`]:
+ If your Space is not found (error 404). Most probably wrong repo_id or your space is private but you
+ are not authenticated.
+ [`~utils.HfHubHTTPError`]:
+ 403 Forbidden: only the owner of a Space can restart it. If you want to restart a Space that you don't
+ own, either ask the owner by opening a Discussion or duplicate the Space.
+ [`~utils.BadRequestError`]:
+ If your Space is a static Space. Static Spaces are always running and never billed. If you want to hide
+ a static Space, you can set it to private.
+ """
+ params = {}
+ if factory_reboot:
+ params["factory"] = "true"
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/restart", headers=self._build_hf_headers(token=token), params=params
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def duplicate_space(
+ self,
+ from_id: str,
+ to_id: Optional[str] = None,
+ *,
+ private: Optional[bool] = None,
+ token: Optional[str] = None,
+ exist_ok: bool = False,
+ hardware: Optional[SpaceHardware] = None,
+ storage: Optional[SpaceStorage] = None,
+ sleep_time: Optional[int] = None,
+ secrets: Optional[List[Dict[str, str]]] = None,
+ variables: Optional[List[Dict[str, str]]] = None,
+ ) -> RepoUrl:
+ """Duplicate a Space.
+
+ Programmatically duplicate a Space. The new Space will be created in your account and will be in the same state
+ as the original Space (running or paused). You can duplicate a Space no matter the current state of a Space.
+
+ Args:
+ from_id (`str`):
+ ID of the Space to duplicate. Example: `"pharma/CLIP-Interrogator"`.
+ to_id (`str`, *optional*):
+ ID of the new Space. Example: `"dog/CLIP-Interrogator"`. If not provided, the new Space will have the same
+ name as the original Space, but in your account.
+ private (`bool`, *optional*):
+ Whether the new Space should be private or not. Defaults to the same privacy as the original Space.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ exist_ok (`bool`, *optional*, defaults to `False`):
+ If `True`, do not raise an error if repo already exists.
+ hardware (`SpaceHardware` or `str`, *optional*):
+ Choice of Hardware. Example: `"t4-medium"`. See [`SpaceHardware`] for a complete list.
+ storage (`SpaceStorage` or `str`, *optional*):
+ Choice of persistent storage tier. Example: `"small"`. See [`SpaceStorage`] for a complete list.
+ sleep_time (`int`, *optional*):
+ Number of seconds of inactivity to wait before a Space is put to sleep. Set to `-1` if you don't want
+ your Space to sleep (default behavior for upgraded hardware). For free hardware, you can't configure
+ the sleep time (value is fixed to 48 hours of inactivity).
+ See https://huggingface.co/docs/hub/spaces-gpus#sleep-time for more details.
+ secrets (`List[Dict[str, str]]`, *optional*):
+ A list of secret keys to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets.
+ variables (`List[Dict[str, str]]`, *optional*):
+ A list of public environment variables to set in your Space. Each item is in the form `{"key": ..., "value": ..., "description": ...}` where description is optional.
+ For more details, see https://huggingface.co/docs/hub/spaces-overview#managing-secrets-and-environment-variables.
+
+ Returns:
+ [`RepoUrl`]: URL to the newly created repo. Value is a subclass of `str` containing
+ attributes like `endpoint`, `repo_type` and `repo_id`.
+
+ Raises:
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the HuggingFace API returned an error
+ - [`~utils.RepositoryNotFoundError`]
+ If one of `from_id` or `to_id` cannot be found. This may be because it doesn't exist,
+ or because it is set to `private` and you do not have access.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import duplicate_space
+
+ # Duplicate a Space to your account
+ >>> duplicate_space("multimodalart/dreambooth-training")
+ RepoUrl('https://huggingface.co/spaces/nateraw/dreambooth-training',...)
+
+ # Can set custom destination id and visibility flag.
+ >>> duplicate_space("multimodalart/dreambooth-training", to_id="my-dreambooth", private=True)
+ RepoUrl('https://huggingface.co/spaces/nateraw/my-dreambooth',...)
+ ```
+ """
+ # Parse to_id if provided
+ parsed_to_id = RepoUrl(to_id) if to_id is not None else None
+
+ # Infer target repo_id
+ to_namespace = ( # set namespace manually or default to username
+ parsed_to_id.namespace
+ if parsed_to_id is not None and parsed_to_id.namespace is not None
+ else self.whoami(token)["name"]
+ )
+ to_repo_name = parsed_to_id.repo_name if to_id is not None else RepoUrl(from_id).repo_name # type: ignore
+
+ # repository must be a valid repo_id (namespace/repo_name).
+ payload: Dict[str, Any] = {"repository": f"{to_namespace}/{to_repo_name}"}
+
+ keys = ["private", "hardware", "storageTier", "sleepTimeSeconds", "secrets", "variables"]
+ values = [private, hardware, storage, sleep_time, secrets, variables]
+ payload.update({k: v for k, v in zip(keys, values) if v is not None})
+
+ if sleep_time is not None and hardware == SpaceHardware.CPU_BASIC:
+ warnings.warn(
+ "If your Space runs on the default 'cpu-basic' hardware, it will go to sleep if inactive for more"
+ " than 48 hours. This value is not configurable. If you don't want your Space to deactivate or if"
+ " you want to set a custom sleep time, you need to upgrade to a paid Hardware.",
+ UserWarning,
+ )
+
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{from_id}/duplicate",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exist_ok and err.response.status_code == 409:
+ # Repo already exists and `exist_ok=True`
+ pass
+ else:
+ raise
+
+ return RepoUrl(r.json()["url"], endpoint=self.endpoint)
+
+ @validate_hf_hub_args
+ def request_space_storage(
+ self,
+ repo_id: str,
+ storage: SpaceStorage,
+ *,
+ token: Optional[str] = None,
+ ) -> SpaceRuntime:
+ """Request persistent storage for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to update. Example: `"HuggingFaceH4/open_llm_leaderboard"`.
+ storage (`str` or [`SpaceStorage`]):
+ Storage tier. Either 'small', 'medium', or 'large'.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+
+
+
+ It is not possible to decrease persistent storage after its granted. To do so, you must delete it
+ via [`delete_space_storage`].
+
+
+ """
+ payload: Dict[str, SpaceStorage] = {"tier": storage}
+ r = get_session().post(
+ f"{self.endpoint}/api/spaces/{repo_id}/storage",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ @validate_hf_hub_args
+ def delete_space_storage(
+ self,
+ repo_id: str,
+ *,
+ token: Optional[str] = None,
+ ) -> SpaceRuntime:
+ """Delete persistent storage for a Space.
+
+ Args:
+ repo_id (`str`):
+ ID of the Space to update. Example: `"HuggingFaceH4/open_llm_leaderboard"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Returns:
+ [`SpaceRuntime`]: Runtime information about a Space including Space stage and hardware.
+ Raises:
+ [`BadRequestError`]
+ If space has no persistent storage.
+
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/spaces/{repo_id}/storage",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(r)
+ return SpaceRuntime(r.json())
+
+ #######################
+ # Inference Endpoints #
+ #######################
+
+ def list_inference_endpoints(
+ self, namespace: Optional[str] = None, *, token: Optional[str] = None
+ ) -> List[InferenceEndpoint]:
+ """Lists all inference endpoints for the given namespace.
+
+ Args:
+ namespace (`str`, *optional*):
+ The namespace to list endpoints for. Defaults to the current user. Set to `"*"` to list all endpoints
+ from all namespaces (i.e. personal namespace and all orgs the user belongs to).
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ List[`InferenceEndpoint`]: A list of all inference endpoints for the given namespace.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> api.list_inference_endpoints()
+ [InferenceEndpoint(name='my-endpoint', ...), ...]
+ ```
+ """
+ # Special case: list all endpoints for all namespaces the user has access to
+ if namespace == "*":
+ user = self.whoami(token=token)
+
+ # List personal endpoints first
+ endpoints: List[InferenceEndpoint] = list_inference_endpoints(namespace=self._get_namespace(token=token))
+
+ # Then list endpoints for all orgs the user belongs to and ignore 401 errors (no billing or no access)
+ for org in user.get("orgs", []):
+ try:
+ endpoints += list_inference_endpoints(namespace=org["name"], token=token)
+ except HfHubHTTPError as error:
+ if error.response.status_code == 401: # Either no billing or user don't have access)
+ logger.debug("Cannot list Inference Endpoints for org '%s': %s", org["name"], error)
+ pass
+
+ return endpoints
+
+ # Normal case: list endpoints for a specific namespace
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().get(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return [
+ InferenceEndpoint.from_raw(endpoint, namespace=namespace, token=token)
+ for endpoint in response.json()["items"]
+ ]
+
+ def create_inference_endpoint(
+ self,
+ name: str,
+ *,
+ repository: str,
+ framework: str,
+ accelerator: str,
+ instance_size: str,
+ instance_type: str,
+ region: str,
+ vendor: str,
+ account_id: Optional[str] = None,
+ min_replica: int = 0,
+ max_replica: int = 1,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ custom_image: Optional[Dict] = None,
+ type: InferenceEndpointType = InferenceEndpointType.PROTECTED,
+ namespace: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> InferenceEndpoint:
+ """Create a new Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The unique name for the new Inference Endpoint.
+ repository (`str`):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ accelerator (`str`):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`):
+ The size or type of the instance to be used for hosting the model (e.g. `"large"`).
+ instance_type (`str`):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"c6i"`).
+ region (`str`):
+ The cloud region in which the Inference Endpoint will be created (e.g. `"us-east-1"`).
+ vendor (`str`):
+ The cloud provider or vendor where the Inference Endpoint will be hosted (e.g. `"aws"`).
+ account_id (`str`, *optional*):
+ The account ID used to link a VPC to a private Inference Endpoint (if applicable).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint. Defaults to 0.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint. Defaults to 1.
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+ custom_image (`Dict`, *optional*):
+ A custom Docker image to use for the Inference Endpoint. This is useful if you want to deploy an
+ Inference Endpoint running on the `text-generation-inference` (TGI) framework (see examples).
+ type ([`InferenceEndpointType]`, *optional*):
+ The type of the Inference Endpoint, which can be `"protected"` (default), `"public"` or `"private"`.
+ namespace (`str`, *optional*):
+ The namespace where the Inference Endpoint will be created. Defaults to the current user's namespace.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the updated Inference Endpoint.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> create_inference_endpoint(
+ ... "my-endpoint-name",
+ ... repository="gpt2",
+ ... framework="pytorch",
+ ... task="text-generation",
+ ... accelerator="cpu",
+ ... vendor="aws",
+ ... region="us-east-1",
+ ... type="protected",
+ ... instance_size="medium",
+ ... instance_type="c6i",
+ ... )
+ >>> endpoint
+ InferenceEndpoint(name='my-endpoint-name', status="pending",...)
+
+ # Run inference on the endpoint
+ >>> endpoint.client.text_generation(...)
+ "..."
+ ```
+
+ ```python
+ # Start an Inference Endpoint running Zephyr-7b-beta on TGI
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> create_inference_endpoint(
+ ... "aws-zephyr-7b-beta-0486",
+ ... repository="HuggingFaceH4/zephyr-7b-beta",
+ ... framework="pytorch",
+ ... task="text-generation",
+ ... accelerator="gpu",
+ ... vendor="aws",
+ ... region="us-east-1",
+ ... type="protected",
+ ... instance_size="medium",
+ ... instance_type="g5.2xlarge",
+ ... custom_image={
+ ... "health_route": "/health",
+ ... "env": {
+ ... "MAX_BATCH_PREFILL_TOKENS": "2048",
+ ... "MAX_INPUT_LENGTH": "1024",
+ ... "MAX_TOTAL_TOKENS": "1512",
+ ... "MODEL_ID": "/repository"
+ ... },
+ ... "url": "ghcr.io/huggingface/text-generation-inference:1.1.0",
+ ... },
+ ... )
+
+ ```
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ image = {"custom": custom_image} if custom_image is not None else {"huggingface": {}}
+ payload: Dict = {
+ "accountId": account_id,
+ "compute": {
+ "accelerator": accelerator,
+ "instanceSize": instance_size,
+ "instanceType": instance_type,
+ "scaling": {
+ "maxReplica": max_replica,
+ "minReplica": min_replica,
+ },
+ },
+ "model": {
+ "framework": framework,
+ "repository": repository,
+ "revision": revision,
+ "task": task,
+ "image": image,
+ },
+ "name": name,
+ "provider": {
+ "region": region,
+ "vendor": vendor,
+ },
+ "type": type,
+ }
+
+ response = get_session().post(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def get_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Optional[str] = None
+ ) -> InferenceEndpoint:
+ """Get information about an Inference Endpoint.
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to retrieve information about.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the requested Inference Endpoint.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import HfApi
+ >>> api = HfApi()
+ >>> endpoint = api.get_inference_endpoint("my-text-to-image")
+ >>> endpoint
+ InferenceEndpoint(name='my-text-to-image', ...)
+
+ # Get status
+ >>> endpoint.status
+ 'running'
+ >>> endpoint.url
+ 'https://my-text-to-image.region.vendor.endpoints.huggingface.cloud'
+
+ # Run inference
+ >>> endpoint.client.text_to_image(...)
+ ```
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().get(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def update_inference_endpoint(
+ self,
+ name: str,
+ *,
+ # Compute update
+ accelerator: Optional[str] = None,
+ instance_size: Optional[str] = None,
+ instance_type: Optional[str] = None,
+ min_replica: Optional[int] = None,
+ max_replica: Optional[int] = None,
+ # Model update
+ repository: Optional[str] = None,
+ framework: Optional[str] = None,
+ revision: Optional[str] = None,
+ task: Optional[str] = None,
+ # Other
+ namespace: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> InferenceEndpoint:
+ """Update an Inference Endpoint.
+
+ This method allows the update of either the compute configuration, the deployed model, or both. All arguments are
+ optional but at least one must be provided.
+
+ For convenience, you can also update an Inference Endpoint using [`InferenceEndpoint.update`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to update.
+
+ accelerator (`str`, *optional*):
+ The hardware accelerator to be used for inference (e.g. `"cpu"`).
+ instance_size (`str`, *optional*):
+ The size or type of the instance to be used for hosting the model (e.g. `"large"`).
+ instance_type (`str`, *optional*):
+ The cloud instance type where the Inference Endpoint will be deployed (e.g. `"c6i"`).
+ min_replica (`int`, *optional*):
+ The minimum number of replicas (instances) to keep running for the Inference Endpoint.
+ max_replica (`int`, *optional*):
+ The maximum number of replicas (instances) to scale to for the Inference Endpoint.
+
+ repository (`str`, *optional*):
+ The name of the model repository associated with the Inference Endpoint (e.g. `"gpt2"`).
+ framework (`str`, *optional*):
+ The machine learning framework used for the model (e.g. `"custom"`).
+ revision (`str`, *optional*):
+ The specific model revision to deploy on the Inference Endpoint (e.g. `"6c0e6080953db56375760c0471a8c5f2929baf11"`).
+ task (`str`, *optional*):
+ The task on which to deploy the model (e.g. `"text-classification"`).
+
+ namespace (`str`, *optional*):
+ The namespace where the Inference Endpoint will be updated. Defaults to the current user's namespace.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the updated Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ payload: Dict = {}
+ if any(value is not None for value in (accelerator, instance_size, instance_type, min_replica, max_replica)):
+ payload["compute"] = {
+ "accelerator": accelerator,
+ "instanceSize": instance_size,
+ "instanceType": instance_type,
+ "scaling": {
+ "maxReplica": max_replica,
+ "minReplica": min_replica,
+ },
+ }
+ if any(value is not None for value in (repository, framework, revision, task)):
+ payload["model"] = {
+ "framework": framework,
+ "repository": repository,
+ "revision": revision,
+ "task": task,
+ "image": {"huggingface": {}},
+ }
+
+ response = get_session().put(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def delete_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """Delete an Inference Endpoint.
+
+ This operation is not reversible. If you don't want to be charged for an Inference Endpoint, it is preferable
+ to pause it with [`pause_inference_endpoint`] or scale it to zero with [`scale_to_zero_inference_endpoint`].
+
+ For convenience, you can also delete an Inference Endpoint using [`InferenceEndpoint.delete`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to delete.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+ """
+ namespace = namespace or self._get_namespace(token=token)
+ response = get_session().delete(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ def pause_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Optional[str] = None
+ ) -> InferenceEndpoint:
+ """Pause an Inference Endpoint.
+
+ A paused Inference Endpoint will not be charged. It can be resumed at any time using [`resume_inference_endpoint`].
+ This is different than scaling the Inference Endpoint to zero with [`scale_to_zero_inference_endpoint`], which
+ would be automatically restarted when a request is made to it.
+
+ For convenience, you can also pause an Inference Endpoint using [`pause_inference_endpoint`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to pause.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the paused Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/pause",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def resume_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Optional[str] = None
+ ) -> InferenceEndpoint:
+ """Resume an Inference Endpoint.
+
+ For convenience, you can also resume an Inference Endpoint using [`InferenceEndpoint.resume`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to resume.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the resumed Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/resume",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def scale_to_zero_inference_endpoint(
+ self, name: str, *, namespace: Optional[str] = None, token: Optional[str] = None
+ ) -> InferenceEndpoint:
+ """Scale Inference Endpoint to zero.
+
+ An Inference Endpoint scaled to zero will not be charged. It will be resume on the next request to it, with a
+ cold start delay. This is different than pausing the Inference Endpoint with [`pause_inference_endpoint`], which
+ would require a manual resume with [`resume_inference_endpoint`].
+
+ For convenience, you can also scale an Inference Endpoint to zero using [`InferenceEndpoint.scale_to_zero`].
+
+ Args:
+ name (`str`):
+ The name of the Inference Endpoint to scale to zero.
+ namespace (`str`, *optional*):
+ The namespace in which the Inference Endpoint is located. Defaults to the current user.
+ token (`str`, *optional*):
+ An authentication token (See https://huggingface.co/settings/token).
+
+ Returns:
+ [`InferenceEndpoint`]: information about the scaled-to-zero Inference Endpoint.
+ """
+ namespace = namespace or self._get_namespace(token=token)
+
+ response = get_session().post(
+ f"{INFERENCE_ENDPOINTS_ENDPOINT}/endpoint/{namespace}/{name}/scale-to-zero",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+
+ return InferenceEndpoint.from_raw(response.json(), namespace=namespace, token=token)
+
+ def _get_namespace(self, token: Optional[str] = None) -> str:
+ """Get the default namespace for the current user."""
+ me = self.whoami(token=token)
+ if me["type"] == "user":
+ return me["name"]
+ else:
+ raise ValueError(
+ "Cannot determine default namespace. You must provide a 'namespace' as input or be logged in as a"
+ " user."
+ )
+
+ ########################
+ # Collection Endpoints #
+ ########################
+ @validate_hf_hub_args
+ def list_collections(
+ self,
+ *,
+ owner: Union[List[str], str, None] = None,
+ item: Union[List[str], str, None] = None,
+ sort: Optional[Literal["lastModified", "trending", "upvotes"]] = None,
+ limit: Optional[int] = None,
+ token: Optional[Union[bool, str]] = None,
+ ) -> Iterable[Collection]:
+ """List collections on the Huggingface Hub, given some filters.
+
+
+
+ When listing collections, the item list per collection is truncated to 4 items maximum. To retrieve all items
+ from a collection, you must use [`get_collection`].
+
+
+
+ Args:
+ owner (`List[str]` or `str`, *optional*):
+ Filter by owner's username.
+ item (`List[str]` or `str`, *optional*):
+ Filter collections containing a particular items. Example: `"models/teknium/OpenHermes-2.5-Mistral-7B"`, `"datasets/squad"` or `"papers/2311.12983"`.
+ sort (`Literal["lastModified", "trending", "upvotes"]`, *optional*):
+ Sort collections by last modified, trending or upvotes.
+ limit (`int`, *optional*):
+ Maximum number of collections to be returned.
+ token (`bool` or `str`, *optional*):
+ An authentication token (see https://huggingface.co/settings/token).
+
+ Returns:
+ `Iterable[Collection]`: an iterable of [`Collection`] objects.
+ """
+ # Construct the API endpoint
+ path = f"{self.endpoint}/api/collections"
+ headers = self._build_hf_headers(token=token)
+ params: Dict = {}
+ if owner is not None:
+ params.update({"owner": owner})
+ if item is not None:
+ params.update({"item": item})
+ if sort is not None:
+ params.update({"sort": sort})
+ if limit is not None:
+ params.update({"limit": limit})
+
+ # Paginate over the results until limit is reached
+ items = paginate(path, headers=headers, params=params)
+ if limit is not None:
+ items = islice(items, limit) # Do not iterate over all pages
+
+ # Parse as Collection and return
+ for position, collection_data in enumerate(items):
+ yield Collection(position=position, **collection_data)
+
+ def get_collection(self, collection_slug: str, *, token: Optional[str] = None) -> Collection:
+ """Gets information about a Collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection of the Hub. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+ >>> collection.title
+ 'Recent models'
+ >>> len(collection.items)
+ 37
+ >>> collection.items[0]
+ CollectionItem(
+ item_object_id='651446103cd773a050bf64c2',
+ item_id='TheBloke/U-Amethyst-20B-AWQ',
+ item_type='model',
+ position=88,
+ note=None
+ )
+ ```
+ """
+ r = get_session().get(
+ f"{self.endpoint}/api/collections/{collection_slug}", headers=self._build_hf_headers(token=token)
+ )
+ hf_raise_for_status(r)
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def create_collection(
+ self,
+ title: str,
+ *,
+ namespace: Optional[str] = None,
+ description: Optional[str] = None,
+ private: bool = False,
+ exists_ok: bool = False,
+ token: Optional[str] = None,
+ ) -> Collection:
+ """Create a new Collection on the Hub.
+
+ Args:
+ title (`str`):
+ Title of the collection to create. Example: `"Recent models"`.
+ namespace (`str`, *optional*):
+ Namespace of the collection to create (username or org). Will default to the owner name.
+ description (`str`, *optional*):
+ Description of the collection to create.
+ private (`bool`, *optional*):
+ Whether the collection should be private or not. Defaults to `False` (i.e. public collection).
+ exists_ok (`bool`, *optional*):
+ If `True`, do not raise an error if collection already exists.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import create_collection
+ >>> collection = create_collection(
+ ... title="ICCV 2023",
+ ... description="Portfolio of models, papers and demos I presented at ICCV 2023",
+ ... )
+ >>> collection.slug
+ "username/iccv-2023-64f9a55bb3115b4f513ec026"
+ ```
+ """
+ if namespace is None:
+ namespace = self.whoami(token)["name"]
+
+ payload = {
+ "title": title,
+ "namespace": namespace,
+ "private": private,
+ }
+ if description is not None:
+ payload["description"] = description
+
+ r = get_session().post(
+ f"{self.endpoint}/api/collections", headers=self._build_hf_headers(token=token), json=payload
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exists_ok and err.response.status_code == 409:
+ # Collection already exists and `exists_ok=True`
+ slug = r.json()["slug"]
+ return self.get_collection(slug, token=token)
+ else:
+ raise
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def update_collection_metadata(
+ self,
+ collection_slug: str,
+ *,
+ title: Optional[str] = None,
+ description: Optional[str] = None,
+ position: Optional[int] = None,
+ private: Optional[bool] = None,
+ theme: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> Collection:
+ """Update metadata of a collection on the Hub.
+
+ All arguments are optional. Only provided metadata will be updated.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ title (`str`):
+ Title of the collection to update.
+ description (`str`, *optional*):
+ Description of the collection to update.
+ position (`int`, *optional*):
+ New position of the collection in the list of collections of the user.
+ private (`bool`, *optional*):
+ Whether the collection should be private or not.
+ theme (`str`, *optional*):
+ Theme of the collection on the Hub.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns: [`Collection`]
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import update_collection_metadata
+ >>> collection = update_collection_metadata(
+ ... collection_slug="username/iccv-2023-64f9a55bb3115b4f513ec026",
+ ... title="ICCV Oct. 2023"
+ ... description="Portfolio of models, datasets, papers and demos I presented at ICCV Oct. 2023",
+ ... private=False,
+ ... theme="pink",
+ ... )
+ >>> collection.slug
+ "username/iccv-oct-2023-64f9a55bb3115b4f513ec026"
+ # ^collection slug got updated but not the trailing ID
+ ```
+ """
+ payload = {
+ "position": position,
+ "private": private,
+ "theme": theme,
+ "title": title,
+ "description": description,
+ }
+ r = get_session().patch(
+ f"{self.endpoint}/api/collections/{collection_slug}",
+ headers=self._build_hf_headers(token=token),
+ # Only send not-none values to the API
+ json={key: value for key, value in payload.items() if value is not None},
+ )
+ hf_raise_for_status(r)
+ return Collection(**{**r.json()["data"], "endpoint": self.endpoint})
+
+ def delete_collection(
+ self, collection_slug: str, *, missing_ok: bool = False, token: Optional[str] = None
+ ) -> None:
+ """Delete a collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to delete. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ missing_ok (`bool`, *optional*):
+ If `True`, do not raise an error if collection doesn't exists.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import delete_collection
+ >>> collection = delete_collection("username/useless-collection-64f9a55bb3115b4f513ec026", missing_ok=True)
+ ```
+
+
+
+ This is a non-revertible action. A deleted collection cannot be restored.
+
+
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/collections/{collection_slug}", headers=self._build_hf_headers(token=token)
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if missing_ok and err.response.status_code == 404:
+ # Collection doesn't exists and `missing_ok=True`
+ return
+ else:
+ raise
+
+ def add_collection_item(
+ self,
+ collection_slug: str,
+ item_id: str,
+ item_type: CollectionItemType_T,
+ *,
+ note: Optional[str] = None,
+ exists_ok: bool = False,
+ token: Optional[str] = None,
+ ) -> Collection:
+ """Add an item to a collection on the Hub.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_id (`str`):
+ ID of the item to add to the collection. It can be the ID of a repo on the Hub (e.g. `"facebook/bart-large-mnli"`)
+ or a paper id (e.g. `"2307.09288"`).
+ item_type (`str`):
+ Type of the item to add. Can be one of `"model"`, `"dataset"`, `"space"` or `"paper"`.
+ note (`str`, *optional*):
+ A note to attach to the item in the collection. The maximum size for a note is 500 characters.
+ exists_ok (`bool`, *optional*):
+ If `True`, do not raise an error if item already exists.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns: [`Collection`]
+
+ Raises:
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ `HTTPError`:
+ HTTP 404 if the item you try to add to the collection does not exist on the Hub.
+ `HTTPError`:
+ HTTP 409 if the item you try to add to the collection is already in the collection (and exists_ok=False)
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import add_collection_item
+ >>> collection = add_collection_item(
+ ... collection_slug="davanstrien/climate-64f99dc2a5067f6b65531bab",
+ ... item_id="pierre-loic/climate-news-articles",
+ ... item_type="dataset"
+ ... )
+ >>> collection.items[-1].item_id
+ "pierre-loic/climate-news-articles"
+ # ^item got added to the collection on last position
+
+ # Add item with a note
+ >>> add_collection_item(
+ ... collection_slug="davanstrien/climate-64f99dc2a5067f6b65531bab",
+ ... item_id="datasets/climate_fever",
+ ... item_type="dataset"
+ ... note="This dataset adopts the FEVER methodology that consists of 1,535 real-world claims regarding climate-change collected on the internet."
+ ... )
+ (...)
+ ```
+ """
+ payload: Dict[str, Any] = {"item": {"id": item_id, "type": item_type}}
+ if note is not None:
+ payload["note"] = note
+ r = get_session().post(
+ f"{self.endpoint}/api/collections/{collection_slug}/items",
+ headers=self._build_hf_headers(token=token),
+ json=payload,
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if exists_ok and err.response.status_code == 409:
+ # Item already exists and `exists_ok=True`
+ return self.get_collection(collection_slug, token=token)
+ else:
+ raise
+ return Collection(**{**r.json(), "endpoint": self.endpoint})
+
+ def update_collection_item(
+ self,
+ collection_slug: str,
+ item_object_id: str,
+ *,
+ note: Optional[str] = None,
+ position: Optional[int] = None,
+ token: Optional[str] = None,
+ ) -> None:
+ """Update an item in a collection.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_object_id (`str`):
+ ID of the item in the collection. This is not the id of the item on the Hub (repo_id or paper id).
+ It must be retrieved from a [`CollectionItem`] object. Example: `collection.items[0].item_object_id`.
+ note (`str`, *optional*):
+ A note to attach to the item in the collection. The maximum size for a note is 500 characters.
+ position (`int`, *optional*):
+ New position of the item in the collection.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection, update_collection_item
+
+ # Get collection first
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+
+ # Update item based on its ID (add note + update position)
+ >>> update_collection_item(
+ ... collection_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026",
+ ... item_object_id=collection.items[-1].item_object_id,
+ ... note="Newly updated model!"
+ ... position=0,
+ ... )
+ ```
+ """
+ payload = {"position": position, "note": note}
+ r = get_session().patch(
+ f"{self.endpoint}/api/collections/{collection_slug}/items/{item_object_id}",
+ headers=self._build_hf_headers(token=token),
+ # Only send not-none values to the API
+ json={key: value for key, value in payload.items() if value is not None},
+ )
+ hf_raise_for_status(r)
+
+ def delete_collection_item(
+ self,
+ collection_slug: str,
+ item_object_id: str,
+ *,
+ missing_ok: bool = False,
+ token: Optional[str] = None,
+ ) -> None:
+ """Delete an item from a collection.
+
+ Args:
+ collection_slug (`str`):
+ Slug of the collection to update. Example: `"TheBloke/recent-models-64f9a55bb3115b4f513ec026"`.
+ item_object_id (`str`):
+ ID of the item in the collection. This is not the id of the item on the Hub (repo_id or paper id).
+ It must be retrieved from a [`CollectionItem`] object. Example: `collection.items[0]._id`.
+ missing_ok (`bool`, *optional*):
+ If `True`, do not raise an error if item doesn't exists.
+ token (`str`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import get_collection, delete_collection_item
+
+ # Get collection first
+ >>> collection = get_collection("TheBloke/recent-models-64f9a55bb3115b4f513ec026")
+
+ # Delete item based on its ID
+ >>> delete_collection_item(
+ ... collection_slug="TheBloke/recent-models-64f9a55bb3115b4f513ec026",
+ ... item_object_id=collection.items[-1].item_object_id,
+ ... )
+ ```
+ """
+ r = get_session().delete(
+ f"{self.endpoint}/api/collections/{collection_slug}/items/{item_object_id}",
+ headers=self._build_hf_headers(token=token),
+ )
+ try:
+ hf_raise_for_status(r)
+ except HTTPError as err:
+ if missing_ok and err.response.status_code == 404:
+ # Item already deleted and `missing_ok=True`
+ return
+ else:
+ raise
+
+ ##########################
+ # Manage access requests #
+ ##########################
+
+ @validate_hf_hub_args
+ def list_pending_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> List[AccessRequest]:
+ """
+ Get pending access requests for a given gated repo.
+
+ A pending request means the user has requested access to the repo but the request has not been processed yet.
+ If the approval mode is automatic, this list should be empty. Pending requests can be accepted or rejected
+ using [`accept_access_request`] and [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_pending_access_requests, accept_access_request
+
+ # List pending requests
+ >>> requests = list_pending_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='pending',
+ fields=None,
+ ),
+ ...
+ ]
+
+ # Accept Clem's request
+ >>> accept_access_request("meta-llama/Llama-2-7b", "clem")
+ ```
+ """
+ return self._list_access_requests(repo_id, "pending", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def list_accepted_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> List[AccessRequest]:
+ """
+ Get accepted access requests for a given gated repo.
+
+ An accepted request means the user has requested access to the repo and the request has been accepted. The user
+ can download any file of the repo. If the approval mode is automatic, this list should contains by default all
+ requests. Accepted requests can be cancelled or rejected at any time using [`cancel_access_request`] and
+ [`reject_access_request`]. A cancelled request will go back to the pending list while a rejected request will
+ go to the rejected list. In both cases, the user will lose access to the repo.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_accepted_access_requests
+
+ >>> requests = list_accepted_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='accepted',
+ fields=None,
+ ),
+ ...
+ ]
+ ```
+ """
+ return self._list_access_requests(repo_id, "accepted", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def list_rejected_access_requests(
+ self, repo_id: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> List[AccessRequest]:
+ """
+ Get rejected access requests for a given gated repo.
+
+ A rejected request means the user has requested access to the repo and the request has been explicitly rejected
+ by a repo owner (either you or another user from your organization). The user cannot download any file of the
+ repo. Rejected requests can be accepted or cancelled at any time using [`accept_access_request`] and
+ [`cancel_access_request`]. A cancelled request will go back to the pending list while an accepted request will
+ go to the accepted list.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to get access requests for.
+ repo_type (`str`, *optional*):
+ The type of the repo to get access requests for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Returns:
+ `List[AccessRequest]`: A list of [`AccessRequest`] objects. Each time contains a `username`, `email`,
+ `status` and `timestamp` attribute. If the gated repo has a custom form, the `fields` attribute will
+ be populated with user's answers.
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import list_rejected_access_requests
+
+ >>> requests = list_rejected_access_requests("meta-llama/Llama-2-7b")
+ >>> len(requests)
+ 411
+ >>> requests[0]
+ [
+ AccessRequest(
+ username='clem',
+ fullname='Clem 🤗',
+ email='***',
+ timestamp=datetime.datetime(2023, 11, 23, 18, 4, 53, 828000, tzinfo=datetime.timezone.utc),
+ status='rejected',
+ fields=None,
+ ),
+ ...
+ ]
+ ```
+ """
+ return self._list_access_requests(repo_id, "rejected", repo_type=repo_type, token=token)
+
+ def _list_access_requests(
+ self,
+ repo_id: str,
+ status: Literal["accepted", "rejected", "pending"],
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> List[AccessRequest]:
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+
+ response = get_session().get(
+ f"{ENDPOINT}/api/{repo_type}s/{repo_id}/user-access-request/{status}",
+ headers=self._build_hf_headers(token=token),
+ )
+ hf_raise_for_status(response)
+ return [
+ AccessRequest(
+ username=request["user"]["user"],
+ fullname=request["user"]["fullname"],
+ email=request["user"]["email"],
+ status=request["status"],
+ timestamp=parse_datetime(request["timestamp"]),
+ fields=request.get("fields"), # only if custom fields in form
+ )
+ for request in response.json()
+ ]
+
+ @validate_hf_hub_args
+ def cancel_access_request(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """
+ Cancel an access request from a user for a given gated repo.
+
+ A cancelled request will go back to the pending list and the user will lose access to the repo.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to cancel access request for.
+ user (`str`):
+ The username of the user which access request should be cancelled.
+ repo_type (`str`, *optional*):
+ The type of the repo to cancel access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ `HTTPError`:
+ HTTP 404 if the user does not exist on the Hub.
+ `HTTPError`:
+ HTTP 404 if the user access request cannot be found.
+ `HTTPError`:
+ HTTP 404 if the user access request is already in the pending list.
+ """
+ self._handle_access_request(repo_id, user, "pending", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def accept_access_request(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """
+ Accept an access request from a user for a given gated repo.
+
+ Once the request is accepted, the user will be able to download any file of the repo and access the community
+ tab. If the approval mode is automatic, you don't have to accept requests manually. An accepted request can be
+ cancelled or rejected at any time using [`cancel_access_request`] and [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to accept access request for.
+ user (`str`):
+ The username of the user which access request should be accepted.
+ repo_type (`str`, *optional*):
+ The type of the repo to accept access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ `HTTPError`:
+ HTTP 404 if the user does not exist on the Hub.
+ `HTTPError`:
+ HTTP 404 if the user access request cannot be found.
+ `HTTPError`:
+ HTTP 404 if the user access request is already in the accepted list.
+ """
+ self._handle_access_request(repo_id, user, "accepted", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def reject_access_request(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """
+ Reject an access request from a user for a given gated repo.
+
+ A rejected request will go to the rejected list. The user cannot download any file of the repo. Rejected
+ requests can be accepted or cancelled at any time using [`accept_access_request`] and [`cancel_access_request`].
+ A cancelled request will go back to the pending list while an accepted request will go to the accepted list.
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to reject access request for.
+ user (`str`):
+ The username of the user which access request should be rejected.
+ repo_type (`str`, *optional*):
+ The type of the repo to reject access request for. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ `HTTPError`:
+ HTTP 404 if the user does not exist on the Hub.
+ `HTTPError`:
+ HTTP 404 if the user access request cannot be found.
+ `HTTPError`:
+ HTTP 404 if the user access request is already in the rejected list.
+ """
+ self._handle_access_request(repo_id, user, "rejected", repo_type=repo_type, token=token)
+
+ @validate_hf_hub_args
+ def _handle_access_request(
+ self,
+ repo_id: str,
+ user: str,
+ status: Literal["accepted", "rejected", "pending"],
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ) -> None:
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+
+ response = get_session().post(
+ f"{ENDPOINT}/api/{repo_type}s/{repo_id}/user-access-request/handle",
+ headers=self._build_hf_headers(token=token),
+ json={"user": user, "status": status},
+ )
+ hf_raise_for_status(response)
+
+ @validate_hf_hub_args
+ def grant_access(
+ self, repo_id: str, user: str, *, repo_type: Optional[str] = None, token: Optional[str] = None
+ ) -> None:
+ """
+ Grant access to a user for a given gated repo.
+
+ Granting access don't require for the user to send an access request by themselves. The user is automatically
+ added to the accepted list meaning they can download the files You can revoke the granted access at any time
+ using [`cancel_access_request`] or [`reject_access_request`].
+
+ For more info about gated repos, see https://huggingface.co/docs/hub/models-gated.
+
+ Args:
+ repo_id (`str`):
+ The id of the repo to grant access to.
+ user (`str`):
+ The username of the user to grant access.
+ repo_type (`str`, *optional*):
+ The type of the repo to grant access to. Must be one of `model`, `dataset` or `space`.
+ Defaults to `model`.
+ token (`str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+
+ Raises:
+ `HTTPError`:
+ HTTP 400 if the repo is not gated.
+ `HTTPError`:
+ HTTP 400 if the user already has access to the repo.
+ `HTTPError`:
+ HTTP 403 if you only have read-only access to the repo. This can be the case if you don't have `write`
+ or `admin` role in the organization the repo belongs to or if you passed a `read` token.
+ `HTTPError`:
+ HTTP 404 if the user does not exist on the Hub.
+ """
+ if repo_type not in REPO_TYPES:
+ raise ValueError(f"Invalid repo type, must be one of {REPO_TYPES}")
+ if repo_type is None:
+ repo_type = REPO_TYPE_MODEL
+
+ response = get_session().post(
+ f"{ENDPOINT}/api/models/{repo_id}/user-access-request/grant",
+ headers=self._build_hf_headers(token=token),
+ json={"user": user},
+ )
+ hf_raise_for_status(response)
+ return response.json()
+
+ #############
+ # Internals #
+ #############
+
+ def _build_hf_headers(
+ self,
+ token: Optional[Union[bool, str]] = None,
+ is_write_action: bool = False,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ ) -> Dict[str, str]:
+ """
+ Alias for [`build_hf_headers`] that uses the token from [`HfApi`] client
+ when `token` is not provided.
+ """
+ if token is None:
+ # Cannot do `token = token or self.token` as token can be `False`.
+ token = self.token
+ return build_hf_headers(
+ token=token,
+ is_write_action=is_write_action,
+ library_name=library_name or self.library_name,
+ library_version=library_version or self.library_version,
+ user_agent=user_agent or self.user_agent,
+ headers=self.headers,
+ )
+
+ def _prepare_upload_folder_deletions(
+ self,
+ repo_id: str,
+ repo_type: Optional[str],
+ revision: Optional[str],
+ token: Optional[str],
+ path_in_repo: str,
+ delete_patterns: Optional[Union[List[str], str]],
+ ) -> List[CommitOperationDelete]:
+ """Generate the list of Delete operations for a commit to delete files from a repo.
+
+ List remote files and match them against the `delete_patterns` constraints. Returns a list of [`CommitOperationDelete`]
+ with the matching items.
+
+ Note: `.gitattributes` file is essential to make a repo work properly on the Hub. This file will always be
+ kept even if it matches the `delete_patterns` constraints.
+ """
+ if delete_patterns is None:
+ # If no delete patterns, no need to list and filter remote files
+ return []
+
+ # List remote files
+ filenames = self.list_repo_files(repo_id=repo_id, revision=revision, repo_type=repo_type, token=token)
+
+ # Compute relative path in repo
+ if path_in_repo and path_in_repo not in (".", "./"):
+ path_in_repo = path_in_repo.strip("/") + "/" # harmonize
+ relpath_to_abspath = {
+ file[len(path_in_repo) :]: file for file in filenames if file.startswith(path_in_repo)
+ }
+ else:
+ relpath_to_abspath = {file: file for file in filenames}
+
+ # Apply filter on relative paths and return
+ return [
+ CommitOperationDelete(path_in_repo=relpath_to_abspath[relpath], is_folder=False)
+ for relpath in filter_repo_objects(relpath_to_abspath.keys(), allow_patterns=delete_patterns)
+ if relpath_to_abspath[relpath] != ".gitattributes"
+ ]
+
+
+def _prepare_upload_folder_additions(
+ folder_path: Union[str, Path],
+ path_in_repo: str,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+) -> List[CommitOperationAdd]:
+ """Generate the list of Add operations for a commit to upload a folder.
+
+ Files not matching the `allow_patterns` (allowlist) and `ignore_patterns` (denylist)
+ constraints are discarded.
+ """
+ folder_path = Path(folder_path).expanduser().resolve()
+ if not folder_path.is_dir():
+ raise ValueError(f"Provided path: '{folder_path}' is not a directory")
+
+ # List files from folder
+ relpath_to_abspath = {
+ path.relative_to(folder_path).as_posix(): path
+ for path in sorted(folder_path.glob("**/*")) # sorted to be deterministic
+ if path.is_file()
+ }
+
+ # Filter files and return
+ # Patterns are applied on the path relative to `folder_path`. `path_in_repo` is prefixed after the filtering.
+ prefix = f"{path_in_repo.strip('/')}/" if path_in_repo else ""
+ return [
+ CommitOperationAdd(
+ path_or_fileobj=relpath_to_abspath[relpath], # absolute path on disk
+ path_in_repo=prefix + relpath, # "absolute" path in repo
+ )
+ for relpath in filter_repo_objects(
+ relpath_to_abspath.keys(), allow_patterns=allow_patterns, ignore_patterns=ignore_patterns
+ )
+ ]
+
+
+def _parse_revision_from_pr_url(pr_url: str) -> str:
+ """Safely parse revision number from a PR url.
+
+ Example:
+ ```py
+ >>> _parse_revision_from_pr_url("https://huggingface.co/bigscience/bloom/discussions/2")
+ "refs/pr/2"
+ ```
+ """
+ re_match = re.match(_REGEX_DISCUSSION_URL, pr_url)
+ if re_match is None:
+ raise RuntimeError(f"Unexpected response from the hub, expected a Pull Request URL but got: '{pr_url}'")
+ return f"refs/pr/{re_match[1]}"
+
+
+api = HfApi()
+
+whoami = api.whoami
+get_token_permission = api.get_token_permission
+
+list_models = api.list_models
+model_info = api.model_info
+
+list_datasets = api.list_datasets
+dataset_info = api.dataset_info
+
+list_spaces = api.list_spaces
+space_info = api.space_info
+
+repo_exists = api.repo_exists
+revision_exists = api.revision_exists
+file_exists = api.file_exists
+repo_info = api.repo_info
+list_repo_files = api.list_repo_files
+list_repo_refs = api.list_repo_refs
+list_repo_commits = api.list_repo_commits
+list_files_info = api.list_files_info
+list_repo_tree = api.list_repo_tree
+get_paths_info = api.get_paths_info
+
+list_metrics = api.list_metrics
+
+get_model_tags = api.get_model_tags
+get_dataset_tags = api.get_dataset_tags
+
+create_commit = api.create_commit
+create_repo = api.create_repo
+delete_repo = api.delete_repo
+update_repo_visibility = api.update_repo_visibility
+super_squash_history = api.super_squash_history
+move_repo = api.move_repo
+upload_file = api.upload_file
+upload_folder = api.upload_folder
+delete_file = api.delete_file
+delete_folder = api.delete_folder
+create_commits_on_pr = api.create_commits_on_pr
+preupload_lfs_files = api.preupload_lfs_files
+create_branch = api.create_branch
+delete_branch = api.delete_branch
+create_tag = api.create_tag
+delete_tag = api.delete_tag
+get_full_repo_name = api.get_full_repo_name
+
+# Safetensors helpers
+get_safetensors_metadata = api.get_safetensors_metadata
+parse_safetensors_file_metadata = api.parse_safetensors_file_metadata
+
+# Background jobs
+run_as_future = api.run_as_future
+
+# Activity API
+list_liked_repos = api.list_liked_repos
+list_repo_likers = api.list_repo_likers
+like = api.like
+unlike = api.unlike
+
+# Community API
+get_discussion_details = api.get_discussion_details
+get_repo_discussions = api.get_repo_discussions
+create_discussion = api.create_discussion
+create_pull_request = api.create_pull_request
+change_discussion_status = api.change_discussion_status
+comment_discussion = api.comment_discussion
+edit_discussion_comment = api.edit_discussion_comment
+rename_discussion = api.rename_discussion
+merge_pull_request = api.merge_pull_request
+
+# Space API
+add_space_secret = api.add_space_secret
+delete_space_secret = api.delete_space_secret
+get_space_variables = api.get_space_variables
+add_space_variable = api.add_space_variable
+delete_space_variable = api.delete_space_variable
+get_space_runtime = api.get_space_runtime
+request_space_hardware = api.request_space_hardware
+set_space_sleep_time = api.set_space_sleep_time
+pause_space = api.pause_space
+restart_space = api.restart_space
+duplicate_space = api.duplicate_space
+request_space_storage = api.request_space_storage
+delete_space_storage = api.delete_space_storage
+
+# Inference Endpoint API
+list_inference_endpoints = api.list_inference_endpoints
+create_inference_endpoint = api.create_inference_endpoint
+get_inference_endpoint = api.get_inference_endpoint
+update_inference_endpoint = api.update_inference_endpoint
+delete_inference_endpoint = api.delete_inference_endpoint
+pause_inference_endpoint = api.pause_inference_endpoint
+resume_inference_endpoint = api.resume_inference_endpoint
+scale_to_zero_inference_endpoint = api.scale_to_zero_inference_endpoint
+
+# Collections API
+get_collection = api.get_collection
+list_collections = api.list_collections
+create_collection = api.create_collection
+update_collection_metadata = api.update_collection_metadata
+delete_collection = api.delete_collection
+add_collection_item = api.add_collection_item
+update_collection_item = api.update_collection_item
+delete_collection_item = api.delete_collection_item
+delete_collection_item = api.delete_collection_item
+
+# Access requests API
+list_pending_access_requests = api.list_pending_access_requests
+list_accepted_access_requests = api.list_accepted_access_requests
+list_rejected_access_requests = api.list_rejected_access_requests
+cancel_access_request = api.cancel_access_request
+accept_access_request = api.accept_access_request
+reject_access_request = api.reject_access_request
+grant_access = api.grant_access
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/hf_file_system.py b/.venv/lib/python3.10/site-packages/huggingface_hub/hf_file_system.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6843b7074d946d7675d4414a91c19519e178227
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/hf_file_system.py
@@ -0,0 +1,867 @@
+import copy
+import os
+import re
+import tempfile
+from collections import deque
+from dataclasses import dataclass, field
+from datetime import datetime
+from itertools import chain
+from pathlib import Path
+from typing import Any, Dict, List, NoReturn, Optional, Tuple, Union
+from urllib.parse import quote, unquote
+
+import fsspec
+from fsspec.callbacks import _DEFAULT_CALLBACK, NoOpCallback, TqdmCallback
+from fsspec.utils import isfilelike
+from requests import Response
+
+from ._commit_api import CommitOperationCopy, CommitOperationDelete
+from .constants import (
+ DEFAULT_REVISION,
+ ENDPOINT,
+ REPO_TYPE_MODEL,
+ REPO_TYPES_MAPPING,
+ REPO_TYPES_URL_PREFIXES,
+)
+from .file_download import hf_hub_url, http_get
+from .hf_api import HfApi, LastCommitInfo, RepoFile
+from .utils import (
+ EntryNotFoundError,
+ HFValidationError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ hf_raise_for_status,
+ http_backoff,
+)
+
+
+# Regex used to match special revisions with "/" in them (see #1710)
+SPECIAL_REFS_REVISION_REGEX = re.compile(
+ r"""
+ (^refs\/convert\/\w+) # `refs/convert/parquet` revisions
+ |
+ (^refs\/pr\/\d+) # PR revisions
+ """,
+ re.VERBOSE,
+)
+
+
+@dataclass
+class HfFileSystemResolvedPath:
+ """Data structure containing information about a resolved Hugging Face file system path."""
+
+ repo_type: str
+ repo_id: str
+ revision: str
+ path_in_repo: str
+ # The part placed after '@' in the initial path. It can be a quoted or unquoted refs revision.
+ # Used to reconstruct the unresolved path to return to the user.
+ _raw_revision: Optional[str] = field(default=None, repr=False)
+
+ def unresolve(self) -> str:
+ repo_path = REPO_TYPES_URL_PREFIXES.get(self.repo_type, "") + self.repo_id
+ if self._raw_revision:
+ return f"{repo_path}@{self._raw_revision}/{self.path_in_repo}".rstrip("/")
+ elif self.revision != DEFAULT_REVISION:
+ return f"{repo_path}@{safe_revision(self.revision)}/{self.path_in_repo}".rstrip("/")
+ else:
+ return f"{repo_path}/{self.path_in_repo}".rstrip("/")
+
+
+class HfFileSystem(fsspec.AbstractFileSystem):
+ """
+ Access a remote Hugging Face Hub repository as if were a local file system.
+
+ Args:
+ token (`str`, *optional*):
+ Authentication token, obtained with [`HfApi.login`] method. Will default to the stored token.
+
+ Usage:
+
+ ```python
+ >>> from huggingface_hub import HfFileSystem
+
+ >>> fs = HfFileSystem()
+
+ >>> # List files
+ >>> fs.glob("my-username/my-model/*.bin")
+ ['my-username/my-model/pytorch_model.bin']
+ >>> fs.ls("datasets/my-username/my-dataset", detail=False)
+ ['datasets/my-username/my-dataset/.gitattributes', 'datasets/my-username/my-dataset/README.md', 'datasets/my-username/my-dataset/data.json']
+
+ >>> # Read/write files
+ >>> with fs.open("my-username/my-model/pytorch_model.bin") as f:
+ ... data = f.read()
+ >>> with fs.open("my-username/my-model/pytorch_model.bin", "wb") as f:
+ ... f.write(data)
+ ```
+ """
+
+ root_marker = ""
+ protocol = "hf"
+
+ def __init__(
+ self,
+ *args,
+ endpoint: Optional[str] = None,
+ token: Optional[str] = None,
+ **storage_options,
+ ):
+ super().__init__(*args, **storage_options)
+ self.endpoint = endpoint or ENDPOINT
+ self.token = token
+ self._api = HfApi(endpoint=endpoint, token=token)
+ # Maps (repo_type, repo_id, revision) to a 2-tuple with:
+ # * the 1st element indicating whether the repositoy and the revision exist
+ # * the 2nd element being the exception raised if the repository or revision doesn't exist
+ self._repo_and_revision_exists_cache: Dict[
+ Tuple[str, str, Optional[str]], Tuple[bool, Optional[Exception]]
+ ] = {}
+
+ def _repo_and_revision_exist(
+ self, repo_type: str, repo_id: str, revision: Optional[str]
+ ) -> Tuple[bool, Optional[Exception]]:
+ if (repo_type, repo_id, revision) not in self._repo_and_revision_exists_cache:
+ try:
+ self._api.repo_info(repo_id, revision=revision, repo_type=repo_type)
+ except (RepositoryNotFoundError, HFValidationError) as e:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = False, e
+ except RevisionNotFoundError as e:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = False, e
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
+ else:
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)] = True, None
+ self._repo_and_revision_exists_cache[(repo_type, repo_id, None)] = True, None
+ return self._repo_and_revision_exists_cache[(repo_type, repo_id, revision)]
+
+ def resolve_path(self, path: str, revision: Optional[str] = None) -> HfFileSystemResolvedPath:
+ def _align_revision_in_path_with_revision(
+ revision_in_path: Optional[str], revision: Optional[str]
+ ) -> Optional[str]:
+ if revision is not None:
+ if revision_in_path is not None and revision_in_path != revision:
+ raise ValueError(
+ f'Revision specified in path ("{revision_in_path}") and in `revision` argument ("{revision}")'
+ " are not the same."
+ )
+ else:
+ revision = revision_in_path
+ return revision
+
+ path = self._strip_protocol(path)
+ if not path:
+ # can't list repositories at root
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+ elif path.split("/")[0] + "/" in REPO_TYPES_URL_PREFIXES.values():
+ if "/" not in path:
+ # can't list repositories at the repository type level
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+ repo_type, path = path.split("/", 1)
+ repo_type = REPO_TYPES_MAPPING[repo_type]
+ else:
+ repo_type = REPO_TYPE_MODEL
+ if path.count("/") > 0:
+ if "@" in path:
+ repo_id, revision_in_path = path.split("@", 1)
+ if "/" in revision_in_path:
+ match = SPECIAL_REFS_REVISION_REGEX.search(revision_in_path)
+ if match is not None and revision in (None, match.group()):
+ # Handle `refs/convert/parquet` and PR revisions separately
+ path_in_repo = SPECIAL_REFS_REVISION_REGEX.sub("", revision_in_path).lstrip("/")
+ revision_in_path = match.group()
+ else:
+ revision_in_path, path_in_repo = revision_in_path.split("/", 1)
+ else:
+ path_in_repo = ""
+ revision = _align_revision_in_path_with_revision(unquote(revision_in_path), revision)
+ repo_and_revision_exist, err = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ _raise_file_not_found(path, err)
+ else:
+ revision_in_path = None
+ repo_id_with_namespace = "/".join(path.split("/")[:2])
+ path_in_repo_with_namespace = "/".join(path.split("/")[2:])
+ repo_id_without_namespace = path.split("/")[0]
+ path_in_repo_without_namespace = "/".join(path.split("/")[1:])
+ repo_id = repo_id_with_namespace
+ path_in_repo = path_in_repo_with_namespace
+ repo_and_revision_exist, err = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ if isinstance(err, (RepositoryNotFoundError, HFValidationError)):
+ repo_id = repo_id_without_namespace
+ path_in_repo = path_in_repo_without_namespace
+ repo_and_revision_exist, _ = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ _raise_file_not_found(path, err)
+ else:
+ _raise_file_not_found(path, err)
+ else:
+ repo_id = path
+ path_in_repo = ""
+ if "@" in path:
+ repo_id, revision_in_path = path.split("@", 1)
+ revision = _align_revision_in_path_with_revision(unquote(revision_in_path), revision)
+ else:
+ revision_in_path = None
+ repo_and_revision_exist, _ = self._repo_and_revision_exist(repo_type, repo_id, revision)
+ if not repo_and_revision_exist:
+ raise NotImplementedError("Access to repositories lists is not implemented.")
+
+ revision = revision if revision is not None else DEFAULT_REVISION
+ return HfFileSystemResolvedPath(repo_type, repo_id, revision, path_in_repo, _raw_revision=revision_in_path)
+
+ def invalidate_cache(self, path: Optional[str] = None) -> None:
+ if not path:
+ self.dircache.clear()
+ self._repo_and_revision_exists_cache.clear()
+ else:
+ path = self.resolve_path(path).unresolve()
+ while path:
+ self.dircache.pop(path, None)
+ path = self._parent(path)
+
+ def _open(
+ self,
+ path: str,
+ mode: str = "rb",
+ revision: Optional[str] = None,
+ block_size: Optional[int] = None,
+ **kwargs,
+ ) -> "HfFileSystemFile":
+ if "a" in mode:
+ raise NotImplementedError("Appending to remote files is not yet supported.")
+ if block_size == 0:
+ return HfFileSystemStreamFile(self, path, mode=mode, revision=revision, block_size=block_size, **kwargs)
+ else:
+ return HfFileSystemFile(self, path, mode=mode, revision=revision, block_size=block_size, **kwargs)
+
+ def _rm(self, path: str, revision: Optional[str] = None, **kwargs) -> None:
+ resolved_path = self.resolve_path(path, revision=revision)
+ self._api.delete_file(
+ path_in_repo=resolved_path.path_in_repo,
+ repo_id=resolved_path.repo_id,
+ token=self.token,
+ repo_type=resolved_path.repo_type,
+ revision=resolved_path.revision,
+ commit_message=kwargs.get("commit_message"),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path.unresolve())
+
+ def rm(
+ self,
+ path: str,
+ recursive: bool = False,
+ maxdepth: Optional[int] = None,
+ revision: Optional[str] = None,
+ **kwargs,
+ ) -> None:
+ resolved_path = self.resolve_path(path, revision=revision)
+ paths = self.expand_path(path, recursive=recursive, maxdepth=maxdepth, revision=revision)
+ paths_in_repo = [self.resolve_path(path).path_in_repo for path in paths if not self.isdir(path)]
+ operations = [CommitOperationDelete(path_in_repo=path_in_repo) for path_in_repo in paths_in_repo]
+ commit_message = f"Delete {path} "
+ commit_message += "recursively " if recursive else ""
+ commit_message += f"up to depth {maxdepth} " if maxdepth is not None else ""
+ # TODO: use `commit_description` to list all the deleted paths?
+ self._api.create_commit(
+ repo_id=resolved_path.repo_id,
+ repo_type=resolved_path.repo_type,
+ token=self.token,
+ operations=operations,
+ revision=resolved_path.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path.unresolve())
+
+ def ls(
+ self, path: str, detail: bool = True, refresh: bool = False, revision: Optional[str] = None, **kwargs
+ ) -> List[Union[str, Dict[str, Any]]]:
+ """List the contents of a directory."""
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ kwargs = {"expand_info": detail, **kwargs}
+ try:
+ out = self._ls_tree(path, refresh=refresh, revision=revision, **kwargs)
+ except EntryNotFoundError:
+ # Path could be a file
+ if not resolved_path.path_in_repo:
+ _raise_file_not_found(path, None)
+ out = self._ls_tree(self._parent(path), refresh=refresh, revision=revision, **kwargs)
+ out = [o for o in out if o["name"] == path]
+ if len(out) == 0:
+ _raise_file_not_found(path, None)
+ return out if detail else [o["name"] for o in out]
+
+ def _ls_tree(
+ self,
+ path: str,
+ recursive: bool = False,
+ refresh: bool = False,
+ revision: Optional[str] = None,
+ expand_info: bool = True,
+ ):
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ root_path = HfFileSystemResolvedPath(
+ resolved_path.repo_type,
+ resolved_path.repo_id,
+ resolved_path.revision,
+ path_in_repo="",
+ _raw_revision=resolved_path._raw_revision,
+ ).unresolve()
+
+ out = []
+ if path in self.dircache and not refresh:
+ cached_path_infos = self.dircache[path]
+ out.extend(cached_path_infos)
+ dirs_not_in_dircache = []
+ if recursive:
+ # Use BFS to traverse the cache and build the "recursive "output
+ # (The Hub uses a so-called "tree first" strategy for the tree endpoint but we sort the output to follow the spec so the result is (eventually) the same)
+ dirs_to_visit = deque(
+ [path_info for path_info in cached_path_infos if path_info["type"] == "directory"]
+ )
+ while dirs_to_visit:
+ dir_info = dirs_to_visit.popleft()
+ if dir_info["name"] not in self.dircache:
+ dirs_not_in_dircache.append(dir_info["name"])
+ else:
+ cached_path_infos = self.dircache[dir_info["name"]]
+ out.extend(cached_path_infos)
+ dirs_to_visit.extend(
+ [path_info for path_info in cached_path_infos if path_info["type"] == "directory"]
+ )
+
+ dirs_not_expanded = []
+ if expand_info:
+ # Check if there are directories with non-expanded entries
+ dirs_not_expanded = [self._parent(o["name"]) for o in out if o["last_commit"] is None]
+
+ if (recursive and dirs_not_in_dircache) or (expand_info and dirs_not_expanded):
+ # If the dircache is incomplete, find the common path of the missing and non-expanded entries
+ # and extend the output with the result of `_ls_tree(common_path, recursive=True)`
+ common_prefix = os.path.commonprefix(dirs_not_in_dircache + dirs_not_expanded)
+ # Get the parent directory if the common prefix itself is not a directory
+ common_path = (
+ common_prefix.rstrip("/")
+ if common_prefix.endswith("/")
+ or common_prefix == root_path
+ or common_prefix in chain(dirs_not_in_dircache, dirs_not_expanded)
+ else self._parent(common_prefix)
+ )
+ out = [o for o in out if not o["name"].startswith(common_path + "/")]
+ for cached_path in self.dircache:
+ if cached_path.startswith(common_path + "/"):
+ self.dircache.pop(cached_path, None)
+ self.dircache.pop(common_path, None)
+ out.extend(
+ self._ls_tree(
+ common_path,
+ recursive=recursive,
+ refresh=True,
+ revision=revision,
+ expand_info=expand_info,
+ )
+ )
+ else:
+ tree = self._api.list_repo_tree(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ recursive=recursive,
+ expand=expand_info,
+ revision=resolved_path.revision,
+ repo_type=resolved_path.repo_type,
+ )
+ for path_info in tree:
+ if isinstance(path_info, RepoFile):
+ cache_path_info = {
+ "name": root_path + "/" + path_info.path,
+ "size": path_info.size,
+ "type": "file",
+ "blob_id": path_info.blob_id,
+ "lfs": path_info.lfs,
+ "last_commit": path_info.last_commit,
+ "security": path_info.security,
+ }
+ else:
+ cache_path_info = {
+ "name": root_path + "/" + path_info.path,
+ "size": 0,
+ "type": "directory",
+ "tree_id": path_info.tree_id,
+ "last_commit": path_info.last_commit,
+ }
+ parent_path = self._parent(cache_path_info["name"])
+ self.dircache.setdefault(parent_path, []).append(cache_path_info)
+ out.append(cache_path_info)
+ return copy.deepcopy(out) # copy to not let users modify the dircache
+
+ def glob(self, path, **kwargs):
+ # Set expand_info=False by default to get a x10 speed boost
+ kwargs = {"expand_info": kwargs.get("detail", False), **kwargs}
+ path = self.resolve_path(path, revision=kwargs.get("revision")).unresolve()
+ return super().glob(path, **kwargs)
+
+ def find(
+ self,
+ path: str,
+ maxdepth: Optional[int] = None,
+ withdirs: bool = False,
+ detail: bool = False,
+ refresh: bool = False,
+ revision: Optional[str] = None,
+ **kwargs,
+ ) -> Union[List[str], Dict[str, Dict[str, Any]]]:
+ if maxdepth:
+ return super().find(
+ path, maxdepth=maxdepth, withdirs=withdirs, detail=detail, refresh=refresh, revision=revision, **kwargs
+ )
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ kwargs = {"expand_info": detail, **kwargs}
+ try:
+ out = self._ls_tree(path, recursive=True, refresh=refresh, revision=resolved_path.revision, **kwargs)
+ except EntryNotFoundError:
+ # Path could be a file
+ if self.info(path, revision=revision, **kwargs)["type"] == "file":
+ out = {path: {}}
+ else:
+ out = {}
+ else:
+ if not withdirs:
+ out = [o for o in out if o["type"] != "directory"]
+ else:
+ # If `withdirs=True`, include the directory itself to be consistent with the spec
+ path_info = self.info(path, revision=resolved_path.revision, **kwargs)
+ out = [path_info] + out if path_info["type"] == "directory" else out
+ out = {o["name"]: o for o in out}
+ names = sorted(out)
+ if not detail:
+ return names
+ else:
+ return {name: out[name] for name in names}
+
+ def cp_file(self, path1: str, path2: str, revision: Optional[str] = None, **kwargs) -> None:
+ resolved_path1 = self.resolve_path(path1, revision=revision)
+ resolved_path2 = self.resolve_path(path2, revision=revision)
+
+ same_repo = (
+ resolved_path1.repo_type == resolved_path2.repo_type and resolved_path1.repo_id == resolved_path2.repo_id
+ )
+
+ if same_repo:
+ commit_message = f"Copy {path1} to {path2}"
+ self._api.create_commit(
+ repo_id=resolved_path1.repo_id,
+ repo_type=resolved_path1.repo_type,
+ revision=resolved_path2.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description", ""),
+ operations=[
+ CommitOperationCopy(
+ src_path_in_repo=resolved_path1.path_in_repo,
+ path_in_repo=resolved_path2.path_in_repo,
+ src_revision=resolved_path1.revision,
+ )
+ ],
+ )
+ else:
+ with self.open(path1, "rb", revision=resolved_path1.revision) as f:
+ content = f.read()
+ commit_message = f"Copy {path1} to {path2}"
+ self._api.upload_file(
+ path_or_fileobj=content,
+ path_in_repo=resolved_path2.path_in_repo,
+ repo_id=resolved_path2.repo_id,
+ token=self.token,
+ repo_type=resolved_path2.repo_type,
+ revision=resolved_path2.revision,
+ commit_message=kwargs.get("commit_message", commit_message),
+ commit_description=kwargs.get("commit_description"),
+ )
+ self.invalidate_cache(path=resolved_path1.unresolve())
+ self.invalidate_cache(path=resolved_path2.unresolve())
+
+ def modified(self, path: str, **kwargs) -> datetime:
+ info = self.info(path, **kwargs)
+ return info["last_commit"]["date"]
+
+ def info(self, path: str, refresh: bool = False, revision: Optional[str] = None, **kwargs) -> Dict[str, Any]:
+ resolved_path = self.resolve_path(path, revision=revision)
+ path = resolved_path.unresolve()
+ expand_info = kwargs.get(
+ "expand_info", True
+ ) # don't expose it as a parameter in the public API to follow the spec
+ if not resolved_path.path_in_repo:
+ # Path is the root directory
+ out = {
+ "name": path,
+ "size": 0,
+ "type": "directory",
+ }
+ if expand_info:
+ last_commit = self._api.list_repo_commits(
+ resolved_path.repo_id, repo_type=resolved_path.repo_type, revision=resolved_path.revision
+ )[-1]
+ out = {
+ **out,
+ "tree_id": None, # TODO: tree_id of the root directory?
+ "last_commit": LastCommitInfo(
+ oid=last_commit.commit_id, title=last_commit.title, date=last_commit.created_at
+ ),
+ }
+ else:
+ out = None
+ parent_path = self._parent(path)
+ if parent_path in self.dircache:
+ # Check if the path is in the cache
+ out1 = [o for o in self.dircache[parent_path] if o["name"] == path]
+ if not out1:
+ _raise_file_not_found(path, None)
+ out = out1[0]
+ if refresh or out is None or (expand_info and out and out["last_commit"] is None):
+ paths_info = self._api.get_paths_info(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ expand=expand_info,
+ revision=resolved_path.revision,
+ repo_type=resolved_path.repo_type,
+ )
+ if not paths_info:
+ _raise_file_not_found(path, None)
+ path_info = paths_info[0]
+ root_path = HfFileSystemResolvedPath(
+ resolved_path.repo_type,
+ resolved_path.repo_id,
+ resolved_path.revision,
+ path_in_repo="",
+ _raw_revision=resolved_path._raw_revision,
+ ).unresolve()
+ if isinstance(path_info, RepoFile):
+ out = {
+ "name": root_path + "/" + path_info.path,
+ "size": path_info.size,
+ "type": "file",
+ "blob_id": path_info.blob_id,
+ "lfs": path_info.lfs,
+ "last_commit": path_info.last_commit,
+ "security": path_info.security,
+ }
+ else:
+ out = {
+ "name": root_path + "/" + path_info.path,
+ "size": 0,
+ "type": "directory",
+ "tree_id": path_info.tree_id,
+ "last_commit": path_info.last_commit,
+ }
+ if not expand_info:
+ out = {k: out[k] for k in ["name", "size", "type"]}
+ assert out is not None
+ return copy.deepcopy(out) # copy to not let users modify the dircache
+
+ def exists(self, path, **kwargs):
+ """Is there a file at the given path"""
+ try:
+ self.info(path, **{**kwargs, "expand_info": False})
+ return True
+ except: # noqa: E722
+ # any exception allowed bar FileNotFoundError?
+ return False
+
+ def isdir(self, path):
+ """Is this entry directory-like?"""
+ try:
+ return self.info(path, expand_info=False)["type"] == "directory"
+ except OSError:
+ return False
+
+ def isfile(self, path):
+ """Is this entry file-like?"""
+ try:
+ return self.info(path, expand_info=False)["type"] == "file"
+ except: # noqa: E722
+ return False
+
+ def url(self, path: str) -> str:
+ """Get the HTTP URL of the given path"""
+ resolved_path = self.resolve_path(path)
+ url = hf_hub_url(
+ resolved_path.repo_id,
+ resolved_path.path_in_repo,
+ repo_type=resolved_path.repo_type,
+ revision=resolved_path.revision,
+ endpoint=self.endpoint,
+ )
+ if self.isdir(path):
+ url = url.replace("/resolve/", "/tree/", 1)
+ return url
+
+ def get_file(self, rpath, lpath, callback=_DEFAULT_CALLBACK, outfile=None, **kwargs) -> None:
+ """Copy single remote file to local."""
+ revision = kwargs.get("revision")
+ unhandled_kwargs = set(kwargs.keys()) - {"revision"}
+ if not isinstance(callback, (NoOpCallback, TqdmCallback)) or len(unhandled_kwargs) > 0:
+ # for now, let's not handle custom callbacks
+ # and let's not handle custom kwargs
+ return super().get_file(rpath, lpath, callback=callback, outfile=outfile, **kwargs)
+
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/47b445ae4c284a82dd15e0287b1ffc410e8fc470/fsspec/spec.py#L883
+ if isfilelike(lpath):
+ outfile = lpath
+ elif self.isdir(rpath):
+ os.makedirs(lpath, exist_ok=True)
+ return None
+
+ if isinstance(lpath, (str, Path)): # otherwise, let's assume it's a file-like object
+ os.makedirs(os.path.dirname(lpath), exist_ok=True)
+
+ # Open file if not already open
+ close_file = False
+ if outfile is None:
+ outfile = open(lpath, "wb")
+ close_file = True
+ initial_pos = outfile.tell()
+
+ # Custom implementation of `get_file` to use `http_get`.
+ resolve_remote_path = self.resolve_path(rpath, revision=revision)
+ expected_size = self.info(rpath, revision=revision)["size"]
+ callback.set_size(expected_size)
+ try:
+ http_get(
+ url=hf_hub_url(
+ repo_id=resolve_remote_path.repo_id,
+ revision=resolve_remote_path.revision,
+ filename=resolve_remote_path.path_in_repo,
+ repo_type=resolve_remote_path.repo_type,
+ endpoint=self.endpoint,
+ ),
+ temp_file=outfile,
+ displayed_filename=rpath,
+ expected_size=expected_size,
+ resume_size=0,
+ headers=self._api._build_hf_headers(),
+ _tqdm_bar=callback.tqdm if isinstance(callback, TqdmCallback) else None,
+ )
+ outfile.seek(initial_pos)
+ finally:
+ # Close file only if we opened it ourselves
+ if close_file:
+ outfile.close()
+
+ @property
+ def transaction(self):
+ """A context within which files are committed together upon exit
+
+ Requires the file class to implement `.commit()` and `.discard()`
+ for the normal and exception cases.
+ """
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/3fbb6fee33b46cccb015607630843dea049d3243/fsspec/spec.py#L231
+ # See https://github.com/huggingface/huggingface_hub/issues/1733
+ raise NotImplementedError("Transactional commits are not supported.")
+
+ def start_transaction(self):
+ """Begin write transaction for deferring files, non-context version"""
+ # Taken from https://github.com/fsspec/filesystem_spec/blob/3fbb6fee33b46cccb015607630843dea049d3243/fsspec/spec.py#L241
+ # See https://github.com/huggingface/huggingface_hub/issues/1733
+ raise NotImplementedError("Transactional commits are not supported.")
+
+
+class HfFileSystemFile(fsspec.spec.AbstractBufferedFile):
+ def __init__(self, fs: HfFileSystem, path: str, revision: Optional[str] = None, **kwargs):
+ try:
+ self.resolved_path = fs.resolve_path(path, revision=revision)
+ except FileNotFoundError as e:
+ if "w" in kwargs.get("mode", ""):
+ raise FileNotFoundError(
+ f"{e}.\nMake sure the repository and revision exist before writing data."
+ ) from e
+ raise
+ super().__init__(fs, self.resolved_path.unresolve(), **kwargs)
+ self.fs: HfFileSystem
+
+ def __del__(self):
+ if not hasattr(self, "resolved_path"):
+ # Means that the constructor failed. Nothing to do.
+ return
+ return super().__del__()
+
+ def _fetch_range(self, start: int, end: int) -> bytes:
+ headers = {
+ "range": f"bytes={start}-{end - 1}",
+ **self.fs._api._build_hf_headers(),
+ }
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ r = http_backoff("GET", url, headers=headers, retry_on_status_codes=(502, 503, 504))
+ hf_raise_for_status(r)
+ return r.content
+
+ def _initiate_upload(self) -> None:
+ self.temp_file = tempfile.NamedTemporaryFile(prefix="hffs-", delete=False)
+
+ def _upload_chunk(self, final: bool = False) -> None:
+ self.buffer.seek(0)
+ block = self.buffer.read()
+ self.temp_file.write(block)
+ if final:
+ self.temp_file.close()
+ self.fs._api.upload_file(
+ path_or_fileobj=self.temp_file.name,
+ path_in_repo=self.resolved_path.path_in_repo,
+ repo_id=self.resolved_path.repo_id,
+ token=self.fs.token,
+ repo_type=self.resolved_path.repo_type,
+ revision=self.resolved_path.revision,
+ commit_message=self.kwargs.get("commit_message"),
+ commit_description=self.kwargs.get("commit_description"),
+ )
+ os.remove(self.temp_file.name)
+ self.fs.invalidate_cache(
+ path=self.resolved_path.unresolve(),
+ )
+
+ def read(self, length=-1):
+ """Read remote file.
+
+ If `length` is not provided or is -1, the entire file is downloaded and read. On POSIX systems and if
+ `hf_transfer` is not enabled, the file is loaded in memory directly. Otherwise, the file is downloaded to a
+ temporary file and read from there.
+ """
+ if self.mode == "rb" and (length is None or length == -1) and self.loc == 0:
+ with self.fs.open(self.path, "rb", block_size=0) as f: # block_size=0 enables fast streaming
+ return f.read()
+ return super().read(length)
+
+ def url(self) -> str:
+ return self.fs.url(self.path)
+
+
+class HfFileSystemStreamFile(fsspec.spec.AbstractBufferedFile):
+ def __init__(
+ self,
+ fs: HfFileSystem,
+ path: str,
+ mode: str = "rb",
+ revision: Optional[str] = None,
+ block_size: int = 0,
+ cache_type: str = "none",
+ **kwargs,
+ ):
+ if block_size != 0:
+ raise ValueError(f"HfFileSystemStreamFile only supports block_size=0 but got {block_size}")
+ if cache_type != "none":
+ raise ValueError(f"HfFileSystemStreamFile only supports cache_type='none' but got {cache_type}")
+ if "w" in mode:
+ raise ValueError(f"HfFileSystemStreamFile only supports reading but got mode='{mode}'")
+ try:
+ self.resolved_path = fs.resolve_path(path, revision=revision)
+ except FileNotFoundError as e:
+ if "w" in kwargs.get("mode", ""):
+ raise FileNotFoundError(
+ f"{e}.\nMake sure the repository and revision exist before writing data."
+ ) from e
+ # avoid an unnecessary .info() call to instantiate .details
+ self.details = {"name": self.resolved_path.unresolve(), "size": None}
+ super().__init__(
+ fs, self.resolved_path.unresolve(), mode=mode, block_size=block_size, cache_type=cache_type, **kwargs
+ )
+ self.response: Optional[Response] = None
+ self.fs: HfFileSystem
+
+ def seek(self, loc: int, whence: int = 0):
+ if loc == 0 and whence == 1:
+ return
+ if loc == self.loc and whence == 0:
+ return
+ raise ValueError("Cannot seek streaming HF file")
+
+ def read(self, length: int = -1):
+ read_args = (length,) if length >= 0 else ()
+ if self.response is None or self.response.raw.isclosed():
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ self.response = http_backoff(
+ "GET",
+ url,
+ headers=self.fs._api._build_hf_headers(),
+ retry_on_status_codes=(502, 503, 504),
+ stream=True,
+ )
+ hf_raise_for_status(self.response)
+ try:
+ out = self.response.raw.read(*read_args)
+ except Exception:
+ self.response.close()
+
+ # Retry by recreating the connection
+ url = hf_hub_url(
+ repo_id=self.resolved_path.repo_id,
+ revision=self.resolved_path.revision,
+ filename=self.resolved_path.path_in_repo,
+ repo_type=self.resolved_path.repo_type,
+ endpoint=self.fs.endpoint,
+ )
+ self.response = http_backoff(
+ "GET",
+ url,
+ headers={"Range": "bytes=%d-" % self.loc, **self.fs._api._build_hf_headers()},
+ retry_on_status_codes=(502, 503, 504),
+ stream=True,
+ )
+ hf_raise_for_status(self.response)
+ try:
+ out = self.response.raw.read(*read_args)
+ except Exception:
+ self.response.close()
+ raise
+ self.loc += len(out)
+ return out
+
+ def url(self) -> str:
+ return self.fs.url(self.path)
+
+ def __del__(self):
+ if not hasattr(self, "resolved_path"):
+ # Means that the constructor failed. Nothing to do.
+ return
+ return super().__del__()
+
+ def __reduce__(self):
+ return reopen, (self.fs, self.path, self.mode, self.blocksize, self.cache.name)
+
+
+def safe_revision(revision: str) -> str:
+ return revision if SPECIAL_REFS_REVISION_REGEX.match(revision) else safe_quote(revision)
+
+
+def safe_quote(s: str) -> str:
+ return quote(s, safe="")
+
+
+def _raise_file_not_found(path: str, err: Optional[Exception]) -> NoReturn:
+ msg = path
+ if isinstance(err, RepositoryNotFoundError):
+ msg = f"{path} (repository not found)"
+ elif isinstance(err, RevisionNotFoundError):
+ msg = f"{path} (revision not found)"
+ elif isinstance(err, HFValidationError):
+ msg = f"{path} (invalid repository id)"
+ raise FileNotFoundError(msg) from err
+
+
+def reopen(fs: HfFileSystem, path: str, mode: str, block_size: int, cache_type: str):
+ return fs.open(path, mode=mode, block_size=block_size, cache_type=cache_type)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/hub_mixin.py b/.venv/lib/python3.10/site-packages/huggingface_hub/hub_mixin.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb42ef4e23b31148d90d6533f3debc91d6ce84d0
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/hub_mixin.py
@@ -0,0 +1,704 @@
+import inspect
+import json
+import os
+from dataclasses import asdict, dataclass, is_dataclass
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union, get_args
+
+from .constants import CONFIG_NAME, PYTORCH_WEIGHTS_NAME, SAFETENSORS_SINGLE_FILE
+from .file_download import hf_hub_download
+from .hf_api import HfApi
+from .repocard import ModelCard, ModelCardData
+from .utils import (
+ EntryNotFoundError,
+ HfHubHTTPError,
+ SoftTemporaryDirectory,
+ is_jsonable,
+ is_safetensors_available,
+ is_torch_available,
+ logging,
+ validate_hf_hub_args,
+)
+from .utils._deprecation import _deprecate_arguments
+
+
+if TYPE_CHECKING:
+ from _typeshed import DataclassInstance
+
+if is_torch_available():
+ import torch # type: ignore
+
+if is_safetensors_available():
+ from safetensors.torch import load_model as load_model_as_safetensor
+ from safetensors.torch import save_model as save_model_as_safetensor
+
+
+logger = logging.get_logger(__name__)
+
+# Generic variable that is either ModelHubMixin or a subclass thereof
+T = TypeVar("T", bound="ModelHubMixin")
+
+DEFAULT_MODEL_CARD = """
+---
+# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/model-cards
+{{ card_data }}
+---
+
+This model has been pushed to the Hub using **{{ library_name }}**:
+- Repo: {{ repo_url | default("[More Information Needed]", true) }}
+- Docs: {{ docs_url | default("[More Information Needed]", true) }}
+"""
+
+
+@dataclass
+class MixinInfo:
+ library_name: Optional[str] = None
+ tags: Optional[List[str]] = None
+ repo_url: Optional[str] = None
+ docs_url: Optional[str] = None
+
+
+class ModelHubMixin:
+ """
+ A generic mixin to integrate ANY machine learning framework with the Hub.
+
+ To integrate your framework, your model class must inherit from this class. Custom logic for saving/loading models
+ have to be overwritten in [`_from_pretrained`] and [`_save_pretrained`]. [`PyTorchModelHubMixin`] is a good example
+ of mixin integration with the Hub. Check out our [integration guide](../guides/integrations) for more instructions.
+
+ When inheriting from [`ModelHubMixin`], you can define class-level attributes. These attributes are not passed to
+ `__init__` but to the class definition itself. This is useful to define metadata about the library integrating
+ [`ModelHubMixin`].
+
+ Args:
+ library_name (`str`, *optional*):
+ Name of the library integrating ModelHubMixin. Used to generate model card.
+ tags (`List[str]`, *optional*):
+ Tags to be added to the model card. Used to generate model card.
+ repo_url (`str`, *optional*):
+ URL of the library repository. Used to generate model card.
+ docs_url (`str`, *optional*):
+ URL of the library documentation. Used to generate model card.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import ModelHubMixin
+
+ # Inherit from ModelHubMixin
+ >>> class MyCustomModel(
+ ... ModelHubMixin,
+ ... library_name="my-library",
+ ... tags=["x-custom-tag"],
+ ... repo_url="https://github.com/huggingface/my-cool-library",
+ ... docs_url="https://huggingface.co/docs/my-cool-library",
+ ... # ^ optional metadata to generate model card
+ ... ):
+ ... def __init__(self, size: int = 512, device: str = "cpu"):
+ ... # define how to initialize your model
+ ... super().__init__()
+ ... ...
+ ...
+ ... def _save_pretrained(self, save_directory: Path) -> None:
+ ... # define how to serialize your model
+ ... ...
+ ...
+ ... @classmethod
+ ... def from_pretrained(
+ ... cls: Type[T],
+ ... pretrained_model_name_or_path: Union[str, Path],
+ ... *,
+ ... force_download: bool = False,
+ ... resume_download: bool = False,
+ ... proxies: Optional[Dict] = None,
+ ... token: Optional[Union[str, bool]] = None,
+ ... cache_dir: Optional[Union[str, Path]] = None,
+ ... local_files_only: bool = False,
+ ... revision: Optional[str] = None,
+ ... **model_kwargs,
+ ... ) -> T:
+ ... # define how to deserialize your model
+ ... ...
+
+ >>> model = MyCustomModel(size=256, device="gpu")
+
+ # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+
+ # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+
+ # Download and initialize weights from the Hub
+ >>> reloaded_model = MyCustomModel.from_pretrained("username/my-awesome-model")
+ >>> reloaded_model._hub_mixin_config
+ {"size": 256, "device": "gpu"}
+
+ # Model card has been correctly populated
+ >>> from huggingface_hub import ModelCard
+ >>> card = ModelCard.load("username/my-awesome-model")
+ >>> card.data.tags
+ ["x-custom-tag", "pytorch_model_hub_mixin", "model_hub_mixin"]
+ >>> card.data.library_name
+ "my-library"
+ ```
+ """
+
+ _hub_mixin_config: Optional[Union[dict, "DataclassInstance"]] = None
+ # ^ optional config attribute automatically set in `from_pretrained`
+ _hub_mixin_info: MixinInfo
+ # ^ information about the library integrating ModelHubMixin (used to generate model card)
+ _hub_mixin_init_parameters: Dict[str, inspect.Parameter]
+ _hub_mixin_jsonable_default_values: Dict[str, Any]
+ _hub_mixin_inject_config: bool
+ # ^ internal values to handle config
+
+ def __init_subclass__(
+ cls,
+ *,
+ library_name: Optional[str] = None,
+ tags: Optional[List[str]] = None,
+ repo_url: Optional[str] = None,
+ docs_url: Optional[str] = None,
+ ) -> None:
+ """Inspect __init__ signature only once when subclassing + handle modelcard."""
+ super().__init_subclass__()
+
+ # Will be reused when creating modelcard
+ tags = tags or []
+ tags.append("model_hub_mixin")
+ cls._hub_mixin_info = MixinInfo(
+ library_name=library_name,
+ tags=tags,
+ repo_url=repo_url,
+ docs_url=docs_url,
+ )
+
+ # Inspect __init__ signature to handle config
+ cls._hub_mixin_init_parameters = dict(inspect.signature(cls.__init__).parameters)
+ cls._hub_mixin_jsonable_default_values = {
+ param.name: param.default
+ for param in cls._hub_mixin_init_parameters.values()
+ if param.default is not inspect.Parameter.empty and is_jsonable(param.default)
+ }
+ cls._hub_mixin_inject_config = "config" in inspect.signature(cls._from_pretrained).parameters
+
+ def __new__(cls, *args, **kwargs) -> "ModelHubMixin":
+ """Create a new instance of the class and handle config.
+
+ 3 cases:
+ - If `self._hub_mixin_config` is already set, do nothing.
+ - If `config` is passed as a dataclass, set it as `self._hub_mixin_config`.
+ - Otherwise, build `self._hub_mixin_config` from default values and passed values.
+ """
+ instance = super().__new__(cls)
+
+ # If `config` is already set, return early
+ if instance._hub_mixin_config is not None:
+ return instance
+
+ # Infer passed values
+ passed_values = {
+ **{
+ key: value
+ for key, value in zip(
+ # [1:] to skip `self` parameter
+ list(cls._hub_mixin_init_parameters)[1:],
+ args,
+ )
+ },
+ **kwargs,
+ }
+
+ # If config passed as dataclass => set it and return early
+ if is_dataclass(passed_values.get("config")):
+ instance._hub_mixin_config = passed_values["config"]
+ return instance
+
+ # Otherwise, build config from default + passed values
+ init_config = {
+ # default values
+ **cls._hub_mixin_jsonable_default_values,
+ # passed values
+ **{key: value for key, value in passed_values.items() if is_jsonable(value)},
+ }
+ init_config.pop("config", {})
+
+ # Populate `init_config` with provided config
+ provided_config = passed_values.get("config")
+ if isinstance(provided_config, dict):
+ init_config.update(provided_config)
+
+ # Set `config` attribute and return
+ if init_config != {}:
+ instance._hub_mixin_config = init_config
+ return instance
+
+ def save_pretrained(
+ self,
+ save_directory: Union[str, Path],
+ *,
+ config: Optional[Union[dict, "DataclassInstance"]] = None,
+ repo_id: Optional[str] = None,
+ push_to_hub: bool = False,
+ **push_to_hub_kwargs,
+ ) -> Optional[str]:
+ """
+ Save weights in local directory.
+
+ Args:
+ save_directory (`str` or `Path`):
+ Path to directory in which the model weights and configuration will be saved.
+ config (`dict` or `DataclassInstance`, *optional*):
+ Model configuration specified as a key/value dictionary or a dataclass instance.
+ push_to_hub (`bool`, *optional*, defaults to `False`):
+ Whether or not to push your model to the Huggingface Hub after saving it.
+ repo_id (`str`, *optional*):
+ ID of your repository on the Hub. Used only if `push_to_hub=True`. Will default to the folder name if
+ not provided.
+ kwargs:
+ Additional key word arguments passed along to the [`~ModelHubMixin.push_to_hub`] method.
+ """
+ save_directory = Path(save_directory)
+ save_directory.mkdir(parents=True, exist_ok=True)
+
+ # Remove config.json if already exists. After `_save_pretrained` we don't want to overwrite config.json
+ # as it might have been saved by the custom `_save_pretrained` already. However we do want to overwrite
+ # an existing config.json if it was not saved by `_save_pretrained`.
+ config_path = save_directory / CONFIG_NAME
+ config_path.unlink(missing_ok=True)
+
+ # save model weights/files (framework-specific)
+ self._save_pretrained(save_directory)
+
+ # save config (if provided and if not serialized yet in `_save_pretrained`)
+ if config is None:
+ config = self._hub_mixin_config
+ if config is not None:
+ if is_dataclass(config):
+ config = asdict(config) # type: ignore[arg-type]
+ if not config_path.exists():
+ config_str = json.dumps(config, sort_keys=True, indent=2)
+ config_path.write_text(config_str)
+
+ # save model card
+ model_card_path = save_directory / "README.md"
+ if not model_card_path.exists(): # do not overwrite if already exists
+ self.generate_model_card().save(save_directory / "README.md")
+
+ # push to the Hub if required
+ if push_to_hub:
+ kwargs = push_to_hub_kwargs.copy() # soft-copy to avoid mutating input
+ if config is not None: # kwarg for `push_to_hub`
+ kwargs["config"] = config
+ if repo_id is None:
+ repo_id = save_directory.name # Defaults to `save_directory` name
+ return self.push_to_hub(repo_id=repo_id, **kwargs)
+ return None
+
+ def _save_pretrained(self, save_directory: Path) -> None:
+ """
+ Overwrite this method in subclass to define how to save your model.
+ Check out our [integration guide](../guides/integrations) for instructions.
+
+ Args:
+ save_directory (`str` or `Path`):
+ Path to directory in which the model weights and configuration will be saved.
+ """
+ raise NotImplementedError
+
+ @classmethod
+ @validate_hf_hub_args
+ def from_pretrained(
+ cls: Type[T],
+ pretrained_model_name_or_path: Union[str, Path],
+ *,
+ force_download: bool = False,
+ resume_download: bool = False,
+ proxies: Optional[Dict] = None,
+ token: Optional[Union[str, bool]] = None,
+ cache_dir: Optional[Union[str, Path]] = None,
+ local_files_only: bool = False,
+ revision: Optional[str] = None,
+ **model_kwargs,
+ ) -> T:
+ """
+ Download a model from the Huggingface Hub and instantiate it.
+
+ Args:
+ pretrained_model_name_or_path (`str`, `Path`):
+ - Either the `model_id` (string) of a model hosted on the Hub, e.g. `bigscience/bloom`.
+ - Or a path to a `directory` containing model weights saved using
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g., `../path/to/my_model_directory/`.
+ revision (`str`, *optional*):
+ Revision of the model on the Hub. Can be a branch name, a git tag or any commit id.
+ Defaults to the latest commit on `main` branch.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
+ the existing cache.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ Whether to delete incompletely received files. Will attempt to resume the download if such a file exists.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint, e.g., `{'http': 'foo.bar:3128',
+ 'http://hostname': 'foo.bar:4012'}`. The proxies are used on every request.
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the local cached file if it exists.
+ model_kwargs (`Dict`, *optional*):
+ Additional kwargs to pass to the model during initialization.
+ """
+ model_id = str(pretrained_model_name_or_path)
+ config_file: Optional[str] = None
+ if os.path.isdir(model_id):
+ if CONFIG_NAME in os.listdir(model_id):
+ config_file = os.path.join(model_id, CONFIG_NAME)
+ else:
+ logger.warning(f"{CONFIG_NAME} not found in {Path(model_id).resolve()}")
+ else:
+ try:
+ config_file = hf_hub_download(
+ repo_id=model_id,
+ filename=CONFIG_NAME,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ except HfHubHTTPError as e:
+ logger.info(f"{CONFIG_NAME} not found on the HuggingFace Hub: {str(e)}")
+
+ # Read config
+ config = None
+ if config_file is not None:
+ with open(config_file, "r", encoding="utf-8") as f:
+ config = json.load(f)
+
+ # Populate model_kwargs from config
+ for param in cls._hub_mixin_init_parameters.values():
+ if param.name not in model_kwargs and param.name in config:
+ model_kwargs[param.name] = config[param.name]
+
+ # Check if `config` argument was passed at init
+ if "config" in cls._hub_mixin_init_parameters:
+ # Check if `config` argument is a dataclass
+ config_annotation = cls._hub_mixin_init_parameters["config"].annotation
+ if config_annotation is inspect.Parameter.empty:
+ pass # no annotation
+ elif is_dataclass(config_annotation):
+ config = _load_dataclass(config_annotation, config)
+ else:
+ # if Optional/Union annotation => check if a dataclass is in the Union
+ for _sub_annotation in get_args(config_annotation):
+ if is_dataclass(_sub_annotation):
+ config = _load_dataclass(_sub_annotation, config)
+ break
+
+ # Forward config to model initialization
+ model_kwargs["config"] = config
+
+ # Inject config if `**kwargs` are expected
+ if is_dataclass(cls):
+ for key in cls.__dataclass_fields__:
+ if key not in model_kwargs and key in config:
+ model_kwargs[key] = config[key]
+ elif any(param.kind == inspect.Parameter.VAR_KEYWORD for param in cls._hub_mixin_init_parameters.values()):
+ for key, value in config.items():
+ if key not in model_kwargs:
+ model_kwargs[key] = value
+
+ # Finally, also inject if `_from_pretrained` expects it
+ if cls._hub_mixin_inject_config:
+ model_kwargs["config"] = config
+
+ instance = cls._from_pretrained(
+ model_id=str(model_id),
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ local_files_only=local_files_only,
+ token=token,
+ **model_kwargs,
+ )
+
+ # Implicitly set the config as instance attribute if not already set by the class
+ # This way `config` will be available when calling `save_pretrained` or `push_to_hub`.
+ if config is not None and (getattr(instance, "_hub_mixin_config", None) in (None, {})):
+ instance._hub_mixin_config = config
+
+ return instance
+
+ @classmethod
+ def _from_pretrained(
+ cls: Type[T],
+ *,
+ model_id: str,
+ revision: Optional[str],
+ cache_dir: Optional[Union[str, Path]],
+ force_download: bool,
+ proxies: Optional[Dict],
+ resume_download: bool,
+ local_files_only: bool,
+ token: Optional[Union[str, bool]],
+ **model_kwargs,
+ ) -> T:
+ """Overwrite this method in subclass to define how to load your model from pretrained.
+
+ Use [`hf_hub_download`] or [`snapshot_download`] to download files from the Hub before loading them. Most
+ args taken as input can be directly passed to those 2 methods. If needed, you can add more arguments to this
+ method using "model_kwargs". For example [`PyTorchModelHubMixin._from_pretrained`] takes as input a `map_location`
+ parameter to set on which device the model should be loaded.
+
+ Check out our [integration guide](../guides/integrations) for more instructions.
+
+ Args:
+ model_id (`str`):
+ ID of the model to load from the Huggingface Hub (e.g. `bigscience/bloom`).
+ revision (`str`, *optional*):
+ Revision of the model on the Hub. Can be a branch name, a git tag or any commit id. Defaults to the
+ latest commit on `main` branch.
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force (re-)downloading the model weights and configuration files from the Hub, overriding
+ the existing cache.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ Whether to delete incompletely received files. Will attempt to resume the download if such a file exists.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint (e.g., `{'http': 'foo.bar:3128',
+ 'http://hostname': 'foo.bar:4012'}`).
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ cache_dir (`str`, `Path`, *optional*):
+ Path to the folder where cached files are stored.
+ local_files_only (`bool`, *optional*, defaults to `False`):
+ If `True`, avoid downloading the file and return the path to the local cached file if it exists.
+ model_kwargs:
+ Additional keyword arguments passed along to the [`~ModelHubMixin._from_pretrained`] method.
+ """
+ raise NotImplementedError
+
+ @_deprecate_arguments(
+ version="0.23.0",
+ deprecated_args=["api_endpoint"],
+ custom_message="Use `HF_ENDPOINT` environment variable instead.",
+ )
+ @validate_hf_hub_args
+ def push_to_hub(
+ self,
+ repo_id: str,
+ *,
+ config: Optional[Union[dict, "DataclassInstance"]] = None,
+ commit_message: str = "Push model using huggingface_hub.",
+ private: bool = False,
+ token: Optional[str] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ # TODO: remove once deprecated
+ api_endpoint: Optional[str] = None,
+ ) -> str:
+ """
+ Upload model checkpoint to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ repo_id (`str`):
+ ID of the repository to push to (example: `"username/my-model"`).
+ config (`dict` or `DataclassInstance`, *optional*):
+ Model configuration specified as a key/value dictionary or a dataclass instance.
+ commit_message (`str`, *optional*):
+ Message to commit while pushing.
+ private (`bool`, *optional*, defaults to `False`):
+ Whether the repository created should be private.
+ api_endpoint (`str`, *optional*):
+ The API endpoint to use when pushing the model to the hub.
+ token (`str`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. By default, it will use the token
+ cached when running `huggingface-cli login`.
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit. Defaults to `False`.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+
+ Returns:
+ The url of the commit of your model in the given repository.
+ """
+ api = HfApi(endpoint=api_endpoint, token=token)
+ repo_id = api.create_repo(repo_id=repo_id, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ self.save_pretrained(saved_path, config=config)
+ return api.upload_folder(
+ repo_id=repo_id,
+ repo_type="model",
+ folder_path=saved_path,
+ commit_message=commit_message,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
+
+ def generate_model_card(self, *args, **kwargs) -> ModelCard:
+ card = ModelCard.from_template(
+ card_data=ModelCardData(**asdict(self._hub_mixin_info)),
+ template_str=DEFAULT_MODEL_CARD,
+ )
+ return card
+
+
+class PyTorchModelHubMixin(ModelHubMixin):
+ """
+ Implementation of [`ModelHubMixin`] to provide model Hub upload/download capabilities to PyTorch models. The model
+ is set in evaluation mode by default using `model.eval()` (dropout modules are deactivated). To train the model,
+ you should first set it back in training mode with `model.train()`.
+
+ Example:
+
+ ```python
+ >>> import torch
+ >>> import torch.nn as nn
+ >>> from huggingface_hub import PyTorchModelHubMixin
+
+ >>> class MyModel(
+ ... nn.Module,
+ ... PyTorchModelHubMixin,
+ ... library_name="keras-nlp",
+ ... repo_url="https://github.com/keras-team/keras-nlp",
+ ... docs_url="https://keras.io/keras_nlp/",
+ ... # ^ optional metadata to generate model card
+ ... ):
+ ... def __init__(self, hidden_size: int = 512, vocab_size: int = 30000, output_size: int = 4):
+ ... super().__init__()
+ ... self.param = nn.Parameter(torch.rand(hidden_size, vocab_size))
+ ... self.linear = nn.Linear(output_size, vocab_size)
+
+ ... def forward(self, x):
+ ... return self.linear(x + self.param)
+ >>> model = MyModel(hidden_size=256)
+
+ # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+
+ # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+
+ # Download and initialize weights from the Hub
+ >>> model = MyModel.from_pretrained("username/my-awesome-model")
+ >>> model.hidden_size
+ 256
+ ```
+ """
+
+ def __init_subclass__(cls, *args, tags: Optional[List[str]] = None, **kwargs) -> None:
+ tags = tags or []
+ tags.append("pytorch_model_hub_mixin")
+ kwargs["tags"] = tags
+ return super().__init_subclass__(*args, **kwargs)
+
+ def _save_pretrained(self, save_directory: Path) -> None:
+ """Save weights from a Pytorch model to a local directory."""
+ model_to_save = self.module if hasattr(self, "module") else self # type: ignore
+ save_model_as_safetensor(model_to_save, str(save_directory / SAFETENSORS_SINGLE_FILE))
+
+ @classmethod
+ def _from_pretrained(
+ cls,
+ *,
+ model_id: str,
+ revision: Optional[str],
+ cache_dir: Optional[Union[str, Path]],
+ force_download: bool,
+ proxies: Optional[Dict],
+ resume_download: bool,
+ local_files_only: bool,
+ token: Union[str, bool, None],
+ map_location: str = "cpu",
+ strict: bool = False,
+ **model_kwargs,
+ ):
+ """Load Pytorch pretrained weights and return the loaded model."""
+ model = cls(**model_kwargs)
+ if os.path.isdir(model_id):
+ print("Loading weights from local directory")
+ model_file = os.path.join(model_id, SAFETENSORS_SINGLE_FILE)
+ return cls._load_as_safetensor(model, model_file, map_location, strict)
+ else:
+ try:
+ model_file = hf_hub_download(
+ repo_id=model_id,
+ filename=SAFETENSORS_SINGLE_FILE,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ return cls._load_as_safetensor(model, model_file, map_location, strict)
+ except EntryNotFoundError:
+ model_file = hf_hub_download(
+ repo_id=model_id,
+ filename=PYTORCH_WEIGHTS_NAME,
+ revision=revision,
+ cache_dir=cache_dir,
+ force_download=force_download,
+ proxies=proxies,
+ resume_download=resume_download,
+ token=token,
+ local_files_only=local_files_only,
+ )
+ return cls._load_as_pickle(model, model_file, map_location, strict)
+
+ @classmethod
+ def _load_as_pickle(cls, model: T, model_file: str, map_location: str, strict: bool) -> T:
+ state_dict = torch.load(model_file, map_location=torch.device(map_location))
+ model.load_state_dict(state_dict, strict=strict) # type: ignore
+ model.eval() # type: ignore
+ return model
+
+ @classmethod
+ def _load_as_safetensor(cls, model: T, model_file: str, map_location: str, strict: bool) -> T:
+ load_model_as_safetensor(model, model_file, strict=strict) # type: ignore [arg-type]
+ if map_location != "cpu":
+ # TODO: remove this once https://github.com/huggingface/safetensors/pull/449 is merged.
+ logger.warning(
+ "Loading model weights on other devices than 'cpu' is not supported natively."
+ " This means that the model is loaded on 'cpu' first and then copied to the device."
+ " This leads to a slower loading time."
+ " Support for loading directly on other devices is planned to be added in future releases."
+ " See https://github.com/huggingface/huggingface_hub/pull/2086 for more details."
+ )
+ model.to(map_location) # type: ignore [attr-defined]
+ return model
+
+
+def _load_dataclass(datacls: Type["DataclassInstance"], data: dict) -> "DataclassInstance":
+ """Load a dataclass instance from a dictionary.
+
+ Fields not expected by the dataclass are ignored.
+ """
+ return datacls(**{k: v for k, v in data.items() if k in datacls.__dataclass_fields__})
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..90c59a5130646e29a4eb556ccbd15a89056218d4
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_client.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_client.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..673195329d3532d32b34d375a62b1db96c4f2731
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_client.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_common.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_common.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4dbceebe83c111f76917fca2139ef87ab59510cc
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_common.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_templating.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_templating.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3cdab81aa203a412af57f79bf5a4d0667312c428
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_templating.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_types.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_types.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1840b4c19fa87305902cd0955b827c094cd2c4aa
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/__pycache__/_types.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_client.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e9f5faa5b3890a1a17da6f981df3f6052ace841
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_client.py
@@ -0,0 +1,2354 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Related resources:
+# https://huggingface.co/tasks
+# https://huggingface.co/docs/huggingface.js/inference/README
+# https://github.com/huggingface/huggingface.js/tree/main/packages/inference/src
+# https://github.com/huggingface/text-generation-inference/tree/main/clients/python
+# https://github.com/huggingface/text-generation-inference/blob/main/clients/python/text_generation/client.py
+# https://huggingface.slack.com/archives/C03E4DQ9LAJ/p1680169099087869
+# https://github.com/huggingface/unity-api#tasks
+#
+# Some TODO:
+# - add all tasks
+#
+# NOTE: the philosophy of this client is "let's make it as easy as possible to use it, even if less optimized". Some
+# examples of how it translates:
+# - Timeout / Server unavailable is handled by the client in a single "timeout" parameter.
+# - Files can be provided as bytes, file paths, or URLs and the client will try to "guess" the type.
+# - Images are parsed as PIL.Image for easier manipulation.
+# - Provides a "recommended model" for each task => suboptimal but user-wise quicker to get a first script running.
+# - Only the main parameters are publicly exposed. Power users can always read the docs for more options.
+import base64
+import logging
+import time
+import warnings
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ Iterable,
+ List,
+ Literal,
+ Optional,
+ Union,
+ overload,
+)
+
+from requests import HTTPError
+from requests.structures import CaseInsensitiveDict
+
+from huggingface_hub.constants import ALL_INFERENCE_API_FRAMEWORKS, INFERENCE_ENDPOINT, MAIN_INFERENCE_API_FRAMEWORKS
+from huggingface_hub.errors import InferenceTimeoutError
+from huggingface_hub.inference._common import (
+ TASKS_EXPECTING_IMAGES,
+ ContentT,
+ ModelStatus,
+ _b64_encode,
+ _b64_to_image,
+ _bytes_to_dict,
+ _bytes_to_image,
+ _bytes_to_list,
+ _fetch_recommended_models,
+ _import_numpy,
+ _is_chat_completion_server,
+ _is_tgi_server,
+ _open_as_binary,
+ _set_as_non_chat_completion_server,
+ _set_as_non_tgi,
+ _stream_chat_completion_response_from_bytes,
+ _stream_chat_completion_response_from_text_generation,
+ _stream_text_generation_response,
+ raise_text_generation_error,
+)
+from huggingface_hub.inference._generated.types import (
+ AudioClassificationOutputElement,
+ AudioToAudioOutputElement,
+ AutomaticSpeechRecognitionOutput,
+ ChatCompletionOutput,
+ ChatCompletionOutputChoice,
+ ChatCompletionOutputChoiceMessage,
+ ChatCompletionStreamOutput,
+ DocumentQuestionAnsweringOutputElement,
+ FillMaskOutputElement,
+ ImageClassificationOutputElement,
+ ImageSegmentationOutputElement,
+ ImageToTextOutput,
+ ObjectDetectionOutputElement,
+ QuestionAnsweringOutputElement,
+ SummarizationOutput,
+ TableQuestionAnsweringOutputElement,
+ TextClassificationOutputElement,
+ TextGenerationOutput,
+ TextGenerationStreamOutput,
+ TokenClassificationOutputElement,
+ TranslationOutput,
+ VisualQuestionAnsweringOutputElement,
+ ZeroShotClassificationOutputElement,
+ ZeroShotImageClassificationOutputElement,
+)
+from huggingface_hub.inference._templating import render_chat_prompt
+from huggingface_hub.inference._types import (
+ ConversationalOutput, # soon to be removed
+)
+from huggingface_hub.utils import (
+ BadRequestError,
+ build_hf_headers,
+ get_session,
+ hf_raise_for_status,
+)
+
+
+if TYPE_CHECKING:
+ import numpy as np
+ from PIL import Image
+
+logger = logging.getLogger(__name__)
+
+
+class InferenceClient:
+ """
+ Initialize a new Inference Client.
+
+ [`InferenceClient`] aims to provide a unified experience to perform inference. The client can be used
+ seamlessly with either the (free) Inference API or self-hosted Inference Endpoints.
+
+ Args:
+ model (`str`, `optional`):
+ The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `bigcode/starcoder`
+ or a URL to a deployed Inference Endpoint. Defaults to None, in which case a recommended model is
+ automatically selected for the task.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Pass `token=False` if you don't want to send your token to the server.
+ timeout (`float`, `optional`):
+ The maximum number of seconds to wait for a response from the server. Loading a new model in Inference
+ API can take up to several minutes. Defaults to None, meaning it will loop until the server is available.
+ headers (`Dict[str, str]`, `optional`):
+ Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
+ Values in this dictionary will override the default values.
+ cookies (`Dict[str, str]`, `optional`):
+ Additional cookies to send to the server.
+ """
+
+ def __init__(
+ self,
+ model: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ timeout: Optional[float] = None,
+ headers: Optional[Dict[str, str]] = None,
+ cookies: Optional[Dict[str, str]] = None,
+ ) -> None:
+ self.model: Optional[str] = model
+ self.token: Union[str, bool, None] = token
+ self.headers = CaseInsensitiveDict(build_hf_headers(token=token)) # contains 'authorization' + 'user-agent'
+ if headers is not None:
+ self.headers.update(headers)
+ self.cookies = cookies
+ self.timeout = timeout
+
+ def __repr__(self):
+ return f""
+
+ @overload
+ def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[False] = ...,
+ ) -> bytes: ...
+
+ @overload
+ def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[True] = ...,
+ ) -> Iterable[bytes]: ...
+
+ @overload
+ def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, Iterable[bytes]]: ...
+
+ def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, Iterable[bytes]]:
+ """
+ Make a POST request to the inference server.
+
+ Args:
+ json (`Union[str, Dict, List]`, *optional*):
+ The JSON data to send in the request body, specific to each task. Defaults to None.
+ data (`Union[str, Path, bytes, BinaryIO]`, *optional*):
+ The content to send in the request body, specific to each task.
+ It can be raw bytes, a pointer to an opened file, a local file path,
+ or a URL to an online resource (image, audio file,...). If both `json` and `data` are passed,
+ `data` will take precedence. At least `json` or `data` must be provided. Defaults to None.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. Will override the model defined at the instance level. Defaults to None.
+ task (`str`, *optional*):
+ The task to perform on the inference. All available tasks can be found
+ [here](https://huggingface.co/tasks). Used only to default to a recommended model if `model` is not
+ provided. At least `model` or `task` must be provided. Defaults to None.
+ stream (`bool`, *optional*):
+ Whether to iterate over streaming APIs.
+
+ Returns:
+ bytes: The raw bytes returned by the server.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ """
+ url = self._resolve_url(model, task)
+
+ if data is not None and json is not None:
+ warnings.warn("Ignoring `json` as `data` is passed as binary.")
+
+ # Set Accept header if relevant
+ headers = self.headers.copy()
+ if task in TASKS_EXPECTING_IMAGES and "Accept" not in headers:
+ headers["Accept"] = "image/png"
+
+ t0 = time.time()
+ timeout = self.timeout
+ while True:
+ with _open_as_binary(data) as data_as_binary:
+ try:
+ response = get_session().post(
+ url,
+ json=json,
+ data=data_as_binary,
+ headers=headers,
+ cookies=self.cookies,
+ timeout=self.timeout,
+ stream=stream,
+ )
+ except TimeoutError as error:
+ # Convert any `TimeoutError` to a `InferenceTimeoutError`
+ raise InferenceTimeoutError(f"Inference call timed out: {url}") from error # type: ignore
+
+ try:
+ hf_raise_for_status(response)
+ return response.iter_lines() if stream else response.content
+ except HTTPError as error:
+ if error.response.status_code == 422 and task is not None:
+ error.args = (
+ f"{error.args[0]}\nMake sure '{task}' task is supported by the model.",
+ ) + error.args[1:]
+ if error.response.status_code == 503:
+ # If Model is unavailable, either raise a TimeoutError...
+ if timeout is not None and time.time() - t0 > timeout:
+ raise InferenceTimeoutError(
+ f"Model not loaded on the server: {url}. Please retry with a higher timeout (current:"
+ f" {self.timeout}).",
+ request=error.request,
+ response=error.response,
+ ) from error
+ # ...or wait 1s and retry
+ logger.info(f"Waiting for model to be loaded on the server: {error}")
+ time.sleep(1)
+ if timeout is not None:
+ timeout = max(self.timeout - (time.time() - t0), 1) # type: ignore
+ continue
+ raise
+
+ def audio_classification(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioClassificationOutputElement]:
+ """
+ Perform audio classification on the provided audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content to classify. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model to use for audio classification. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio classification will be used.
+
+ Returns:
+ `List[AudioClassificationOutputElement]`: List of [`AudioClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.audio_classification("audio.flac")
+ [
+ AudioClassificationOutputElement(score=0.4976358711719513, label='hap'),
+ AudioClassificationOutputElement(score=0.3677836060523987, label='neu'),
+ ...
+ ]
+ ```
+ """
+ response = self.post(data=audio, model=model, task="audio-classification")
+ return AudioClassificationOutputElement.parse_obj_as_list(response)
+
+ def audio_to_audio(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioToAudioOutputElement]:
+ """
+ Performs multiple tasks related to audio-to-audio depending on the model (eg: speech enhancement, source separation).
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content for the model. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model can be any model which takes an audio file and returns another audio file. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio_to_audio will be used.
+
+ Returns:
+ `List[AudioToAudioOutputElement]`: A list of [`AudioToAudioOutputElement`] items containing audios label, content-type, and audio content in blob.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> audio_output = client.audio_to_audio("audio.flac")
+ >>> for i, item in enumerate(audio_output):
+ >>> with open(f"output_{i}.flac", "wb") as f:
+ f.write(item.blob)
+ ```
+ """
+ response = self.post(data=audio, model=model, task="audio-to-audio")
+ audio_output = AudioToAudioOutputElement.parse_obj_as_list(response)
+ for item in audio_output:
+ item.blob = base64.b64decode(item.blob)
+ return audio_output
+
+ def automatic_speech_recognition(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> AutomaticSpeechRecognitionOutput:
+ """
+ Perform automatic speech recognition (ASR or audio-to-text) on the given audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The content to transcribe. It can be raw audio bytes, local audio file, or a URL to an audio file.
+ model (`str`, *optional*):
+ The model to use for ASR. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for ASR will be used.
+
+ Returns:
+ [`AutomaticSpeechRecognitionOutput`]: An item containing the transcribed text and optionally the timestamp chunks.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.automatic_speech_recognition("hello_world.flac").text
+ "hello world"
+ ```
+ """
+ response = self.post(data=audio, model=model, task="automatic-speech-recognition")
+ return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
+
+ @overload
+ def chat_completion( # type: ignore
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[False] = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> ChatCompletionOutput: ...
+
+ @overload
+ def chat_completion( # type: ignore
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[True] = True,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> Iterable[ChatCompletionStreamOutput]: ...
+
+ @overload
+ def chat_completion(
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]: ...
+
+ def chat_completion(
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]:
+ """
+ A method for completing conversations using a specified language model.
+
+
+
+ If the model is served by a server supporting chat-completion, the method will directly call the server's
+ `/v1/chat/completions` endpoint. If the server does not support chat-completion, the method will render the
+ chat template client-side based on the information fetched from the Hub API. In this case, you will need to
+ have `minijinja` template engine installed. Run `pip install "huggingface_hub[inference]"` or `pip install minijinja`
+ to install it.
+
+
+
+ Args:
+ messages (List[Union[`SystemMessage`, `UserMessage`, `AssistantMessage`]]):
+ Conversation history consisting of roles and content pairs.
+ model (`str`, *optional*):
+ The model to use for chat-completion. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for chat-based text-generation will be used.
+ See https://huggingface.co/tasks/text-generation for more details.
+ frequency_penalty (`float`, optional):
+ Penalizes new tokens based on their existing frequency
+ in the text so far. Range: [-2.0, 2.0]. Defaults to 0.0.
+ max_tokens (`int`, optional):
+ Maximum number of tokens allowed in the response. Defaults to 20.
+ seed (Optional[`int`], optional):
+ Seed for reproducible control flow. Defaults to None.
+ stop (Optional[`str`], optional):
+ Up to four strings which trigger the end of the response.
+ Defaults to None.
+ stream (`bool`, optional):
+ Enable realtime streaming of responses. Defaults to False.
+ temperature (`float`, optional):
+ Controls randomness of the generations. Lower values ensure
+ less random completions. Range: [0, 2]. Defaults to 1.0.
+ top_p (`float`, optional):
+ Fraction of the most likely next words to sample from.
+ Must be between 0 and 1. Defaults to 1.0.
+
+ Returns:
+ `Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False`, the generated text is returned as a [`ChatCompletionOutput`] (default).
+ - if `stream=True`, the generated text is returned token by token as a sequence of [`ChatCompletionStreamOutput`].
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
+ >>> client.chat_completion(messages, max_tokens=100)
+ ChatCompletionOutput(
+ choices=[
+ ChatCompletionOutputChoice(
+ finish_reason='eos_token',
+ index=0,
+ message=ChatCompletionOutputChoiceMessage(
+ content='The capital of France is Paris. The official name of the city is "Ville de Paris" (City of Paris) and the name of the country\'s governing body, which is located in Paris, is "La République française" (The French Republic). \nI hope that helps! Let me know if you need any further information.'
+ )
+ )
+ ],
+ created=1710498360
+ )
+
+ >>> for token in client.chat_completion(messages, max_tokens=10, stream=True):
+ ... print(token)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content='The', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' capital', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ (...)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' may', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=None, role=None), index=0, finish_reason='length')], created=1710498504)
+ ```
+ """
+ # determine model
+ model = model or self.model or self.get_recommended_model("text-generation")
+
+ if _is_chat_completion_server(model):
+ # First, let's consider the server has a `/v1/chat/completions` endpoint.
+ # If that's the case, we don't have to render the chat template client-side.
+ model_url = self._resolve_url(model)
+ if not model_url.endswith("/chat/completions"):
+ model_url += "/v1/chat/completions"
+
+ try:
+ data = self.post(
+ model=model_url,
+ json=dict(
+ model="tgi", # random string
+ messages=messages,
+ max_tokens=max_tokens,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_p=top_p,
+ stream=stream,
+ ),
+ stream=stream,
+ )
+ except HTTPError:
+ # Let's consider the server is not a chat completion server.
+ # Then we call again `chat_completion` which will render the chat template client side.
+ # (can be HTTP 500, HTTP 400, HTTP 404 depending on the server)
+ _set_as_non_chat_completion_server(model)
+ return self.chat_completion(
+ messages=messages,
+ model=model,
+ stream=stream,
+ max_tokens=max_tokens,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_p=top_p,
+ )
+
+ if stream:
+ return _stream_chat_completion_response_from_bytes(data) # type: ignore[arg-type]
+
+ return ChatCompletionOutput.parse_obj_as_instance(data) # type: ignore[arg-type]
+
+ # At this point, we know the server is not a chat completion server.
+ # We need to render the chat template client side based on the information we can fetch from
+ # the Hub API.
+
+ model_id = None
+ if model.startswith(("http://", "https://")):
+ # If URL, we need to know which model is served. This is not always possible.
+ # A workaround is to list the user Inference Endpoints and check if one of them correspond to the model URL.
+ # If not, we raise an error.
+ # TODO: fix when we have a proper API for this (at least for Inference Endpoints)
+ # TODO: what if Sagemaker URL?
+ # TODO: what if Azure URL?
+ from ..hf_api import HfApi
+
+ for endpoint in HfApi(token=self.token).list_inference_endpoints():
+ if endpoint.url == model:
+ model_id = endpoint.repository
+ break
+ else:
+ model_id = model
+
+ if model_id is None:
+ # If we don't have the model ID, we can't fetch the chat template.
+ # We raise an error.
+ raise ValueError(
+ "Request can't be processed as the model ID can't be inferred from model URL. "
+ "This is needed to fetch the chat template from the Hub since the model is not "
+ "served with a Chat-completion API."
+ )
+
+ # fetch chat template + tokens
+ prompt = render_chat_prompt(model_id=model_id, token=self.token, messages=messages)
+
+ # generate response
+ stop_sequences = [stop] if isinstance(stop, str) else stop
+ text_generation_output = self.text_generation(
+ prompt=prompt,
+ details=True,
+ stream=stream,
+ model=model,
+ max_new_tokens=max_tokens,
+ seed=seed,
+ stop_sequences=stop_sequences,
+ temperature=temperature,
+ top_p=top_p,
+ )
+
+ created = int(time.time())
+
+ if stream:
+ return _stream_chat_completion_response_from_text_generation(text_generation_output) # type: ignore [arg-type]
+
+ if isinstance(text_generation_output, TextGenerationOutput):
+ # General use case => format ChatCompletionOutput from text generation details
+ content: str = text_generation_output.generated_text
+ finish_reason: str = text_generation_output.details.finish_reason # type: ignore[union-attr]
+ else:
+ # Corner case: if server doesn't support details (e.g. if not a TGI server), we only receive an output string.
+ # In such a case, `finish_reason` is set to `"unk"`.
+ content = text_generation_output # type: ignore[assignment]
+ finish_reason = "unk"
+
+ return ChatCompletionOutput(
+ created=created,
+ choices=[
+ ChatCompletionOutputChoice(
+ finish_reason=finish_reason, # type: ignore
+ index=0,
+ message=ChatCompletionOutputChoiceMessage(
+ content=content,
+ role="assistant",
+ ),
+ )
+ ],
+ )
+
+ def conversational(
+ self,
+ text: str,
+ generated_responses: Optional[List[str]] = None,
+ past_user_inputs: Optional[List[str]] = None,
+ *,
+ parameters: Optional[Dict[str, Any]] = None,
+ model: Optional[str] = None,
+ ) -> ConversationalOutput:
+ """
+ Generate conversational responses based on the given input text (i.e. chat with the API).
+
+
+
+ [`InferenceClient.conversational`] API is deprecated and will be removed in a future release. Please use
+ [`InferenceClient.chat_completion`] instead.
+
+
+
+ Args:
+ text (`str`):
+ The last input from the user in the conversation.
+ generated_responses (`List[str]`, *optional*):
+ A list of strings corresponding to the earlier replies from the model. Defaults to None.
+ past_user_inputs (`List[str]`, *optional*):
+ A list of strings corresponding to the earlier replies from the user. Should be the same length as
+ `generated_responses`. Defaults to None.
+ parameters (`Dict[str, Any]`, *optional*):
+ Additional parameters for the conversational task. Defaults to None. For more details about the available
+ parameters, please refer to [this page](https://huggingface.co/docs/api-inference/detailed_parameters#conversational-task)
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `Dict`: The generated conversational output.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> output = client.conversational("Hi, who are you?")
+ >>> output
+ {'generated_text': 'I am the one who knocks.', 'conversation': {'generated_responses': ['I am the one who knocks.'], 'past_user_inputs': ['Hi, who are you?']}, 'warnings': ['Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.']}
+ >>> client.conversational(
+ ... "Wow, that's scary!",
+ ... generated_responses=output["conversation"]["generated_responses"],
+ ... past_user_inputs=output["conversation"]["past_user_inputs"],
+ ... )
+ ```
+ """
+ warnings.warn(
+ "'InferenceClient.conversational' is deprecated and will be removed starting from huggingface_hub>=0.25. "
+ "Please use the more appropriate 'InferenceClient.chat_completion' API instead.",
+ FutureWarning,
+ )
+ payload: Dict[str, Any] = {"inputs": {"text": text}}
+ if generated_responses is not None:
+ payload["inputs"]["generated_responses"] = generated_responses
+ if past_user_inputs is not None:
+ payload["inputs"]["past_user_inputs"] = past_user_inputs
+ if parameters is not None:
+ payload["parameters"] = parameters
+ response = self.post(json=payload, model=model, task="conversational")
+ return _bytes_to_dict(response) # type: ignore
+
+ def document_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ ) -> List[DocumentQuestionAnsweringOutputElement]:
+ """
+ Answer questions on document images.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the document question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended document question answering model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[DocumentQuestionAnsweringOutputElement]`: a list of [`DocumentQuestionAnsweringOutputElement`] items containing the predicted label, associated probability, word ids, and page number.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.document_question_answering(image="https://huggingface.co/spaces/impira/docquery/resolve/2359223c1837a7587402bda0f2643382a6eefeab/invoice.png", question="What is the invoice number?")
+ [DocumentQuestionAnsweringOutputElement(score=0.42515629529953003, answer='us-001', start=16, end=16)]
+ ```
+ """
+ payload: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ response = self.post(json=payload, model=model, task="document-question-answering")
+ return DocumentQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ def feature_extraction(self, text: str, *, model: Optional[str] = None) -> "np.ndarray":
+ """
+ Generate embeddings for a given text.
+
+ Args:
+ text (`str`):
+ The text to embed.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `np.ndarray`: The embedding representing the input text as a float32 numpy array.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.feature_extraction("Hi, who are you?")
+ array([[ 2.424802 , 2.93384 , 1.1750331 , ..., 1.240499, -0.13776633, -0.7889173 ],
+ [-0.42943227, -0.6364878 , -1.693462 , ..., 0.41978157, -2.4336355 , 0.6162071 ],
+ ...,
+ [ 0.28552425, -0.928395 , -1.2077185 , ..., 0.76810825, -2.1069427 , 0.6236161 ]], dtype=float32)
+ ```
+ """
+ response = self.post(json={"inputs": text}, model=model, task="feature-extraction")
+ np = _import_numpy()
+ return np.array(_bytes_to_dict(response), dtype="float32")
+
+ def fill_mask(self, text: str, *, model: Optional[str] = None) -> List[FillMaskOutputElement]:
+ """
+ Fill in a hole with a missing word (token to be precise).
+
+ Args:
+ text (`str`):
+ a string to be filled from, must contain the [MASK] token (check model card for exact name of the mask).
+ model (`str`, *optional*):
+ The model to use for the fill mask task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended fill mask model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[FillMaskOutputElement]`: a list of [`FillMaskOutputElement`] items containing the predicted label, associated
+ probability, token reference, and completed text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.fill_mask("The goal of life is .")
+ [
+ FillMaskOutputElement(score=0.06897063553333282, token=11098, token_str=' happiness', sequence='The goal of life is happiness.'),
+ FillMaskOutputElement(score=0.06554922461509705, token=45075, token_str=' immortality', sequence='The goal of life is immortality.')
+ ]
+ ```
+ """
+ response = self.post(json={"inputs": text}, model=model, task="fill-mask")
+ return FillMaskOutputElement.parse_obj_as_list(response)
+
+ def image_classification(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ImageClassificationOutputElement]:
+ """
+ Perform image classification on the given image using the specified model.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to classify. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image classification. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image classification will be used.
+
+ Returns:
+ `List[ImageClassificationOutputElement]`: a list of [`ImageClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_classification("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ [ImageClassificationOutputElement(score=0.9779096841812134, label='Blenheim spaniel'), ...]
+ ```
+ """
+ response = self.post(data=image, model=model, task="image-classification")
+ return ImageClassificationOutputElement.parse_obj_as_list(response)
+
+ def image_segmentation(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ImageSegmentationOutputElement]:
+ """
+ Perform image segmentation on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to segment. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image segmentation. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image segmentation will be used.
+
+ Returns:
+ `List[ImageSegmentationOutputElement]`: A list of [`ImageSegmentationOutputElement`] items containing the segmented masks and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_segmentation("cat.jpg"):
+ [ImageSegmentationOutputElement(score=0.989008, label='LABEL_184', mask=), ...]
+ ```
+ """
+ response = self.post(data=image, model=model, task="image-segmentation")
+ output = ImageSegmentationOutputElement.parse_obj_as_list(response)
+ for item in output:
+ item.mask = _b64_to_image(item.mask)
+ return output
+
+ def image_to_image(
+ self,
+ image: ContentT,
+ prompt: Optional[str] = None,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Perform image-to-image translation using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for translation. It can be raw bytes, an image file, or a URL to an online image.
+ prompt (`str`, *optional*):
+ The text prompt to guide the image generation.
+ negative_prompt (`str`, *optional*):
+ A negative prompt to guide the translation process.
+ height (`int`, *optional*):
+ The height in pixels of the generated image.
+ width (`int`, *optional*):
+ The width in pixels of the generated image.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
+ usually at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Image`: The translated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> image = client.image_to_image("cat.jpg", prompt="turn the cat into a tiger")
+ >>> image.save("tiger.jpg")
+ ```
+ """
+ parameters = {
+ "prompt": prompt,
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ }
+ if all(parameter is None for parameter in parameters.values()):
+ # Either only an image to send => send as raw bytes
+ data = image
+ payload: Optional[Dict[str, Any]] = None
+ else:
+ # Or an image + some parameters => use base64 encoding
+ data = None
+ payload = {"inputs": _b64_encode(image)}
+ for key, value in parameters.items():
+ if value is not None:
+ payload.setdefault("parameters", {})[key] = value
+
+ response = self.post(json=payload, data=data, model=model, task="image-to-image")
+ return _bytes_to_image(response)
+
+ def image_to_text(self, image: ContentT, *, model: Optional[str] = None) -> ImageToTextOutput:
+ """
+ Takes an input image and return text.
+
+ Models can have very different outputs depending on your use case (image captioning, optical character recognition
+ (OCR), Pix2Struct, etc). Please have a look to the model card to learn more about a model's specificities.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image..
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`ImageToTextOutput`]: The generated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.image_to_text("cat.jpg")
+ 'a cat standing in a grassy field '
+ >>> client.image_to_text("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ 'a dog laying on the grass next to a flower pot '
+ ```
+ """
+ response = self.post(data=image, model=model, task="image-to-text")
+ return ImageToTextOutput.parse_obj_as_instance(response)
+
+ def list_deployed_models(
+ self, frameworks: Union[None, str, Literal["all"], List[str]] = None
+ ) -> Dict[str, List[str]]:
+ """
+ List models currently deployed on the Inference API service.
+
+ This helper checks deployed models framework by framework. By default, it will check the 4 main frameworks that
+ are supported and account for 95% of the hosted models. However, if you want a complete list of models you can
+ specify `frameworks="all"` as input. Alternatively, if you know before-hand which framework you are interested
+ in, you can also restrict to search to this one (e.g. `frameworks="text-generation-inference"`). The more
+ frameworks are checked, the more time it will take.
+
+
+
+ This endpoint is mostly useful for discoverability. If you already know which model you want to use and want to
+ check its availability, you can directly use [`~InferenceClient.get_model_status`].
+
+
+
+ Args:
+ frameworks (`Literal["all"]` or `List[str]` or `str`, *optional*):
+ The frameworks to filter on. By default only a subset of the available frameworks are tested. If set to
+ "all", all available frameworks will be tested. It is also possible to provide a single framework or a
+ custom set of frameworks to check.
+
+ Returns:
+ `Dict[str, List[str]]`: A dictionary mapping task names to a sorted list of model IDs.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ # Discover zero-shot-classification models currently deployed
+ >>> models = client.list_deployed_models()
+ >>> models["zero-shot-classification"]
+ ['Narsil/deberta-large-mnli-zero-cls', 'facebook/bart-large-mnli', ...]
+
+ # List from only 1 framework
+ >>> client.list_deployed_models("text-generation-inference")
+ {'text-generation': ['bigcode/starcoder', 'meta-llama/Llama-2-70b-chat-hf', ...], ...}
+ ```
+ """
+ # Resolve which frameworks to check
+ if frameworks is None:
+ frameworks = MAIN_INFERENCE_API_FRAMEWORKS
+ elif frameworks == "all":
+ frameworks = ALL_INFERENCE_API_FRAMEWORKS
+ elif isinstance(frameworks, str):
+ frameworks = [frameworks]
+ frameworks = list(set(frameworks))
+
+ # Fetch them iteratively
+ models_by_task: Dict[str, List[str]] = {}
+
+ def _unpack_response(framework: str, items: List[Dict]) -> None:
+ for model in items:
+ if framework == "sentence-transformers":
+ # Model running with the `sentence-transformers` framework can work with both tasks even if not
+ # branded as such in the API response
+ models_by_task.setdefault("feature-extraction", []).append(model["model_id"])
+ models_by_task.setdefault("sentence-similarity", []).append(model["model_id"])
+ else:
+ models_by_task.setdefault(model["task"], []).append(model["model_id"])
+
+ for framework in frameworks:
+ response = get_session().get(f"{INFERENCE_ENDPOINT}/framework/{framework}", headers=self.headers)
+ hf_raise_for_status(response)
+ _unpack_response(framework, response.json())
+
+ # Sort alphabetically for discoverability and return
+ for task, models in models_by_task.items():
+ models_by_task[task] = sorted(set(models), key=lambda x: x.lower())
+ return models_by_task
+
+ def object_detection(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ObjectDetectionOutputElement]:
+ """
+ Perform object detection on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to detect objects on. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for object detection. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for object detection (DETR) will be used.
+
+ Returns:
+ `List[ObjectDetectionOutputElement]`: A list of [`ObjectDetectionOutputElement`] items containing the bounding boxes and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If the request output is not a List.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.object_detection("people.jpg"):
+ [ObjectDetectionOutputElement(score=0.9486683011054993, label='person', box=ObjectDetectionBoundingBox(xmin=59, ymin=39, xmax=420, ymax=510)), ...]
+ ```
+ """
+ # detect objects
+ response = self.post(data=image, model=model, task="object-detection")
+ return ObjectDetectionOutputElement.parse_obj_as_list(response)
+
+ def question_answering(
+ self, question: str, context: str, *, model: Optional[str] = None
+ ) -> QuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from a given text.
+
+ Args:
+ question (`str`):
+ Question to be answered.
+ context (`str`):
+ The context of the question.
+ model (`str`):
+ The model to use for the question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint.
+
+ Returns:
+ [`QuestionAnsweringOutputElement`]: an question answering output containing the score, start index, end index, and answer.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.question_answering(question="What's my name?", context="My name is Clara and I live in Berkeley.")
+ QuestionAnsweringOutputElement(score=0.9326562285423279, start=11, end=16, answer='Clara')
+ ```
+ """
+
+ payload: Dict[str, Any] = {"question": question, "context": context}
+ response = self.post(
+ json=payload,
+ model=model,
+ task="question-answering",
+ )
+ return QuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ def sentence_similarity(
+ self, sentence: str, other_sentences: List[str], *, model: Optional[str] = None
+ ) -> List[float]:
+ """
+ Compute the semantic similarity between a sentence and a list of other sentences by comparing their embeddings.
+
+ Args:
+ sentence (`str`):
+ The main sentence to compare to others.
+ other_sentences (`List[str]`):
+ The list of sentences to compare to.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[float]`: The embedding representing the input text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.sentence_similarity(
+ ... "Machine learning is so easy.",
+ ... other_sentences=[
+ ... "Deep learning is so straightforward.",
+ ... "This is so difficult, like rocket science.",
+ ... "I can't believe how much I struggled with this.",
+ ... ],
+ ... )
+ [0.7785726189613342, 0.45876261591911316, 0.2906220555305481]
+ ```
+ """
+ response = self.post(
+ json={"inputs": {"source_sentence": sentence, "sentences": other_sentences}},
+ model=model,
+ task="sentence-similarity",
+ )
+ return _bytes_to_list(response)
+
+ def summarization(
+ self,
+ text: str,
+ *,
+ parameters: Optional[Dict[str, Any]] = None,
+ model: Optional[str] = None,
+ ) -> SummarizationOutput:
+ """
+ Generate a summary of a given text using a specified model.
+
+ Args:
+ text (`str`):
+ The input text to summarize.
+ parameters (`Dict[str, Any]`, *optional*):
+ Additional parameters for summarization. Check out this [page](https://huggingface.co/docs/api-inference/detailed_parameters#summarization-task)
+ for more details.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`SummarizationOutput`]: The generated summary text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.summarization("The Eiffel tower...")
+ SummarizationOutput(generated_text="The Eiffel tower is one of the most famous landmarks in the world....")
+ ```
+ """
+ payload: Dict[str, Any] = {"inputs": text}
+ if parameters is not None:
+ payload["parameters"] = parameters
+ response = self.post(json=payload, model=model, task="summarization")
+ return SummarizationOutput.parse_obj_as_list(response)[0]
+
+ def table_question_answering(
+ self, table: Dict[str, Any], query: str, *, model: Optional[str] = None
+ ) -> TableQuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from information given in a table.
+
+ Args:
+ table (`str`):
+ A table of data represented as a dict of lists where entries are headers and the lists are all the
+ values, all lists must have the same size.
+ query (`str`):
+ The query in plain text that you want to ask the table.
+ model (`str`):
+ The model to use for the table-question-answering task. Can be a model ID hosted on the Hugging Face
+ Hub or a URL to a deployed Inference Endpoint.
+
+ Returns:
+ [`TableQuestionAnsweringOutputElement`]: a table question answering output containing the answer, coordinates, cells and the aggregator used.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> query = "How many stars does the transformers repository have?"
+ >>> table = {"Repository": ["Transformers", "Datasets", "Tokenizers"], "Stars": ["36542", "4512", "3934"]}
+ >>> client.table_question_answering(table, query, model="google/tapas-base-finetuned-wtq")
+ TableQuestionAnsweringOutputElement(answer='36542', coordinates=[[0, 1]], cells=['36542'], aggregator='AVERAGE')
+ ```
+ """
+ response = self.post(
+ json={
+ "query": query,
+ "table": table,
+ },
+ model=model,
+ task="table-question-answering",
+ )
+ return TableQuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ def tabular_classification(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[str]:
+ """
+ Classifying a target category (a group) based on a set of attributes.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes to classify.
+ model (`str`, *optional*):
+ The model to use for the tabular classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of labels, one per row in the initial table.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> table = {
+ ... "fixed_acidity": ["7.4", "7.8", "10.3"],
+ ... "volatile_acidity": ["0.7", "0.88", "0.32"],
+ ... "citric_acid": ["0", "0", "0.45"],
+ ... "residual_sugar": ["1.9", "2.6", "6.4"],
+ ... "chlorides": ["0.076", "0.098", "0.073"],
+ ... "free_sulfur_dioxide": ["11", "25", "5"],
+ ... "total_sulfur_dioxide": ["34", "67", "13"],
+ ... "density": ["0.9978", "0.9968", "0.9976"],
+ ... "pH": ["3.51", "3.2", "3.23"],
+ ... "sulphates": ["0.56", "0.68", "0.82"],
+ ... "alcohol": ["9.4", "9.8", "12.6"],
+ ... }
+ >>> client.tabular_classification(table=table, model="julien-c/wine-quality")
+ ["5", "5", "5"]
+ ```
+ """
+ response = self.post(json={"table": table}, model=model, task="tabular-classification")
+ return _bytes_to_list(response)
+
+ def tabular_regression(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[float]:
+ """
+ Predicting a numerical target value given a set of attributes/features in a table.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes stored in a table. The attributes used to predict the target can be both numerical and categorical.
+ model (`str`, *optional*):
+ The model to use for the tabular regression task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular regression model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of predicted numerical target values.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> table = {
+ ... "Height": ["11.52", "12.48", "12.3778"],
+ ... "Length1": ["23.2", "24", "23.9"],
+ ... "Length2": ["25.4", "26.3", "26.5"],
+ ... "Length3": ["30", "31.2", "31.1"],
+ ... "Species": ["Bream", "Bream", "Bream"],
+ ... "Width": ["4.02", "4.3056", "4.6961"],
+ ... }
+ >>> client.tabular_regression(table, model="scikit-learn/Fish-Weight")
+ [110, 120, 130]
+ ```
+ """
+ response = self.post(json={"table": table}, model=model, task="tabular-regression")
+ return _bytes_to_list(response)
+
+ def text_classification(self, text: str, *, model: Optional[str] = None) -> List[TextClassificationOutputElement]:
+ """
+ Perform text classification (e.g. sentiment-analysis) on the given text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the text classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended text classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[TextClassificationOutputElement]`: a list of [`TextClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.text_classification("I like you")
+ [
+ TextClassificationOutputElement(label='POSITIVE', score=0.9998695850372314),
+ TextClassificationOutputElement(label='NEGATIVE', score=0.0001304351753788069),
+ ]
+ ```
+ """
+ response = self.post(json={"inputs": text}, model=model, task="text-classification")
+ return TextClassificationOutputElement.parse_obj_as_list(response)[0] # type: ignore [return-value]
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> str: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> TextGenerationOutput: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> Iterable[str]: ...
+
+ @overload
+ def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> Iterable[TextGenerationStreamOutput]: ...
+
+ @overload
+ def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: bool = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> Union[TextGenerationOutput, Iterable[TextGenerationStreamOutput]]: ...
+
+ def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: bool = False,
+ stream: bool = False,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ decoder_input_details: bool = False,
+ ) -> Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]:
+ """
+ Given a prompt, generate the following text.
+
+ API endpoint is supposed to run with the `text-generation-inference` backend (TGI). This backend is the
+ go-to solution to run large language models at scale. However, for some smaller models (e.g. "gpt2") the
+ default `transformers` + `api-inference` solution is still in use. Both approaches have very similar APIs, but
+ not exactly the same. This method is compatible with both approaches but some parameters are only available for
+ `text-generation-inference`. If some parameters are ignored, a warning message is triggered but the process
+ continues correctly.
+
+ To learn more about the TGI project, please refer to https://github.com/huggingface/text-generation-inference.
+
+ Args:
+ prompt (`str`):
+ Input text.
+ details (`bool`, *optional*):
+ By default, text_generation returns a string. Pass `details=True` if you want a detailed output (tokens,
+ probabilities, seed, finish reason, etc.). Only available for models running on with the
+ `text-generation-inference` backend.
+ stream (`bool`, *optional*):
+ By default, text_generation returns the full generated text. Pass `stream=True` if you want a stream of
+ tokens to be returned. Only available for models running on with the `text-generation-inference`
+ backend.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ do_sample (`bool`):
+ Activate logits sampling
+ max_new_tokens (`int`):
+ Maximum number of generated tokens
+ best_of (`int`):
+ Generate best_of sequences and return the one if the highest token logprobs
+ repetition_penalty (`float`):
+ The parameter for repetition penalty. 1.0 means no penalty. See [this
+ paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
+ return_full_text (`bool`):
+ Whether to prepend the prompt to the generated text
+ seed (`int`):
+ Random sampling seed
+ stop_sequences (`List[str]`):
+ Stop generating tokens if a member of `stop_sequences` is generated
+ temperature (`float`):
+ The value used to module the logits distribution.
+ top_k (`int`):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`):
+ If set to < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or
+ higher are kept for generation.
+ truncate (`int`):
+ Truncate inputs tokens to the given size
+ typical_p (`float`):
+ Typical Decoding mass
+ See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666) for more information
+ watermark (`bool`):
+ Watermarking with [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)
+ decoder_input_details (`bool`):
+ Return the decoder input token logprobs and ids. You must set `details=True` as well for it to be taken
+ into account. Defaults to `False`.
+
+ Returns:
+ `Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False` and `details=False`, the generated text is returned as a `str` (default)
+ - if `stream=True` and `details=False`, the generated text is returned token by token as a `Iterable[str]`
+ - if `stream=False` and `details=True`, the generated text is returned with more details as a [`~huggingface_hub.TextGenerationOutput`]
+ - if `details=True` and `stream=True`, the generated text is returned token by token as a iterable of [`~huggingface_hub.TextGenerationStreamOutput`]
+
+ Raises:
+ `ValidationError`:
+ If input values are not valid. No HTTP call is made to the server.
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ # Case 1: generate text
+ >>> client.text_generation("The huggingface_hub library is ", max_new_tokens=12)
+ '100% open source and built to be easy to use.'
+
+ # Case 2: iterate over the generated tokens. Useful for large generation.
+ >>> for token in client.text_generation("The huggingface_hub library is ", max_new_tokens=12, stream=True):
+ ... print(token)
+ 100
+ %
+ open
+ source
+ and
+ built
+ to
+ be
+ easy
+ to
+ use
+ .
+
+ # Case 3: get more details about the generation process.
+ >>> client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True)
+ TextGenerationOutput(
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationDetails(
+ finish_reason='length',
+ generated_tokens=12,
+ seed=None,
+ prefill=[
+ TextGenerationPrefillToken(id=487, text='The', logprob=None),
+ TextGenerationPrefillToken(id=53789, text=' hugging', logprob=-13.171875),
+ (...)
+ TextGenerationPrefillToken(id=204, text=' ', logprob=-7.0390625)
+ ],
+ tokens=[
+ TokenElement(id=1425, text='100', logprob=-1.0175781, special=False),
+ TokenElement(id=16, text='%', logprob=-0.0463562, special=False),
+ (...)
+ TokenElement(id=25, text='.', logprob=-0.5703125, special=False)
+ ],
+ best_of_sequences=None
+ )
+ )
+
+ # Case 4: iterate over the generated tokens with more details.
+ # Last object is more complete, containing the full generated text and the finish reason.
+ >>> for details in client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True, stream=True):
+ ... print(details)
+ ...
+ TextGenerationStreamOutput(token=TokenElement(id=1425, text='100', logprob=-1.0175781, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=16, text='%', logprob=-0.0463562, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1314, text=' open', logprob=-1.3359375, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3178, text=' source', logprob=-0.28100586, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=273, text=' and', logprob=-0.5961914, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3426, text=' built', logprob=-1.9423828, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-1.4121094, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=314, text=' be', logprob=-1.5224609, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1833, text=' easy', logprob=-2.1132812, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-0.08520508, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=745, text=' use', logprob=-0.39453125, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(
+ id=25,
+ text='.',
+ logprob=-0.5703125,
+ special=False),
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationStreamDetails(finish_reason='length', generated_tokens=12, seed=None)
+ )
+ ```
+ """
+ if decoder_input_details and not details:
+ warnings.warn(
+ "`decoder_input_details=True` has been passed to the server but `details=False` is set meaning that"
+ " the output from the server will be truncated."
+ )
+ decoder_input_details = False
+
+ # Build payload
+ payload = {
+ "inputs": prompt,
+ "parameters": {
+ "best_of": best_of,
+ "decoder_input_details": decoder_input_details,
+ "details": details,
+ "do_sample": do_sample,
+ "max_new_tokens": max_new_tokens,
+ "repetition_penalty": repetition_penalty,
+ "return_full_text": return_full_text,
+ "seed": seed,
+ "stop": stop_sequences if stop_sequences is not None else [],
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_p": top_p,
+ "truncate": truncate,
+ "typical_p": typical_p,
+ "watermark": watermark,
+ },
+ "stream": stream,
+ }
+
+ # Remove some parameters if not a TGI server
+ if not _is_tgi_server(model):
+ parameters: Dict = payload["parameters"] # type: ignore [assignment]
+
+ ignored_parameters = []
+ for key in "watermark", "details", "decoder_input_details", "best_of", "stop", "return_full_text":
+ if parameters[key] is not None:
+ ignored_parameters.append(key)
+ del parameters[key]
+ if len(ignored_parameters) > 0:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Ignoring parameters"
+ f" {ignored_parameters}.",
+ UserWarning,
+ )
+ if details:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Parameter `details=True` will"
+ " be ignored meaning only the generated text will be returned.",
+ UserWarning,
+ )
+ details = False
+ if stream:
+ raise ValueError(
+ "API endpoint/model for text-generation is not served via TGI. Cannot return output as a stream."
+ " Please pass `stream=False` as input."
+ )
+
+ # Handle errors separately for more precise error messages
+ try:
+ bytes_output = self.post(json=payload, model=model, task="text-generation", stream=stream) # type: ignore
+ except HTTPError as e:
+ if isinstance(e, BadRequestError) and "The following `model_kwargs` are not used by the model" in str(e):
+ _set_as_non_tgi(model)
+ return self.text_generation( # type: ignore
+ prompt=prompt,
+ details=details,
+ stream=stream,
+ model=model,
+ do_sample=do_sample,
+ max_new_tokens=max_new_tokens,
+ best_of=best_of,
+ repetition_penalty=repetition_penalty,
+ return_full_text=return_full_text,
+ seed=seed,
+ stop_sequences=stop_sequences,
+ temperature=temperature,
+ top_k=top_k,
+ top_p=top_p,
+ truncate=truncate,
+ typical_p=typical_p,
+ watermark=watermark,
+ decoder_input_details=decoder_input_details,
+ )
+ raise_text_generation_error(e)
+
+ # Parse output
+ if stream:
+ return _stream_text_generation_response(bytes_output, details) # type: ignore
+
+ data = _bytes_to_dict(bytes_output)[0] # type: ignore[arg-type]
+ return TextGenerationOutput.parse_obj_as_instance(data) if details else data["generated_text"]
+
+ def text_to_image(
+ self,
+ prompt: str,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[float] = None,
+ width: Optional[float] = None,
+ num_inference_steps: Optional[float] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Generate an image based on a given text using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate an image from.
+ negative_prompt (`str`, *optional*):
+ An optional negative prompt for the image generation.
+ height (`float`, *optional*):
+ The height in pixels of the image to generate.
+ width (`float`, *optional*):
+ The width in pixels of the image to generate.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
+ usually at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Image`: The generated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> image = client.text_to_image("An astronaut riding a horse on the moon.")
+ >>> image.save("astronaut.png")
+
+ >>> image = client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... negative_prompt="low resolution, blurry",
+ ... model="stabilityai/stable-diffusion-2-1",
+ ... )
+ >>> image.save("better_astronaut.png")
+ ```
+ """
+ payload = {"inputs": prompt}
+ parameters = {
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ }
+ for key, value in parameters.items():
+ if value is not None:
+ payload.setdefault("parameters", {})[key] = value # type: ignore
+ response = self.post(json=payload, model=model, task="text-to-image")
+ return _bytes_to_image(response)
+
+ def text_to_speech(self, text: str, *, model: Optional[str] = None) -> bytes:
+ """
+ Synthesize an audio of a voice pronouncing a given text.
+
+ Args:
+ text (`str`):
+ The text to synthesize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `bytes`: The generated audio.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from pathlib import Path
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> audio = client.text_to_speech("Hello world")
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+ """
+ return self.post(json={"inputs": text}, model=model, task="text-to-speech")
+
+ def token_classification(
+ self, text: str, *, model: Optional[str] = None
+ ) -> List[TokenClassificationOutputElement]:
+ """
+ Perform token classification on the given text.
+ Usually used for sentence parsing, either grammatical, or Named Entity Recognition (NER) to understand keywords contained within text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the token classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended token classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[TokenClassificationOutputElement]`: List of [`TokenClassificationOutputElement`] items containing the entity group, confidence score, word, start and end index.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.token_classification("My name is Sarah Jessica Parker but you can call me Jessica")
+ [
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9971321225166321,
+ word='Sarah Jessica Parker',
+ start=11,
+ end=31,
+ ),
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9773476123809814,
+ word='Jessica',
+ start=52,
+ end=59,
+ )
+ ]
+ ```
+ """
+ payload: Dict[str, Any] = {"inputs": text}
+ response = self.post(
+ json=payload,
+ model=model,
+ task="token-classification",
+ )
+ return TokenClassificationOutputElement.parse_obj_as_list(response)
+
+ def translation(
+ self, text: str, *, model: Optional[str] = None, src_lang: Optional[str] = None, tgt_lang: Optional[str] = None
+ ) -> TranslationOutput:
+ """
+ Convert text from one language to another.
+
+ Check out https://huggingface.co/tasks/translation for more information on how to choose the best model for
+ your specific use case. Source and target languages usually depend on the model.
+ However, it is possible to specify source and target languages for certain models. If you are working with one of these models,
+ you can use `src_lang` and `tgt_lang` arguments to pass the relevant information.
+ You can find this information in the model card.
+
+ Args:
+ text (`str`):
+ A string to be translated.
+ model (`str`, *optional*):
+ The model to use for the translation task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended translation model will be used.
+ Defaults to None.
+ src_lang (`str`, *optional*):
+ Source language of the translation task, i.e. input language. Cannot be passed without `tgt_lang`.
+ tgt_lang (`str`, *optional*):
+ Target language of the translation task, i.e. output language. Cannot be passed without `src_lang`.
+
+ Returns:
+ [`TranslationOutput`]: The generated translated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If only one of the `src_lang` and `tgt_lang` arguments are provided.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.translation("My name is Wolfgang and I live in Berlin")
+ 'Mein Name ist Wolfgang und ich lebe in Berlin.'
+ >>> client.translation("My name is Wolfgang and I live in Berlin", model="Helsinki-NLP/opus-mt-en-fr")
+ TranslationOutput(translation_text='Je m\'appelle Wolfgang et je vis à Berlin.')
+ ```
+
+ Specifying languages:
+ ```py
+ >>> client.translation("My name is Sarah Jessica Parker but you can call me Jessica", model="facebook/mbart-large-50-many-to-many-mmt", src_lang="en_XX", tgt_lang="fr_XX")
+ "Mon nom est Sarah Jessica Parker mais vous pouvez m\'appeler Jessica"
+ ```
+ """
+ # Throw error if only one of `src_lang` and `tgt_lang` was given
+ if src_lang is not None and tgt_lang is None:
+ raise ValueError("You cannot specify `src_lang` without specifying `tgt_lang`.")
+
+ if src_lang is None and tgt_lang is not None:
+ raise ValueError("You cannot specify `tgt_lang` without specifying `src_lang`.")
+
+ # If both `src_lang` and `tgt_lang` are given, pass them to the request body
+ payload: Dict = {"inputs": text}
+ if src_lang and tgt_lang:
+ payload["parameters"] = {"src_lang": src_lang, "tgt_lang": tgt_lang}
+ response = self.post(json=payload, model=model, task="translation")
+ return TranslationOutput.parse_obj_as_list(response)[0]
+
+ def visual_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ ) -> List[VisualQuestionAnsweringOutputElement]:
+ """
+ Answering open-ended questions based on an image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the visual question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended visual question answering model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[VisualQuestionAnsweringOutputElement]`: a list of [`VisualQuestionAnsweringOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.visual_question_answering(
+ ... image="https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg",
+ ... question="What is the animal doing?"
+ ... )
+ [
+ VisualQuestionAnsweringOutputElement(score=0.778609573841095, answer='laying down'),
+ VisualQuestionAnsweringOutputElement(score=0.6957435607910156, answer='sitting'),
+ ]
+ ```
+ """
+ payload: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ response = self.post(json=payload, model=model, task="visual-question-answering")
+ return VisualQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ def zero_shot_classification(
+ self, text: str, labels: List[str], *, multi_label: bool = False, model: Optional[str] = None
+ ) -> List[ZeroShotClassificationOutputElement]:
+ """
+ Provide as input a text and a set of candidate labels to classify the input text.
+
+ Args:
+ text (`str`):
+ The input text to classify.
+ labels (`List[str]`):
+ List of string possible labels. There must be at least 2 labels.
+ multi_label (`bool`):
+ Boolean that is set to True if classes can overlap.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `List[ZeroShotClassificationOutputElement]`: List of [`ZeroShotClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> text = (
+ ... "A new model offers an explanation for how the Galilean satellites formed around the solar system's"
+ ... "largest world. Konstantin Batygin did not set out to solve one of the solar system's most puzzling"
+ ... " mysteries when he went for a run up a hill in Nice, France."
+ ... )
+ >>> labels = ["space & cosmos", "scientific discovery", "microbiology", "robots", "archeology"]
+ >>> client.zero_shot_classification(text, labels)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.7961668968200684),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.18570658564567566),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.00730885099619627),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.006258360575884581),
+ ZeroShotClassificationOutputElement(label='robots', score=0.004559356719255447),
+ ]
+ >>> client.zero_shot_classification(text, labels, multi_label=True)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.9829297661781311),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.755190908908844),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.0005462635890580714),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.00047131875180639327),
+ ZeroShotClassificationOutputElement(label='robots', score=0.00030448526376858354),
+ ]
+ ```
+ """
+ # Raise ValueError if input is less than 2 labels
+ if len(labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ response = self.post(
+ json={
+ "inputs": text,
+ "parameters": {
+ "candidate_labels": ",".join(labels),
+ "multi_label": multi_label,
+ },
+ },
+ model=model,
+ task="zero-shot-classification",
+ )
+ output = _bytes_to_dict(response)
+ return [
+ ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
+ for label, score in zip(output["labels"], output["scores"])
+ ]
+
+ def zero_shot_image_classification(
+ self, image: ContentT, labels: List[str], *, model: Optional[str] = None
+ ) -> List[ZeroShotImageClassificationOutputElement]:
+ """
+ Provide input image and text labels to predict text labels for the image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image.
+ labels (`List[str]`):
+ List of string possible labels. There must be at least 2 labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `List[ZeroShotImageClassificationOutputElement]`: List of [`ZeroShotImageClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `HTTPError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+
+ >>> client.zero_shot_image_classification(
+ ... "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg",
+ ... labels=["dog", "cat", "horse"],
+ ... )
+ [ZeroShotImageClassificationOutputElement(label='dog', score=0.956),...]
+ ```
+ """
+ # Raise ValueError if input is less than 2 labels
+ if len(labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ response = self.post(
+ json={"image": _b64_encode(image), "parameters": {"candidate_labels": ",".join(labels)}},
+ model=model,
+ task="zero-shot-image-classification",
+ )
+ return ZeroShotImageClassificationOutputElement.parse_obj_as_list(response)
+
+ def _resolve_url(self, model: Optional[str] = None, task: Optional[str] = None) -> str:
+ model = model or self.model
+
+ # If model is already a URL, ignore `task` and return directly
+ if model is not None and (model.startswith("http://") or model.startswith("https://")):
+ return model
+
+ # # If no model but task is set => fetch the recommended one for this task
+ if model is None:
+ if task is None:
+ raise ValueError(
+ "You must specify at least a model (repo_id or URL) or a task, either when instantiating"
+ " `InferenceClient` or when making a request."
+ )
+ model = self.get_recommended_model(task)
+ logger.info(
+ f"Using recommended model {model} for task {task}. Note that it is"
+ f" encouraged to explicitly set `model='{model}'` as the recommended"
+ " models list might get updated without prior notice."
+ )
+
+ # Compute InferenceAPI url
+ return (
+ # Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
+ f"{INFERENCE_ENDPOINT}/pipeline/{task}/{model}"
+ if task in ("feature-extraction", "sentence-similarity")
+ # Otherwise, we use the default endpoint
+ else f"{INFERENCE_ENDPOINT}/models/{model}"
+ )
+
+ @staticmethod
+ def get_recommended_model(task: str) -> str:
+ """
+ Get the model Hugging Face recommends for the input task.
+
+ Args:
+ task (`str`):
+ The Hugging Face task to get which model Hugging Face recommends.
+ All available tasks can be found [here](https://huggingface.co/tasks).
+
+ Returns:
+ `str`: Name of the model recommended for the input task.
+
+ Raises:
+ `ValueError`: If Hugging Face has no recommendation for the input task.
+ """
+ model = _fetch_recommended_models().get(task)
+ if model is None:
+ raise ValueError(
+ f"Task {task} has no recommended model. Please specify a model"
+ " explicitly. Visit https://huggingface.co/tasks for more info."
+ )
+ return model
+
+ def get_model_status(self, model: Optional[str] = None) -> ModelStatus:
+ """
+ Get the status of a model hosted on the Inference API.
+
+
+
+ This endpoint is mostly useful when you already know which model you want to use and want to check its
+ availability. If you want to discover already deployed models, you should rather use [`~InferenceClient.list_deployed_models`].
+
+
+
+ Args:
+ model (`str`, *optional*):
+ Identifier of the model for witch the status gonna be checked. If model is not provided,
+ the model associated with this instance of [`InferenceClient`] will be used. Only InferenceAPI service can be checked so the
+ identifier cannot be a URL.
+
+
+ Returns:
+ [`ModelStatus`]: An instance of ModelStatus dataclass, containing information,
+ about the state of the model: load, state, compute type and framework.
+
+ Example:
+ ```py
+ >>> from huggingface_hub import InferenceClient
+ >>> client = InferenceClient()
+ >>> client.get_model_status("bigcode/starcoder")
+ ModelStatus(loaded=True, state='Loaded', compute_type='gpu', framework='text-generation-inference')
+ ```
+ """
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith("https://"):
+ raise NotImplementedError("Model status is only available for Inference API endpoints.")
+ url = f"{INFERENCE_ENDPOINT}/status/{model}"
+
+ response = get_session().get(url, headers=self.headers)
+ hf_raise_for_status(response)
+ response_data = response.json()
+
+ if "error" in response_data:
+ raise ValueError(response_data["error"])
+
+ return ModelStatus(
+ loaded=response_data["loaded"],
+ state=response_data["state"],
+ compute_type=response_data["compute_type"],
+ framework=response_data["framework"],
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_common.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..01ea32572dfe8561abb4ee211bb71a7acc2cd173
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_common.py
@@ -0,0 +1,482 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities used by both the sync and async inference clients."""
+
+import base64
+import io
+import json
+import logging
+import time
+from contextlib import contextmanager
+from dataclasses import dataclass
+from pathlib import Path
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncIterable,
+ BinaryIO,
+ ContextManager,
+ Dict,
+ Generator,
+ Iterable,
+ List,
+ Literal,
+ NoReturn,
+ Optional,
+ Set,
+ Union,
+ overload,
+)
+
+from requests import HTTPError
+
+from huggingface_hub.errors import (
+ GenerationError,
+ IncompleteGenerationError,
+ OverloadedError,
+ TextGenerationError,
+ UnknownError,
+ ValidationError,
+)
+
+from ..constants import ENDPOINT
+from ..utils import (
+ build_hf_headers,
+ get_session,
+ hf_raise_for_status,
+ is_aiohttp_available,
+ is_numpy_available,
+ is_pillow_available,
+)
+from ._generated.types import (
+ ChatCompletionStreamOutput,
+ ChatCompletionStreamOutputChoice,
+ ChatCompletionStreamOutputDelta,
+ TextGenerationStreamOutput,
+)
+
+
+if TYPE_CHECKING:
+ from aiohttp import ClientResponse, ClientSession
+ from PIL import Image
+
+# TYPES
+UrlT = str
+PathT = Union[str, Path]
+BinaryT = Union[bytes, BinaryIO]
+ContentT = Union[BinaryT, PathT, UrlT]
+
+# Use to set a Accept: image/png header
+TASKS_EXPECTING_IMAGES = {"text-to-image", "image-to-image"}
+
+logger = logging.getLogger(__name__)
+
+
+# Add dataclass for ModelStatus. We use this dataclass in get_model_status function.
+@dataclass
+class ModelStatus:
+ """
+ This Dataclass represents the the model status in the Hugging Face Inference API.
+
+ Args:
+ loaded (`bool`):
+ If the model is currently loaded into Hugging Face's InferenceAPI. Models
+ are loaded on-demand, leading to the user's first request taking longer.
+ If a model is loaded, you can be assured that it is in a healthy state.
+ state (`str`):
+ The current state of the model. This can be 'Loaded', 'Loadable', 'TooBig'.
+ If a model's state is 'Loadable', it's not too big and has a supported
+ backend. Loadable models are automatically loaded when the user first
+ requests inference on the endpoint. This means it is transparent for the
+ user to load a model, except that the first call takes longer to complete.
+ compute_type (`Dict`):
+ Information about the compute resource the model is using or will use, such as 'gpu' type and number of
+ replicas.
+ framework (`str`):
+ The name of the framework that the model was built with, such as 'transformers'
+ or 'text-generation-inference'.
+ """
+
+ loaded: bool
+ state: str
+ compute_type: Dict
+ framework: str
+
+
+## IMPORT UTILS
+
+
+def _import_aiohttp():
+ # Make sure `aiohttp` is installed on the machine.
+ if not is_aiohttp_available():
+ raise ImportError("Please install aiohttp to use `AsyncInferenceClient` (`pip install aiohttp`).")
+ import aiohttp
+
+ return aiohttp
+
+
+def _import_numpy():
+ """Make sure `numpy` is installed on the machine."""
+ if not is_numpy_available():
+ raise ImportError("Please install numpy to use deal with embeddings (`pip install numpy`).")
+ import numpy
+
+ return numpy
+
+
+def _import_pil_image():
+ """Make sure `PIL` is installed on the machine."""
+ if not is_pillow_available():
+ raise ImportError(
+ "Please install Pillow to use deal with images (`pip install Pillow`). If you don't want the image to be"
+ " post-processed, use `client.post(...)` and get the raw response from the server."
+ )
+ from PIL import Image
+
+ return Image
+
+
+## RECOMMENDED MODELS
+
+# Will be globally fetched only once (see '_fetch_recommended_models')
+_RECOMMENDED_MODELS: Optional[Dict[str, Optional[str]]] = None
+
+
+def _fetch_recommended_models() -> Dict[str, Optional[str]]:
+ global _RECOMMENDED_MODELS
+ if _RECOMMENDED_MODELS is None:
+ response = get_session().get(f"{ENDPOINT}/api/tasks", headers=build_hf_headers())
+ hf_raise_for_status(response)
+ _RECOMMENDED_MODELS = {
+ task: _first_or_none(details["widgetModels"]) for task, details in response.json().items()
+ }
+ return _RECOMMENDED_MODELS
+
+
+def _first_or_none(items: List[Any]) -> Optional[Any]:
+ try:
+ return items[0] or None
+ except IndexError:
+ return None
+
+
+## ENCODING / DECODING UTILS
+
+
+@overload
+def _open_as_binary(
+ content: ContentT,
+) -> ContextManager[BinaryT]: ... # means "if input is not None, output is not None"
+
+
+@overload
+def _open_as_binary(
+ content: Literal[None],
+) -> ContextManager[Literal[None]]: ... # means "if input is None, output is None"
+
+
+@contextmanager # type: ignore
+def _open_as_binary(content: Optional[ContentT]) -> Generator[Optional[BinaryT], None, None]:
+ """Open `content` as a binary file, either from a URL, a local path, or raw bytes.
+
+ Do nothing if `content` is None,
+
+ TODO: handle a PIL.Image as input
+ TODO: handle base64 as input
+ """
+ # If content is a string => must be either a URL or a path
+ if isinstance(content, str):
+ if content.startswith("https://") or content.startswith("http://"):
+ logger.debug(f"Downloading content from {content}")
+ yield get_session().get(content).content # TODO: retrieve as stream and pipe to post request ?
+ return
+ content = Path(content)
+ if not content.exists():
+ raise FileNotFoundError(
+ f"File not found at {content}. If `data` is a string, it must either be a URL or a path to a local"
+ " file. To pass raw content, please encode it as bytes first."
+ )
+
+ # If content is a Path => open it
+ if isinstance(content, Path):
+ logger.debug(f"Opening content from {content}")
+ with content.open("rb") as f:
+ yield f
+ else:
+ # Otherwise: already a file-like object or None
+ yield content
+
+
+def _b64_encode(content: ContentT) -> str:
+ """Encode a raw file (image, audio) into base64. Can be byes, an opened file, a path or a URL."""
+ with _open_as_binary(content) as data:
+ data_as_bytes = data if isinstance(data, bytes) else data.read()
+ return base64.b64encode(data_as_bytes).decode()
+
+
+def _b64_to_image(encoded_image: str) -> "Image":
+ """Parse a base64-encoded string into a PIL Image."""
+ Image = _import_pil_image()
+ return Image.open(io.BytesIO(base64.b64decode(encoded_image)))
+
+
+def _bytes_to_list(content: bytes) -> List:
+ """Parse bytes from a Response object into a Python list.
+
+ Expects the response body to be JSON-encoded data.
+
+ NOTE: This is exactly the same implementation as `_bytes_to_dict` and will not complain if the returned data is a
+ dictionary. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
+ """
+ return json.loads(content.decode())
+
+
+def _bytes_to_dict(content: bytes) -> Dict:
+ """Parse bytes from a Response object into a Python dictionary.
+
+ Expects the response body to be JSON-encoded data.
+
+ NOTE: This is exactly the same implementation as `_bytes_to_list` and will not complain if the returned data is a
+ list. The only advantage of having both is to help the user (and mypy) understand what kind of data to expect.
+ """
+ return json.loads(content.decode())
+
+
+def _bytes_to_image(content: bytes) -> "Image":
+ """Parse bytes from a Response object into a PIL Image.
+
+ Expects the response body to be raw bytes. To deal with b64 encoded images, use `_b64_to_image` instead.
+ """
+ Image = _import_pil_image()
+ return Image.open(io.BytesIO(content))
+
+
+## STREAMING UTILS
+
+
+def _stream_text_generation_response(
+ bytes_output_as_lines: Iterable[bytes], details: bool
+) -> Union[Iterable[str], Iterable[TextGenerationStreamOutput]]:
+ """Used in `InferenceClient.text_generation`."""
+ # Parse ServerSentEvents
+ for byte_payload in bytes_output_as_lines:
+ output = _format_text_generation_stream_output(byte_payload, details)
+ if output is not None:
+ yield output
+
+
+async def _async_stream_text_generation_response(
+ bytes_output_as_lines: AsyncIterable[bytes], details: bool
+) -> Union[AsyncIterable[str], AsyncIterable[TextGenerationStreamOutput]]:
+ """Used in `AsyncInferenceClient.text_generation`."""
+ # Parse ServerSentEvents
+ async for byte_payload in bytes_output_as_lines:
+ output = _format_text_generation_stream_output(byte_payload, details)
+ if output is not None:
+ yield output
+
+
+def _format_text_generation_stream_output(
+ byte_payload: bytes, details: bool
+) -> Optional[Union[str, TextGenerationStreamOutput]]:
+ if not byte_payload.startswith(b"data:"):
+ return None # empty line
+
+ # Decode payload
+ payload = byte_payload.decode("utf-8")
+ json_payload = json.loads(payload.lstrip("data:").rstrip("/n"))
+
+ # Either an error as being returned
+ if json_payload.get("error") is not None:
+ raise _parse_text_generation_error(json_payload["error"], json_payload.get("error_type"))
+
+ # Or parse token payload
+ output = TextGenerationStreamOutput.parse_obj_as_instance(json_payload)
+ return output.token.text if not details else output
+
+
+def _stream_chat_completion_response_from_text_generation(
+ text_generation_output: Iterable[TextGenerationStreamOutput],
+) -> Iterable[ChatCompletionStreamOutput]:
+ """Used in `InferenceClient.chat_completion`."""
+ created = int(time.time())
+ for item in text_generation_output:
+ yield _format_chat_completion_stream_output_from_text_generation(item, created)
+
+
+async def _async_stream_chat_completion_response_from_text_generation(
+ text_generation_output: AsyncIterable[TextGenerationStreamOutput],
+) -> AsyncIterable[ChatCompletionStreamOutput]:
+ """Used in `AsyncInferenceClient.chat_completion`."""
+ created = int(time.time())
+ async for item in text_generation_output:
+ yield _format_chat_completion_stream_output_from_text_generation(item, created)
+
+
+def _format_chat_completion_stream_output_from_text_generation(
+ item: TextGenerationStreamOutput, created: int
+) -> ChatCompletionStreamOutput:
+ if item.details is None:
+ # new token generated => return delta
+ return ChatCompletionStreamOutput(
+ choices=[
+ ChatCompletionStreamOutputChoice(
+ delta=ChatCompletionStreamOutputDelta(
+ role="assistant",
+ content=item.token.text,
+ ),
+ finish_reason=None,
+ index=0,
+ )
+ ],
+ created=created,
+ )
+ else:
+ # generation is completed => return finish reason
+ return ChatCompletionStreamOutput(
+ choices=[
+ ChatCompletionStreamOutputChoice(
+ delta=ChatCompletionStreamOutputDelta(),
+ finish_reason=item.details.finish_reason,
+ index=0,
+ )
+ ],
+ created=created,
+ )
+
+
+def _stream_chat_completion_response_from_bytes(
+ bytes_lines: Iterable[bytes],
+) -> Iterable[ChatCompletionStreamOutput]:
+ """Used in `InferenceClient.chat_completion` if model is served with TGI."""
+ for item in bytes_lines:
+ output = _format_chat_completion_stream_output_from_text_generation_from_bytes(item)
+ if output is not None:
+ yield output
+
+
+async def _async_stream_chat_completion_response_from_bytes(
+ bytes_lines: AsyncIterable[bytes],
+) -> AsyncIterable[ChatCompletionStreamOutput]:
+ """Used in `AsyncInferenceClient.chat_completion`."""
+ async for item in bytes_lines:
+ output = _format_chat_completion_stream_output_from_text_generation_from_bytes(item)
+ if output is not None:
+ yield output
+
+
+def _format_chat_completion_stream_output_from_text_generation_from_bytes(
+ byte_payload: bytes,
+) -> Optional[ChatCompletionStreamOutput]:
+ if not byte_payload.startswith(b"data:"):
+ return None # empty line
+
+ # Decode payload
+ payload = byte_payload.decode("utf-8")
+ json_payload = json.loads(payload.lstrip("data:").rstrip("/n"))
+ return ChatCompletionStreamOutput.parse_obj_as_instance(json_payload)
+
+
+async def _async_yield_from(client: "ClientSession", response: "ClientResponse") -> AsyncIterable[bytes]:
+ async for byte_payload in response.content:
+ yield byte_payload
+ await client.close()
+
+
+# "TGI servers" are servers running with the `text-generation-inference` backend.
+# This backend is the go-to solution to run large language models at scale. However,
+# for some smaller models (e.g. "gpt2") the default `transformers` + `api-inference`
+# solution is still in use.
+#
+# Both approaches have very similar APIs, but not exactly the same. What we do first in
+# the `text_generation` method is to assume the model is served via TGI. If we realize
+# it's not the case (i.e. we receive an HTTP 400 Bad Request), we fallback to the
+# default API with a warning message. We remember for each model if it's a TGI server
+# or not using `_NON_TGI_SERVERS` global variable.
+#
+# In addition, TGI servers have a built-in API route for chat-completion, which is not
+# available on the default API. We use this route to provide a more consistent behavior
+# when available.
+#
+# For more details, see https://github.com/huggingface/text-generation-inference and
+# https://huggingface.co/docs/api-inference/detailed_parameters#text-generation-task.
+
+_NON_TGI_SERVERS: Set[Optional[str]] = set()
+
+
+def _set_as_non_tgi(model: Optional[str]) -> None:
+ _NON_TGI_SERVERS.add(model)
+
+
+def _is_tgi_server(model: Optional[str]) -> bool:
+ return model not in _NON_TGI_SERVERS
+
+
+_NON_CHAT_COMPLETION_SERVER: Set[str] = set()
+
+
+def _set_as_non_chat_completion_server(model: str) -> None:
+ print("Set as non chat completion", model)
+ _NON_CHAT_COMPLETION_SERVER.add(model)
+
+
+def _is_chat_completion_server(model: str) -> bool:
+ return model not in _NON_CHAT_COMPLETION_SERVER
+
+
+# TEXT GENERATION ERRORS
+# ----------------------
+# Text-generation errors are parsed separately to handle as much as possible the errors returned by the text generation
+# inference project (https://github.com/huggingface/text-generation-inference).
+# ----------------------
+
+
+def raise_text_generation_error(http_error: HTTPError) -> NoReturn:
+ """
+ Try to parse text-generation-inference error message and raise HTTPError in any case.
+
+ Args:
+ error (`HTTPError`):
+ The HTTPError that have been raised.
+ """
+ # Try to parse a Text Generation Inference error
+
+ try:
+ # Hacky way to retrieve payload in case of aiohttp error
+ payload = getattr(http_error, "response_error_payload", None) or http_error.response.json()
+ error = payload.get("error")
+ error_type = payload.get("error_type")
+ except Exception: # no payload
+ raise http_error
+
+ # If error_type => more information than `hf_raise_for_status`
+ if error_type is not None:
+ exception = _parse_text_generation_error(error, error_type)
+ raise exception from http_error
+
+ # Otherwise, fallback to default error
+ raise http_error
+
+
+def _parse_text_generation_error(error: Optional[str], error_type: Optional[str]) -> TextGenerationError:
+ if error_type == "generation":
+ return GenerationError(error) # type: ignore
+ if error_type == "incomplete_generation":
+ return IncompleteGenerationError(error) # type: ignore
+ if error_type == "overloaded":
+ return OverloadedError(error) # type: ignore
+ if error_type == "validation":
+ return ValidationError(error) # type: ignore
+ return UnknownError(error) # type: ignore
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a244ed2df4265604da023f0c6e5566aab61629d0
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cc5aa50bbc90b337f41cb7d13f5a68aac901133d
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/__pycache__/_async_client.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/_async_client.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/_async_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1e5032d1c0e6683b93e80bd7a123fb0a6126e3c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/_async_client.py
@@ -0,0 +1,2389 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# WARNING
+# This entire file has been adapted from the sync-client code in `src/huggingface_hub/inference/_client.py`.
+# Any change in InferenceClient will be automatically reflected in AsyncInferenceClient.
+# To re-generate the code, run `make style` or `python ./utils/generate_async_inference_client.py --update`.
+# WARNING
+import asyncio
+import base64
+import logging
+import time
+import warnings
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncIterable,
+ Dict,
+ List,
+ Literal,
+ Optional,
+ Union,
+ overload,
+)
+
+from requests.structures import CaseInsensitiveDict
+
+from huggingface_hub.constants import ALL_INFERENCE_API_FRAMEWORKS, INFERENCE_ENDPOINT, MAIN_INFERENCE_API_FRAMEWORKS
+from huggingface_hub.errors import InferenceTimeoutError
+from huggingface_hub.inference._common import (
+ TASKS_EXPECTING_IMAGES,
+ ContentT,
+ ModelStatus,
+ _async_stream_chat_completion_response_from_bytes,
+ _async_stream_chat_completion_response_from_text_generation,
+ _async_stream_text_generation_response,
+ _b64_encode,
+ _b64_to_image,
+ _bytes_to_dict,
+ _bytes_to_image,
+ _bytes_to_list,
+ _fetch_recommended_models,
+ _import_numpy,
+ _is_chat_completion_server,
+ _is_tgi_server,
+ _open_as_binary,
+ _set_as_non_chat_completion_server,
+ _set_as_non_tgi,
+ raise_text_generation_error,
+)
+from huggingface_hub.inference._generated.types import (
+ AudioClassificationOutputElement,
+ AudioToAudioOutputElement,
+ AutomaticSpeechRecognitionOutput,
+ ChatCompletionOutput,
+ ChatCompletionOutputChoice,
+ ChatCompletionOutputChoiceMessage,
+ ChatCompletionStreamOutput,
+ DocumentQuestionAnsweringOutputElement,
+ FillMaskOutputElement,
+ ImageClassificationOutputElement,
+ ImageSegmentationOutputElement,
+ ImageToTextOutput,
+ ObjectDetectionOutputElement,
+ QuestionAnsweringOutputElement,
+ SummarizationOutput,
+ TableQuestionAnsweringOutputElement,
+ TextClassificationOutputElement,
+ TextGenerationOutput,
+ TextGenerationStreamOutput,
+ TokenClassificationOutputElement,
+ TranslationOutput,
+ VisualQuestionAnsweringOutputElement,
+ ZeroShotClassificationOutputElement,
+ ZeroShotImageClassificationOutputElement,
+)
+from huggingface_hub.inference._templating import render_chat_prompt
+from huggingface_hub.inference._types import (
+ ConversationalOutput, # soon to be removed
+)
+from huggingface_hub.utils import (
+ build_hf_headers,
+)
+
+from .._common import _async_yield_from, _import_aiohttp
+
+
+if TYPE_CHECKING:
+ import numpy as np
+ from PIL import Image
+
+logger = logging.getLogger(__name__)
+
+
+class AsyncInferenceClient:
+ """
+ Initialize a new Inference Client.
+
+ [`InferenceClient`] aims to provide a unified experience to perform inference. The client can be used
+ seamlessly with either the (free) Inference API or self-hosted Inference Endpoints.
+
+ Args:
+ model (`str`, `optional`):
+ The model to run inference with. Can be a model id hosted on the Hugging Face Hub, e.g. `bigcode/starcoder`
+ or a URL to a deployed Inference Endpoint. Defaults to None, in which case a recommended model is
+ automatically selected for the task.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+ Pass `token=False` if you don't want to send your token to the server.
+ timeout (`float`, `optional`):
+ The maximum number of seconds to wait for a response from the server. Loading a new model in Inference
+ API can take up to several minutes. Defaults to None, meaning it will loop until the server is available.
+ headers (`Dict[str, str]`, `optional`):
+ Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
+ Values in this dictionary will override the default values.
+ cookies (`Dict[str, str]`, `optional`):
+ Additional cookies to send to the server.
+ """
+
+ def __init__(
+ self,
+ model: Optional[str] = None,
+ token: Union[str, bool, None] = None,
+ timeout: Optional[float] = None,
+ headers: Optional[Dict[str, str]] = None,
+ cookies: Optional[Dict[str, str]] = None,
+ ) -> None:
+ self.model: Optional[str] = model
+ self.token: Union[str, bool, None] = token
+ self.headers = CaseInsensitiveDict(build_hf_headers(token=token)) # contains 'authorization' + 'user-agent'
+ if headers is not None:
+ self.headers.update(headers)
+ self.cookies = cookies
+ self.timeout = timeout
+
+ def __repr__(self):
+ return f""
+
+ @overload
+ async def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[False] = ...,
+ ) -> bytes: ...
+
+ @overload
+ async def post( # type: ignore[misc]
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: Literal[True] = ...,
+ ) -> AsyncIterable[bytes]: ...
+
+ @overload
+ async def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, AsyncIterable[bytes]]: ...
+
+ async def post(
+ self,
+ *,
+ json: Optional[Union[str, Dict, List]] = None,
+ data: Optional[ContentT] = None,
+ model: Optional[str] = None,
+ task: Optional[str] = None,
+ stream: bool = False,
+ ) -> Union[bytes, AsyncIterable[bytes]]:
+ """
+ Make a POST request to the inference server.
+
+ Args:
+ json (`Union[str, Dict, List]`, *optional*):
+ The JSON data to send in the request body, specific to each task. Defaults to None.
+ data (`Union[str, Path, bytes, BinaryIO]`, *optional*):
+ The content to send in the request body, specific to each task.
+ It can be raw bytes, a pointer to an opened file, a local file path,
+ or a URL to an online resource (image, audio file,...). If both `json` and `data` are passed,
+ `data` will take precedence. At least `json` or `data` must be provided. Defaults to None.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. Will override the model defined at the instance level. Defaults to None.
+ task (`str`, *optional*):
+ The task to perform on the inference. All available tasks can be found
+ [here](https://huggingface.co/tasks). Used only to default to a recommended model if `model` is not
+ provided. At least `model` or `task` must be provided. Defaults to None.
+ stream (`bool`, *optional*):
+ Whether to iterate over streaming APIs.
+
+ Returns:
+ bytes: The raw bytes returned by the server.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ """
+
+ aiohttp = _import_aiohttp()
+
+ url = self._resolve_url(model, task)
+
+ if data is not None and json is not None:
+ warnings.warn("Ignoring `json` as `data` is passed as binary.")
+
+ # Set Accept header if relevant
+ headers = self.headers.copy()
+ if task in TASKS_EXPECTING_IMAGES and "Accept" not in headers:
+ headers["Accept"] = "image/png"
+
+ t0 = time.time()
+ timeout = self.timeout
+ while True:
+ with _open_as_binary(data) as data_as_binary:
+ # Do not use context manager as we don't want to close the connection immediately when returning
+ # a stream
+ client = aiohttp.ClientSession(
+ headers=headers, cookies=self.cookies, timeout=aiohttp.ClientTimeout(self.timeout)
+ )
+
+ try:
+ response = await client.post(url, json=json, data=data_as_binary)
+ response_error_payload = None
+ if response.status != 200:
+ try:
+ response_error_payload = await response.json() # get payload before connection closed
+ except Exception:
+ pass
+ response.raise_for_status()
+ if stream:
+ return _async_yield_from(client, response)
+ else:
+ content = await response.read()
+ await client.close()
+ return content
+ except asyncio.TimeoutError as error:
+ await client.close()
+ # Convert any `TimeoutError` to a `InferenceTimeoutError`
+ raise InferenceTimeoutError(f"Inference call timed out: {url}") from error # type: ignore
+ except aiohttp.ClientResponseError as error:
+ error.response_error_payload = response_error_payload
+ await client.close()
+ if response.status == 422 and task is not None:
+ error.message += f". Make sure '{task}' task is supported by the model."
+ if response.status == 503:
+ # If Model is unavailable, either raise a TimeoutError...
+ if timeout is not None and time.time() - t0 > timeout:
+ raise InferenceTimeoutError(
+ f"Model not loaded on the server: {url}. Please retry with a higher timeout"
+ f" (current: {self.timeout}).",
+ request=error.request,
+ response=error.response,
+ ) from error
+ # ...or wait 1s and retry
+ logger.info(f"Waiting for model to be loaded on the server: {error}")
+ time.sleep(1)
+ if timeout is not None:
+ timeout = max(self.timeout - (time.time() - t0), 1) # type: ignore
+ continue
+ raise error
+
+ async def audio_classification(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioClassificationOutputElement]:
+ """
+ Perform audio classification on the provided audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content to classify. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model to use for audio classification. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio classification will be used.
+
+ Returns:
+ `List[AudioClassificationOutputElement]`: List of [`AudioClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.audio_classification("audio.flac")
+ [
+ AudioClassificationOutputElement(score=0.4976358711719513, label='hap'),
+ AudioClassificationOutputElement(score=0.3677836060523987, label='neu'),
+ ...
+ ]
+ ```
+ """
+ response = await self.post(data=audio, model=model, task="audio-classification")
+ return AudioClassificationOutputElement.parse_obj_as_list(response)
+
+ async def audio_to_audio(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[AudioToAudioOutputElement]:
+ """
+ Performs multiple tasks related to audio-to-audio depending on the model (eg: speech enhancement, source separation).
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The audio content for the model. It can be raw audio bytes, a local audio file, or a URL pointing to an
+ audio file.
+ model (`str`, *optional*):
+ The model can be any model which takes an audio file and returns another audio file. Can be a model ID hosted on the Hugging Face Hub
+ or a URL to a deployed Inference Endpoint. If not provided, the default recommended model for
+ audio_to_audio will be used.
+
+ Returns:
+ `List[AudioToAudioOutputElement]`: A list of [`AudioToAudioOutputElement`] items containing audios label, content-type, and audio content in blob.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> audio_output = await client.audio_to_audio("audio.flac")
+ >>> async for i, item in enumerate(audio_output):
+ >>> with open(f"output_{i}.flac", "wb") as f:
+ f.write(item.blob)
+ ```
+ """
+ response = await self.post(data=audio, model=model, task="audio-to-audio")
+ audio_output = AudioToAudioOutputElement.parse_obj_as_list(response)
+ for item in audio_output:
+ item.blob = base64.b64decode(item.blob)
+ return audio_output
+
+ async def automatic_speech_recognition(
+ self,
+ audio: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> AutomaticSpeechRecognitionOutput:
+ """
+ Perform automatic speech recognition (ASR or audio-to-text) on the given audio content.
+
+ Args:
+ audio (Union[str, Path, bytes, BinaryIO]):
+ The content to transcribe. It can be raw audio bytes, local audio file, or a URL to an audio file.
+ model (`str`, *optional*):
+ The model to use for ASR. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for ASR will be used.
+
+ Returns:
+ [`AutomaticSpeechRecognitionOutput`]: An item containing the transcribed text and optionally the timestamp chunks.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.automatic_speech_recognition("hello_world.flac").text
+ "hello world"
+ ```
+ """
+ response = await self.post(data=audio, model=model, task="automatic-speech-recognition")
+ return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
+
+ @overload
+ async def chat_completion( # type: ignore
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[False] = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> ChatCompletionOutput: ...
+
+ @overload
+ async def chat_completion( # type: ignore
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: Literal[True] = True,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> AsyncIterable[ChatCompletionStreamOutput]: ...
+
+ @overload
+ async def chat_completion(
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> Union[ChatCompletionOutput, AsyncIterable[ChatCompletionStreamOutput]]: ...
+
+ async def chat_completion(
+ self,
+ messages: List[Dict[str, str]],
+ *,
+ model: Optional[str] = None,
+ stream: bool = False,
+ max_tokens: int = 20,
+ seed: Optional[int] = None,
+ stop: Optional[Union[List[str], str]] = None,
+ temperature: float = 1.0,
+ top_p: Optional[float] = None,
+ ) -> Union[ChatCompletionOutput, AsyncIterable[ChatCompletionStreamOutput]]:
+ """
+ A method for completing conversations using a specified language model.
+
+
+
+ If the model is served by a server supporting chat-completion, the method will directly call the server's
+ `/v1/chat/completions` endpoint. If the server does not support chat-completion, the method will render the
+ chat template client-side based on the information fetched from the Hub API. In this case, you will need to
+ have `minijinja` template engine installed. Run `pip install "huggingface_hub[inference]"` or `pip install minijinja`
+ to install it.
+
+
+
+ Args:
+ messages (List[Union[`SystemMessage`, `UserMessage`, `AssistantMessage`]]):
+ Conversation history consisting of roles and content pairs.
+ model (`str`, *optional*):
+ The model to use for chat-completion. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. If not provided, the default recommended model for chat-based text-generation will be used.
+ See https://huggingface.co/tasks/text-generation for more details.
+ frequency_penalty (`float`, optional):
+ Penalizes new tokens based on their existing frequency
+ in the text so far. Range: [-2.0, 2.0]. Defaults to 0.0.
+ max_tokens (`int`, optional):
+ Maximum number of tokens allowed in the response. Defaults to 20.
+ seed (Optional[`int`], optional):
+ Seed for reproducible control flow. Defaults to None.
+ stop (Optional[`str`], optional):
+ Up to four strings which trigger the end of the response.
+ Defaults to None.
+ stream (`bool`, optional):
+ Enable realtime streaming of responses. Defaults to False.
+ temperature (`float`, optional):
+ Controls randomness of the generations. Lower values ensure
+ less random completions. Range: [0, 2]. Defaults to 1.0.
+ top_p (`float`, optional):
+ Fraction of the most likely next words to sample from.
+ Must be between 0 and 1. Defaults to 1.0.
+
+ Returns:
+ `Union[ChatCompletionOutput, Iterable[ChatCompletionStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False`, the generated text is returned as a [`ChatCompletionOutput`] (default).
+ - if `stream=True`, the generated text is returned token by token as a sequence of [`ChatCompletionStreamOutput`].
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> messages = [{"role": "user", "content": "What is the capital of France?"}]
+ >>> client = AsyncInferenceClient("HuggingFaceH4/zephyr-7b-beta")
+ >>> await client.chat_completion(messages, max_tokens=100)
+ ChatCompletionOutput(
+ choices=[
+ ChatCompletionOutputChoice(
+ finish_reason='eos_token',
+ index=0,
+ message=ChatCompletionOutputChoiceMessage(
+ content='The capital of France is Paris. The official name of the city is "Ville de Paris" (City of Paris) and the name of the country\'s governing body, which is located in Paris, is "La République française" (The French Republic). \nI hope that helps! Let me know if you need any further information.'
+ )
+ )
+ ],
+ created=1710498360
+ )
+
+ >>> async for token in await client.chat_completion(messages, max_tokens=10, stream=True):
+ ... print(token)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content='The', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' capital', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ (...)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=' may', role='assistant'), index=0, finish_reason=None)], created=1710498504)
+ ChatCompletionStreamOutput(choices=[ChatCompletionStreamOutputChoice(delta=ChatCompletionStreamOutputDelta(content=None, role=None), index=0, finish_reason='length')], created=1710498504)
+ ```
+ """
+ # determine model
+ model = model or self.model or self.get_recommended_model("text-generation")
+
+ if _is_chat_completion_server(model):
+ # First, let's consider the server has a `/v1/chat/completions` endpoint.
+ # If that's the case, we don't have to render the chat template client-side.
+ model_url = self._resolve_url(model)
+ if not model_url.endswith("/chat/completions"):
+ model_url += "/v1/chat/completions"
+
+ try:
+ data = await self.post(
+ model=model_url,
+ json=dict(
+ model="tgi", # random string
+ messages=messages,
+ max_tokens=max_tokens,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_p=top_p,
+ stream=stream,
+ ),
+ stream=stream,
+ )
+ except _import_aiohttp().ClientResponseError:
+ # Let's consider the server is not a chat completion server.
+ # Then we call again `chat_completion` which will render the chat template client side.
+ # (can be HTTP 500, HTTP 400, HTTP 404 depending on the server)
+ _set_as_non_chat_completion_server(model)
+ return await self.chat_completion(
+ messages=messages,
+ model=model,
+ stream=stream,
+ max_tokens=max_tokens,
+ seed=seed,
+ stop=stop,
+ temperature=temperature,
+ top_p=top_p,
+ )
+
+ if stream:
+ return _async_stream_chat_completion_response_from_bytes(data) # type: ignore[arg-type]
+
+ return ChatCompletionOutput.parse_obj_as_instance(data) # type: ignore[arg-type]
+
+ # At this point, we know the server is not a chat completion server.
+ # We need to render the chat template client side based on the information we can fetch from
+ # the Hub API.
+
+ model_id = None
+ if model.startswith(("http://", "https://")):
+ # If URL, we need to know which model is served. This is not always possible.
+ # A workaround is to list the user Inference Endpoints and check if one of them correspond to the model URL.
+ # If not, we raise an error.
+ # TODO: fix when we have a proper API for this (at least for Inference Endpoints)
+ # TODO: what if Sagemaker URL?
+ # TODO: what if Azure URL?
+ from ..hf_api import HfApi
+
+ for endpoint in HfApi(token=self.token).list_inference_endpoints():
+ if endpoint.url == model:
+ model_id = endpoint.repository
+ break
+ else:
+ model_id = model
+
+ if model_id is None:
+ # If we don't have the model ID, we can't fetch the chat template.
+ # We raise an error.
+ raise ValueError(
+ "Request can't be processed as the model ID can't be inferred from model URL. "
+ "This is needed to fetch the chat template from the Hub since the model is not "
+ "served with a Chat-completion API."
+ )
+
+ # fetch chat template + tokens
+ prompt = render_chat_prompt(model_id=model_id, token=self.token, messages=messages)
+
+ # generate response
+ stop_sequences = [stop] if isinstance(stop, str) else stop
+ text_generation_output = await self.text_generation(
+ prompt=prompt,
+ details=True,
+ stream=stream,
+ model=model,
+ max_new_tokens=max_tokens,
+ seed=seed,
+ stop_sequences=stop_sequences,
+ temperature=temperature,
+ top_p=top_p,
+ )
+
+ created = int(time.time())
+
+ if stream:
+ return _async_stream_chat_completion_response_from_text_generation(text_generation_output) # type: ignore [arg-type]
+
+ if isinstance(text_generation_output, TextGenerationOutput):
+ # General use case => format ChatCompletionOutput from text generation details
+ content: str = text_generation_output.generated_text
+ finish_reason: str = text_generation_output.details.finish_reason # type: ignore[union-attr]
+ else:
+ # Corner case: if server doesn't support details (e.g. if not a TGI server), we only receive an output string.
+ # In such a case, `finish_reason` is set to `"unk"`.
+ content = text_generation_output # type: ignore[assignment]
+ finish_reason = "unk"
+
+ return ChatCompletionOutput(
+ created=created,
+ choices=[
+ ChatCompletionOutputChoice(
+ finish_reason=finish_reason, # type: ignore
+ index=0,
+ message=ChatCompletionOutputChoiceMessage(
+ content=content,
+ role="assistant",
+ ),
+ )
+ ],
+ )
+
+ async def conversational(
+ self,
+ text: str,
+ generated_responses: Optional[List[str]] = None,
+ past_user_inputs: Optional[List[str]] = None,
+ *,
+ parameters: Optional[Dict[str, Any]] = None,
+ model: Optional[str] = None,
+ ) -> ConversationalOutput:
+ """
+ Generate conversational responses based on the given input text (i.e. chat with the API).
+
+
+
+ [`InferenceClient.conversational`] API is deprecated and will be removed in a future release. Please use
+ [`InferenceClient.chat_completion`] instead.
+
+
+
+ Args:
+ text (`str`):
+ The last input from the user in the conversation.
+ generated_responses (`List[str]`, *optional*):
+ A list of strings corresponding to the earlier replies from the model. Defaults to None.
+ past_user_inputs (`List[str]`, *optional*):
+ A list of strings corresponding to the earlier replies from the user. Should be the same length as
+ `generated_responses`. Defaults to None.
+ parameters (`Dict[str, Any]`, *optional*):
+ Additional parameters for the conversational task. Defaults to None. For more details about the available
+ parameters, please refer to [this page](https://huggingface.co/docs/api-inference/detailed_parameters#conversational-task)
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `Dict`: The generated conversational output.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> output = await client.conversational("Hi, who are you?")
+ >>> output
+ {'generated_text': 'I am the one who knocks.', 'conversation': {'generated_responses': ['I am the one who knocks.'], 'past_user_inputs': ['Hi, who are you?']}, 'warnings': ['Setting `pad_token_id` to `eos_token_id`:50256 async for open-end generation.']}
+ >>> await client.conversational(
+ ... "Wow, that's scary!",
+ ... generated_responses=output["conversation"]["generated_responses"],
+ ... past_user_inputs=output["conversation"]["past_user_inputs"],
+ ... )
+ ```
+ """
+ warnings.warn(
+ "'InferenceClient.conversational' is deprecated and will be removed starting from huggingface_hub>=0.25. "
+ "Please use the more appropriate 'InferenceClient.chat_completion' API instead.",
+ FutureWarning,
+ )
+ payload: Dict[str, Any] = {"inputs": {"text": text}}
+ if generated_responses is not None:
+ payload["inputs"]["generated_responses"] = generated_responses
+ if past_user_inputs is not None:
+ payload["inputs"]["past_user_inputs"] = past_user_inputs
+ if parameters is not None:
+ payload["parameters"] = parameters
+ response = await self.post(json=payload, model=model, task="conversational")
+ return _bytes_to_dict(response) # type: ignore
+
+ async def document_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ ) -> List[DocumentQuestionAnsweringOutputElement]:
+ """
+ Answer questions on document images.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the document question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended document question answering model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[DocumentQuestionAnsweringOutputElement]`: a list of [`DocumentQuestionAnsweringOutputElement`] items containing the predicted label, associated probability, word ids, and page number.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.document_question_answering(image="https://huggingface.co/spaces/impira/docquery/resolve/2359223c1837a7587402bda0f2643382a6eefeab/invoice.png", question="What is the invoice number?")
+ [DocumentQuestionAnsweringOutputElement(score=0.42515629529953003, answer='us-001', start=16, end=16)]
+ ```
+ """
+ payload: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ response = await self.post(json=payload, model=model, task="document-question-answering")
+ return DocumentQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ async def feature_extraction(self, text: str, *, model: Optional[str] = None) -> "np.ndarray":
+ """
+ Generate embeddings for a given text.
+
+ Args:
+ text (`str`):
+ The text to embed.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `np.ndarray`: The embedding representing the input text as a float32 numpy array.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.feature_extraction("Hi, who are you?")
+ array([[ 2.424802 , 2.93384 , 1.1750331 , ..., 1.240499, -0.13776633, -0.7889173 ],
+ [-0.42943227, -0.6364878 , -1.693462 , ..., 0.41978157, -2.4336355 , 0.6162071 ],
+ ...,
+ [ 0.28552425, -0.928395 , -1.2077185 , ..., 0.76810825, -2.1069427 , 0.6236161 ]], dtype=float32)
+ ```
+ """
+ response = await self.post(json={"inputs": text}, model=model, task="feature-extraction")
+ np = _import_numpy()
+ return np.array(_bytes_to_dict(response), dtype="float32")
+
+ async def fill_mask(self, text: str, *, model: Optional[str] = None) -> List[FillMaskOutputElement]:
+ """
+ Fill in a hole with a missing word (token to be precise).
+
+ Args:
+ text (`str`):
+ a string to be filled from, must contain the [MASK] token (check model card for exact name of the mask).
+ model (`str`, *optional*):
+ The model to use for the fill mask task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended fill mask model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[FillMaskOutputElement]`: a list of [`FillMaskOutputElement`] items containing the predicted label, associated
+ probability, token reference, and completed text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.fill_mask("The goal of life is .")
+ [
+ FillMaskOutputElement(score=0.06897063553333282, token=11098, token_str=' happiness', sequence='The goal of life is happiness.'),
+ FillMaskOutputElement(score=0.06554922461509705, token=45075, token_str=' immortality', sequence='The goal of life is immortality.')
+ ]
+ ```
+ """
+ response = await self.post(json={"inputs": text}, model=model, task="fill-mask")
+ return FillMaskOutputElement.parse_obj_as_list(response)
+
+ async def image_classification(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ImageClassificationOutputElement]:
+ """
+ Perform image classification on the given image using the specified model.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to classify. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image classification. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image classification will be used.
+
+ Returns:
+ `List[ImageClassificationOutputElement]`: a list of [`ImageClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_classification("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ [ImageClassificationOutputElement(score=0.9779096841812134, label='Blenheim spaniel'), ...]
+ ```
+ """
+ response = await self.post(data=image, model=model, task="image-classification")
+ return ImageClassificationOutputElement.parse_obj_as_list(response)
+
+ async def image_segmentation(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ImageSegmentationOutputElement]:
+ """
+ Perform image segmentation on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to segment. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for image segmentation. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for image segmentation will be used.
+
+ Returns:
+ `List[ImageSegmentationOutputElement]`: A list of [`ImageSegmentationOutputElement`] items containing the segmented masks and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_segmentation("cat.jpg"):
+ [ImageSegmentationOutputElement(score=0.989008, label='LABEL_184', mask=), ...]
+ ```
+ """
+ response = await self.post(data=image, model=model, task="image-segmentation")
+ output = ImageSegmentationOutputElement.parse_obj_as_list(response)
+ for item in output:
+ item.mask = _b64_to_image(item.mask)
+ return output
+
+ async def image_to_image(
+ self,
+ image: ContentT,
+ prompt: Optional[str] = None,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[int] = None,
+ width: Optional[int] = None,
+ num_inference_steps: Optional[int] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Perform image-to-image translation using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for translation. It can be raw bytes, an image file, or a URL to an online image.
+ prompt (`str`, *optional*):
+ The text prompt to guide the image generation.
+ negative_prompt (`str`, *optional*):
+ A negative prompt to guide the translation process.
+ height (`int`, *optional*):
+ The height in pixels of the generated image.
+ width (`int`, *optional*):
+ The width in pixels of the generated image.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
+ usually at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Image`: The translated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> image = await client.image_to_image("cat.jpg", prompt="turn the cat into a tiger")
+ >>> image.save("tiger.jpg")
+ ```
+ """
+ parameters = {
+ "prompt": prompt,
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ }
+ if all(parameter is None for parameter in parameters.values()):
+ # Either only an image to send => send as raw bytes
+ data = image
+ payload: Optional[Dict[str, Any]] = None
+ else:
+ # Or an image + some parameters => use base64 encoding
+ data = None
+ payload = {"inputs": _b64_encode(image)}
+ for key, value in parameters.items():
+ if value is not None:
+ payload.setdefault("parameters", {})[key] = value
+
+ response = await self.post(json=payload, data=data, model=model, task="image-to-image")
+ return _bytes_to_image(response)
+
+ async def image_to_text(self, image: ContentT, *, model: Optional[str] = None) -> ImageToTextOutput:
+ """
+ Takes an input image and return text.
+
+ Models can have very different outputs depending on your use case (image captioning, optical character recognition
+ (OCR), Pix2Struct, etc). Please have a look to the model card to learn more about a model's specificities.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image..
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`ImageToTextOutput`]: The generated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.image_to_text("cat.jpg")
+ 'a cat standing in a grassy field '
+ >>> await client.image_to_text("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg")
+ 'a dog laying on the grass next to a flower pot '
+ ```
+ """
+ response = await self.post(data=image, model=model, task="image-to-text")
+ return ImageToTextOutput.parse_obj_as_instance(response)
+
+ async def list_deployed_models(
+ self, frameworks: Union[None, str, Literal["all"], List[str]] = None
+ ) -> Dict[str, List[str]]:
+ """
+ List models currently deployed on the Inference API service.
+
+ This helper checks deployed models framework by framework. By default, it will check the 4 main frameworks that
+ are supported and account for 95% of the hosted models. However, if you want a complete list of models you can
+ specify `frameworks="all"` as input. Alternatively, if you know before-hand which framework you are interested
+ in, you can also restrict to search to this one (e.g. `frameworks="text-generation-inference"`). The more
+ frameworks are checked, the more time it will take.
+
+
+
+ This endpoint is mostly useful for discoverability. If you already know which model you want to use and want to
+ check its availability, you can directly use [`~InferenceClient.get_model_status`].
+
+
+
+ Args:
+ frameworks (`Literal["all"]` or `List[str]` or `str`, *optional*):
+ The frameworks to filter on. By default only a subset of the available frameworks are tested. If set to
+ "all", all available frameworks will be tested. It is also possible to provide a single framework or a
+ custom set of frameworks to check.
+
+ Returns:
+ `Dict[str, List[str]]`: A dictionary mapping task names to a sorted list of model IDs.
+
+ Example:
+ ```py
+ # Must be run in an async contextthon
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ # Discover zero-shot-classification models currently deployed
+ >>> models = await client.list_deployed_models()
+ >>> models["zero-shot-classification"]
+ ['Narsil/deberta-large-mnli-zero-cls', 'facebook/bart-large-mnli', ...]
+
+ # List from only 1 framework
+ >>> await client.list_deployed_models("text-generation-inference")
+ {'text-generation': ['bigcode/starcoder', 'meta-llama/Llama-2-70b-chat-hf', ...], ...}
+ ```
+ """
+ # Resolve which frameworks to check
+ if frameworks is None:
+ frameworks = MAIN_INFERENCE_API_FRAMEWORKS
+ elif frameworks == "all":
+ frameworks = ALL_INFERENCE_API_FRAMEWORKS
+ elif isinstance(frameworks, str):
+ frameworks = [frameworks]
+ frameworks = list(set(frameworks))
+
+ # Fetch them iteratively
+ models_by_task: Dict[str, List[str]] = {}
+
+ def _unpack_response(framework: str, items: List[Dict]) -> None:
+ for model in items:
+ if framework == "sentence-transformers":
+ # Model running with the `sentence-transformers` framework can work with both tasks even if not
+ # branded as such in the API response
+ models_by_task.setdefault("feature-extraction", []).append(model["model_id"])
+ models_by_task.setdefault("sentence-similarity", []).append(model["model_id"])
+ else:
+ models_by_task.setdefault(model["task"], []).append(model["model_id"])
+
+ async def _fetch_framework(framework: str) -> None:
+ async with _import_aiohttp().ClientSession(headers=self.headers) as client:
+ response = await client.get(f"{INFERENCE_ENDPOINT}/framework/{framework}")
+ response.raise_for_status()
+ _unpack_response(framework, await response.json())
+
+ import asyncio
+
+ await asyncio.gather(*[_fetch_framework(framework) for framework in frameworks])
+
+ # Sort alphabetically for discoverability and return
+ for task, models in models_by_task.items():
+ models_by_task[task] = sorted(set(models), key=lambda x: x.lower())
+ return models_by_task
+
+ async def object_detection(
+ self,
+ image: ContentT,
+ *,
+ model: Optional[str] = None,
+ ) -> List[ObjectDetectionOutputElement]:
+ """
+ Perform object detection on the given image using the specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The image to detect objects on. It can be raw bytes, an image file, or a URL to an online image.
+ model (`str`, *optional*):
+ The model to use for object detection. Can be a model ID hosted on the Hugging Face Hub or a URL to a
+ deployed Inference Endpoint. If not provided, the default recommended model for object detection (DETR) will be used.
+
+ Returns:
+ `List[ObjectDetectionOutputElement]`: A list of [`ObjectDetectionOutputElement`] items containing the bounding boxes and associated attributes.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If the request output is not a List.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.object_detection("people.jpg"):
+ [ObjectDetectionOutputElement(score=0.9486683011054993, label='person', box=ObjectDetectionBoundingBox(xmin=59, ymin=39, xmax=420, ymax=510)), ...]
+ ```
+ """
+ # detect objects
+ response = await self.post(data=image, model=model, task="object-detection")
+ return ObjectDetectionOutputElement.parse_obj_as_list(response)
+
+ async def question_answering(
+ self, question: str, context: str, *, model: Optional[str] = None
+ ) -> QuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from a given text.
+
+ Args:
+ question (`str`):
+ Question to be answered.
+ context (`str`):
+ The context of the question.
+ model (`str`):
+ The model to use for the question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint.
+
+ Returns:
+ [`QuestionAnsweringOutputElement`]: an question answering output containing the score, start index, end index, and answer.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.question_answering(question="What's my name?", context="My name is Clara and I live in Berkeley.")
+ QuestionAnsweringOutputElement(score=0.9326562285423279, start=11, end=16, answer='Clara')
+ ```
+ """
+
+ payload: Dict[str, Any] = {"question": question, "context": context}
+ response = await self.post(
+ json=payload,
+ model=model,
+ task="question-answering",
+ )
+ return QuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ async def sentence_similarity(
+ self, sentence: str, other_sentences: List[str], *, model: Optional[str] = None
+ ) -> List[float]:
+ """
+ Compute the semantic similarity between a sentence and a list of other sentences by comparing their embeddings.
+
+ Args:
+ sentence (`str`):
+ The main sentence to compare to others.
+ other_sentences (`List[str]`):
+ The list of sentences to compare to.
+ model (`str`, *optional*):
+ The model to use for the conversational task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended conversational model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[float]`: The embedding representing the input text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.sentence_similarity(
+ ... "Machine learning is so easy.",
+ ... other_sentences=[
+ ... "Deep learning is so straightforward.",
+ ... "This is so difficult, like rocket science.",
+ ... "I can't believe how much I struggled with this.",
+ ... ],
+ ... )
+ [0.7785726189613342, 0.45876261591911316, 0.2906220555305481]
+ ```
+ """
+ response = await self.post(
+ json={"inputs": {"source_sentence": sentence, "sentences": other_sentences}},
+ model=model,
+ task="sentence-similarity",
+ )
+ return _bytes_to_list(response)
+
+ async def summarization(
+ self,
+ text: str,
+ *,
+ parameters: Optional[Dict[str, Any]] = None,
+ model: Optional[str] = None,
+ ) -> SummarizationOutput:
+ """
+ Generate a summary of a given text using a specified model.
+
+ Args:
+ text (`str`):
+ The input text to summarize.
+ parameters (`Dict[str, Any]`, *optional*):
+ Additional parameters for summarization. Check out this [page](https://huggingface.co/docs/api-inference/detailed_parameters#summarization-task)
+ for more details.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ [`SummarizationOutput`]: The generated summary text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.summarization("The Eiffel tower...")
+ SummarizationOutput(generated_text="The Eiffel tower is one of the most famous landmarks in the world....")
+ ```
+ """
+ payload: Dict[str, Any] = {"inputs": text}
+ if parameters is not None:
+ payload["parameters"] = parameters
+ response = await self.post(json=payload, model=model, task="summarization")
+ return SummarizationOutput.parse_obj_as_list(response)[0]
+
+ async def table_question_answering(
+ self, table: Dict[str, Any], query: str, *, model: Optional[str] = None
+ ) -> TableQuestionAnsweringOutputElement:
+ """
+ Retrieve the answer to a question from information given in a table.
+
+ Args:
+ table (`str`):
+ A table of data represented as a dict of lists where entries are headers and the lists are all the
+ values, all lists must have the same size.
+ query (`str`):
+ The query in plain text that you want to ask the table.
+ model (`str`):
+ The model to use for the table-question-answering task. Can be a model ID hosted on the Hugging Face
+ Hub or a URL to a deployed Inference Endpoint.
+
+ Returns:
+ [`TableQuestionAnsweringOutputElement`]: a table question answering output containing the answer, coordinates, cells and the aggregator used.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> query = "How many stars does the transformers repository have?"
+ >>> table = {"Repository": ["Transformers", "Datasets", "Tokenizers"], "Stars": ["36542", "4512", "3934"]}
+ >>> await client.table_question_answering(table, query, model="google/tapas-base-finetuned-wtq")
+ TableQuestionAnsweringOutputElement(answer='36542', coordinates=[[0, 1]], cells=['36542'], aggregator='AVERAGE')
+ ```
+ """
+ response = await self.post(
+ json={
+ "query": query,
+ "table": table,
+ },
+ model=model,
+ task="table-question-answering",
+ )
+ return TableQuestionAnsweringOutputElement.parse_obj_as_instance(response)
+
+ async def tabular_classification(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[str]:
+ """
+ Classifying a target category (a group) based on a set of attributes.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes to classify.
+ model (`str`, *optional*):
+ The model to use for the tabular classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of labels, one per row in the initial table.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> table = {
+ ... "fixed_acidity": ["7.4", "7.8", "10.3"],
+ ... "volatile_acidity": ["0.7", "0.88", "0.32"],
+ ... "citric_acid": ["0", "0", "0.45"],
+ ... "residual_sugar": ["1.9", "2.6", "6.4"],
+ ... "chlorides": ["0.076", "0.098", "0.073"],
+ ... "free_sulfur_dioxide": ["11", "25", "5"],
+ ... "total_sulfur_dioxide": ["34", "67", "13"],
+ ... "density": ["0.9978", "0.9968", "0.9976"],
+ ... "pH": ["3.51", "3.2", "3.23"],
+ ... "sulphates": ["0.56", "0.68", "0.82"],
+ ... "alcohol": ["9.4", "9.8", "12.6"],
+ ... }
+ >>> await client.tabular_classification(table=table, model="julien-c/wine-quality")
+ ["5", "5", "5"]
+ ```
+ """
+ response = await self.post(json={"table": table}, model=model, task="tabular-classification")
+ return _bytes_to_list(response)
+
+ async def tabular_regression(self, table: Dict[str, Any], *, model: Optional[str] = None) -> List[float]:
+ """
+ Predicting a numerical target value given a set of attributes/features in a table.
+
+ Args:
+ table (`Dict[str, Any]`):
+ Set of attributes stored in a table. The attributes used to predict the target can be both numerical and categorical.
+ model (`str`, *optional*):
+ The model to use for the tabular regression task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended tabular regression model will be used.
+ Defaults to None.
+
+ Returns:
+ `List`: a list of predicted numerical target values.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> table = {
+ ... "Height": ["11.52", "12.48", "12.3778"],
+ ... "Length1": ["23.2", "24", "23.9"],
+ ... "Length2": ["25.4", "26.3", "26.5"],
+ ... "Length3": ["30", "31.2", "31.1"],
+ ... "Species": ["Bream", "Bream", "Bream"],
+ ... "Width": ["4.02", "4.3056", "4.6961"],
+ ... }
+ >>> await client.tabular_regression(table, model="scikit-learn/Fish-Weight")
+ [110, 120, 130]
+ ```
+ """
+ response = await self.post(json={"table": table}, model=model, task="tabular-regression")
+ return _bytes_to_list(response)
+
+ async def text_classification(
+ self, text: str, *, model: Optional[str] = None
+ ) -> List[TextClassificationOutputElement]:
+ """
+ Perform text classification (e.g. sentiment-analysis) on the given text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the text classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended text classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[TextClassificationOutputElement]`: a list of [`TextClassificationOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.text_classification("I like you")
+ [
+ TextClassificationOutputElement(label='POSITIVE', score=0.9998695850372314),
+ TextClassificationOutputElement(label='NEGATIVE', score=0.0001304351753788069),
+ ]
+ ```
+ """
+ response = await self.post(json={"inputs": text}, model=model, task="text-classification")
+ return TextClassificationOutputElement.parse_obj_as_list(response)[0] # type: ignore [return-value]
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> str: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[False] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> TextGenerationOutput: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[False] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> AsyncIterable[str]: ...
+
+ @overload
+ async def text_generation( # type: ignore
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: Literal[True] = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> AsyncIterable[TextGenerationStreamOutput]: ...
+
+ @overload
+ async def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: Literal[True] = ...,
+ stream: bool = ...,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ ) -> Union[TextGenerationOutput, AsyncIterable[TextGenerationStreamOutput]]: ...
+
+ async def text_generation(
+ self,
+ prompt: str,
+ *,
+ details: bool = False,
+ stream: bool = False,
+ model: Optional[str] = None,
+ do_sample: bool = False,
+ max_new_tokens: int = 20,
+ best_of: Optional[int] = None,
+ repetition_penalty: Optional[float] = None,
+ return_full_text: bool = False,
+ seed: Optional[int] = None,
+ stop_sequences: Optional[List[str]] = None,
+ temperature: Optional[float] = None,
+ top_k: Optional[int] = None,
+ top_p: Optional[float] = None,
+ truncate: Optional[int] = None,
+ typical_p: Optional[float] = None,
+ watermark: bool = False,
+ decoder_input_details: bool = False,
+ ) -> Union[str, TextGenerationOutput, AsyncIterable[str], AsyncIterable[TextGenerationStreamOutput]]:
+ """
+ Given a prompt, generate the following text.
+
+ API endpoint is supposed to run with the `text-generation-inference` backend (TGI). This backend is the
+ go-to solution to run large language models at scale. However, for some smaller models (e.g. "gpt2") the
+ default `transformers` + `api-inference` solution is still in use. Both approaches have very similar APIs, but
+ not exactly the same. This method is compatible with both approaches but some parameters are only available for
+ `text-generation-inference`. If some parameters are ignored, a warning message is triggered but the process
+ continues correctly.
+
+ To learn more about the TGI project, please refer to https://github.com/huggingface/text-generation-inference.
+
+ Args:
+ prompt (`str`):
+ Input text.
+ details (`bool`, *optional*):
+ By default, text_generation returns a string. Pass `details=True` if you want a detailed output (tokens,
+ probabilities, seed, finish reason, etc.). Only available for models running on with the
+ `text-generation-inference` backend.
+ stream (`bool`, *optional*):
+ By default, text_generation returns the full generated text. Pass `stream=True` if you want a stream of
+ tokens to be returned. Only available for models running on with the `text-generation-inference`
+ backend.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+ do_sample (`bool`):
+ Activate logits sampling
+ max_new_tokens (`int`):
+ Maximum number of generated tokens
+ best_of (`int`):
+ Generate best_of sequences and return the one if the highest token logprobs
+ repetition_penalty (`float`):
+ The parameter for repetition penalty. 1.0 means no penalty. See [this
+ paper](https://arxiv.org/pdf/1909.05858.pdf) for more details.
+ return_full_text (`bool`):
+ Whether to prepend the prompt to the generated text
+ seed (`int`):
+ Random sampling seed
+ stop_sequences (`List[str]`):
+ Stop generating tokens if a member of `stop_sequences` is generated
+ temperature (`float`):
+ The value used to module the logits distribution.
+ top_k (`int`):
+ The number of highest probability vocabulary tokens to keep for top-k-filtering.
+ top_p (`float`):
+ If set to < 1, only the smallest set of most probable tokens with probabilities that add up to `top_p` or
+ higher are kept for generation.
+ truncate (`int`):
+ Truncate inputs tokens to the given size
+ typical_p (`float`):
+ Typical Decoding mass
+ See [Typical Decoding for Natural Language Generation](https://arxiv.org/abs/2202.00666) for more information
+ watermark (`bool`):
+ Watermarking with [A Watermark for Large Language Models](https://arxiv.org/abs/2301.10226)
+ decoder_input_details (`bool`):
+ Return the decoder input token logprobs and ids. You must set `details=True` as well for it to be taken
+ into account. Defaults to `False`.
+
+ Returns:
+ `Union[str, TextGenerationOutput, Iterable[str], Iterable[TextGenerationStreamOutput]]`:
+ Generated text returned from the server:
+ - if `stream=False` and `details=False`, the generated text is returned as a `str` (default)
+ - if `stream=True` and `details=False`, the generated text is returned token by token as a `Iterable[str]`
+ - if `stream=False` and `details=True`, the generated text is returned with more details as a [`~huggingface_hub.TextGenerationOutput`]
+ - if `details=True` and `stream=True`, the generated text is returned token by token as a iterable of [`~huggingface_hub.TextGenerationStreamOutput`]
+
+ Raises:
+ `ValidationError`:
+ If input values are not valid. No HTTP call is made to the server.
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ # Case 1: generate text
+ >>> await client.text_generation("The huggingface_hub library is ", max_new_tokens=12)
+ '100% open source and built to be easy to use.'
+
+ # Case 2: iterate over the generated tokens. Useful async for large generation.
+ >>> async for token in await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, stream=True):
+ ... print(token)
+ 100
+ %
+ open
+ source
+ and
+ built
+ to
+ be
+ easy
+ to
+ use
+ .
+
+ # Case 3: get more details about the generation process.
+ >>> await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True)
+ TextGenerationOutput(
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationDetails(
+ finish_reason='length',
+ generated_tokens=12,
+ seed=None,
+ prefill=[
+ TextGenerationPrefillToken(id=487, text='The', logprob=None),
+ TextGenerationPrefillToken(id=53789, text=' hugging', logprob=-13.171875),
+ (...)
+ TextGenerationPrefillToken(id=204, text=' ', logprob=-7.0390625)
+ ],
+ tokens=[
+ TokenElement(id=1425, text='100', logprob=-1.0175781, special=False),
+ TokenElement(id=16, text='%', logprob=-0.0463562, special=False),
+ (...)
+ TokenElement(id=25, text='.', logprob=-0.5703125, special=False)
+ ],
+ best_of_sequences=None
+ )
+ )
+
+ # Case 4: iterate over the generated tokens with more details.
+ # Last object is more complete, containing the full generated text and the finish reason.
+ >>> async for details in await client.text_generation("The huggingface_hub library is ", max_new_tokens=12, details=True, stream=True):
+ ... print(details)
+ ...
+ TextGenerationStreamOutput(token=TokenElement(id=1425, text='100', logprob=-1.0175781, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=16, text='%', logprob=-0.0463562, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1314, text=' open', logprob=-1.3359375, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3178, text=' source', logprob=-0.28100586, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=273, text=' and', logprob=-0.5961914, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=3426, text=' built', logprob=-1.9423828, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-1.4121094, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=314, text=' be', logprob=-1.5224609, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=1833, text=' easy', logprob=-2.1132812, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=271, text=' to', logprob=-0.08520508, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(id=745, text=' use', logprob=-0.39453125, special=False), generated_text=None, details=None)
+ TextGenerationStreamOutput(token=TokenElement(
+ id=25,
+ text='.',
+ logprob=-0.5703125,
+ special=False),
+ generated_text='100% open source and built to be easy to use.',
+ details=TextGenerationStreamDetails(finish_reason='length', generated_tokens=12, seed=None)
+ )
+ ```
+ """
+ if decoder_input_details and not details:
+ warnings.warn(
+ "`decoder_input_details=True` has been passed to the server but `details=False` is set meaning that"
+ " the output from the server will be truncated."
+ )
+ decoder_input_details = False
+
+ # Build payload
+ payload = {
+ "inputs": prompt,
+ "parameters": {
+ "best_of": best_of,
+ "decoder_input_details": decoder_input_details,
+ "details": details,
+ "do_sample": do_sample,
+ "max_new_tokens": max_new_tokens,
+ "repetition_penalty": repetition_penalty,
+ "return_full_text": return_full_text,
+ "seed": seed,
+ "stop": stop_sequences if stop_sequences is not None else [],
+ "temperature": temperature,
+ "top_k": top_k,
+ "top_p": top_p,
+ "truncate": truncate,
+ "typical_p": typical_p,
+ "watermark": watermark,
+ },
+ "stream": stream,
+ }
+
+ # Remove some parameters if not a TGI server
+ if not _is_tgi_server(model):
+ parameters: Dict = payload["parameters"] # type: ignore [assignment]
+
+ ignored_parameters = []
+ for key in "watermark", "details", "decoder_input_details", "best_of", "stop", "return_full_text":
+ if parameters[key] is not None:
+ ignored_parameters.append(key)
+ del parameters[key]
+ if len(ignored_parameters) > 0:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Ignoring parameters"
+ f" {ignored_parameters}.",
+ UserWarning,
+ )
+ if details:
+ warnings.warn(
+ "API endpoint/model for text-generation is not served via TGI. Parameter `details=True` will"
+ " be ignored meaning only the generated text will be returned.",
+ UserWarning,
+ )
+ details = False
+ if stream:
+ raise ValueError(
+ "API endpoint/model for text-generation is not served via TGI. Cannot return output as a stream."
+ " Please pass `stream=False` as input."
+ )
+
+ # Handle errors separately for more precise error messages
+ try:
+ bytes_output = await self.post(json=payload, model=model, task="text-generation", stream=stream) # type: ignore
+ except _import_aiohttp().ClientResponseError as e:
+ error_message = getattr(e, "response_error_payload", {}).get("error", "")
+ if e.code == 400 and "The following `model_kwargs` are not used by the model" in error_message:
+ _set_as_non_tgi(model)
+ return await self.text_generation( # type: ignore
+ prompt=prompt,
+ details=details,
+ stream=stream,
+ model=model,
+ do_sample=do_sample,
+ max_new_tokens=max_new_tokens,
+ best_of=best_of,
+ repetition_penalty=repetition_penalty,
+ return_full_text=return_full_text,
+ seed=seed,
+ stop_sequences=stop_sequences,
+ temperature=temperature,
+ top_k=top_k,
+ top_p=top_p,
+ truncate=truncate,
+ typical_p=typical_p,
+ watermark=watermark,
+ decoder_input_details=decoder_input_details,
+ )
+ raise_text_generation_error(e)
+
+ # Parse output
+ if stream:
+ return _async_stream_text_generation_response(bytes_output, details) # type: ignore
+
+ data = _bytes_to_dict(bytes_output)[0] # type: ignore[arg-type]
+ return TextGenerationOutput.parse_obj_as_instance(data) if details else data["generated_text"]
+
+ async def text_to_image(
+ self,
+ prompt: str,
+ *,
+ negative_prompt: Optional[str] = None,
+ height: Optional[float] = None,
+ width: Optional[float] = None,
+ num_inference_steps: Optional[float] = None,
+ guidance_scale: Optional[float] = None,
+ model: Optional[str] = None,
+ **kwargs,
+ ) -> "Image":
+ """
+ Generate an image based on a given text using a specified model.
+
+
+
+ You must have `PIL` installed if you want to work with images (`pip install Pillow`).
+
+
+
+ Args:
+ prompt (`str`):
+ The prompt to generate an image from.
+ negative_prompt (`str`, *optional*):
+ An optional negative prompt for the image generation.
+ height (`float`, *optional*):
+ The height in pixels of the image to generate.
+ width (`float`, *optional*):
+ The width in pixels of the image to generate.
+ num_inference_steps (`int`, *optional*):
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
+ expense of slower inference.
+ guidance_scale (`float`, *optional*):
+ Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
+ usually at the expense of lower image quality.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `Image`: The generated image.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> image = await client.text_to_image("An astronaut riding a horse on the moon.")
+ >>> image.save("astronaut.png")
+
+ >>> image = await client.text_to_image(
+ ... "An astronaut riding a horse on the moon.",
+ ... negative_prompt="low resolution, blurry",
+ ... model="stabilityai/stable-diffusion-2-1",
+ ... )
+ >>> image.save("better_astronaut.png")
+ ```
+ """
+ payload = {"inputs": prompt}
+ parameters = {
+ "negative_prompt": negative_prompt,
+ "height": height,
+ "width": width,
+ "num_inference_steps": num_inference_steps,
+ "guidance_scale": guidance_scale,
+ **kwargs,
+ }
+ for key, value in parameters.items():
+ if value is not None:
+ payload.setdefault("parameters", {})[key] = value # type: ignore
+ response = await self.post(json=payload, model=model, task="text-to-image")
+ return _bytes_to_image(response)
+
+ async def text_to_speech(self, text: str, *, model: Optional[str] = None) -> bytes:
+ """
+ Synthesize an audio of a voice pronouncing a given text.
+
+ Args:
+ text (`str`):
+ The text to synthesize.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `bytes`: The generated audio.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from pathlib import Path
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> audio = await client.text_to_speech("Hello world")
+ >>> Path("hello_world.flac").write_bytes(audio)
+ ```
+ """
+ return await self.post(json={"inputs": text}, model=model, task="text-to-speech")
+
+ async def token_classification(
+ self, text: str, *, model: Optional[str] = None
+ ) -> List[TokenClassificationOutputElement]:
+ """
+ Perform token classification on the given text.
+ Usually used for sentence parsing, either grammatical, or Named Entity Recognition (NER) to understand keywords contained within text.
+
+ Args:
+ text (`str`):
+ A string to be classified.
+ model (`str`, *optional*):
+ The model to use for the token classification task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended token classification model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[TokenClassificationOutputElement]`: List of [`TokenClassificationOutputElement`] items containing the entity group, confidence score, word, start and end index.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.token_classification("My name is Sarah Jessica Parker but you can call me Jessica")
+ [
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9971321225166321,
+ word='Sarah Jessica Parker',
+ start=11,
+ end=31,
+ ),
+ TokenClassificationOutputElement(
+ entity_group='PER',
+ score=0.9773476123809814,
+ word='Jessica',
+ start=52,
+ end=59,
+ )
+ ]
+ ```
+ """
+ payload: Dict[str, Any] = {"inputs": text}
+ response = await self.post(
+ json=payload,
+ model=model,
+ task="token-classification",
+ )
+ return TokenClassificationOutputElement.parse_obj_as_list(response)
+
+ async def translation(
+ self, text: str, *, model: Optional[str] = None, src_lang: Optional[str] = None, tgt_lang: Optional[str] = None
+ ) -> TranslationOutput:
+ """
+ Convert text from one language to another.
+
+ Check out https://huggingface.co/tasks/translation for more information on how to choose the best model for
+ your specific use case. Source and target languages usually depend on the model.
+ However, it is possible to specify source and target languages for certain models. If you are working with one of these models,
+ you can use `src_lang` and `tgt_lang` arguments to pass the relevant information.
+ You can find this information in the model card.
+
+ Args:
+ text (`str`):
+ A string to be translated.
+ model (`str`, *optional*):
+ The model to use for the translation task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended translation model will be used.
+ Defaults to None.
+ src_lang (`str`, *optional*):
+ Source language of the translation task, i.e. input language. Cannot be passed without `tgt_lang`.
+ tgt_lang (`str`, *optional*):
+ Target language of the translation task, i.e. output language. Cannot be passed without `src_lang`.
+
+ Returns:
+ [`TranslationOutput`]: The generated translated text.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+ `ValueError`:
+ If only one of the `src_lang` and `tgt_lang` arguments are provided.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.translation("My name is Wolfgang and I live in Berlin")
+ 'Mein Name ist Wolfgang und ich lebe in Berlin.'
+ >>> await client.translation("My name is Wolfgang and I live in Berlin", model="Helsinki-NLP/opus-mt-en-fr")
+ TranslationOutput(translation_text='Je m\'appelle Wolfgang et je vis à Berlin.')
+ ```
+
+ Specifying languages:
+ ```py
+ >>> client.translation("My name is Sarah Jessica Parker but you can call me Jessica", model="facebook/mbart-large-50-many-to-many-mmt", src_lang="en_XX", tgt_lang="fr_XX")
+ "Mon nom est Sarah Jessica Parker mais vous pouvez m\'appeler Jessica"
+ ```
+ """
+ # Throw error if only one of `src_lang` and `tgt_lang` was given
+ if src_lang is not None and tgt_lang is None:
+ raise ValueError("You cannot specify `src_lang` without specifying `tgt_lang`.")
+
+ if src_lang is None and tgt_lang is not None:
+ raise ValueError("You cannot specify `tgt_lang` without specifying `src_lang`.")
+
+ # If both `src_lang` and `tgt_lang` are given, pass them to the request body
+ payload: Dict = {"inputs": text}
+ if src_lang and tgt_lang:
+ payload["parameters"] = {"src_lang": src_lang, "tgt_lang": tgt_lang}
+ response = await self.post(json=payload, model=model, task="translation")
+ return TranslationOutput.parse_obj_as_list(response)[0]
+
+ async def visual_question_answering(
+ self,
+ image: ContentT,
+ question: str,
+ *,
+ model: Optional[str] = None,
+ ) -> List[VisualQuestionAnsweringOutputElement]:
+ """
+ Answering open-ended questions based on an image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image for the context. It can be raw bytes, an image file, or a URL to an online image.
+ question (`str`):
+ Question to be answered.
+ model (`str`, *optional*):
+ The model to use for the visual question answering task. Can be a model ID hosted on the Hugging Face Hub or a URL to
+ a deployed Inference Endpoint. If not provided, the default recommended visual question answering model will be used.
+ Defaults to None.
+
+ Returns:
+ `List[VisualQuestionAnsweringOutputElement]`: a list of [`VisualQuestionAnsweringOutputElement`] items containing the predicted label and associated probability.
+
+ Raises:
+ `InferenceTimeoutError`:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.visual_question_answering(
+ ... image="https://huggingface.co/datasets/mishig/sample_images/resolve/main/tiger.jpg",
+ ... question="What is the animal doing?"
+ ... )
+ [
+ VisualQuestionAnsweringOutputElement(score=0.778609573841095, answer='laying down'),
+ VisualQuestionAnsweringOutputElement(score=0.6957435607910156, answer='sitting'),
+ ]
+ ```
+ """
+ payload: Dict[str, Any] = {"question": question, "image": _b64_encode(image)}
+ response = await self.post(json=payload, model=model, task="visual-question-answering")
+ return VisualQuestionAnsweringOutputElement.parse_obj_as_list(response)
+
+ async def zero_shot_classification(
+ self, text: str, labels: List[str], *, multi_label: bool = False, model: Optional[str] = None
+ ) -> List[ZeroShotClassificationOutputElement]:
+ """
+ Provide as input a text and a set of candidate labels to classify the input text.
+
+ Args:
+ text (`str`):
+ The input text to classify.
+ labels (`List[str]`):
+ List of string possible labels. There must be at least 2 labels.
+ multi_label (`bool`):
+ Boolean that is set to True if classes can overlap.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `List[ZeroShotClassificationOutputElement]`: List of [`ZeroShotClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> text = (
+ ... "A new model offers an explanation async for how the Galilean satellites formed around the solar system's"
+ ... "largest world. Konstantin Batygin did not set out to solve one of the solar system's most puzzling"
+ ... " mysteries when he went async for a run up a hill in Nice, France."
+ ... )
+ >>> labels = ["space & cosmos", "scientific discovery", "microbiology", "robots", "archeology"]
+ >>> await client.zero_shot_classification(text, labels)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.7961668968200684),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.18570658564567566),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.00730885099619627),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.006258360575884581),
+ ZeroShotClassificationOutputElement(label='robots', score=0.004559356719255447),
+ ]
+ >>> await client.zero_shot_classification(text, labels, multi_label=True)
+ [
+ ZeroShotClassificationOutputElement(label='scientific discovery', score=0.9829297661781311),
+ ZeroShotClassificationOutputElement(label='space & cosmos', score=0.755190908908844),
+ ZeroShotClassificationOutputElement(label='microbiology', score=0.0005462635890580714),
+ ZeroShotClassificationOutputElement(label='archeology', score=0.00047131875180639327),
+ ZeroShotClassificationOutputElement(label='robots', score=0.00030448526376858354),
+ ]
+ ```
+ """
+ # Raise ValueError if input is less than 2 labels
+ if len(labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ response = await self.post(
+ json={
+ "inputs": text,
+ "parameters": {
+ "candidate_labels": ",".join(labels),
+ "multi_label": multi_label,
+ },
+ },
+ model=model,
+ task="zero-shot-classification",
+ )
+ output = _bytes_to_dict(response)
+ return [
+ ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
+ for label, score in zip(output["labels"], output["scores"])
+ ]
+
+ async def zero_shot_image_classification(
+ self, image: ContentT, labels: List[str], *, model: Optional[str] = None
+ ) -> List[ZeroShotImageClassificationOutputElement]:
+ """
+ Provide input image and text labels to predict text labels for the image.
+
+ Args:
+ image (`Union[str, Path, bytes, BinaryIO]`):
+ The input image to caption. It can be raw bytes, an image file, or a URL to an online image.
+ labels (`List[str]`):
+ List of string possible labels. There must be at least 2 labels.
+ model (`str`, *optional*):
+ The model to use for inference. Can be a model ID hosted on the Hugging Face Hub or a URL to a deployed
+ Inference Endpoint. This parameter overrides the model defined at the instance level. Defaults to None.
+
+ Returns:
+ `List[ZeroShotImageClassificationOutputElement]`: List of [`ZeroShotImageClassificationOutputElement`] items containing the predicted labels and their confidence.
+
+ Raises:
+ [`InferenceTimeoutError`]:
+ If the model is unavailable or the request times out.
+ `aiohttp.ClientResponseError`:
+ If the request fails with an HTTP error status code other than HTTP 503.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+
+ >>> await client.zero_shot_image_classification(
+ ... "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Cute_dog.jpg/320px-Cute_dog.jpg",
+ ... labels=["dog", "cat", "horse"],
+ ... )
+ [ZeroShotImageClassificationOutputElement(label='dog', score=0.956),...]
+ ```
+ """
+ # Raise ValueError if input is less than 2 labels
+ if len(labels) < 2:
+ raise ValueError("You must specify at least 2 classes to compare.")
+
+ response = await self.post(
+ json={"image": _b64_encode(image), "parameters": {"candidate_labels": ",".join(labels)}},
+ model=model,
+ task="zero-shot-image-classification",
+ )
+ return ZeroShotImageClassificationOutputElement.parse_obj_as_list(response)
+
+ def _resolve_url(self, model: Optional[str] = None, task: Optional[str] = None) -> str:
+ model = model or self.model
+
+ # If model is already a URL, ignore `task` and return directly
+ if model is not None and (model.startswith("http://") or model.startswith("https://")):
+ return model
+
+ # # If no model but task is set => fetch the recommended one for this task
+ if model is None:
+ if task is None:
+ raise ValueError(
+ "You must specify at least a model (repo_id or URL) or a task, either when instantiating"
+ " `InferenceClient` or when making a request."
+ )
+ model = self.get_recommended_model(task)
+ logger.info(
+ f"Using recommended model {model} for task {task}. Note that it is"
+ f" encouraged to explicitly set `model='{model}'` as the recommended"
+ " models list might get updated without prior notice."
+ )
+
+ # Compute InferenceAPI url
+ return (
+ # Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
+ f"{INFERENCE_ENDPOINT}/pipeline/{task}/{model}"
+ if task in ("feature-extraction", "sentence-similarity")
+ # Otherwise, we use the default endpoint
+ else f"{INFERENCE_ENDPOINT}/models/{model}"
+ )
+
+ @staticmethod
+ def get_recommended_model(task: str) -> str:
+ """
+ Get the model Hugging Face recommends for the input task.
+
+ Args:
+ task (`str`):
+ The Hugging Face task to get which model Hugging Face recommends.
+ All available tasks can be found [here](https://huggingface.co/tasks).
+
+ Returns:
+ `str`: Name of the model recommended for the input task.
+
+ Raises:
+ `ValueError`: If Hugging Face has no recommendation for the input task.
+ """
+ model = _fetch_recommended_models().get(task)
+ if model is None:
+ raise ValueError(
+ f"Task {task} has no recommended model. Please specify a model"
+ " explicitly. Visit https://huggingface.co/tasks for more info."
+ )
+ return model
+
+ async def get_model_status(self, model: Optional[str] = None) -> ModelStatus:
+ """
+ Get the status of a model hosted on the Inference API.
+
+
+
+ This endpoint is mostly useful when you already know which model you want to use and want to check its
+ availability. If you want to discover already deployed models, you should rather use [`~InferenceClient.list_deployed_models`].
+
+
+
+ Args:
+ model (`str`, *optional*):
+ Identifier of the model for witch the status gonna be checked. If model is not provided,
+ the model associated with this instance of [`InferenceClient`] will be used. Only InferenceAPI service can be checked so the
+ identifier cannot be a URL.
+
+
+ Returns:
+ [`ModelStatus`]: An instance of ModelStatus dataclass, containing information,
+ about the state of the model: load, state, compute type and framework.
+
+ Example:
+ ```py
+ # Must be run in an async context
+ >>> from huggingface_hub import AsyncInferenceClient
+ >>> client = AsyncInferenceClient()
+ >>> await client.get_model_status("bigcode/starcoder")
+ ModelStatus(loaded=True, state='Loaded', compute_type='gpu', framework='text-generation-inference')
+ ```
+ """
+ model = model or self.model
+ if model is None:
+ raise ValueError("Model id not provided.")
+ if model.startswith("https://"):
+ raise NotImplementedError("Model status is only available for Inference API endpoints.")
+ url = f"{INFERENCE_ENDPOINT}/status/{model}"
+
+ async with _import_aiohttp().ClientSession(headers=self.headers) as client:
+ response = await client.get(url)
+ response.raise_for_status()
+ response_data = await response.json()
+
+ if "error" in response_data:
+ raise ValueError(response_data["error"])
+
+ return ModelStatus(
+ loaded=response_data["loaded"],
+ state=response_data["state"],
+ compute_type=response_data["compute_type"],
+ framework=response_data["framework"],
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ece102c4d7a7d1a6c5ee46e066a0a3b867e342e0
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__init__.py
@@ -0,0 +1,115 @@
+# This file is auto-generated by `utils/generate_inference_types.py`.
+# Do not modify it manually.
+#
+# ruff: noqa: F401
+
+from .audio_classification import (
+ AudioClassificationInput,
+ AudioClassificationOutputElement,
+ AudioClassificationParameters,
+)
+from .audio_to_audio import AudioToAudioInput, AudioToAudioOutputElement
+from .automatic_speech_recognition import (
+ AutomaticSpeechRecognitionGenerationParameters,
+ AutomaticSpeechRecognitionInput,
+ AutomaticSpeechRecognitionOutput,
+ AutomaticSpeechRecognitionOutputChunk,
+ AutomaticSpeechRecognitionParameters,
+)
+from .base import BaseInferenceType
+from .chat_completion import (
+ ChatCompletionInput,
+ ChatCompletionInputMessage,
+ ChatCompletionOutput,
+ ChatCompletionOutputChoice,
+ ChatCompletionOutputChoiceMessage,
+ ChatCompletionStreamOutput,
+ ChatCompletionStreamOutputChoice,
+ ChatCompletionStreamOutputDelta,
+)
+from .depth_estimation import DepthEstimationInput, DepthEstimationOutput
+from .document_question_answering import (
+ DocumentQuestionAnsweringInput,
+ DocumentQuestionAnsweringInputData,
+ DocumentQuestionAnsweringOutputElement,
+ DocumentQuestionAnsweringParameters,
+)
+from .feature_extraction import FeatureExtractionInput
+from .fill_mask import FillMaskInput, FillMaskOutputElement, FillMaskParameters
+from .image_classification import (
+ ImageClassificationInput,
+ ImageClassificationOutputElement,
+ ImageClassificationParameters,
+)
+from .image_segmentation import ImageSegmentationInput, ImageSegmentationOutputElement, ImageSegmentationParameters
+from .image_to_image import ImageToImageInput, ImageToImageOutput, ImageToImageParameters, ImageToImageTargetSize
+from .image_to_text import ImageToTextGenerationParameters, ImageToTextInput, ImageToTextOutput, ImageToTextParameters
+from .object_detection import (
+ ObjectDetectionBoundingBox,
+ ObjectDetectionInput,
+ ObjectDetectionOutputElement,
+ ObjectDetectionParameters,
+)
+from .question_answering import (
+ QuestionAnsweringInput,
+ QuestionAnsweringInputData,
+ QuestionAnsweringOutputElement,
+ QuestionAnsweringParameters,
+)
+from .sentence_similarity import SentenceSimilarityInput, SentenceSimilarityInputData
+from .summarization import SummarizationGenerationParameters, SummarizationInput, SummarizationOutput
+from .table_question_answering import (
+ TableQuestionAnsweringInput,
+ TableQuestionAnsweringInputData,
+ TableQuestionAnsweringOutputElement,
+)
+from .text2text_generation import Text2TextGenerationInput, Text2TextGenerationOutput, Text2TextGenerationParameters
+from .text_classification import TextClassificationInput, TextClassificationOutputElement, TextClassificationParameters
+from .text_generation import (
+ TextGenerationInput,
+ TextGenerationOutput,
+ TextGenerationOutputDetails,
+ TextGenerationOutputSequenceDetails,
+ TextGenerationOutputToken,
+ TextGenerationParameters,
+ TextGenerationPrefillToken,
+ TextGenerationStreamDetails,
+ TextGenerationStreamOutput,
+)
+from .text_to_audio import TextToAudioGenerationParameters, TextToAudioInput, TextToAudioOutput, TextToAudioParameters
+from .text_to_image import TextToImageInput, TextToImageOutput, TextToImageParameters, TextToImageTargetSize
+from .token_classification import (
+ TokenClassificationInput,
+ TokenClassificationOutputElement,
+ TokenClassificationParameters,
+)
+from .translation import TranslationGenerationParameters, TranslationInput, TranslationOutput
+from .video_classification import (
+ VideoClassificationInput,
+ VideoClassificationOutputElement,
+ VideoClassificationParameters,
+)
+from .visual_question_answering import (
+ VisualQuestionAnsweringInput,
+ VisualQuestionAnsweringInputData,
+ VisualQuestionAnsweringOutputElement,
+ VisualQuestionAnsweringParameters,
+)
+from .zero_shot_classification import (
+ ZeroShotClassificationInput,
+ ZeroShotClassificationInputData,
+ ZeroShotClassificationOutputElement,
+ ZeroShotClassificationParameters,
+)
+from .zero_shot_image_classification import (
+ ZeroShotImageClassificationInput,
+ ZeroShotImageClassificationInputData,
+ ZeroShotImageClassificationOutputElement,
+ ZeroShotImageClassificationParameters,
+)
+from .zero_shot_object_detection import (
+ ZeroShotObjectDetectionBoundingBox,
+ ZeroShotObjectDetectionInput,
+ ZeroShotObjectDetectionInputData,
+ ZeroShotObjectDetectionOutputElement,
+)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..edc6b8a0713fff9c04a5311d498237dfe3140fa8
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e5950a40f7b9ab35a80dfe228d35d528e8448bce
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_to_audio.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_to_audio.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ec5df464756b4e557505a26379bb51e97e2f1657
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/audio_to_audio.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/automatic_speech_recognition.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/automatic_speech_recognition.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fe6ce2e483bed1454754d6574cac6c0f508797a3
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/automatic_speech_recognition.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/base.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/base.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4606afd4f8657cf381e0cabdb6153c352d9d4fee
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/base.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/chat_completion.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/chat_completion.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..846e96e3c8ebe1aa95175a82307c242ddc0eb6a1
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/chat_completion.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/depth_estimation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/depth_estimation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..09d025141514e85c034bae7617cd9974e2f0592c
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/depth_estimation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/document_question_answering.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/document_question_answering.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a306c318f0d25ae07b76358146bca667b0097d6f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/document_question_answering.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/feature_extraction.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/feature_extraction.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d95a326f08935132ae9fdf9618803200eb3c0dd9
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/feature_extraction.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/fill_mask.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/fill_mask.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..15019b3f39c0525dedf1f5de8f8a283da45a0860
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/fill_mask.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7d787b351b1cb853e395182a7dbc762bc67021d3
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_segmentation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_segmentation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2e4051058e7e6212f9dd67718e5da1b655b60416
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_segmentation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_image.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_image.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ac914a7aa226e9e32dbeadfa8291efd8a3444d3f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_image.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_text.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_text.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..257612c2cbcf21872e1a8d62b418fe962e26321d
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/image_to_text.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/object_detection.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/object_detection.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1ad5d18a61d0cb45040a88f4826adde1ded76809
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/object_detection.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/question_answering.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/question_answering.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1e3214e88c4047d9fb8c2f53b0e420807be8d496
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/question_answering.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/sentence_similarity.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/sentence_similarity.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2d7ff48260b3561dbc002b3b051cdccdbdeca218
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/sentence_similarity.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/summarization.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/summarization.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..93036f3f50dbeef6b83dbcd28a3e0b7c89b61e30
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/summarization.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/table_question_answering.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/table_question_answering.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..629013fca9f5a8af0bb0032a3d208b8a00294109
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/table_question_answering.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text2text_generation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text2text_generation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6c6d37f153848e17ea004f57e5f21f4ab3e87e3b
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text2text_generation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3200032a34fb1dc8fd5d5df5d7c55eebd80bed2a
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_generation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_generation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1860d2b5548d53ee9b9d8b300f3027b29f59f6ba
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_generation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_audio.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_audio.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..490c9ddd68b8cc888ea223538a55bd73015162df
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_audio.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_image.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_image.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c4a01c7b6277fb1eee5728b74dba312a13d209b6
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/text_to_image.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/token_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/token_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5e57638d93b055bd32a5d10feda91648216043a3
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/token_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/translation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/translation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..526cf49c22a5c47db12dbab9485072e236aa6506
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/translation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/video_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/video_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f08a2b2fcdb3f2a2123b895bcf883439aea4d79c
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/video_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/visual_question_answering.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/visual_question_answering.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e0e0f3e0c02b4f3756e4140375194040b0633468
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/visual_question_answering.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a548a5276203815e65e3d02bc00d196ddac218e7
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_image_classification.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_image_classification.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..23687bf5c25e2fc845a76ef513b5e6242c5a11da
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_image_classification.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_object_detection.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_object_detection.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d88e4a95e7f49f688cb54868be5e943f306aae70
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/__pycache__/zero_shot_object_detection.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..914ba44960b5edca2f182bd1c3f15e9f01bce3b9
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_classification.py
@@ -0,0 +1,43 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+ClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass
+class AudioClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Audio Classification
+ """
+
+ function_to_apply: Optional["ClassificationOutputTransform"] = None
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass
+class AudioClassificationInput(BaseInferenceType):
+ """Inputs for Audio Classification inference"""
+
+ inputs: Any
+ """The input audio data"""
+ parameters: Optional[AudioClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class AudioClassificationOutputElement(BaseInferenceType):
+ """Outputs for Audio Classification inference"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f473ed106c7d168784ae8e96db18f46237d065e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/audio_to_audio.py
@@ -0,0 +1,31 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class AudioToAudioInput(BaseInferenceType):
+ """Inputs for Audio to Audio inference"""
+
+ inputs: Any
+ """The input audio data"""
+
+
+@dataclass
+class AudioToAudioOutputElement(BaseInferenceType):
+ """Outputs of inference for the Audio To Audio task
+ A generated audio file with its label.
+ """
+
+ blob: Any
+ """The generated audio file."""
+ content_type: str
+ """The content type of audio file."""
+ label: str
+ """The label of the audio file."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py
new file mode 100644
index 0000000000000000000000000000000000000000..24a5238ab6b33ea13df79a1ea197b4f07b39c1ec
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/automatic_speech_recognition.py
@@ -0,0 +1,116 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Literal, Optional, Union
+
+from .base import BaseInferenceType
+
+
+EarlyStoppingEnum = Literal["never"]
+
+
+@dataclass
+class AutomaticSpeechRecognitionGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process
+ Ad-hoc parametrization of the text generation process
+ """
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "EarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over maxLength."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over maxLength."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass
+class AutomaticSpeechRecognitionParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Automatic Speech Recognition
+ """
+
+ generate: Optional[AutomaticSpeechRecognitionGenerationParameters] = None
+ """Parametrization of the text generation process"""
+ return_timestamps: Optional[bool] = None
+ """Whether to output corresponding timestamps with the generated text"""
+
+
+@dataclass
+class AutomaticSpeechRecognitionInput(BaseInferenceType):
+ """Inputs for Automatic Speech Recognition inference"""
+
+ inputs: Any
+ """The input audio data"""
+ parameters: Optional[AutomaticSpeechRecognitionParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class AutomaticSpeechRecognitionOutputChunk(BaseInferenceType):
+ text: str
+ """A chunk of text identified by the model"""
+ timestamps: List[float]
+ """The start and end timestamps corresponding with the text"""
+
+
+@dataclass
+class AutomaticSpeechRecognitionOutput(BaseInferenceType):
+ """Outputs of inference for the Automatic Speech Recognition task"""
+
+ text: str
+ """The recognized text."""
+ chunks: Optional[List[AutomaticSpeechRecognitionOutputChunk]] = None
+ """When returnTimestamps is enabled, chunks contains a list of audio chunks identified by
+ the model.
+ """
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/base.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..8783484d2d466a74c1c634508c39a7e9cb2851a3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/base.py
@@ -0,0 +1,149 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a base class for all inference types."""
+
+import inspect
+import json
+import warnings
+from dataclasses import asdict, dataclass
+from typing import Any, Dict, List, Type, TypeVar, Union, get_args
+
+
+T = TypeVar("T", bound="BaseInferenceType")
+
+
+@dataclass
+class BaseInferenceType(dict):
+ """Base class for all inference types.
+
+ Object is a dataclass and a dict for backward compatibility but plan is to remove the dict part in the future.
+
+ Handle parsing from dict, list and json strings in a permissive way to ensure future-compatibility (e.g. all fields
+ are made optional, and non-expected fields are added as dict attributes).
+ """
+
+ @classmethod
+ def parse_obj_as_list(cls: Type[T], data: Union[bytes, str, List, Dict]) -> List[T]:
+ """Alias to parse server response and return a single instance.
+
+ See `parse_obj` for more details.
+ """
+ output = cls.parse_obj(data)
+ if not isinstance(output, list):
+ raise ValueError(f"Invalid input data for {cls}. Expected a list, but got {type(output)}.")
+ return output
+
+ @classmethod
+ def parse_obj_as_instance(cls: Type[T], data: Union[bytes, str, List, Dict]) -> T:
+ """Alias to parse server response and return a single instance.
+
+ See `parse_obj` for more details.
+ """
+ output = cls.parse_obj(data)
+ if isinstance(output, list):
+ raise ValueError(f"Invalid input data for {cls}. Expected a single instance, but got a list.")
+ return output
+
+ @classmethod
+ def parse_obj(cls: Type[T], data: Union[bytes, str, List, Dict]) -> Union[List[T], T]:
+ """Parse server response as a dataclass or list of dataclasses.
+
+ To enable future-compatibility, we want to handle cases where the server return more fields than expected.
+ In such cases, we don't want to raise an error but still create the dataclass object. Remaining fields are
+ added as dict attributes.
+ """
+ # Parse server response (from bytes)
+ if isinstance(data, bytes):
+ data = data.decode()
+ if isinstance(data, str):
+ data = json.loads(data)
+
+ # If a list, parse each item individually
+ if isinstance(data, List):
+ return [cls.parse_obj(d) for d in data] # type: ignore [misc]
+
+ # At this point, we expect a dict
+ if not isinstance(data, dict):
+ raise ValueError(f"Invalid data type: {type(data)}")
+
+ init_values = {}
+ other_values = {}
+ for key, value in data.items():
+ key = normalize_key(key)
+ if key in cls.__dataclass_fields__ and cls.__dataclass_fields__[key].init:
+ if isinstance(value, dict) or isinstance(value, list):
+ field_type = cls.__dataclass_fields__[key].type
+
+ # if `field_type` is a `BaseInferenceType`, parse it
+ if inspect.isclass(field_type) and issubclass(field_type, BaseInferenceType):
+ value = field_type.parse_obj(value)
+
+ # otherwise, recursively parse nested dataclasses (if possible)
+ # `get_args` returns handle Union and Optional for us
+ else:
+ expected_types = get_args(field_type)
+ for expected_type in expected_types:
+ if getattr(expected_type, "_name", None) == "List":
+ expected_type = get_args(expected_type)[
+ 0
+ ] # assume same type for all items in the list
+ if inspect.isclass(expected_type) and issubclass(expected_type, BaseInferenceType):
+ value = expected_type.parse_obj(value)
+ break
+ init_values[key] = value
+ else:
+ other_values[key] = value
+
+ # Make all missing fields default to None
+ # => ensure that dataclass initialization will never fail even if the server does not return all fields.
+ for key in cls.__dataclass_fields__:
+ if key not in init_values:
+ init_values[key] = None
+
+ # Initialize dataclass with expected values
+ item = cls(**init_values)
+
+ # Add remaining fields as dict attributes
+ item.update(other_values)
+ return item
+
+ def __post_init__(self):
+ self.update(asdict(self))
+
+ def __setitem__(self, __key: Any, __value: Any) -> None:
+ # Hacky way to keep dataclass values in sync when dict is updated
+ super().__setitem__(__key, __value)
+ if __key in self.__dataclass_fields__ and getattr(self, __key, None) != __value:
+ self.__setattr__(__key, __value)
+ return
+
+ def __setattr__(self, __name: str, __value: Any) -> None:
+ # Hacky way to keep dict values is sync when dataclass is updated
+ super().__setattr__(__name, __value)
+ if self.get(__name) != __value:
+ self[__name] = __value
+ return
+
+ def __getitem__(self, __key: Any) -> Any:
+ warnings.warn(
+ f"Accessing '{self.__class__.__name__}' values through dict is deprecated and "
+ "will be removed from version '0.25'. Use dataclass attributes instead.",
+ FutureWarning,
+ )
+ return super().__getitem__(__key)
+
+
+def normalize_key(key: str) -> str:
+ # e.g "content-type" -> "content_type", "Accept" -> "accept"
+ return key.replace("-", "_").replace(" ", "_").lower()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py
new file mode 100644
index 0000000000000000000000000000000000000000..43e24f814b5ea8c4fea6d0ea00d6fef3883da8d1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/chat_completion.py
@@ -0,0 +1,106 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import List, Literal, Optional, Union
+
+from .base import BaseInferenceType
+
+
+ChatCompletionMessageRole = Literal["assistant", "system", "user"]
+
+
+@dataclass
+class ChatCompletionInputMessage(BaseInferenceType):
+ content: str
+ """The content of the message."""
+ role: "ChatCompletionMessageRole"
+
+
+@dataclass
+class ChatCompletionInput(BaseInferenceType):
+ """Inputs for ChatCompletion inference"""
+
+ messages: List[ChatCompletionInputMessage]
+ frequency_penalty: Optional[float] = None
+ """Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing
+ frequency in the text so far, decreasing the model's likelihood to repeat the same line
+ verbatim.
+ """
+ max_tokens: Optional[int] = None
+ """The maximum number of tokens that can be generated in the chat completion."""
+ seed: Optional[int] = None
+ """The random sampling seed."""
+ stop: Optional[Union[List[str], str]] = None
+ """Stop generating tokens if a stop token is generated."""
+ stream: Optional[bool] = None
+ """If set, partial message deltas will be sent."""
+ temperature: Optional[float] = None
+ """The value used to modulate the logits distribution."""
+ top_p: Optional[float] = None
+ """If set to < 1, only the smallest set of most probable tokens with probabilities that add
+ up to `top_p` or higher are kept for generation.
+ """
+
+
+ChatCompletionFinishReason = Literal["length", "eos_token", "stop_sequence"]
+
+
+@dataclass
+class ChatCompletionOutputChoiceMessage(BaseInferenceType):
+ content: str
+ """The content of the chat completion message."""
+ role: "ChatCompletionMessageRole"
+
+
+@dataclass
+class ChatCompletionOutputChoice(BaseInferenceType):
+ finish_reason: "ChatCompletionFinishReason"
+ """The reason why the generation was stopped."""
+ index: int
+ """The index of the choice in the list of choices."""
+ message: ChatCompletionOutputChoiceMessage
+
+
+@dataclass
+class ChatCompletionOutput(BaseInferenceType):
+ """Outputs for Chat Completion inference"""
+
+ choices: List[ChatCompletionOutputChoice]
+ """A list of chat completion choices."""
+ created: int
+ """The Unix timestamp (in seconds) of when the chat completion was created."""
+
+
+@dataclass
+class ChatCompletionStreamOutputDelta(BaseInferenceType):
+ """A chat completion delta generated by streamed model responses."""
+
+ content: Optional[str] = None
+ """The contents of the chunk message."""
+ role: Optional[str] = None
+ """The role of the author of this message."""
+
+
+@dataclass
+class ChatCompletionStreamOutputChoice(BaseInferenceType):
+ delta: ChatCompletionStreamOutputDelta
+ """A chat completion delta generated by streamed model responses."""
+ index: int
+ """The index of the choice in the list of choices."""
+ finish_reason: Optional["ChatCompletionFinishReason"] = None
+ """The reason why the generation was stopped."""
+
+
+@dataclass
+class ChatCompletionStreamOutput(BaseInferenceType):
+ """Chat Completion Stream Output"""
+
+ choices: List[ChatCompletionStreamOutputChoice]
+ """A list of chat completion choices."""
+ created: int
+ """The Unix timestamp (in seconds) of when the chat completion was created. Each chunk has
+ the same timestamp.
+ """
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py
new file mode 100644
index 0000000000000000000000000000000000000000..fbaa5feeadff9721ba543cb77121b98c17e3ee8c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/depth_estimation.py
@@ -0,0 +1,29 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class DepthEstimationInput(BaseInferenceType):
+ """Inputs for Depth Estimation inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class DepthEstimationOutput(BaseInferenceType):
+ """Outputs of inference for the Depth Estimation task"""
+
+ depth: Any
+ """The predicted depth as an image"""
+ predicted_depth: Any
+ """The predicted depth as a tensor"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..c68be4bde00a98fbce46a2ef6a93bb549d4d920b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/document_question_answering.py
@@ -0,0 +1,85 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Optional, Union
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class DocumentQuestionAnsweringInputData(BaseInferenceType):
+ """One (document, question) pair to answer"""
+
+ image: Any
+ """The image on which the question is asked"""
+ question: str
+ """A question to ask of the document"""
+
+
+@dataclass
+class DocumentQuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Document Question Answering
+ """
+
+ doc_stride: Optional[int] = None
+ """If the words in the document are too long to fit with the question for the model, it will
+ be split in several chunks with some overlap. This argument controls the size of that
+ overlap.
+ """
+ handle_impossible_answer: Optional[bool] = None
+ """Whether to accept impossible as an answer"""
+ lang: Optional[str] = None
+ """Language to use while running OCR. Defaults to english."""
+ max_answer_len: Optional[int] = None
+ """The maximum length of predicted answers (e.g., only answers with a shorter length are
+ considered).
+ """
+ max_question_len: Optional[int] = None
+ """The maximum length of the question after tokenization. It will be truncated if needed."""
+ max_seq_len: Optional[int] = None
+ """The maximum length of the total sentence (context + question) in tokens of each chunk
+ passed to the model. The context will be split in several chunks (using doc_stride as
+ overlap) if needed.
+ """
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Can return less
+ than top_k answers if there are not enough options available within the context.
+ """
+ word_boxes: Optional[List[Union[List[float], str]]] = None
+ """A list of words and bounding boxes (normalized 0->1000). If provided, the inference will
+ skip the OCR step and use the provided bounding boxes instead.
+ """
+
+
+@dataclass
+class DocumentQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Document Question Answering inference"""
+
+ inputs: DocumentQuestionAnsweringInputData
+ """One (document, question) pair to answer"""
+ parameters: Optional[DocumentQuestionAnsweringParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class DocumentQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Document Question Answering task"""
+
+ answer: str
+ """The answer to the question."""
+ end: int
+ """The end word index of the answer (in the OCR’d version of the input or provided word
+ boxes).
+ """
+ score: float
+ """The probability associated to the answer."""
+ start: int
+ """The start word index of the answer (in the OCR’d version of the input or provided word
+ boxes).
+ """
+ words: List[int]
+ """The index of each word/box pair that is in the answer"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py
new file mode 100644
index 0000000000000000000000000000000000000000..df563e671a68926df1d96898879ae775f0d20a6c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/feature_extraction.py
@@ -0,0 +1,19 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class FeatureExtractionInput(BaseInferenceType):
+ """Inputs for Text Embedding inference"""
+
+ inputs: str
+ """The text to get the embeddings of"""
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1fddf96fbb7c76c8ffee0c170c6554c8b4e2bf8
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/fill_mask.py
@@ -0,0 +1,50 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class FillMaskParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Fill Mask
+ """
+
+ targets: Optional[List[str]] = None
+ """When passed, the model will limit the scores to the passed targets instead of looking up
+ in the whole vocabulary. If the provided targets are not in the model vocab, they will be
+ tokenized and the first resulting token will be used (with a warning, and that might be
+ slower).
+ """
+ top_k: Optional[int] = None
+ """When passed, overrides the number of predictions to return."""
+
+
+@dataclass
+class FillMaskInput(BaseInferenceType):
+ """Inputs for Fill Mask inference"""
+
+ inputs: str
+ """The text with masked tokens"""
+ parameters: Optional[FillMaskParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class FillMaskOutputElement(BaseInferenceType):
+ """Outputs of inference for the Fill Mask task"""
+
+ score: float
+ """The corresponding probability"""
+ sequence: str
+ """The corresponding input with the mask token prediction."""
+ token: int
+ """The predicted token id (to replace the masked one)."""
+ token_str: Any
+ fill_mask_output_token_str: Optional[str] = None
+ """The predicted token (to replace the masked one)."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd52db005a0be62e7f063c0a16569a1fc2b273da
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_classification.py
@@ -0,0 +1,43 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+ClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass
+class ImageClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Image Classification
+ """
+
+ function_to_apply: Optional["ClassificationOutputTransform"] = None
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass
+class ImageClassificationInput(BaseInferenceType):
+ """Inputs for Image Classification inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ImageClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ImageClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Image Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..67dd7c28b3cddd21d495ada70b7689a098accfd6
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_segmentation.py
@@ -0,0 +1,52 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+ImageSegmentationSubtask = Literal["instance", "panoptic", "semantic"]
+
+
+@dataclass
+class ImageSegmentationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Image Segmentation
+ """
+
+ mask_threshold: Optional[float] = None
+ """Threshold to use when turning the predicted masks into binary values."""
+ overlap_mask_area_threshold: Optional[float] = None
+ """Mask overlap threshold to eliminate small, disconnected segments."""
+ subtask: Optional["ImageSegmentationSubtask"] = None
+ """Segmentation task to be performed, depending on model capabilities."""
+ threshold: Optional[float] = None
+ """Probability threshold to filter out predicted masks."""
+
+
+@dataclass
+class ImageSegmentationInput(BaseInferenceType):
+ """Inputs for Image Segmentation inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ImageSegmentationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ImageSegmentationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Image Segmentation task
+ A predicted mask / segment
+ """
+
+ label: str
+ """The label of the predicted segment"""
+ mask: Any
+ """The corresponding mask as a black-and-white image"""
+ score: Optional[float] = None
+ """The score or confidence degreee the model has"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c208ede6f7f2fb73b5dd059fe71bc8d2c4ca140
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_image.py
@@ -0,0 +1,55 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class ImageToImageTargetSize(BaseInferenceType):
+ """The size in pixel of the output image"""
+
+ height: int
+ width: int
+
+
+@dataclass
+class ImageToImageParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Image To Image
+ """
+
+ guidance_scale: Optional[float] = None
+ """For diffusion models. A higher guidance scale value encourages the model to generate
+ images closely linked to the text prompt at the expense of lower image quality.
+ """
+ negative_prompt: Optional[List[str]] = None
+ """One or several prompt to guide what NOT to include in image generation."""
+ num_inference_steps: Optional[int] = None
+ """For diffusion models. The number of denoising steps. More denoising steps usually lead to
+ a higher quality image at the expense of slower inference.
+ """
+ target_size: Optional[ImageToImageTargetSize] = None
+ """The size in pixel of the output image"""
+
+
+@dataclass
+class ImageToImageInput(BaseInferenceType):
+ """Inputs for Image To Image inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ImageToImageParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ImageToImageOutput(BaseInferenceType):
+ """Outputs of inference for the Image To Image task"""
+
+ image: Any
+ """The output image"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ebb9a9bc667bdb0d2afd7bb8e482fc18f6634d7
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/image_to_text.py
@@ -0,0 +1,105 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional, Union
+
+from .base import BaseInferenceType
+
+
+EarlyStoppingEnum = Literal["never"]
+
+
+@dataclass
+class ImageToTextGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process
+ Ad-hoc parametrization of the text generation process
+ """
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "EarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over maxLength."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over maxLength."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass
+class ImageToTextParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Image To Text
+ """
+
+ generate: Optional[ImageToTextGenerationParameters] = None
+ """Parametrization of the text generation process"""
+ max_new_tokens: Optional[int] = None
+ """The amount of maximum tokens to generate."""
+
+
+@dataclass
+class ImageToTextInput(BaseInferenceType):
+ """Inputs for Image To Text inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ImageToTextParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ImageToTextOutput(BaseInferenceType):
+ """Outputs of inference for the Image To Text task"""
+
+ generated_text: Any
+ image_to_text_output_generated_text: Optional[str] = None
+ """The generated text."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/object_detection.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/object_detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..42b03a841b793fd4cb301bf51695bd35054a6af2
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/object_detection.py
@@ -0,0 +1,55 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class ObjectDetectionParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Object Detection
+ """
+
+ threshold: Optional[float] = None
+ """The probability necessary to make a prediction."""
+
+
+@dataclass
+class ObjectDetectionInput(BaseInferenceType):
+ """Inputs for Object Detection inference"""
+
+ inputs: Any
+ """The input image data"""
+ parameters: Optional[ObjectDetectionParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ObjectDetectionBoundingBox(BaseInferenceType):
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+
+ xmax: int
+ xmin: int
+ ymax: int
+ ymin: int
+
+
+@dataclass
+class ObjectDetectionOutputElement(BaseInferenceType):
+ """Outputs of inference for the Object Detection task"""
+
+ box: ObjectDetectionBoundingBox
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+ label: str
+ """The predicted label for the bounding box"""
+ score: float
+ """The associated score / probability"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/question_answering.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..3810fc594af5cf0712cb0cb0db077383220b175a
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/question_answering.py
@@ -0,0 +1,77 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class QuestionAnsweringInputData(BaseInferenceType):
+ """One (context, question) pair to answer"""
+
+ context: str
+ """The context to be used for answering the question"""
+ question: str
+ """The question to be answered"""
+
+
+@dataclass
+class QuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Question Answering
+ """
+
+ align_to_words: Optional[bool] = None
+ """Attempts to align the answer to real words. Improves quality on space separated
+ languages. Might hurt on non-space-separated languages (like Japanese or Chinese)
+ """
+ doc_stride: Optional[int] = None
+ """If the context is too long to fit with the question for the model, it will be split in
+ several chunks with some overlap. This argument controls the size of that overlap.
+ """
+ handle_impossible_answer: Optional[bool] = None
+ """Whether to accept impossible as an answer."""
+ max_answer_len: Optional[int] = None
+ """The maximum length of predicted answers (e.g., only answers with a shorter length are
+ considered).
+ """
+ max_question_len: Optional[int] = None
+ """The maximum length of the question after tokenization. It will be truncated if needed."""
+ max_seq_len: Optional[int] = None
+ """The maximum length of the total sentence (context + question) in tokens of each chunk
+ passed to the model. The context will be split in several chunks (using docStride as
+ overlap) if needed.
+ """
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Note that we
+ return less than topk answers if there are not enough options available within the
+ context.
+ """
+
+
+@dataclass
+class QuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Question Answering inference"""
+
+ inputs: QuestionAnsweringInputData
+ """One (context, question) pair to answer"""
+ parameters: Optional[QuestionAnsweringParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class QuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Question Answering task"""
+
+ answer: str
+ """The answer to the question."""
+ end: int
+ """The character position in the input where the answer ends."""
+ score: float
+ """The probability associated to the answer."""
+ start: int
+ """The character position in the input where the answer begins."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py
new file mode 100644
index 0000000000000000000000000000000000000000..944bfccbf76e8c322dbf95a286746c6e1e25a55b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/sentence_similarity.py
@@ -0,0 +1,28 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class SentenceSimilarityInputData(BaseInferenceType):
+ sentences: List[str]
+ """A list of strings which will be compared against the source_sentence."""
+ source_sentence: str
+ """The string that you wish to compare the other strings with. This can be a phrase,
+ sentence, or longer passage, depending on the model being used.
+ """
+
+
+@dataclass
+class SentenceSimilarityInput(BaseInferenceType):
+ """Inputs for Sentence similarity inference"""
+
+ inputs: SentenceSimilarityInputData
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/summarization.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/summarization.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6a00e53264bd9a53a24d2ee7b12f428c068a117
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/summarization.py
@@ -0,0 +1,46 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+SummarizationGenerationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass
+class SummarizationGenerationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text2text Generation
+ """
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm"""
+ truncation: Optional["SummarizationGenerationTruncationStrategy"] = None
+ """The truncation strategy to use"""
+
+
+@dataclass
+class SummarizationInput(BaseInferenceType):
+ """Inputs for Summarization inference
+ Inputs for Text2text Generation inference
+ """
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[SummarizationGenerationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class SummarizationOutput(BaseInferenceType):
+ """Outputs of inference for the Summarization task"""
+
+ summary_text: str
+ """The summarized text."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..6cb9fff641fd4ed2d8e797e59ae7b5f21f94c838
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/table_question_answering.py
@@ -0,0 +1,45 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class TableQuestionAnsweringInputData(BaseInferenceType):
+ """One (table, question) pair to answer"""
+
+ question: str
+ """The question to be answered about the table"""
+ table: Dict[str, List[str]]
+ """The table to serve as context for the questions"""
+
+
+@dataclass
+class TableQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Table Question Answering inference"""
+
+ inputs: TableQuestionAnsweringInputData
+ """One (table, question) pair to answer"""
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TableQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Table Question Answering task"""
+
+ answer: str
+ """The answer of the question given the table. If there is an aggregator, the answer will be
+ preceded by `AGGREGATOR >`.
+ """
+ cells: List[str]
+ """List of strings made up of the answer cell values."""
+ coordinates: List[List[int]]
+ """Coordinates of the cells of the answers."""
+ aggregator: Optional[str] = None
+ """If the model has an aggregator, this returns the aggregator."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py
new file mode 100644
index 0000000000000000000000000000000000000000..955494c5ef6b86e12b3927dfd90e44a5db25c2e6
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text2text_generation.py
@@ -0,0 +1,45 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+Text2TextGenerationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass
+class Text2TextGenerationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text2text Generation
+ """
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm"""
+ truncation: Optional["Text2TextGenerationTruncationStrategy"] = None
+ """The truncation strategy to use"""
+
+
+@dataclass
+class Text2TextGenerationInput(BaseInferenceType):
+ """Inputs for Text2text Generation inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[Text2TextGenerationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class Text2TextGenerationOutput(BaseInferenceType):
+ """Outputs of inference for the Text2text Generation task"""
+
+ generated_text: Any
+ text2_text_generation_output_generated_text: Optional[str] = None
+ """The generated text."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf61a4eebcf367b4ab15e8970bfac8e1d8f8458d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_classification.py
@@ -0,0 +1,43 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Literal, Optional
+
+from .base import BaseInferenceType
+
+
+ClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass
+class TextClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text Classification
+ """
+
+ function_to_apply: Optional["ClassificationOutputTransform"] = None
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass
+class TextClassificationInput(BaseInferenceType):
+ """Inputs for Text Classification inference"""
+
+ inputs: str
+ """The text to classify"""
+ parameters: Optional[TextClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TextClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Text Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_generation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_generation.py
new file mode 100644
index 0000000000000000000000000000000000000000..2866985071741b26d69c1afc8902738cff10ef03
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_generation.py
@@ -0,0 +1,161 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import List, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class TextGenerationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text Generation
+ """
+
+ best_of: Optional[int] = None
+ """The number of sampling queries to run. Only the best one (in terms of total logprob) will
+ be returned.
+ """
+ decoder_input_details: Optional[bool] = None
+ """Whether or not to output decoder input details"""
+ details: Optional[bool] = None
+ """Whether or not to output details"""
+ do_sample: Optional[bool] = None
+ """Whether to use logits sampling instead of greedy decoding when generating new tokens."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate."""
+ repetition_penalty: Optional[float] = None
+ """The parameter for repetition penalty. A value of 1.0 means no penalty. See [this
+ paper](https://hf.co/papers/1909.05858) for more details.
+ """
+ return_full_text: Optional[bool] = None
+ """Whether to prepend the prompt to the generated text."""
+ seed: Optional[int] = None
+ """The random sampling seed."""
+ stop_sequences: Optional[List[str]] = None
+ """Stop generating tokens if a member of `stop_sequences` is generated."""
+ temperature: Optional[float] = None
+ """The value used to modulate the logits distribution."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to < 1, only the smallest set of most probable tokens with probabilities that add
+ up to `top_p` or higher are kept for generation.
+ """
+ truncate: Optional[int] = None
+ """Truncate input tokens to the given size."""
+ typical_p: Optional[float] = None
+ """Typical Decoding mass. See [Typical Decoding for Natural Language
+ Generation](https://hf.co/papers/2202.00666) for more information
+ """
+ watermark: Optional[bool] = None
+ """Watermarking with [A Watermark for Large Language Models](https://hf.co/papers/2301.10226)"""
+
+
+@dataclass
+class TextGenerationInput(BaseInferenceType):
+ """Inputs for Text Generation inference"""
+
+ inputs: str
+ """The text to initialize generation with"""
+ parameters: Optional[TextGenerationParameters] = None
+ """Additional inference parameters"""
+ stream: Optional[bool] = None
+ """Whether to stream output tokens"""
+
+
+TextGenerationFinishReason = Literal["length", "eos_token", "stop_sequence"]
+
+
+@dataclass
+class TextGenerationPrefillToken(BaseInferenceType):
+ id: int
+ logprob: float
+ text: str
+ """The text associated with that token"""
+
+
+@dataclass
+class TextGenerationOutputToken(BaseInferenceType):
+ """Generated token."""
+
+ id: int
+ special: bool
+ """Whether or not that token is a special one"""
+ text: str
+ """The text associated with that token"""
+ logprob: Optional[float] = None
+
+
+@dataclass
+class TextGenerationOutputSequenceDetails(BaseInferenceType):
+ finish_reason: "TextGenerationFinishReason"
+ generated_text: str
+ """The generated text"""
+ generated_tokens: int
+ """The number of generated tokens"""
+ prefill: List[TextGenerationPrefillToken]
+ tokens: List[TextGenerationOutputToken]
+ """The generated tokens and associated details"""
+ seed: Optional[int] = None
+ """The random seed used for generation"""
+ top_tokens: Optional[List[List[TextGenerationOutputToken]]] = None
+ """Most likely tokens"""
+
+
+@dataclass
+class TextGenerationOutputDetails(BaseInferenceType):
+ """When enabled, details about the generation"""
+
+ finish_reason: "TextGenerationFinishReason"
+ """The reason why the generation was stopped."""
+ generated_tokens: int
+ """The number of generated tokens"""
+ prefill: List[TextGenerationPrefillToken]
+ tokens: List[TextGenerationOutputToken]
+ """The generated tokens and associated details"""
+ best_of_sequences: Optional[List[TextGenerationOutputSequenceDetails]] = None
+ """Details about additional sequences when best_of is provided"""
+ seed: Optional[int] = None
+ """The random seed used for generation"""
+ top_tokens: Optional[List[List[TextGenerationOutputToken]]] = None
+ """Most likely tokens"""
+
+
+@dataclass
+class TextGenerationOutput(BaseInferenceType):
+ """Outputs for Text Generation inference"""
+
+ generated_text: str
+ """The generated text"""
+ details: Optional[TextGenerationOutputDetails] = None
+ """When enabled, details about the generation"""
+
+
+@dataclass
+class TextGenerationStreamDetails(BaseInferenceType):
+ """Generation details. Only available when the generation is finished."""
+
+ finish_reason: "TextGenerationFinishReason"
+ """The reason why the generation was stopped."""
+ generated_tokens: int
+ """The number of generated tokens"""
+ seed: int
+ """The random seed used for generation"""
+
+
+@dataclass
+class TextGenerationStreamOutput(BaseInferenceType):
+ """Text Generation Stream Output"""
+
+ token: TextGenerationOutputToken
+ """Generated token."""
+ details: Optional[TextGenerationStreamDetails] = None
+ """Generation details. Only available when the generation is finished."""
+ generated_text: Optional[str] = None
+ """The complete generated text. Only available when the generation is finished."""
+ index: Optional[int] = None
+ """The token index within the stream. Optional to support older clients that omit it."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd8369de4b26cf8ef38cf8cfbafdc1a8bb12d552
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_audio.py
@@ -0,0 +1,105 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional, Union
+
+from .base import BaseInferenceType
+
+
+EarlyStoppingEnum = Literal["never"]
+
+
+@dataclass
+class TextToAudioGenerationParameters(BaseInferenceType):
+ """Parametrization of the text generation process
+ Ad-hoc parametrization of the text generation process
+ """
+
+ do_sample: Optional[bool] = None
+ """Whether to use sampling instead of greedy decoding when generating new tokens."""
+ early_stopping: Optional[Union[bool, "EarlyStoppingEnum"]] = None
+ """Controls the stopping condition for beam-based methods."""
+ epsilon_cutoff: Optional[float] = None
+ """If set to float strictly between 0 and 1, only tokens with a conditional probability
+ greater than epsilon_cutoff will be sampled. In the paper, suggested values range from
+ 3e-4 to 9e-4, depending on the size of the model. See [Truncation Sampling as Language
+ Model Desmoothing](https://hf.co/papers/2210.15191) for more details.
+ """
+ eta_cutoff: Optional[float] = None
+ """Eta sampling is a hybrid of locally typical sampling and epsilon sampling. If set to
+ float strictly between 0 and 1, a token is only considered if it is greater than either
+ eta_cutoff or sqrt(eta_cutoff) * exp(-entropy(softmax(next_token_logits))). The latter
+ term is intuitively the expected next token probability, scaled by sqrt(eta_cutoff). In
+ the paper, suggested values range from 3e-4 to 2e-3, depending on the size of the model.
+ See [Truncation Sampling as Language Model Desmoothing](https://hf.co/papers/2210.15191)
+ for more details.
+ """
+ max_length: Optional[int] = None
+ """The maximum length (in tokens) of the generated text, including the input."""
+ max_new_tokens: Optional[int] = None
+ """The maximum number of tokens to generate. Takes precedence over maxLength."""
+ min_length: Optional[int] = None
+ """The minimum length (in tokens) of the generated text, including the input."""
+ min_new_tokens: Optional[int] = None
+ """The minimum number of tokens to generate. Takes precedence over maxLength."""
+ num_beam_groups: Optional[int] = None
+ """Number of groups to divide num_beams into in order to ensure diversity among different
+ groups of beams. See [this paper](https://hf.co/papers/1610.02424) for more details.
+ """
+ num_beams: Optional[int] = None
+ """Number of beams to use for beam search."""
+ penalty_alpha: Optional[float] = None
+ """The value balances the model confidence and the degeneration penalty in contrastive
+ search decoding.
+ """
+ temperature: Optional[float] = None
+ """The value used to modulate the next token probabilities."""
+ top_k: Optional[int] = None
+ """The number of highest probability vocabulary tokens to keep for top-k-filtering."""
+ top_p: Optional[float] = None
+ """If set to float < 1, only the smallest set of most probable tokens with probabilities
+ that add up to top_p or higher are kept for generation.
+ """
+ typical_p: Optional[float] = None
+ """Local typicality measures how similar the conditional probability of predicting a target
+ token next is to the expected conditional probability of predicting a random token next,
+ given the partial text already generated. If set to float < 1, the smallest set of the
+ most locally typical tokens with probabilities that add up to typical_p or higher are
+ kept for generation. See [this paper](https://hf.co/papers/2202.00666) for more details.
+ """
+ use_cache: Optional[bool] = None
+ """Whether the model should use the past last key/values attentions to speed up decoding"""
+
+
+@dataclass
+class TextToAudioParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text To Audio
+ """
+
+ generate: Optional[TextToAudioGenerationParameters] = None
+ """Parametrization of the text generation process"""
+
+
+@dataclass
+class TextToAudioInput(BaseInferenceType):
+ """Inputs for Text To Audio inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TextToAudioParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TextToAudioOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Audio task"""
+
+ audio: Any
+ """The generated audio waveform."""
+ sampling_rate: Any
+ text_to_audio_output_sampling_rate: Optional[float] = None
+ """The sampling rate of the generated audio waveform."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..40e53ab016d3a6f2098d26eadab9cf51805c31b1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/text_to_image.py
@@ -0,0 +1,57 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class TextToImageTargetSize(BaseInferenceType):
+ """The size in pixel of the output image"""
+
+ height: int
+ width: int
+
+
+@dataclass
+class TextToImageParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text To Image
+ """
+
+ guidance_scale: Optional[float] = None
+ """For diffusion models. A higher guidance scale value encourages the model to generate
+ images closely linked to the text prompt at the expense of lower image quality.
+ """
+ negative_prompt: Optional[List[str]] = None
+ """One or several prompt to guide what NOT to include in image generation."""
+ num_inference_steps: Optional[int] = None
+ """For diffusion models. The number of denoising steps. More denoising steps usually lead to
+ a higher quality image at the expense of slower inference.
+ """
+ scheduler: Optional[str] = None
+ """For diffusion models. Override the scheduler with a compatible one"""
+ target_size: Optional[TextToImageTargetSize] = None
+ """The size in pixel of the output image"""
+
+
+@dataclass
+class TextToImageInput(BaseInferenceType):
+ """Inputs for Text To Image inference"""
+
+ inputs: str
+ """The input text data (sometimes called "prompt\""""
+ parameters: Optional[TextToImageParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TextToImageOutput(BaseInferenceType):
+ """Outputs of inference for the Text To Image task"""
+
+ image: Any
+ """The generated image"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/token_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/token_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d60ea27eedbfe28096435c84e4002c0d9a64bc6
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/token_classification.py
@@ -0,0 +1,53 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+TokenClassificationAggregationStrategy = Literal["none", "simple", "first", "average", "max"]
+
+
+@dataclass
+class TokenClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Token Classification
+ """
+
+ aggregation_strategy: Optional["TokenClassificationAggregationStrategy"] = None
+ """The strategy used to fuse tokens based on model predictions"""
+ ignore_labels: Optional[List[str]] = None
+ """A list of labels to ignore"""
+ stride: Optional[int] = None
+ """The number of overlapping tokens between chunks when splitting the input text."""
+
+
+@dataclass
+class TokenClassificationInput(BaseInferenceType):
+ """Inputs for Token Classification inference"""
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TokenClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TokenClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Token Classification task"""
+
+ label: Any
+ score: float
+ """The associated score / probability"""
+ end: Optional[int] = None
+ """The character position in the input where this group ends."""
+ entity_group: Optional[str] = None
+ """The predicted label for that group of tokens"""
+ start: Optional[int] = None
+ """The character position in the input where this group begins."""
+ word: Optional[str] = None
+ """The corresponding text"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/translation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/translation.py
new file mode 100644
index 0000000000000000000000000000000000000000..e06ad2b72d35dcf814b110112cd882cb4b4cc616
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/translation.py
@@ -0,0 +1,46 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+TranslationGenerationTruncationStrategy = Literal["do_not_truncate", "longest_first", "only_first", "only_second"]
+
+
+@dataclass
+class TranslationGenerationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Text2text Generation
+ """
+
+ clean_up_tokenization_spaces: Optional[bool] = None
+ """Whether to clean up the potential extra spaces in the text output."""
+ generate_parameters: Optional[Dict[str, Any]] = None
+ """Additional parametrization of the text generation algorithm"""
+ truncation: Optional["TranslationGenerationTruncationStrategy"] = None
+ """The truncation strategy to use"""
+
+
+@dataclass
+class TranslationInput(BaseInferenceType):
+ """Inputs for Translation inference
+ Inputs for Text2text Generation inference
+ """
+
+ inputs: str
+ """The input text data"""
+ parameters: Optional[TranslationGenerationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class TranslationOutput(BaseInferenceType):
+ """Outputs of inference for the Translation task"""
+
+ translation_text: str
+ """The translated text."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/video_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/video_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c5a9d55a81fab6fc71e1226e7776a7a68ee688f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/video_classification.py
@@ -0,0 +1,47 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Literal, Optional
+
+from .base import BaseInferenceType
+
+
+ClassificationOutputTransform = Literal["sigmoid", "softmax", "none"]
+
+
+@dataclass
+class VideoClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Video Classification
+ """
+
+ frame_sampling_rate: Optional[int] = None
+ """The sampling rate used to select frames from the video."""
+ function_to_apply: Optional["ClassificationOutputTransform"] = None
+ num_frames: Optional[int] = None
+ """The number of sampled frames to consider for classification."""
+ top_k: Optional[int] = None
+ """When specified, limits the output to the top K most probable classes."""
+
+
+@dataclass
+class VideoClassificationInput(BaseInferenceType):
+ """Inputs for Video Classification inference"""
+
+ inputs: Any
+ """The input video data"""
+ parameters: Optional[VideoClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class VideoClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Video Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ab7c14d8ab032c2e9bf24c835520182cb1b5e5f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/visual_question_answering.py
@@ -0,0 +1,53 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class VisualQuestionAnsweringInputData(BaseInferenceType):
+ """One (image, question) pair to answer"""
+
+ image: Any
+ """The image."""
+ question: Any
+ """The question to answer based on the image."""
+
+
+@dataclass
+class VisualQuestionAnsweringParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Visual Question Answering
+ """
+
+ top_k: Optional[int] = None
+ """The number of answers to return (will be chosen by order of likelihood). Note that we
+ return less than topk answers if there are not enough options available within the
+ context.
+ """
+
+
+@dataclass
+class VisualQuestionAnsweringInput(BaseInferenceType):
+ """Inputs for Visual Question Answering inference"""
+
+ inputs: VisualQuestionAnsweringInputData
+ """One (image, question) pair to answer"""
+ parameters: Optional[VisualQuestionAnsweringParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class VisualQuestionAnsweringOutputElement(BaseInferenceType):
+ """Outputs of inference for the Visual Question Answering task"""
+
+ label: Any
+ score: float
+ """The associated score / probability"""
+ answer: Optional[str] = None
+ """The answer to the question"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c55ebf218ca3314993aacd7eaa8c1910b5ab63e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_classification.py
@@ -0,0 +1,56 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class ZeroShotClassificationInputData(BaseInferenceType):
+ """The input text data, with candidate labels"""
+
+ candidate_labels: List[str]
+ """The set of possible class labels to classify the text into."""
+ text: str
+ """The text to classify"""
+
+
+@dataclass
+class ZeroShotClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Zero Shot Classification
+ """
+
+ hypothesis_template: Optional[str] = None
+ """The sentence used in conjunction with candidateLabels to attempt the text classification
+ by replacing the placeholder with the candidate labels.
+ """
+ multi_label: Optional[bool] = None
+ """Whether multiple candidate labels can be true. If false, the scores are normalized such
+ that the sum of the label likelihoods for each sequence is 1. If true, the labels are
+ considered independent and probabilities are normalized for each candidate.
+ """
+
+
+@dataclass
+class ZeroShotClassificationInput(BaseInferenceType):
+ """Inputs for Zero Shot Classification inference"""
+
+ inputs: ZeroShotClassificationInputData
+ """The input text data, with candidate labels"""
+ parameters: Optional[ZeroShotClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ZeroShotClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d635187d7ed2f92eb239dc1e4ee4754394dad4c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_image_classification.py
@@ -0,0 +1,51 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class ZeroShotImageClassificationInputData(BaseInferenceType):
+ """The input image data, with candidate labels"""
+
+ candidate_labels: List[str]
+ """The candidate labels for this image"""
+ image: Any
+ """The image data to classify"""
+
+
+@dataclass
+class ZeroShotImageClassificationParameters(BaseInferenceType):
+ """Additional inference parameters
+ Additional inference parameters for Zero Shot Image Classification
+ """
+
+ hypothesis_template: Optional[str] = None
+ """The sentence used in conjunction with candidateLabels to attempt the text classification
+ by replacing the placeholder with the candidate labels.
+ """
+
+
+@dataclass
+class ZeroShotImageClassificationInput(BaseInferenceType):
+ """Inputs for Zero Shot Image Classification inference"""
+
+ inputs: ZeroShotImageClassificationInputData
+ """The input image data, with candidate labels"""
+ parameters: Optional[ZeroShotImageClassificationParameters] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ZeroShotImageClassificationOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Image Classification task"""
+
+ label: str
+ """The predicted class label."""
+ score: float
+ """The corresponding probability."""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..42a21568c9c652eb307cf2bd44ee9aa06ab4df7b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_generated/types/zero_shot_object_detection.py
@@ -0,0 +1,55 @@
+# Inference code generated from the JSON schema spec in @huggingface/tasks.
+#
+# See:
+# - script: https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/scripts/inference-codegen.ts
+# - specs: https://github.com/huggingface/huggingface.js/tree/main/packages/tasks/src/tasks.
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional
+
+from .base import BaseInferenceType
+
+
+@dataclass
+class ZeroShotObjectDetectionInputData(BaseInferenceType):
+ """The input image data, with candidate labels"""
+
+ candidate_labels: List[str]
+ """The candidate labels for this image"""
+ image: Any
+ """The image data to generate bounding boxes from"""
+
+
+@dataclass
+class ZeroShotObjectDetectionInput(BaseInferenceType):
+ """Inputs for Zero Shot Object Detection inference"""
+
+ inputs: ZeroShotObjectDetectionInputData
+ """The input image data, with candidate labels"""
+ parameters: Optional[Dict[str, Any]] = None
+ """Additional inference parameters"""
+
+
+@dataclass
+class ZeroShotObjectDetectionBoundingBox(BaseInferenceType):
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+
+ xmax: int
+ xmin: int
+ ymax: int
+ ymin: int
+
+
+@dataclass
+class ZeroShotObjectDetectionOutputElement(BaseInferenceType):
+ """Outputs of inference for the Zero Shot Object Detection task"""
+
+ box: ZeroShotObjectDetectionBoundingBox
+ """The predicted bounding box. Coordinates are relative to the top left corner of the input
+ image.
+ """
+ label: str
+ """A candidate label"""
+ score: float
+ """The associated score / probability"""
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_templating.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_templating.py
new file mode 100644
index 0000000000000000000000000000000000000000..10c349c24d676c75989b08bcf28181a497056a2e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_templating.py
@@ -0,0 +1,105 @@
+from functools import lru_cache
+from typing import Callable, Dict, List, Optional, Union
+
+from ..utils import HfHubHTTPError, RepositoryNotFoundError, is_minijinja_available
+
+
+class TemplateError(Exception):
+ """Any error raised while trying to fetch or render a chat template."""
+
+
+def _import_minijinja():
+ if not is_minijinja_available():
+ raise ImportError("Cannot render template. Please install minijinja using `pip install minijinja`.")
+ import minijinja # noqa: F401
+
+ return minijinja
+
+
+def render_chat_prompt(
+ *,
+ model_id: str,
+ messages: List[Dict[str, str]],
+ token: Union[str, bool, None] = None,
+ add_generation_prompt: bool = True,
+ **kwargs,
+) -> str:
+ """Render a chat prompt using a model's chat template.
+
+ Args:
+ model_id (`str`):
+ The model id.
+ messages (`List[Dict[str, str]]`):
+ The list of messages to render.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns:
+ `str`: The rendered chat prompt.
+
+ Raises:
+ `TemplateError`: If there's any issue while fetching, compiling or rendering the chat template.
+ """
+ minijinja = _import_minijinja()
+ template = _fetch_and_compile_template(model_id=model_id, token=token)
+
+ try:
+ return template(messages=messages, add_generation_prompt=add_generation_prompt, **kwargs)
+ except minijinja.TemplateError as e:
+ raise TemplateError(f"Error while trying to render chat prompt for model '{model_id}': {e}") from e
+
+
+@lru_cache # TODO: lru_cache for raised exceptions
+def _fetch_and_compile_template(*, model_id: str, token: Union[str, None]) -> Callable:
+ """Fetch and compile a model's chat template.
+
+ Method is cached to avoid fetching the same model's config multiple times.
+
+ Args:
+ model_id (`str`):
+ The model id.
+ token (`str` or `bool`, *optional*):
+ Hugging Face token. Will default to the locally saved token if not provided.
+
+ Returns:
+ `Callable`: A callable that takes a list of messages and returns the rendered chat prompt.
+ """
+ from huggingface_hub.hf_api import HfApi
+
+ minijinja = _import_minijinja()
+
+ # 1. fetch config from API
+ try:
+ config = HfApi(token=token).model_info(model_id).config
+ except RepositoryNotFoundError as e:
+ raise TemplateError(f"Cannot render chat template: model '{model_id}' not found.") from e
+ except HfHubHTTPError as e:
+ raise TemplateError(f"Error while trying to fetch chat template for model '{model_id}': {e}") from e
+
+ # 2. check config validity
+ if config is None:
+ raise TemplateError(f"Config not found for model '{model_id}'.")
+ tokenizer_config = config.get("tokenizer_config")
+ if tokenizer_config is None:
+ raise TemplateError(f"Tokenizer config not found for model '{model_id}'.")
+ if tokenizer_config.get("chat_template") is None:
+ raise TemplateError(f"Chat template not found in tokenizer_config for model '{model_id}'.")
+ chat_template = tokenizer_config["chat_template"]
+ if not isinstance(chat_template, str):
+ raise TemplateError(f"Chat template must be a string, not '{type(chat_template)}' (model: {model_id}).")
+
+ special_tokens: Dict[str, Optional[str]] = {}
+ for key, value in tokenizer_config.items():
+ if "token" in key:
+ if isinstance(value, str):
+ special_tokens[key] = value
+ elif isinstance(value, dict) and value.get("__type") == "AddedToken":
+ special_tokens[key] = value.get("content")
+
+ # 3. compile template and return
+ env = minijinja.Environment()
+ try:
+ env.add_template("chat_template", chat_template)
+ except minijinja.TemplateError as e:
+ raise TemplateError(f"Error while trying to compile chat template for model '{model_id}': {e}") from e
+ return lambda **kwargs: env.render_template("chat_template", **kwargs, **special_tokens)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_types.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..70c2137210c436c29398921178b6e8f45b6a4182
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference/_types.py
@@ -0,0 +1,52 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List, TypedDict
+
+
+# Legacy types
+# Types are now generated from the JSON schema spec in @huggingface/tasks.
+# See ./src/huggingface_hub/inference/_generated/types
+
+
+class ConversationalOutputConversation(TypedDict):
+ """Dictionary containing the "conversation" part of a [`~InferenceClient.conversational`] task.
+
+ Args:
+ generated_responses (`List[str]`):
+ A list of the responses from the model.
+ past_user_inputs (`List[str]`):
+ A list of the inputs from the user. Must be the same length as `generated_responses`.
+ """
+
+ generated_responses: List[str]
+ past_user_inputs: List[str]
+
+
+class ConversationalOutput(TypedDict):
+ """Dictionary containing the output of a [`~InferenceClient.conversational`] task.
+
+ Args:
+ generated_text (`str`):
+ The last response from the model.
+ conversation (`ConversationalOutputConversation`):
+ The past conversation.
+ warnings (`List[str]`):
+ A list of warnings associated with the process.
+ """
+
+ conversation: ConversationalOutputConversation
+ generated_text: str
+ warnings: List[str]
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/inference_api.py b/.venv/lib/python3.10/site-packages/huggingface_hub/inference_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..c889a6d8720a5242f50f81955916df3cb33357e1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/inference_api.py
@@ -0,0 +1,217 @@
+import io
+from typing import Any, Dict, List, Optional, Union
+
+from .constants import INFERENCE_ENDPOINT
+from .hf_api import HfApi
+from .utils import build_hf_headers, get_session, is_pillow_available, logging, validate_hf_hub_args
+from .utils._deprecation import _deprecate_method
+
+
+logger = logging.get_logger(__name__)
+
+
+ALL_TASKS = [
+ # NLP
+ "text-classification",
+ "token-classification",
+ "table-question-answering",
+ "question-answering",
+ "zero-shot-classification",
+ "translation",
+ "summarization",
+ "conversational",
+ "feature-extraction",
+ "text-generation",
+ "text2text-generation",
+ "fill-mask",
+ "sentence-similarity",
+ # Audio
+ "text-to-speech",
+ "automatic-speech-recognition",
+ "audio-to-audio",
+ "audio-classification",
+ "voice-activity-detection",
+ # Computer vision
+ "image-classification",
+ "object-detection",
+ "image-segmentation",
+ "text-to-image",
+ "image-to-image",
+ # Others
+ "tabular-classification",
+ "tabular-regression",
+]
+
+
+class InferenceApi:
+ """Client to configure requests and make calls to the HuggingFace Inference API.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.inference_api import InferenceApi
+
+ >>> # Mask-fill example
+ >>> inference = InferenceApi("bert-base-uncased")
+ >>> inference(inputs="The goal of life is [MASK].")
+ [{'sequence': 'the goal of life is life.', 'score': 0.10933292657136917, 'token': 2166, 'token_str': 'life'}]
+
+ >>> # Question Answering example
+ >>> inference = InferenceApi("deepset/roberta-base-squad2")
+ >>> inputs = {
+ ... "question": "What's my name?",
+ ... "context": "My name is Clara and I live in Berkeley.",
+ ... }
+ >>> inference(inputs)
+ {'score': 0.9326569437980652, 'start': 11, 'end': 16, 'answer': 'Clara'}
+
+ >>> # Zero-shot example
+ >>> inference = InferenceApi("typeform/distilbert-base-uncased-mnli")
+ >>> inputs = "Hi, I recently bought a device from your company but it is not working as advertised and I would like to get reimbursed!"
+ >>> params = {"candidate_labels": ["refund", "legal", "faq"]}
+ >>> inference(inputs, params)
+ {'sequence': 'Hi, I recently bought a device from your company but it is not working as advertised and I would like to get reimbursed!', 'labels': ['refund', 'faq', 'legal'], 'scores': [0.9378499388694763, 0.04914155602455139, 0.013008488342165947]}
+
+ >>> # Overriding configured task
+ >>> inference = InferenceApi("bert-base-uncased", task="feature-extraction")
+
+ >>> # Text-to-image
+ >>> inference = InferenceApi("stabilityai/stable-diffusion-2-1")
+ >>> inference("cat")
+
+
+ >>> # Return as raw response to parse the output yourself
+ >>> inference = InferenceApi("mio/amadeus")
+ >>> response = inference("hello world", raw_response=True)
+ >>> response.headers
+ {"Content-Type": "audio/flac", ...}
+ >>> response.content # raw bytes from server
+ b'(...)'
+ ```
+ """
+
+ @validate_hf_hub_args
+ @_deprecate_method(
+ version="1.0",
+ message=(
+ "`InferenceApi` client is deprecated in favor of the more feature-complete `InferenceClient`. Check out"
+ " this guide to learn how to convert your script to use it:"
+ " https://huggingface.co/docs/huggingface_hub/guides/inference#legacy-inferenceapi-client."
+ ),
+ )
+ def __init__(
+ self,
+ repo_id: str,
+ task: Optional[str] = None,
+ token: Optional[str] = None,
+ gpu: bool = False,
+ ):
+ """Inits headers and API call information.
+
+ Args:
+ repo_id (``str``):
+ Id of repository (e.g. `user/bert-base-uncased`).
+ task (``str``, `optional`, defaults ``None``):
+ Whether to force a task instead of using task specified in the
+ repository.
+ token (`str`, `optional`):
+ The API token to use as HTTP bearer authorization. This is not
+ the authentication token. You can find the token in
+ https://huggingface.co/settings/token. Alternatively, you can
+ find both your organizations and personal API tokens using
+ `HfApi().whoami(token)`.
+ gpu (`bool`, `optional`, defaults `False`):
+ Whether to use GPU instead of CPU for inference(requires Startup
+ plan at least).
+ """
+ self.options = {"wait_for_model": True, "use_gpu": gpu}
+ self.headers = build_hf_headers(token=token)
+
+ # Configure task
+ model_info = HfApi(token=token).model_info(repo_id=repo_id)
+ if not model_info.pipeline_tag and not task:
+ raise ValueError(
+ "Task not specified in the repository. Please add it to the model card"
+ " using pipeline_tag"
+ " (https://huggingface.co/docs#how-is-a-models-type-of-inference-api-and-widget-determined)"
+ )
+
+ if task and task != model_info.pipeline_tag:
+ if task not in ALL_TASKS:
+ raise ValueError(f"Invalid task {task}. Make sure it's valid.")
+
+ logger.warning(
+ "You're using a different task than the one specified in the"
+ " repository. Be sure to know what you're doing :)"
+ )
+ self.task = task
+ else:
+ assert model_info.pipeline_tag is not None, "Pipeline tag cannot be None"
+ self.task = model_info.pipeline_tag
+
+ self.api_url = f"{INFERENCE_ENDPOINT}/pipeline/{self.task}/{repo_id}"
+
+ def __repr__(self):
+ # Do not add headers to repr to avoid leaking token.
+ return f"InferenceAPI(api_url='{self.api_url}', task='{self.task}', options={self.options})"
+
+ def __call__(
+ self,
+ inputs: Optional[Union[str, Dict, List[str], List[List[str]]]] = None,
+ params: Optional[Dict] = None,
+ data: Optional[bytes] = None,
+ raw_response: bool = False,
+ ) -> Any:
+ """Make a call to the Inference API.
+
+ Args:
+ inputs (`str` or `Dict` or `List[str]` or `List[List[str]]`, *optional*):
+ Inputs for the prediction.
+ params (`Dict`, *optional*):
+ Additional parameters for the models. Will be sent as `parameters` in the
+ payload.
+ data (`bytes`, *optional*):
+ Bytes content of the request. In this case, leave `inputs` and `params` empty.
+ raw_response (`bool`, defaults to `False`):
+ If `True`, the raw `Response` object is returned. You can parse its content
+ as preferred. By default, the content is parsed into a more practical format
+ (json dictionary or PIL Image for example).
+ """
+ # Build payload
+ payload: Dict[str, Any] = {
+ "options": self.options,
+ }
+ if inputs:
+ payload["inputs"] = inputs
+ if params:
+ payload["parameters"] = params
+
+ # Make API call
+ response = get_session().post(self.api_url, headers=self.headers, json=payload, data=data)
+
+ # Let the user handle the response
+ if raw_response:
+ return response
+
+ # By default, parse the response for the user.
+ content_type = response.headers.get("Content-Type") or ""
+ if content_type.startswith("image"):
+ if not is_pillow_available():
+ raise ImportError(
+ f"Task '{self.task}' returned as image but Pillow is not installed."
+ " Please install it (`pip install Pillow`) or pass"
+ " `raw_response=True` to get the raw `Response` object and parse"
+ " the image by yourself."
+ )
+
+ from PIL import Image
+
+ return Image.open(io.BytesIO(response.content))
+ elif content_type == "application/json":
+ return response.json()
+ else:
+ raise NotImplementedError(
+ f"{content_type} output type is not implemented yet. You can pass"
+ " `raw_response=True` to get the raw `Response` object and parse the"
+ " output by yourself."
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/keras_mixin.py b/.venv/lib/python3.10/site-packages/huggingface_hub/keras_mixin.py
new file mode 100644
index 0000000000000000000000000000000000000000..5584ce84a250b18d3620089e6f6240a37bb47baa
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/keras_mixin.py
@@ -0,0 +1,502 @@
+import collections.abc as collections
+import json
+import os
+import warnings
+from functools import wraps
+from pathlib import Path
+from shutil import copytree
+from typing import Any, Dict, List, Optional, Union
+
+from huggingface_hub import ModelHubMixin, snapshot_download
+from huggingface_hub.utils import (
+ get_tf_version,
+ is_graphviz_available,
+ is_pydot_available,
+ is_tf_available,
+ yaml_dump,
+)
+
+from .constants import CONFIG_NAME
+from .hf_api import HfApi
+from .utils import SoftTemporaryDirectory, logging, validate_hf_hub_args
+from .utils._typing import CallableT
+
+
+logger = logging.get_logger(__name__)
+
+keras = None
+if is_tf_available():
+ # Depending on which version of TensorFlow is installed, we need to import
+ # keras from the correct location.
+ # See https://github.com/tensorflow/tensorflow/releases/tag/v2.16.1.
+ # Note: saving a keras model only works with Keras<3.0.
+ try:
+ import tf_keras as keras # type: ignore
+ except ImportError:
+ import tensorflow as tf # type: ignore
+
+ keras = tf.keras
+
+
+def _requires_keras_2_model(fn: CallableT) -> CallableT:
+ # Wrapper to raise if user tries to save a Keras 3.x model
+ @wraps(fn)
+ def _inner(model, *args, **kwargs):
+ if not hasattr(model, "history"): # hacky way to check if model is Keras 2.x
+ raise NotImplementedError(
+ f"Cannot use '{fn.__name__}': Keras 3.x is not supported."
+ " Please save models manually and upload them using `upload_folder` or `huggingface-cli upload`."
+ )
+ return fn(model, *args, **kwargs)
+
+ return _inner # type: ignore [return-value]
+
+
+def _flatten_dict(dictionary, parent_key=""):
+ """Flatten a nested dictionary.
+ Reference: https://stackoverflow.com/a/6027615/10319735
+
+ Args:
+ dictionary (`dict`):
+ The nested dictionary to be flattened.
+ parent_key (`str`):
+ The parent key to be prefixed to the children keys.
+ Necessary for recursing over the nested dictionary.
+
+ Returns:
+ The flattened dictionary.
+ """
+ items = []
+ for key, value in dictionary.items():
+ new_key = f"{parent_key}.{key}" if parent_key else key
+ if isinstance(value, collections.MutableMapping):
+ items.extend(
+ _flatten_dict(
+ value,
+ new_key,
+ ).items()
+ )
+ else:
+ items.append((new_key, value))
+ return dict(items)
+
+
+def _create_hyperparameter_table(model):
+ """Parse hyperparameter dictionary into a markdown table."""
+ table = None
+ if model.optimizer is not None:
+ optimizer_params = model.optimizer.get_config()
+ # flatten the configuration
+ optimizer_params = _flatten_dict(optimizer_params)
+ optimizer_params["training_precision"] = keras.mixed_precision.global_policy().name
+ table = "| Hyperparameters | Value |\n| :-- | :-- |\n"
+ for key, value in optimizer_params.items():
+ table += f"| {key} | {value} |\n"
+ return table
+
+
+def _plot_network(model, save_directory):
+ keras.utils.plot_model(
+ model,
+ to_file=f"{save_directory}/model.png",
+ show_shapes=False,
+ show_dtype=False,
+ show_layer_names=True,
+ rankdir="TB",
+ expand_nested=False,
+ dpi=96,
+ layer_range=None,
+ )
+
+
+def _create_model_card(
+ model,
+ repo_dir: Path,
+ plot_model: bool = True,
+ metadata: Optional[dict] = None,
+):
+ """
+ Creates a model card for the repository.
+
+ Do not overwrite an existing README.md file.
+ """
+ readme_path = repo_dir / "README.md"
+ if readme_path.exists():
+ return
+
+ hyperparameters = _create_hyperparameter_table(model)
+ if plot_model and is_graphviz_available() and is_pydot_available():
+ _plot_network(model, repo_dir)
+ if metadata is None:
+ metadata = {}
+ metadata["library_name"] = "keras"
+ model_card: str = "---\n"
+ model_card += yaml_dump(metadata, default_flow_style=False)
+ model_card += "---\n"
+ model_card += "\n## Model description\n\nMore information needed\n"
+ model_card += "\n## Intended uses & limitations\n\nMore information needed\n"
+ model_card += "\n## Training and evaluation data\n\nMore information needed\n"
+ if hyperparameters is not None:
+ model_card += "\n## Training procedure\n"
+ model_card += "\n### Training hyperparameters\n"
+ model_card += "\nThe following hyperparameters were used during training:\n\n"
+ model_card += hyperparameters
+ model_card += "\n"
+ if plot_model and os.path.exists(f"{repo_dir}/model.png"):
+ model_card += "\n ## Model Plot\n"
+ model_card += "\n"
+ model_card += "\nView Model Plot
\n"
+ path_to_plot = "./model.png"
+ model_card += f"\n![Model Image]({path_to_plot})\n"
+ model_card += "\n "
+
+ readme_path.write_text(model_card)
+
+
+@_requires_keras_2_model
+def save_pretrained_keras(
+ model,
+ save_directory: Union[str, Path],
+ config: Optional[Dict[str, Any]] = None,
+ include_optimizer: bool = False,
+ plot_model: bool = True,
+ tags: Optional[Union[list, str]] = None,
+ **model_save_kwargs,
+):
+ """
+ Saves a Keras model to save_directory in SavedModel format. Use this if
+ you're using the Functional or Sequential APIs.
+
+ Args:
+ model (`Keras.Model`):
+ The [Keras
+ model](https://www.tensorflow.org/api_docs/python/tf/keras/Model)
+ you'd like to save. The model must be compiled and built.
+ save_directory (`str` or `Path`):
+ Specify directory in which you want to save the Keras model.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ include_optimizer(`bool`, *optional*, defaults to `False`):
+ Whether or not to include optimizer in serialization.
+ plot_model (`bool`, *optional*, defaults to `True`):
+ Setting this to `True` will plot the model and put it in the model
+ card. Requires graphviz and pydot to be installed.
+ tags (Union[`str`,`list`], *optional*):
+ List of tags that are related to model or string of a single tag. See example tags
+ [here](https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1).
+ model_save_kwargs(`dict`, *optional*):
+ model_save_kwargs will be passed to
+ [`tf.keras.models.save_model()`](https://www.tensorflow.org/api_docs/python/tf/keras/models/save_model).
+ """
+ if keras is None:
+ raise ImportError("Called a Tensorflow-specific function but could not import it.")
+
+ if not model.built:
+ raise ValueError("Model should be built before trying to save")
+
+ save_directory = Path(save_directory)
+ save_directory.mkdir(parents=True, exist_ok=True)
+
+ # saving config
+ if config:
+ if not isinstance(config, dict):
+ raise RuntimeError(f"Provided config to save_pretrained_keras should be a dict. Got: '{type(config)}'")
+
+ with (save_directory / CONFIG_NAME).open("w") as f:
+ json.dump(config, f)
+
+ metadata = {}
+ if isinstance(tags, list):
+ metadata["tags"] = tags
+ elif isinstance(tags, str):
+ metadata["tags"] = [tags]
+
+ task_name = model_save_kwargs.pop("task_name", None)
+ if task_name is not None:
+ warnings.warn(
+ "`task_name` input argument is deprecated. Pass `tags` instead.",
+ FutureWarning,
+ )
+ if "tags" in metadata:
+ metadata["tags"].append(task_name)
+ else:
+ metadata["tags"] = [task_name]
+
+ if model.history is not None:
+ if model.history.history != {}:
+ path = save_directory / "history.json"
+ if path.exists():
+ warnings.warn(
+ "`history.json` file already exists, it will be overwritten by the history of this version.",
+ UserWarning,
+ )
+ with path.open("w", encoding="utf-8") as f:
+ json.dump(model.history.history, f, indent=2, sort_keys=True)
+
+ _create_model_card(model, save_directory, plot_model, metadata)
+ keras.models.save_model(model, save_directory, include_optimizer=include_optimizer, **model_save_kwargs)
+
+
+def from_pretrained_keras(*args, **kwargs) -> "KerasModelHubMixin":
+ r"""
+ Instantiate a pretrained Keras model from a pre-trained model from the Hub.
+ The model is expected to be in `SavedModel` format.
+
+ Args:
+ pretrained_model_name_or_path (`str` or `os.PathLike`):
+ Can be either:
+ - A string, the `model id` of a pretrained model hosted inside a
+ model repo on huggingface.co. Valid model ids can be located
+ at the root-level, like `bert-base-uncased`, or namespaced
+ under a user or organization name, like
+ `dbmdz/bert-base-german-cased`.
+ - You can add `revision` by appending `@` at the end of model_id
+ simply like this: `dbmdz/bert-base-german-cased@main` Revision
+ is the specific model version to use. It can be a branch name,
+ a tag name, or a commit id, since we use a git-based system
+ for storing models and other artifacts on huggingface.co, so
+ `revision` can be any identifier allowed by git.
+ - A path to a `directory` containing model weights saved using
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g.,
+ `./my_model_directory/`.
+ - `None` if you are both providing the configuration and state
+ dictionary (resp. with keyword arguments `config` and
+ `state_dict`).
+ force_download (`bool`, *optional*, defaults to `False`):
+ Whether to force the (re-)download of the model weights and
+ configuration files, overriding the cached versions if they exist.
+ resume_download (`bool`, *optional*, defaults to `False`):
+ Whether to delete incompletely received files. Will attempt to
+ resume the download if such a file exists.
+ proxies (`Dict[str, str]`, *optional*):
+ A dictionary of proxy servers to use by protocol or endpoint, e.g.,
+ `{'http': 'foo.bar:3128', 'http://hostname': 'foo.bar:4012'}`. The
+ proxies are used on each request.
+ token (`str` or `bool`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. If
+ `True`, will use the token generated when running `transformers-cli
+ login` (stored in `~/.huggingface`).
+ cache_dir (`Union[str, os.PathLike]`, *optional*):
+ Path to a directory in which a downloaded pretrained model
+ configuration should be cached if the standard cache should not be
+ used.
+ local_files_only(`bool`, *optional*, defaults to `False`):
+ Whether to only look at local files (i.e., do not try to download
+ the model).
+ model_kwargs (`Dict`, *optional*):
+ model_kwargs will be passed to the model during initialization
+
+
+
+ Passing `token=True` is required when you want to use a private
+ model.
+
+
+ """
+ return KerasModelHubMixin.from_pretrained(*args, **kwargs)
+
+
+@validate_hf_hub_args
+@_requires_keras_2_model
+def push_to_hub_keras(
+ model,
+ repo_id: str,
+ *,
+ config: Optional[dict] = None,
+ commit_message: str = "Push Keras model using huggingface_hub.",
+ private: bool = False,
+ api_endpoint: Optional[str] = None,
+ token: Optional[str] = None,
+ branch: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ delete_patterns: Optional[Union[List[str], str]] = None,
+ log_dir: Optional[str] = None,
+ include_optimizer: bool = False,
+ tags: Optional[Union[list, str]] = None,
+ plot_model: bool = True,
+ **model_save_kwargs,
+):
+ """
+ Upload model checkpoint to the Hub.
+
+ Use `allow_patterns` and `ignore_patterns` to precisely filter which files should be pushed to the hub. Use
+ `delete_patterns` to delete existing remote files in the same commit. See [`upload_folder`] reference for more
+ details.
+
+ Args:
+ model (`Keras.Model`):
+ The [Keras model](`https://www.tensorflow.org/api_docs/python/tf/keras/Model`) you'd like to push to the
+ Hub. The model must be compiled and built.
+ repo_id (`str`):
+ ID of the repository to push to (example: `"username/my-model"`).
+ commit_message (`str`, *optional*, defaults to "Add Keras model"):
+ Message to commit while pushing.
+ private (`bool`, *optional*, defaults to `False`):
+ Whether the repository created should be private.
+ api_endpoint (`str`, *optional*):
+ The API endpoint to use when pushing the model to the hub.
+ token (`str`, *optional*):
+ The token to use as HTTP bearer authorization for remote files. If
+ not set, will use the token set when logging in with
+ `huggingface-cli login` (stored in `~/.huggingface`).
+ branch (`str`, *optional*):
+ The git branch on which to push the model. This defaults to
+ the default branch as specified in your repository, which
+ defaults to `"main"`.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `branch` with that commit.
+ Defaults to `False`.
+ config (`dict`, *optional*):
+ Configuration object to be saved alongside the model weights.
+ allow_patterns (`List[str]` or `str`, *optional*):
+ If provided, only files matching at least one pattern are pushed.
+ ignore_patterns (`List[str]` or `str`, *optional*):
+ If provided, files matching any of the patterns are not pushed.
+ delete_patterns (`List[str]` or `str`, *optional*):
+ If provided, remote files matching any of the patterns will be deleted from the repo.
+ log_dir (`str`, *optional*):
+ TensorBoard logging directory to be pushed. The Hub automatically
+ hosts and displays a TensorBoard instance if log files are included
+ in the repository.
+ include_optimizer (`bool`, *optional*, defaults to `False`):
+ Whether or not to include optimizer during serialization.
+ tags (Union[`list`, `str`], *optional*):
+ List of tags that are related to model or string of a single tag. See example tags
+ [here](https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1).
+ plot_model (`bool`, *optional*, defaults to `True`):
+ Setting this to `True` will plot the model and put it in the model
+ card. Requires graphviz and pydot to be installed.
+ model_save_kwargs(`dict`, *optional*):
+ model_save_kwargs will be passed to
+ [`tf.keras.models.save_model()`](https://www.tensorflow.org/api_docs/python/tf/keras/models/save_model).
+
+ Returns:
+ The url of the commit of your model in the given repository.
+ """
+ api = HfApi(endpoint=api_endpoint)
+ repo_id = api.create_repo(repo_id=repo_id, token=token, private=private, exist_ok=True).repo_id
+
+ # Push the files to the repo in a single commit
+ with SoftTemporaryDirectory() as tmp:
+ saved_path = Path(tmp) / repo_id
+ save_pretrained_keras(
+ model,
+ saved_path,
+ config=config,
+ include_optimizer=include_optimizer,
+ tags=tags,
+ plot_model=plot_model,
+ **model_save_kwargs,
+ )
+
+ # If `log_dir` provided, delete remote logs and upload new ones
+ if log_dir is not None:
+ delete_patterns = (
+ []
+ if delete_patterns is None
+ else (
+ [delete_patterns] # convert `delete_patterns` to a list
+ if isinstance(delete_patterns, str)
+ else delete_patterns
+ )
+ )
+ delete_patterns.append("logs/*")
+ copytree(log_dir, saved_path / "logs")
+
+ return api.upload_folder(
+ repo_type="model",
+ repo_id=repo_id,
+ folder_path=saved_path,
+ commit_message=commit_message,
+ token=token,
+ revision=branch,
+ create_pr=create_pr,
+ allow_patterns=allow_patterns,
+ ignore_patterns=ignore_patterns,
+ delete_patterns=delete_patterns,
+ )
+
+
+class KerasModelHubMixin(ModelHubMixin):
+ """
+ Implementation of [`ModelHubMixin`] to provide model Hub upload/download
+ capabilities to Keras models.
+
+
+ ```python
+ >>> import tensorflow as tf
+ >>> from huggingface_hub import KerasModelHubMixin
+
+
+ >>> class MyModel(tf.keras.Model, KerasModelHubMixin):
+ ... def __init__(self, **kwargs):
+ ... super().__init__()
+ ... self.config = kwargs.pop("config", None)
+ ... self.dummy_inputs = ...
+ ... self.layer = ...
+
+ ... def call(self, *args):
+ ... return ...
+
+
+ >>> # Initialize and compile the model as you normally would
+ >>> model = MyModel()
+ >>> model.compile(...)
+ >>> # Build the graph by training it or passing dummy inputs
+ >>> _ = model(model.dummy_inputs)
+ >>> # Save model weights to local directory
+ >>> model.save_pretrained("my-awesome-model")
+ >>> # Push model weights to the Hub
+ >>> model.push_to_hub("my-awesome-model")
+ >>> # Download and initialize weights from the Hub
+ >>> model = MyModel.from_pretrained("username/super-cool-model")
+ ```
+ """
+
+ def _save_pretrained(self, save_directory):
+ save_pretrained_keras(self, save_directory)
+
+ @classmethod
+ def _from_pretrained(
+ cls,
+ model_id,
+ revision,
+ cache_dir,
+ force_download,
+ proxies,
+ resume_download,
+ local_files_only,
+ token,
+ config: Optional[Dict[str, Any]] = None,
+ **model_kwargs,
+ ):
+ """Here we just call [`from_pretrained_keras`] function so both the mixin and
+ functional APIs stay in sync.
+
+ TODO - Some args above aren't used since we are calling
+ snapshot_download instead of hf_hub_download.
+ """
+ if keras is None:
+ raise ImportError("Called a TensorFlow-specific function but could not import it.")
+
+ # Root is either a local filepath matching model_id or a cached snapshot
+ if not os.path.isdir(model_id):
+ storage_folder = snapshot_download(
+ repo_id=model_id,
+ revision=revision,
+ cache_dir=cache_dir,
+ library_name="keras",
+ library_version=get_tf_version(),
+ )
+ else:
+ storage_folder = model_id
+
+ # TODO: change this in a future PR. We are not returning a KerasModelHubMixin instance here...
+ model = keras.models.load_model(storage_folder)
+
+ # For now, we add a new attribute, config, to store the config loaded from the hub/a local dir.
+ model.config = config
+
+ return model
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/lfs.py b/.venv/lib/python3.10/site-packages/huggingface_hub/lfs.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6fb9e210b522ab6f6730a3ab52c90d0e692e9d5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/lfs.py
@@ -0,0 +1,541 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Git LFS related type definitions and utilities"""
+
+import inspect
+import io
+import os
+import re
+import warnings
+from contextlib import AbstractContextManager
+from dataclasses import dataclass
+from math import ceil
+from os.path import getsize
+from pathlib import Path
+from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, Optional, Tuple, TypedDict
+from urllib.parse import unquote
+
+from huggingface_hub.constants import ENDPOINT, HF_HUB_ENABLE_HF_TRANSFER, REPO_TYPES_URL_PREFIXES
+
+from .utils import (
+ build_hf_headers,
+ fix_hf_endpoint_in_url,
+ get_session,
+ hf_raise_for_status,
+ http_backoff,
+ logging,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils.sha import sha256, sha_fileobj
+
+
+if TYPE_CHECKING:
+ from ._commit_api import CommitOperationAdd
+
+logger = logging.get_logger(__name__)
+
+OID_REGEX = re.compile(r"^[0-9a-f]{40}$")
+
+LFS_MULTIPART_UPLOAD_COMMAND = "lfs-multipart-upload"
+
+LFS_HEADERS = {
+ "Accept": "application/vnd.git-lfs+json",
+ "Content-Type": "application/vnd.git-lfs+json",
+}
+
+
+@dataclass
+class UploadInfo:
+ """
+ Dataclass holding required information to determine whether a blob
+ should be uploaded to the hub using the LFS protocol or the regular protocol
+
+ Args:
+ sha256 (`bytes`):
+ SHA256 hash of the blob
+ size (`int`):
+ Size in bytes of the blob
+ sample (`bytes`):
+ First 512 bytes of the blob
+ """
+
+ sha256: bytes
+ size: int
+ sample: bytes
+
+ @classmethod
+ def from_path(cls, path: str):
+ size = getsize(path)
+ with io.open(path, "rb") as file:
+ sample = file.peek(512)[:512]
+ sha = sha_fileobj(file)
+ return cls(size=size, sha256=sha, sample=sample)
+
+ @classmethod
+ def from_bytes(cls, data: bytes):
+ sha = sha256(data).digest()
+ return cls(size=len(data), sample=data[:512], sha256=sha)
+
+ @classmethod
+ def from_fileobj(cls, fileobj: BinaryIO):
+ sample = fileobj.read(512)
+ fileobj.seek(0, io.SEEK_SET)
+ sha = sha_fileobj(fileobj)
+ size = fileobj.tell()
+ fileobj.seek(0, io.SEEK_SET)
+ return cls(size=size, sha256=sha, sample=sample)
+
+
+@validate_hf_hub_args
+def post_lfs_batch_info(
+ upload_infos: Iterable[UploadInfo],
+ token: Optional[str],
+ repo_type: str,
+ repo_id: str,
+ revision: Optional[str] = None,
+ endpoint: Optional[str] = None,
+ headers: Optional[Dict[str, str]] = None,
+) -> Tuple[List[dict], List[dict]]:
+ """
+ Requests the LFS batch endpoint to retrieve upload instructions
+
+ Learn more: https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
+
+ Args:
+ upload_infos (`Iterable` of `UploadInfo`):
+ `UploadInfo` for the files that are being uploaded, typically obtained
+ from `CommitOperationAdd.upload_info`
+ repo_type (`str`):
+ Type of the repo to upload to: `"model"`, `"dataset"` or `"space"`.
+ repo_id (`str`):
+ A namespace (user or an organization) and a repo name separated
+ by a `/`.
+ revision (`str`, *optional*):
+ The git revision to upload to.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request
+
+ Returns:
+ `LfsBatchInfo`: 2-tuple:
+ - First element is the list of upload instructions from the server
+ - Second element is an list of errors, if any
+
+ Raises:
+ `ValueError`: If an argument is invalid or the server response is malformed
+
+ `HTTPError`: If the server returned an error
+ """
+ endpoint = endpoint if endpoint is not None else ENDPOINT
+ url_prefix = ""
+ if repo_type in REPO_TYPES_URL_PREFIXES:
+ url_prefix = REPO_TYPES_URL_PREFIXES[repo_type]
+ batch_url = f"{endpoint}/{url_prefix}{repo_id}.git/info/lfs/objects/batch"
+ payload: Dict = {
+ "operation": "upload",
+ "transfers": ["basic", "multipart"],
+ "objects": [
+ {
+ "oid": upload.sha256.hex(),
+ "size": upload.size,
+ }
+ for upload in upload_infos
+ ],
+ "hash_algo": "sha256",
+ }
+ if revision is not None:
+ payload["ref"] = {"name": unquote(revision)} # revision has been previously 'quoted'
+
+ headers = {
+ **LFS_HEADERS,
+ **build_hf_headers(token=token),
+ **(headers or {}),
+ }
+ resp = get_session().post(batch_url, headers=headers, json=payload)
+ hf_raise_for_status(resp)
+ batch_info = resp.json()
+
+ objects = batch_info.get("objects", None)
+ if not isinstance(objects, list):
+ raise ValueError("Malformed response from server")
+
+ return (
+ [_validate_batch_actions(obj) for obj in objects if "error" not in obj],
+ [_validate_batch_error(obj) for obj in objects if "error" in obj],
+ )
+
+
+class PayloadPartT(TypedDict):
+ partNumber: int
+ etag: str
+
+
+class CompletionPayloadT(TypedDict):
+ """Payload that will be sent to the Hub when uploading multi-part."""
+
+ oid: str
+ parts: List[PayloadPartT]
+
+
+def lfs_upload(
+ operation: "CommitOperationAdd",
+ lfs_batch_action: Dict,
+ token: Optional[str] = None,
+ headers: Optional[Dict[str, str]] = None,
+ endpoint: Optional[str] = None,
+) -> None:
+ """
+ Handles uploading a given object to the Hub with the LFS protocol.
+
+ Can be a No-op if the content of the file is already present on the hub large file storage.
+
+ Args:
+ operation (`CommitOperationAdd`):
+ The add operation triggering this upload.
+ lfs_batch_action (`dict`):
+ Upload instructions from the LFS batch endpoint for this object. See [`~utils.lfs.post_lfs_batch_info`] for
+ more details.
+ headers (`dict`, *optional*):
+ Headers to include in the request, including authentication and user agent headers.
+
+ Raises:
+ - `ValueError` if `lfs_batch_action` is improperly formatted
+ - `HTTPError` if the upload resulted in an error
+ """
+ # 0. If LFS file is already present, skip upload
+ _validate_batch_actions(lfs_batch_action)
+ actions = lfs_batch_action.get("actions")
+ if actions is None:
+ # The file was already uploaded
+ logger.debug(f"Content of file {operation.path_in_repo} is already present upstream - skipping upload")
+ return
+
+ # 1. Validate server response (check required keys in dict)
+ upload_action = lfs_batch_action["actions"]["upload"]
+ _validate_lfs_action(upload_action)
+ verify_action = lfs_batch_action["actions"].get("verify")
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+
+ # 2. Upload file (either single part or multi-part)
+ header = upload_action.get("header", {})
+ chunk_size = header.get("chunk_size")
+ upload_url = fix_hf_endpoint_in_url(upload_action["href"], endpoint=endpoint)
+ if chunk_size is not None:
+ try:
+ chunk_size = int(chunk_size)
+ except (ValueError, TypeError):
+ raise ValueError(
+ f"Malformed response from LFS batch endpoint: `chunk_size` should be an integer. Got '{chunk_size}'."
+ )
+ _upload_multi_part(operation=operation, header=header, chunk_size=chunk_size, upload_url=upload_url)
+ else:
+ _upload_single_part(operation=operation, upload_url=upload_url)
+
+ # 3. Verify upload went well
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+ verify_url = fix_hf_endpoint_in_url(verify_action["href"], endpoint)
+ verify_resp = get_session().post(
+ verify_url,
+ headers=build_hf_headers(token=token, headers=headers),
+ json={"oid": operation.upload_info.sha256.hex(), "size": operation.upload_info.size},
+ )
+ hf_raise_for_status(verify_resp)
+ logger.debug(f"{operation.path_in_repo}: Upload successful")
+
+
+def _validate_lfs_action(lfs_action: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (
+ isinstance(lfs_action.get("href"), str)
+ and (lfs_action.get("header") is None or isinstance(lfs_action.get("header"), dict))
+ ):
+ raise ValueError("lfs_action is improperly formatted")
+ return lfs_action
+
+
+def _validate_batch_actions(lfs_batch_actions: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (isinstance(lfs_batch_actions.get("oid"), str) and isinstance(lfs_batch_actions.get("size"), int)):
+ raise ValueError("lfs_batch_actions is improperly formatted")
+
+ upload_action = lfs_batch_actions.get("actions", {}).get("upload")
+ verify_action = lfs_batch_actions.get("actions", {}).get("verify")
+ if upload_action is not None:
+ _validate_lfs_action(upload_action)
+ if verify_action is not None:
+ _validate_lfs_action(verify_action)
+ return lfs_batch_actions
+
+
+def _validate_batch_error(lfs_batch_error: dict):
+ """validates response from the LFS batch endpoint"""
+ if not (isinstance(lfs_batch_error.get("oid"), str) and isinstance(lfs_batch_error.get("size"), int)):
+ raise ValueError("lfs_batch_error is improperly formatted")
+ error_info = lfs_batch_error.get("error")
+ if not (
+ isinstance(error_info, dict)
+ and isinstance(error_info.get("message"), str)
+ and isinstance(error_info.get("code"), int)
+ ):
+ raise ValueError("lfs_batch_error is improperly formatted")
+ return lfs_batch_error
+
+
+def _upload_single_part(operation: "CommitOperationAdd", upload_url: str) -> None:
+ """
+ Uploads `fileobj` as a single PUT HTTP request (basic LFS transfer protocol)
+
+ Args:
+ upload_url (`str`):
+ The URL to PUT the file to.
+ fileobj:
+ The file-like object holding the data to upload.
+
+ Returns: `requests.Response`
+
+ Raises: `requests.HTTPError` if the upload resulted in an error
+ """
+ with operation.as_file(with_tqdm=True) as fileobj:
+ # S3 might raise a transient 500 error -> let's retry if that happens
+ response = http_backoff("PUT", upload_url, data=fileobj, retry_on_status_codes=(500, 502, 503, 504))
+ hf_raise_for_status(response)
+
+
+def _upload_multi_part(operation: "CommitOperationAdd", header: Dict, chunk_size: int, upload_url: str) -> None:
+ """
+ Uploads file using HF multipart LFS transfer protocol.
+ """
+ # 1. Get upload URLs for each part
+ sorted_parts_urls = _get_sorted_parts_urls(header=header, upload_info=operation.upload_info, chunk_size=chunk_size)
+
+ # 2. Upload parts (either with hf_transfer or in pure Python)
+ use_hf_transfer = HF_HUB_ENABLE_HF_TRANSFER
+ if (
+ HF_HUB_ENABLE_HF_TRANSFER
+ and not isinstance(operation.path_or_fileobj, str)
+ and not isinstance(operation.path_or_fileobj, Path)
+ ):
+ warnings.warn(
+ "hf_transfer is enabled but does not support uploading from bytes or BinaryIO, falling back to regular"
+ " upload"
+ )
+ use_hf_transfer = False
+
+ response_headers = (
+ _upload_parts_hf_transfer(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
+ if use_hf_transfer
+ else _upload_parts_iteratively(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
+ )
+
+ # 3. Send completion request
+ completion_res = get_session().post(
+ upload_url,
+ json=_get_completion_payload(response_headers, operation.upload_info.sha256.hex()),
+ headers=LFS_HEADERS,
+ )
+ hf_raise_for_status(completion_res)
+
+
+def _get_sorted_parts_urls(header: Dict, upload_info: UploadInfo, chunk_size: int) -> List[str]:
+ sorted_part_upload_urls = [
+ upload_url
+ for _, upload_url in sorted(
+ [
+ (int(part_num, 10), upload_url)
+ for part_num, upload_url in header.items()
+ if part_num.isdigit() and len(part_num) > 0
+ ],
+ key=lambda t: t[0],
+ )
+ ]
+ num_parts = len(sorted_part_upload_urls)
+ if num_parts != ceil(upload_info.size / chunk_size):
+ raise ValueError("Invalid server response to upload large LFS file")
+ return sorted_part_upload_urls
+
+
+def _get_completion_payload(response_headers: List[Dict], oid: str) -> CompletionPayloadT:
+ parts: List[PayloadPartT] = []
+ for part_number, header in enumerate(response_headers):
+ etag = header.get("etag")
+ if etag is None or etag == "":
+ raise ValueError(f"Invalid etag (`{etag}`) returned for part {part_number + 1}")
+ parts.append(
+ {
+ "partNumber": part_number + 1,
+ "etag": etag,
+ }
+ )
+ return {"oid": oid, "parts": parts}
+
+
+def _upload_parts_iteratively(
+ operation: "CommitOperationAdd", sorted_parts_urls: List[str], chunk_size: int
+) -> List[Dict]:
+ headers = []
+ with operation.as_file(with_tqdm=True) as fileobj:
+ for part_idx, part_upload_url in enumerate(sorted_parts_urls):
+ with SliceFileObj(
+ fileobj,
+ seek_from=chunk_size * part_idx,
+ read_limit=chunk_size,
+ ) as fileobj_slice:
+ # S3 might raise a transient 500 error -> let's retry if that happens
+ part_upload_res = http_backoff(
+ "PUT", part_upload_url, data=fileobj_slice, retry_on_status_codes=(500, 502, 503, 504)
+ )
+ hf_raise_for_status(part_upload_res)
+ headers.append(part_upload_res.headers)
+ return headers # type: ignore
+
+
+def _upload_parts_hf_transfer(
+ operation: "CommitOperationAdd", sorted_parts_urls: List[str], chunk_size: int
+) -> List[Dict]:
+ # Upload file using an external Rust-based package. Upload is faster but support less features (no progress bars).
+ try:
+ from hf_transfer import multipart_upload
+ except ImportError:
+ raise ValueError(
+ "Fast uploading using 'hf_transfer' is enabled (HF_HUB_ENABLE_HF_TRANSFER=1) but 'hf_transfer' package is"
+ " not available in your environment. Try `pip install hf_transfer`."
+ )
+
+ supports_callback = "callback" in inspect.signature(multipart_upload).parameters
+ if not supports_callback:
+ warnings.warn(
+ "You are using an outdated version of `hf_transfer`. Consider upgrading to latest version to enable progress bars using `pip install -U hf_transfer`."
+ )
+
+ total = operation.upload_info.size
+ desc = operation.path_in_repo
+ if len(desc) > 40:
+ desc = f"(…){desc[-40:]}"
+
+ # set `disable=None` rather than `disable=False` by default to disable progress bar when no TTY attached
+ # see https://github.com/huggingface/huggingface_hub/pull/2000
+ disable = True if (logger.getEffectiveLevel() == logging.NOTSET) else None
+
+ with tqdm(unit="B", unit_scale=True, total=total, initial=0, desc=desc, disable=disable) as progress:
+ try:
+ output = multipart_upload(
+ file_path=operation.path_or_fileobj,
+ parts_urls=sorted_parts_urls,
+ chunk_size=chunk_size,
+ max_files=128,
+ parallel_failures=127, # could be removed
+ max_retries=5,
+ **({"callback": progress.update} if supports_callback else {}),
+ )
+ except Exception as e:
+ raise RuntimeError(
+ "An error occurred while uploading using `hf_transfer`. Consider disabling HF_HUB_ENABLE_HF_TRANSFER for"
+ " better error handling."
+ ) from e
+ if not supports_callback:
+ progress.update(total)
+ return output
+
+
+class SliceFileObj(AbstractContextManager):
+ """
+ Utility context manager to read a *slice* of a seekable file-like object as a seekable, file-like object.
+
+ This is NOT thread safe
+
+ Inspired by stackoverflow.com/a/29838711/593036
+
+ Credits to @julien-c
+
+ Args:
+ fileobj (`BinaryIO`):
+ A file-like object to slice. MUST implement `tell()` and `seek()` (and `read()` of course).
+ `fileobj` will be reset to its original position when exiting the context manager.
+ seek_from (`int`):
+ The start of the slice (offset from position 0 in bytes).
+ read_limit (`int`):
+ The maximum number of bytes to read from the slice.
+
+ Attributes:
+ previous_position (`int`):
+ The previous position
+
+ Examples:
+
+ Reading 200 bytes with an offset of 128 bytes from a file (ie bytes 128 to 327):
+ ```python
+ >>> with open("path/to/file", "rb") as file:
+ ... with SliceFileObj(file, seek_from=128, read_limit=200) as fslice:
+ ... fslice.read(...)
+ ```
+
+ Reading a file in chunks of 512 bytes
+ ```python
+ >>> import os
+ >>> chunk_size = 512
+ >>> file_size = os.getsize("path/to/file")
+ >>> with open("path/to/file", "rb") as file:
+ ... for chunk_idx in range(ceil(file_size / chunk_size)):
+ ... with SliceFileObj(file, seek_from=chunk_idx * chunk_size, read_limit=chunk_size) as fslice:
+ ... chunk = fslice.read(...)
+
+ ```
+ """
+
+ def __init__(self, fileobj: BinaryIO, seek_from: int, read_limit: int):
+ self.fileobj = fileobj
+ self.seek_from = seek_from
+ self.read_limit = read_limit
+
+ def __enter__(self):
+ self._previous_position = self.fileobj.tell()
+ end_of_stream = self.fileobj.seek(0, os.SEEK_END)
+ self._len = min(self.read_limit, end_of_stream - self.seek_from)
+ # ^^ The actual number of bytes that can be read from the slice
+ self.fileobj.seek(self.seek_from, io.SEEK_SET)
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.fileobj.seek(self._previous_position, io.SEEK_SET)
+
+ def read(self, n: int = -1):
+ pos = self.tell()
+ if pos >= self._len:
+ return b""
+ remaining_amount = self._len - pos
+ data = self.fileobj.read(remaining_amount if n < 0 else min(n, remaining_amount))
+ return data
+
+ def tell(self) -> int:
+ return self.fileobj.tell() - self.seek_from
+
+ def seek(self, offset: int, whence: int = os.SEEK_SET) -> int:
+ start = self.seek_from
+ end = start + self._len
+ if whence in (os.SEEK_SET, os.SEEK_END):
+ offset = start + offset if whence == os.SEEK_SET else end + offset
+ offset = max(start, min(offset, end))
+ whence = os.SEEK_SET
+ elif whence == os.SEEK_CUR:
+ cur_pos = self.fileobj.tell()
+ offset = max(start - cur_pos, min(offset, end - cur_pos))
+ else:
+ raise ValueError(f"whence value {whence} is not supported")
+ return self.fileobj.seek(offset, whence) - self.seek_from
+
+ def __iter__(self):
+ yield self.read(n=4 * 1024 * 1024)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/repocard.py b/.venv/lib/python3.10/site-packages/huggingface_hub/repocard.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ea7fc136cef1e9d78d3c4f44b1516a1ebe64158
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/repocard.py
@@ -0,0 +1,828 @@
+import os
+import re
+from pathlib import Path
+from typing import Any, Dict, Literal, Optional, Type, Union
+
+import requests
+import yaml
+
+from huggingface_hub.file_download import hf_hub_download
+from huggingface_hub.hf_api import upload_file
+from huggingface_hub.repocard_data import (
+ CardData,
+ DatasetCardData,
+ EvalResult,
+ ModelCardData,
+ SpaceCardData,
+ eval_results_to_model_index,
+ model_index_to_eval_results,
+)
+from huggingface_hub.utils import get_session, is_jinja_available, yaml_dump
+
+from .constants import REPOCARD_NAME
+from .utils import EntryNotFoundError, SoftTemporaryDirectory, logging, validate_hf_hub_args
+
+
+logger = logging.get_logger(__name__)
+
+
+TEMPLATE_MODELCARD_PATH = Path(__file__).parent / "templates" / "modelcard_template.md"
+TEMPLATE_DATASETCARD_PATH = Path(__file__).parent / "templates" / "datasetcard_template.md"
+
+# exact same regex as in the Hub server. Please keep in sync.
+# See https://github.com/huggingface/moon-landing/blob/main/server/lib/ViewMarkdown.ts#L18
+REGEX_YAML_BLOCK = re.compile(r"^(\s*---[\r\n]+)([\S\s]*?)([\r\n]+---(\r\n|\n|$))")
+
+
+class RepoCard:
+ card_data_class = CardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "model"
+
+ def __init__(self, content: str, ignore_metadata_errors: bool = False):
+ """Initialize a RepoCard from string content. The content should be a
+ Markdown file with a YAML block at the beginning and a Markdown body.
+
+ Args:
+ content (`str`): The content of the Markdown file.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> text = '''
+ ... ---
+ ... language: en
+ ... license: mit
+ ... ---
+ ...
+ ... # My repo
+ ... '''
+ >>> card = RepoCard(text)
+ >>> card.data.to_dict()
+ {'language': 'en', 'license': 'mit'}
+ >>> card.text
+ '\\n# My repo\\n'
+
+ ```
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ when the content of the repo card metadata is not a dictionary.
+
+
+ """
+
+ # Set the content of the RepoCard, as well as underlying .data and .text attributes.
+ # See the `content` property setter for more details.
+ self.ignore_metadata_errors = ignore_metadata_errors
+ self.content = content
+
+ @property
+ def content(self):
+ """The content of the RepoCard, including the YAML block and the Markdown body."""
+ line_break = _detect_line_ending(self._content) or "\n"
+ return f"---{line_break}{self.data.to_yaml(line_break=line_break)}{line_break}---{line_break}{self.text}"
+
+ @content.setter
+ def content(self, content: str):
+ """Set the content of the RepoCard."""
+ self._content = content
+
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ # Metadata found in the YAML block
+ yaml_block = match.group(2)
+ self.text = content[match.end() :]
+ data_dict = yaml.safe_load(yaml_block)
+
+ if data_dict is None:
+ data_dict = {}
+
+ # The YAML block's data should be a dictionary
+ if not isinstance(data_dict, dict):
+ raise ValueError("repo card metadata block should be a dict")
+ else:
+ # Model card without metadata... create empty metadata
+ logger.warning("Repo card metadata block was not found. Setting CardData to empty.")
+ data_dict = {}
+ self.text = content
+
+ self.data = self.card_data_class(**data_dict, ignore_metadata_errors=self.ignore_metadata_errors)
+
+ def __str__(self):
+ return self.content
+
+ def save(self, filepath: Union[Path, str]):
+ r"""Save a RepoCard to a file.
+
+ Args:
+ filepath (`Union[Path, str]`): Filepath to the markdown file to save.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> card = RepoCard("---\nlanguage: en\n---\n# This is a test repo card")
+ >>> card.save("/tmp/test.md")
+
+ ```
+ """
+ filepath = Path(filepath)
+ filepath.parent.mkdir(parents=True, exist_ok=True)
+ # Preserve newlines as in the existing file.
+ with open(filepath, mode="w", newline="", encoding="utf-8") as f:
+ f.write(str(self))
+
+ @classmethod
+ def load(
+ cls,
+ repo_id_or_path: Union[str, Path],
+ repo_type: Optional[str] = None,
+ token: Optional[str] = None,
+ ignore_metadata_errors: bool = False,
+ ):
+ """Initialize a RepoCard from a Hugging Face Hub repo's README.md or a local filepath.
+
+ Args:
+ repo_id_or_path (`Union[str, Path]`):
+ The repo ID associated with a Hugging Face Hub repo or a local filepath.
+ repo_type (`str`, *optional*):
+ The type of Hugging Face repo to push to. Defaults to None, which will use use "model". Other options
+ are "dataset" and "space". Not used when loading from a local filepath. If this is called from a child
+ class, the default value will be the child class's `repo_type`.
+ token (`str`, *optional*):
+ Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to the stored token.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+
+ Returns:
+ [`huggingface_hub.repocard.RepoCard`]: The RepoCard (or subclass) initialized from the repo's
+ README.md file or filepath.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard import RepoCard
+ >>> card = RepoCard.load("nateraw/food")
+ >>> assert card.data.tags == ["generated_from_trainer", "image-classification", "pytorch"]
+
+ ```
+ """
+
+ if Path(repo_id_or_path).exists():
+ card_path = Path(repo_id_or_path)
+ elif isinstance(repo_id_or_path, str):
+ card_path = Path(
+ hf_hub_download(
+ repo_id_or_path,
+ REPOCARD_NAME,
+ repo_type=repo_type or cls.repo_type,
+ token=token,
+ )
+ )
+ else:
+ raise ValueError(f"Cannot load RepoCard: path not found on disk ({repo_id_or_path}).")
+
+ # Preserve newlines in the existing file.
+ with card_path.open(mode="r", newline="", encoding="utf-8") as f:
+ return cls(f.read(), ignore_metadata_errors=ignore_metadata_errors)
+
+ def validate(self, repo_type: Optional[str] = None):
+ """Validates card against Hugging Face Hub's card validation logic.
+ Using this function requires access to the internet, so it is only called
+ internally by [`huggingface_hub.repocard.RepoCard.push_to_hub`].
+
+ Args:
+ repo_type (`str`, *optional*, defaults to "model"):
+ The type of Hugging Face repo to push to. Options are "model", "dataset", and "space".
+ If this function is called from a child class, the default will be the child class's `repo_type`.
+
+
+ Raises the following errors:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if the card fails validation checks.
+ - [`HTTPError`](https://requests.readthedocs.io/en/latest/api/#requests.HTTPError)
+ if the request to the Hub API fails for any other reason.
+
+
+ """
+
+ # If repo type is provided, otherwise, use the repo type of the card.
+ repo_type = repo_type or self.repo_type
+
+ body = {
+ "repoType": repo_type,
+ "content": str(self),
+ }
+ headers = {"Accept": "text/plain"}
+
+ try:
+ r = get_session().post("https://huggingface.co/api/validate-yaml", body, headers=headers)
+ r.raise_for_status()
+ except requests.exceptions.HTTPError as exc:
+ if r.status_code == 400:
+ raise ValueError(r.text)
+ else:
+ raise exc
+
+ def push_to_hub(
+ self,
+ repo_id: str,
+ token: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: Optional[bool] = None,
+ parent_commit: Optional[str] = None,
+ ):
+ """Push a RepoCard to a Hugging Face Hub repo.
+
+ Args:
+ repo_id (`str`):
+ The repo ID of the Hugging Face Hub repo to push to. Example: "nateraw/food".
+ token (`str`, *optional*):
+ Authentication token, obtained with `huggingface_hub.HfApi.login` method. Will default to
+ the stored token.
+ repo_type (`str`, *optional*, defaults to "model"):
+ The type of Hugging Face repo to push to. Options are "model", "dataset", and "space". If this
+ function is called by a child class, it will default to the child class's `repo_type`.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit.
+ commit_description (`str`, *optional*)
+ The description of the generated commit.
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the `"main"` branch.
+ create_pr (`bool`, *optional*):
+ Whether or not to create a Pull Request with this commit. Defaults to `False`.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ Returns:
+ `str`: URL of the commit which updated the card metadata.
+ """
+
+ # If repo type is provided, otherwise, use the repo type of the card.
+ repo_type = repo_type or self.repo_type
+
+ # Validate card before pushing to hub
+ self.validate(repo_type=repo_type)
+
+ with SoftTemporaryDirectory() as tmpdir:
+ tmp_path = Path(tmpdir) / REPOCARD_NAME
+ tmp_path.write_text(str(self))
+ url = upload_file(
+ path_or_fileobj=str(tmp_path),
+ path_in_repo=REPOCARD_NAME,
+ repo_id=repo_id,
+ token=token,
+ repo_type=repo_type,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ revision=revision,
+ parent_commit=parent_commit,
+ )
+ return url
+
+ @classmethod
+ def from_template(
+ cls,
+ card_data: CardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a RepoCard from a template. By default, it uses the default template.
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.CardData`):
+ A huggingface_hub.CardData instance containing the metadata you want to include in the YAML
+ header of the repo card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.repocard.RepoCard`]: A RepoCard instance with the specified card data and content from the
+ template.
+ """
+ if is_jinja_available():
+ import jinja2
+ else:
+ raise ImportError(
+ "Using RepoCard.from_template requires Jinja2 to be installed. Please"
+ " install it with `pip install Jinja2`."
+ )
+
+ kwargs = card_data.to_dict().copy()
+ kwargs.update(template_kwargs) # Template_kwargs have priority
+
+ if template_path is not None:
+ template_str = Path(template_path).read_text()
+ if template_str is None:
+ template_str = Path(cls.default_template_path).read_text()
+ template = jinja2.Template(template_str)
+ content = template.render(card_data=card_data.to_yaml(), **kwargs)
+ return cls(content)
+
+
+class ModelCard(RepoCard):
+ card_data_class = ModelCardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "model"
+
+ @classmethod
+ def from_template( # type: ignore # violates Liskov property but easier to use
+ cls,
+ card_data: ModelCardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a ModelCard from a template. By default, it uses the default template, which can be found here:
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/modelcard_template.md
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.ModelCardData`):
+ A huggingface_hub.ModelCardData instance containing the metadata you want to include in the YAML
+ header of the model card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.ModelCard`]: A ModelCard instance with the specified card data and content from the
+ template.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import ModelCard, ModelCardData, EvalResult
+
+ >>> # Using the Default Template
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... license='mit',
+ ... library_name='timm',
+ ... tags=['image-classification', 'resnet'],
+ ... datasets=['beans'],
+ ... metrics=['accuracy'],
+ ... )
+ >>> card = ModelCard.from_template(
+ ... card_data,
+ ... model_description='This model does x + y...'
+ ... )
+
+ >>> # Including Evaluation Results
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... tags=['image-classification', 'resnet'],
+ ... eval_results=[
+ ... EvalResult(
+ ... task_type='image-classification',
+ ... dataset_type='beans',
+ ... dataset_name='Beans',
+ ... metric_type='accuracy',
+ ... metric_value=0.9,
+ ... ),
+ ... ],
+ ... model_name='my-cool-model',
+ ... )
+ >>> card = ModelCard.from_template(card_data)
+
+ >>> # Using a Custom Template
+ >>> card_data = ModelCardData(
+ ... language='en',
+ ... tags=['image-classification', 'resnet']
+ ... )
+ >>> card = ModelCard.from_template(
+ ... card_data=card_data,
+ ... template_path='./src/huggingface_hub/templates/modelcard_template.md',
+ ... custom_template_var='custom value', # will be replaced in template if it exists
+ ... )
+
+ ```
+ """
+ return super().from_template(card_data, template_path, template_str, **template_kwargs)
+
+
+class DatasetCard(RepoCard):
+ card_data_class = DatasetCardData
+ default_template_path = TEMPLATE_DATASETCARD_PATH
+ repo_type = "dataset"
+
+ @classmethod
+ def from_template( # type: ignore # violates Liskov property but easier to use
+ cls,
+ card_data: DatasetCardData,
+ template_path: Optional[str] = None,
+ template_str: Optional[str] = None,
+ **template_kwargs,
+ ):
+ """Initialize a DatasetCard from a template. By default, it uses the default template, which can be found here:
+ https://github.com/huggingface/huggingface_hub/blob/main/src/huggingface_hub/templates/datasetcard_template.md
+
+ Templates are Jinja2 templates that can be customized by passing keyword arguments.
+
+ Args:
+ card_data (`huggingface_hub.DatasetCardData`):
+ A huggingface_hub.DatasetCardData instance containing the metadata you want to include in the YAML
+ header of the dataset card on the Hugging Face Hub.
+ template_path (`str`, *optional*):
+ A path to a markdown file with optional Jinja template variables that can be filled
+ in with `template_kwargs`. Defaults to the default template.
+
+ Returns:
+ [`huggingface_hub.DatasetCard`]: A DatasetCard instance with the specified card data and content from the
+ template.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import DatasetCard, DatasetCardData
+
+ >>> # Using the Default Template
+ >>> card_data = DatasetCardData(
+ ... language='en',
+ ... license='mit',
+ ... annotations_creators='crowdsourced',
+ ... task_categories=['text-classification'],
+ ... task_ids=['sentiment-classification', 'text-scoring'],
+ ... multilinguality='monolingual',
+ ... pretty_name='My Text Classification Dataset',
+ ... )
+ >>> card = DatasetCard.from_template(
+ ... card_data,
+ ... pretty_name=card_data.pretty_name,
+ ... )
+
+ >>> # Using a Custom Template
+ >>> card_data = DatasetCardData(
+ ... language='en',
+ ... license='mit',
+ ... )
+ >>> card = DatasetCard.from_template(
+ ... card_data=card_data,
+ ... template_path='./src/huggingface_hub/templates/datasetcard_template.md',
+ ... custom_template_var='custom value', # will be replaced in template if it exists
+ ... )
+
+ ```
+ """
+ return super().from_template(card_data, template_path, template_str, **template_kwargs)
+
+
+class SpaceCard(RepoCard):
+ card_data_class = SpaceCardData
+ default_template_path = TEMPLATE_MODELCARD_PATH
+ repo_type = "space"
+
+
+def _detect_line_ending(content: str) -> Literal["\r", "\n", "\r\n", None]: # noqa: F722
+ """Detect the line ending of a string. Used by RepoCard to avoid making huge diff on newlines.
+
+ Uses same implementation as in Hub server, keep it in sync.
+
+ Returns:
+ str: The detected line ending of the string.
+ """
+ cr = content.count("\r")
+ lf = content.count("\n")
+ crlf = content.count("\r\n")
+ if cr + lf == 0:
+ return None
+ if crlf == cr and crlf == lf:
+ return "\r\n"
+ if cr > lf:
+ return "\r"
+ else:
+ return "\n"
+
+
+def metadata_load(local_path: Union[str, Path]) -> Optional[Dict]:
+ content = Path(local_path).read_text()
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ yaml_block = match.group(2)
+ data = yaml.safe_load(yaml_block)
+ if data is None or isinstance(data, dict):
+ return data
+ raise ValueError("repo card metadata block should be a dict")
+ else:
+ return None
+
+
+def metadata_save(local_path: Union[str, Path], data: Dict) -> None:
+ """
+ Save the metadata dict in the upper YAML part Trying to preserve newlines as
+ in the existing file. Docs about open() with newline="" parameter:
+ https://docs.python.org/3/library/functions.html?highlight=open#open Does
+ not work with "^M" linebreaks, which are replaced by \n
+ """
+ line_break = "\n"
+ content = ""
+ # try to detect existing newline character
+ if os.path.exists(local_path):
+ with open(local_path, "r", newline="", encoding="utf8") as readme:
+ content = readme.read()
+ if isinstance(readme.newlines, tuple):
+ line_break = readme.newlines[0]
+ elif isinstance(readme.newlines, str):
+ line_break = readme.newlines
+
+ # creates a new file if it not
+ with open(local_path, "w", newline="", encoding="utf8") as readme:
+ data_yaml = yaml_dump(data, sort_keys=False, line_break=line_break)
+ # sort_keys: keep dict order
+ match = REGEX_YAML_BLOCK.search(content)
+ if match:
+ output = content[: match.start()] + f"---{line_break}{data_yaml}---{line_break}" + content[match.end() :]
+ else:
+ output = f"---{line_break}{data_yaml}---{line_break}{content}"
+
+ readme.write(output)
+ readme.close()
+
+
+def metadata_eval_result(
+ *,
+ model_pretty_name: str,
+ task_pretty_name: str,
+ task_id: str,
+ metrics_pretty_name: str,
+ metrics_id: str,
+ metrics_value: Any,
+ dataset_pretty_name: str,
+ dataset_id: str,
+ metrics_config: Optional[str] = None,
+ metrics_verified: bool = False,
+ dataset_config: Optional[str] = None,
+ dataset_split: Optional[str] = None,
+ dataset_revision: Optional[str] = None,
+ metrics_verification_token: Optional[str] = None,
+) -> Dict:
+ """
+ Creates a metadata dict with the result from a model evaluated on a dataset.
+
+ Args:
+ model_pretty_name (`str`):
+ The name of the model in natural language.
+ task_pretty_name (`str`):
+ The name of a task in natural language.
+ task_id (`str`):
+ Example: automatic-speech-recognition. A task id.
+ metrics_pretty_name (`str`):
+ A name for the metric in natural language. Example: Test WER.
+ metrics_id (`str`):
+ Example: wer. A metric id from https://hf.co/metrics.
+ metrics_value (`Any`):
+ The value from the metric. Example: 20.0 or "20.0 ± 1.2".
+ dataset_pretty_name (`str`):
+ The name of the dataset in natural language.
+ dataset_id (`str`):
+ Example: common_voice. A dataset id from https://hf.co/datasets.
+ metrics_config (`str`, *optional*):
+ The name of the metric configuration used in `load_metric()`.
+ Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ metrics_verified (`bool`, *optional*, defaults to `False`):
+ Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ dataset_config (`str`, *optional*):
+ Example: fr. The name of the dataset configuration used in `load_dataset()`.
+ dataset_split (`str`, *optional*):
+ Example: test. The name of the dataset split used in `load_dataset()`.
+ dataset_revision (`str`, *optional*):
+ Example: 5503434ddd753f426f4b38109466949a1217c2bb. The name of the dataset dataset revision
+ used in `load_dataset()`.
+ metrics_verification_token (`bool`, *optional*):
+ A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+
+ Returns:
+ `dict`: a metadata dict with the result from a model evaluated on a dataset.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import metadata_eval_result
+ >>> results = metadata_eval_result(
+ ... model_pretty_name="RoBERTa fine-tuned on ReactionGIF",
+ ... task_pretty_name="Text Classification",
+ ... task_id="text-classification",
+ ... metrics_pretty_name="Accuracy",
+ ... metrics_id="accuracy",
+ ... metrics_value=0.2662102282047272,
+ ... dataset_pretty_name="ReactionJPEG",
+ ... dataset_id="julien-c/reactionjpeg",
+ ... dataset_config="default",
+ ... dataset_split="test",
+ ... )
+ >>> results == {
+ ... 'model-index': [
+ ... {
+ ... 'name': 'RoBERTa fine-tuned on ReactionGIF',
+ ... 'results': [
+ ... {
+ ... 'task': {
+ ... 'type': 'text-classification',
+ ... 'name': 'Text Classification'
+ ... },
+ ... 'dataset': {
+ ... 'name': 'ReactionJPEG',
+ ... 'type': 'julien-c/reactionjpeg',
+ ... 'config': 'default',
+ ... 'split': 'test'
+ ... },
+ ... 'metrics': [
+ ... {
+ ... 'type': 'accuracy',
+ ... 'value': 0.2662102282047272,
+ ... 'name': 'Accuracy',
+ ... 'verified': False
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ True
+
+ ```
+ """
+
+ return {
+ "model-index": eval_results_to_model_index(
+ model_name=model_pretty_name,
+ eval_results=[
+ EvalResult(
+ task_name=task_pretty_name,
+ task_type=task_id,
+ metric_name=metrics_pretty_name,
+ metric_type=metrics_id,
+ metric_value=metrics_value,
+ dataset_name=dataset_pretty_name,
+ dataset_type=dataset_id,
+ metric_config=metrics_config,
+ verified=metrics_verified,
+ verify_token=metrics_verification_token,
+ dataset_config=dataset_config,
+ dataset_split=dataset_split,
+ dataset_revision=dataset_revision,
+ )
+ ],
+ )
+ }
+
+
+@validate_hf_hub_args
+def metadata_update(
+ repo_id: str,
+ metadata: Dict,
+ *,
+ repo_type: Optional[str] = None,
+ overwrite: bool = False,
+ token: Optional[str] = None,
+ commit_message: Optional[str] = None,
+ commit_description: Optional[str] = None,
+ revision: Optional[str] = None,
+ create_pr: bool = False,
+ parent_commit: Optional[str] = None,
+) -> str:
+ """
+ Updates the metadata in the README.md of a repository on the Hugging Face Hub.
+ If the README.md file doesn't exist yet, a new one is created with metadata and an
+ the default ModelCard or DatasetCard template. For `space` repo, an error is thrown
+ as a Space cannot exist without a `README.md` file.
+
+ Args:
+ repo_id (`str`):
+ The name of the repository.
+ metadata (`dict`):
+ A dictionary containing the metadata to be updated.
+ repo_type (`str`, *optional*):
+ Set to `"dataset"` or `"space"` if updating to a dataset or space,
+ `None` or `"model"` if updating to a model. Default is `None`.
+ overwrite (`bool`, *optional*, defaults to `False`):
+ If set to `True` an existing field can be overwritten, otherwise
+ attempting to overwrite an existing field will cause an error.
+ token (`str`, *optional*):
+ The Hugging Face authentication token.
+ commit_message (`str`, *optional*):
+ The summary / title / first line of the generated commit. Defaults to
+ `f"Update metadata with huggingface_hub"`
+ commit_description (`str` *optional*)
+ The description of the generated commit
+ revision (`str`, *optional*):
+ The git revision to commit from. Defaults to the head of the
+ `"main"` branch.
+ create_pr (`boolean`, *optional*):
+ Whether or not to create a Pull Request from `revision` with that commit.
+ Defaults to `False`.
+ parent_commit (`str`, *optional*):
+ The OID / SHA of the parent commit, as a hexadecimal string. Shorthands (7 first characters) are also supported.
+ If specified and `create_pr` is `False`, the commit will fail if `revision` does not point to `parent_commit`.
+ If specified and `create_pr` is `True`, the pull request will be created from `parent_commit`.
+ Specifying `parent_commit` ensures the repo has not changed before committing the changes, and can be
+ especially useful if the repo is updated / committed to concurrently.
+ Returns:
+ `str`: URL of the commit which updated the card metadata.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import metadata_update
+ >>> metadata = {'model-index': [{'name': 'RoBERTa fine-tuned on ReactionGIF',
+ ... 'results': [{'dataset': {'name': 'ReactionGIF',
+ ... 'type': 'julien-c/reactiongif'},
+ ... 'metrics': [{'name': 'Recall',
+ ... 'type': 'recall',
+ ... 'value': 0.7762102282047272}],
+ ... 'task': {'name': 'Text Classification',
+ ... 'type': 'text-classification'}}]}]}
+ >>> url = metadata_update("hf-internal-testing/reactiongif-roberta-card", metadata)
+
+ ```
+ """
+ commit_message = commit_message if commit_message is not None else "Update metadata with huggingface_hub"
+
+ # Card class given repo_type
+ card_class: Type[RepoCard]
+ if repo_type is None or repo_type == "model":
+ card_class = ModelCard
+ elif repo_type == "dataset":
+ card_class = DatasetCard
+ elif repo_type == "space":
+ card_class = RepoCard
+ else:
+ raise ValueError(f"Unknown repo_type: {repo_type}")
+
+ # Either load repo_card from the Hub or create an empty one.
+ # NOTE: Will not create the repo if it doesn't exist.
+ try:
+ card = card_class.load(repo_id, token=token, repo_type=repo_type)
+ except EntryNotFoundError:
+ if repo_type == "space":
+ raise ValueError("Cannot update metadata on a Space that doesn't contain a `README.md` file.")
+
+ # Initialize a ModelCard or DatasetCard from default template and no data.
+ card = card_class.from_template(CardData())
+
+ for key, value in metadata.items():
+ if key == "model-index":
+ # if the new metadata doesn't include a name, either use existing one or repo name
+ if "name" not in value[0]:
+ value[0]["name"] = getattr(card, "model_name", repo_id)
+ model_name, new_results = model_index_to_eval_results(value)
+ if card.data.eval_results is None:
+ card.data.eval_results = new_results
+ card.data.model_name = model_name
+ else:
+ existing_results = card.data.eval_results
+
+ # Iterate over new results
+ # Iterate over existing results
+ # If both results describe the same metric but value is different:
+ # If overwrite=True: overwrite the metric value
+ # Else: raise ValueError
+ # Else: append new result to existing ones.
+ for new_result in new_results:
+ result_found = False
+ for existing_result in existing_results:
+ if new_result.is_equal_except_value(existing_result):
+ if new_result != existing_result and not overwrite:
+ raise ValueError(
+ "You passed a new value for the existing metric"
+ f" 'name: {new_result.metric_name}, type: "
+ f"{new_result.metric_type}'. Set `overwrite=True`"
+ " to overwrite existing metrics."
+ )
+ result_found = True
+ existing_result.metric_value = new_result.metric_value
+ if existing_result.verified is True:
+ existing_result.verify_token = new_result.verify_token
+ if not result_found:
+ card.data.eval_results.append(new_result)
+ else:
+ # Any metadata that is not a result metric
+ if card.data.get(key) is not None and not overwrite and card.data.get(key) != value:
+ raise ValueError(
+ f"You passed a new value for the existing meta data field '{key}'."
+ " Set `overwrite=True` to overwrite existing metadata."
+ )
+ else:
+ card.data[key] = value
+
+ return card.push_to_hub(
+ repo_id,
+ token=token,
+ repo_type=repo_type,
+ commit_message=commit_message,
+ commit_description=commit_description,
+ create_pr=create_pr,
+ revision=revision,
+ parent_commit=parent_commit,
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/repocard_data.py b/.venv/lib/python3.10/site-packages/huggingface_hub/repocard_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..c85c450cdbad00f1e2850fab431b3b22862a8539
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/repocard_data.py
@@ -0,0 +1,729 @@
+import copy
+from collections import defaultdict
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+from huggingface_hub.utils import logging, yaml_dump
+
+
+logger = logging.get_logger(__name__)
+
+
+@dataclass
+class EvalResult:
+ """
+ Flattened representation of individual evaluation results found in model-index of Model Cards.
+
+ For more information on the model-index spec, see https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1.
+
+ Args:
+ task_type (`str`):
+ The task identifier. Example: "image-classification".
+ dataset_type (`str`):
+ The dataset identifier. Example: "common_voice". Use dataset id from https://hf.co/datasets.
+ dataset_name (`str`):
+ A pretty name for the dataset. Example: "Common Voice (French)".
+ metric_type (`str`):
+ The metric identifier. Example: "wer". Use metric id from https://hf.co/metrics.
+ metric_value (`Any`):
+ The metric value. Example: 0.9 or "20.0 ± 1.2".
+ task_name (`str`, *optional*):
+ A pretty name for the task. Example: "Speech Recognition".
+ dataset_config (`str`, *optional*):
+ The name of the dataset configuration used in `load_dataset()`.
+ Example: fr in `load_dataset("common_voice", "fr")`. See the `datasets` docs for more info:
+ https://hf.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
+ dataset_split (`str`, *optional*):
+ The split used in `load_dataset()`. Example: "test".
+ dataset_revision (`str`, *optional*):
+ The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
+ Example: 5503434ddd753f426f4b38109466949a1217c2bb
+ dataset_args (`Dict[str, Any]`, *optional*):
+ The arguments passed during `Metric.compute()`. Example for `bleu`: `{"max_order": 4}`
+ metric_name (`str`, *optional*):
+ A pretty name for the metric. Example: "Test WER".
+ metric_config (`str`, *optional*):
+ The name of the metric configuration used in `load_metric()`.
+ Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
+ metric_args (`Dict[str, Any]`, *optional*):
+ The arguments passed during `Metric.compute()`. Example for `bleu`: max_order: 4
+ verified (`bool`, *optional*):
+ Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ verify_token (`str`, *optional*):
+ A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+ source_name (`str`, *optional*):
+ The name of the source of the evaluation result. Example: "Open LLM Leaderboard".
+ source_url (`str`, *optional*):
+ The URL of the source of the evaluation result. Example: "https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard".
+ """
+
+ # Required
+
+ # The task identifier
+ # Example: automatic-speech-recognition
+ task_type: str
+
+ # The dataset identifier
+ # Example: common_voice. Use dataset id from https://hf.co/datasets
+ dataset_type: str
+
+ # A pretty name for the dataset.
+ # Example: Common Voice (French)
+ dataset_name: str
+
+ # The metric identifier
+ # Example: wer. Use metric id from https://hf.co/metrics
+ metric_type: str
+
+ # Value of the metric.
+ # Example: 20.0 or "20.0 ± 1.2"
+ metric_value: Any
+
+ # Optional
+
+ # A pretty name for the task.
+ # Example: Speech Recognition
+ task_name: Optional[str] = None
+
+ # The name of the dataset configuration used in `load_dataset()`.
+ # Example: fr in `load_dataset("common_voice", "fr")`.
+ # See the `datasets` docs for more info:
+ # https://huggingface.co/docs/datasets/package_reference/loading_methods#datasets.load_dataset.name
+ dataset_config: Optional[str] = None
+
+ # The split used in `load_dataset()`.
+ # Example: test
+ dataset_split: Optional[str] = None
+
+ # The revision (AKA Git Sha) of the dataset used in `load_dataset()`.
+ # Example: 5503434ddd753f426f4b38109466949a1217c2bb
+ dataset_revision: Optional[str] = None
+
+ # The arguments passed during `Metric.compute()`.
+ # Example for `bleu`: max_order: 4
+ dataset_args: Optional[Dict[str, Any]] = None
+
+ # A pretty name for the metric.
+ # Example: Test WER
+ metric_name: Optional[str] = None
+
+ # The name of the metric configuration used in `load_metric()`.
+ # Example: bleurt-large-512 in `load_metric("bleurt", "bleurt-large-512")`.
+ # See the `datasets` docs for more info: https://huggingface.co/docs/datasets/v2.1.0/en/loading#load-configurations
+ metric_config: Optional[str] = None
+
+ # The arguments passed during `Metric.compute()`.
+ # Example for `bleu`: max_order: 4
+ metric_args: Optional[Dict[str, Any]] = None
+
+ # Indicates whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not. Automatically computed by Hugging Face, do not set.
+ verified: Optional[bool] = None
+
+ # A JSON Web Token that is used to verify whether the metrics originate from Hugging Face's [evaluation service](https://huggingface.co/spaces/autoevaluate/model-evaluator) or not.
+ verify_token: Optional[str] = None
+
+ # The name of the source of the evaluation result.
+ # Example: Open LLM Leaderboard
+ source_name: Optional[str] = None
+
+ # The URL of the source of the evaluation result.
+ # Example: https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard
+ source_url: Optional[str] = None
+
+ @property
+ def unique_identifier(self) -> tuple:
+ """Returns a tuple that uniquely identifies this evaluation."""
+ return (
+ self.task_type,
+ self.dataset_type,
+ self.dataset_config,
+ self.dataset_split,
+ self.dataset_revision,
+ )
+
+ def is_equal_except_value(self, other: "EvalResult") -> bool:
+ """
+ Return True if `self` and `other` describe exactly the same metric but with a
+ different value.
+ """
+ for key, _ in self.__dict__.items():
+ if key == "metric_value":
+ continue
+ # For metrics computed by Hugging Face's evaluation service, `verify_token` is derived from `metric_value`,
+ # so we exclude it here in the comparison.
+ if key != "verify_token" and getattr(self, key) != getattr(other, key):
+ return False
+ return True
+
+ def __post_init__(self) -> None:
+ if self.source_name is not None and self.source_url is None:
+ raise ValueError("If `source_name` is provided, `source_url` must also be provided.")
+
+
+@dataclass
+class CardData:
+ """Structure containing metadata from a RepoCard.
+
+ [`CardData`] is the parent class of [`ModelCardData`] and [`DatasetCardData`].
+
+ Metadata can be exported as a dictionary or YAML. Export can be customized to alter the representation of the data
+ (example: flatten evaluation results). `CardData` behaves as a dictionary (can get, pop, set values) but do not
+ inherit from `dict` to allow this export step.
+ """
+
+ def __init__(self, ignore_metadata_errors: bool = False, **kwargs):
+ self.__dict__.update(kwargs)
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Converts CardData to a dict.
+
+ Returns:
+ `dict`: CardData represented as a dictionary ready to be dumped to a YAML
+ block for inclusion in a README.md file.
+ """
+
+ data_dict = copy.deepcopy(self.__dict__)
+ self._to_dict(data_dict)
+ return _remove_none(data_dict)
+
+ def _to_dict(self, data_dict):
+ """Use this method in child classes to alter the dict representation of the data. Alter the dict in-place.
+
+ Args:
+ data_dict (`dict`): The raw dict representation of the card data.
+ """
+ pass
+
+ def to_yaml(self, line_break=None) -> str:
+ """Dumps CardData to a YAML block for inclusion in a README.md file.
+
+ Args:
+ line_break (str, *optional*):
+ The line break to use when dumping to yaml.
+
+ Returns:
+ `str`: CardData represented as a YAML block.
+ """
+ return yaml_dump(self.to_dict(), sort_keys=False, line_break=line_break).strip()
+
+ def __repr__(self):
+ return repr(self.__dict__)
+
+ def __str__(self):
+ return self.to_yaml()
+
+ def get(self, key: str, default: Any = None) -> Any:
+ """Get value for a given metadata key."""
+ return self.__dict__.get(key, default)
+
+ def pop(self, key: str, default: Any = None) -> Any:
+ """Pop value for a given metadata key."""
+ return self.__dict__.pop(key, default)
+
+ def __getitem__(self, key: str) -> Any:
+ """Get value for a given metadata key."""
+ return self.__dict__[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ """Set value for a given metadata key."""
+ self.__dict__[key] = value
+
+ def __contains__(self, key: str) -> bool:
+ """Check if a given metadata key is set."""
+ return key in self.__dict__
+
+ def __len__(self) -> int:
+ """Return the number of metadata keys set."""
+ return len(self.__dict__)
+
+
+class ModelCardData(CardData):
+ """Model Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ Args:
+ language (`Union[str, List[str]]`, *optional*):
+ Language of model's training data or metadata. It must be an ISO 639-1, 639-2 or
+ 639-3 code (two/three letters), or a special value like "code", "multilingual". Defaults to `None`.
+ license (`str`, *optional*):
+ License of this model. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses. Defaults to None.
+ library_name (`str`, *optional*):
+ Name of library used by this model. Example: keras or any library from
+ https://github.com/huggingface/huggingface.js/blob/main/packages/tasks/src/model-libraries.ts.
+ Defaults to None.
+ tags (`List[str]`, *optional*):
+ List of tags to add to your model that can be used when filtering on the Hugging
+ Face Hub. Defaults to None.
+ base_model (`str` or `List[str]`, *optional*):
+ The identifier of the base model from which the model derives. This is applicable for example if your model is a
+ fine-tune or adapter of an existing model. The value must be the ID of a model on the Hub (or a list of IDs
+ if your model derives from multiple models). Defaults to None.
+ datasets (`List[str]`, *optional*):
+ List of datasets that were used to train this model. Should be a dataset ID
+ found on https://hf.co/datasets. Defaults to None.
+ metrics (`List[str]`, *optional*):
+ List of metrics used to evaluate this model. Should be a metric name that can be found
+ at https://hf.co/metrics. Example: 'accuracy'. Defaults to None.
+ eval_results (`Union[List[EvalResult], EvalResult]`, *optional*):
+ List of `huggingface_hub.EvalResult` that define evaluation results of the model. If provided,
+ `model_name` is used to as a name on PapersWithCode's leaderboards. Defaults to `None`.
+ model_name (`str`, *optional*):
+ A name for this model. It is used along with
+ `eval_results` to construct the `model-index` within the card's metadata. The name
+ you supply here is what will be used on PapersWithCode's leaderboards. If None is provided
+ then the repo name is used as a default. Defaults to None.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+ kwargs (`dict`, *optional*):
+ Additional metadata that will be added to the model card. Defaults to None.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import ModelCardData
+ >>> card_data = ModelCardData(
+ ... language="en",
+ ... license="mit",
+ ... library_name="timm",
+ ... tags=['image-classification', 'resnet'],
+ ... )
+ >>> card_data.to_dict()
+ {'language': 'en', 'license': 'mit', 'library_name': 'timm', 'tags': ['image-classification', 'resnet']}
+
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ language: Optional[Union[str, List[str]]] = None,
+ license: Optional[str] = None,
+ library_name: Optional[str] = None,
+ tags: Optional[List[str]] = None,
+ base_model: Optional[Union[str, List[str]]] = None,
+ datasets: Optional[List[str]] = None,
+ metrics: Optional[List[str]] = None,
+ eval_results: Optional[List[EvalResult]] = None,
+ model_name: Optional[str] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.language = language
+ self.license = license
+ self.library_name = library_name
+ self.tags = _to_unique_list(tags)
+ self.base_model = base_model
+ self.datasets = datasets
+ self.metrics = metrics
+ self.eval_results = eval_results
+ self.model_name = model_name
+
+ model_index = kwargs.pop("model-index", None)
+ if model_index:
+ try:
+ model_name, eval_results = model_index_to_eval_results(model_index)
+ self.model_name = model_name
+ self.eval_results = eval_results
+ except (KeyError, TypeError) as error:
+ if ignore_metadata_errors:
+ logger.warning("Invalid model-index. Not loading eval results into CardData.")
+ else:
+ raise ValueError(
+ f"Invalid `model_index` in metadata cannot be parsed: {error.__class__} {error}. Pass"
+ " `ignore_metadata_errors=True` to ignore this error while loading a Model Card. Warning:"
+ " some information will be lost. Use it at your own risk."
+ )
+
+ super().__init__(**kwargs)
+
+ if self.eval_results:
+ if type(self.eval_results) == EvalResult:
+ self.eval_results = [self.eval_results]
+ if self.model_name is None:
+ raise ValueError("Passing `eval_results` requires `model_name` to be set.")
+
+ def _to_dict(self, data_dict):
+ """Format the internal data dict. In this case, we convert eval results to a valid model index"""
+ if self.eval_results is not None:
+ data_dict["model-index"] = eval_results_to_model_index(self.model_name, self.eval_results)
+ del data_dict["eval_results"], data_dict["model_name"]
+
+
+class DatasetCardData(CardData):
+ """Dataset Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ Args:
+ language (`List[str]`, *optional*):
+ Language of dataset's data or metadata. It must be an ISO 639-1, 639-2 or
+ 639-3 code (two/three letters), or a special value like "code", "multilingual".
+ license (`Union[str, List[str]]`, *optional*):
+ License(s) of this dataset. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses.
+ annotations_creators (`Union[str, List[str]]`, *optional*):
+ How the annotations for the dataset were created.
+ Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'no-annotation', 'other'.
+ language_creators (`Union[str, List[str]]`, *optional*):
+ How the text-based data in the dataset was created.
+ Options are: 'found', 'crowdsourced', 'expert-generated', 'machine-generated', 'other'
+ multilinguality (`Union[str, List[str]]`, *optional*):
+ Whether the dataset is multilingual.
+ Options are: 'monolingual', 'multilingual', 'translation', 'other'.
+ size_categories (`Union[str, List[str]]`, *optional*):
+ The number of examples in the dataset. Options are: 'n<1K', '1K1T', and 'other'.
+ source_datasets (`List[str]]`, *optional*):
+ Indicates whether the dataset is an original dataset or extended from another existing dataset.
+ Options are: 'original' and 'extended'.
+ task_categories (`Union[str, List[str]]`, *optional*):
+ What categories of task does the dataset support?
+ task_ids (`Union[str, List[str]]`, *optional*):
+ What specific tasks does the dataset support?
+ paperswithcode_id (`str`, *optional*):
+ ID of the dataset on PapersWithCode.
+ pretty_name (`str`, *optional*):
+ A more human-readable name for the dataset. (ex. "Cats vs. Dogs")
+ train_eval_index (`Dict`, *optional*):
+ A dictionary that describes the necessary spec for doing evaluation on the Hub.
+ If not provided, it will be gathered from the 'train-eval-index' key of the kwargs.
+ config_names (`Union[str, List[str]]`, *optional*):
+ A list of the available dataset configs for the dataset.
+ """
+
+ def __init__(
+ self,
+ *,
+ language: Optional[Union[str, List[str]]] = None,
+ license: Optional[Union[str, List[str]]] = None,
+ annotations_creators: Optional[Union[str, List[str]]] = None,
+ language_creators: Optional[Union[str, List[str]]] = None,
+ multilinguality: Optional[Union[str, List[str]]] = None,
+ size_categories: Optional[Union[str, List[str]]] = None,
+ source_datasets: Optional[List[str]] = None,
+ task_categories: Optional[Union[str, List[str]]] = None,
+ task_ids: Optional[Union[str, List[str]]] = None,
+ paperswithcode_id: Optional[str] = None,
+ pretty_name: Optional[str] = None,
+ train_eval_index: Optional[Dict] = None,
+ config_names: Optional[Union[str, List[str]]] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.annotations_creators = annotations_creators
+ self.language_creators = language_creators
+ self.language = language
+ self.license = license
+ self.multilinguality = multilinguality
+ self.size_categories = size_categories
+ self.source_datasets = source_datasets
+ self.task_categories = task_categories
+ self.task_ids = task_ids
+ self.paperswithcode_id = paperswithcode_id
+ self.pretty_name = pretty_name
+ self.config_names = config_names
+
+ # TODO - maybe handle this similarly to EvalResult?
+ self.train_eval_index = train_eval_index or kwargs.pop("train-eval-index", None)
+ super().__init__(**kwargs)
+
+ def _to_dict(self, data_dict):
+ data_dict["train-eval-index"] = data_dict.pop("train_eval_index")
+
+
+class SpaceCardData(CardData):
+ """Space Card Metadata that is used by Hugging Face Hub when included at the top of your README.md
+
+ To get an exhaustive reference of Spaces configuration, please visit https://huggingface.co/docs/hub/spaces-config-reference#spaces-configuration-reference.
+
+ Args:
+ title (`str`, *optional*)
+ Title of the Space.
+ sdk (`str`, *optional*)
+ SDK of the Space (one of `gradio`, `streamlit`, `docker`, or `static`).
+ sdk_version (`str`, *optional*)
+ Version of the used SDK (if Gradio/Streamlit sdk).
+ python_version (`str`, *optional*)
+ Python version used in the Space (if Gradio/Streamlit sdk).
+ app_file (`str`, *optional*)
+ Path to your main application file (which contains either gradio or streamlit Python code, or static html code).
+ Path is relative to the root of the repository.
+ app_port (`str`, *optional*)
+ Port on which your application is running. Used only if sdk is `docker`.
+ license (`str`, *optional*)
+ License of this model. Example: apache-2.0 or any license from
+ https://huggingface.co/docs/hub/repositories-licenses.
+ duplicated_from (`str`, *optional*)
+ ID of the original Space if this is a duplicated Space.
+ models (List[`str`], *optional*)
+ List of models related to this Space. Should be a dataset ID found on https://hf.co/models.
+ datasets (`List[str]`, *optional*)
+ List of datasets related to this Space. Should be a dataset ID found on https://hf.co/datasets.
+ tags (`List[str]`, *optional*)
+ List of tags to add to your Space that can be used when filtering on the Hub.
+ ignore_metadata_errors (`str`):
+ If True, errors while parsing the metadata section will be ignored. Some information might be lost during
+ the process. Use it at your own risk.
+ kwargs (`dict`, *optional*):
+ Additional metadata that will be added to the space card.
+
+ Example:
+ ```python
+ >>> from huggingface_hub import SpaceCardData
+ >>> card_data = SpaceCardData(
+ ... title="Dreambooth Training",
+ ... license="mit",
+ ... sdk="gradio",
+ ... duplicated_from="multimodalart/dreambooth-training"
+ ... )
+ >>> card_data.to_dict()
+ {'title': 'Dreambooth Training', 'sdk': 'gradio', 'license': 'mit', 'duplicated_from': 'multimodalart/dreambooth-training'}
+ ```
+ """
+
+ def __init__(
+ self,
+ *,
+ title: Optional[str] = None,
+ sdk: Optional[str] = None,
+ sdk_version: Optional[str] = None,
+ python_version: Optional[str] = None,
+ app_file: Optional[str] = None,
+ app_port: Optional[int] = None,
+ license: Optional[str] = None,
+ duplicated_from: Optional[str] = None,
+ models: Optional[List[str]] = None,
+ datasets: Optional[List[str]] = None,
+ tags: Optional[List[str]] = None,
+ ignore_metadata_errors: bool = False,
+ **kwargs,
+ ):
+ self.title = title
+ self.sdk = sdk
+ self.sdk_version = sdk_version
+ self.python_version = python_version
+ self.app_file = app_file
+ self.app_port = app_port
+ self.license = license
+ self.duplicated_from = duplicated_from
+ self.models = models
+ self.datasets = datasets
+ self.tags = _to_unique_list(tags)
+ super().__init__(**kwargs)
+
+
+def model_index_to_eval_results(model_index: List[Dict[str, Any]]) -> Tuple[str, List[EvalResult]]:
+ """Takes in a model index and returns the model name and a list of `huggingface_hub.EvalResult` objects.
+
+ A detailed spec of the model index can be found here:
+ https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+
+ Args:
+ model_index (`List[Dict[str, Any]]`):
+ A model index data structure, likely coming from a README.md file on the
+ Hugging Face Hub.
+
+ Returns:
+ model_name (`str`):
+ The name of the model as found in the model index. This is used as the
+ identifier for the model on leaderboards like PapersWithCode.
+ eval_results (`List[EvalResult]`):
+ A list of `huggingface_hub.EvalResult` objects containing the metrics
+ reported in the provided model_index.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard_data import model_index_to_eval_results
+ >>> # Define a minimal model index
+ >>> model_index = [
+ ... {
+ ... "name": "my-cool-model",
+ ... "results": [
+ ... {
+ ... "task": {
+ ... "type": "image-classification"
+ ... },
+ ... "dataset": {
+ ... "type": "beans",
+ ... "name": "Beans"
+ ... },
+ ... "metrics": [
+ ... {
+ ... "type": "accuracy",
+ ... "value": 0.9
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... }
+ ... ]
+ >>> model_name, eval_results = model_index_to_eval_results(model_index)
+ >>> model_name
+ 'my-cool-model'
+ >>> eval_results[0].task_type
+ 'image-classification'
+ >>> eval_results[0].metric_type
+ 'accuracy'
+
+ ```
+ """
+
+ eval_results = []
+ for elem in model_index:
+ name = elem["name"]
+ results = elem["results"]
+ for result in results:
+ task_type = result["task"]["type"]
+ task_name = result["task"].get("name")
+ dataset_type = result["dataset"]["type"]
+ dataset_name = result["dataset"]["name"]
+ dataset_config = result["dataset"].get("config")
+ dataset_split = result["dataset"].get("split")
+ dataset_revision = result["dataset"].get("revision")
+ dataset_args = result["dataset"].get("args")
+ source_name = result.get("source", {}).get("name")
+ source_url = result.get("source", {}).get("url")
+
+ for metric in result["metrics"]:
+ metric_type = metric["type"]
+ metric_value = metric["value"]
+ metric_name = metric.get("name")
+ metric_args = metric.get("args")
+ metric_config = metric.get("config")
+ verified = metric.get("verified")
+ verify_token = metric.get("verifyToken")
+
+ eval_result = EvalResult(
+ task_type=task_type, # Required
+ dataset_type=dataset_type, # Required
+ dataset_name=dataset_name, # Required
+ metric_type=metric_type, # Required
+ metric_value=metric_value, # Required
+ task_name=task_name,
+ dataset_config=dataset_config,
+ dataset_split=dataset_split,
+ dataset_revision=dataset_revision,
+ dataset_args=dataset_args,
+ metric_name=metric_name,
+ metric_args=metric_args,
+ metric_config=metric_config,
+ verified=verified,
+ verify_token=verify_token,
+ source_name=source_name,
+ source_url=source_url,
+ )
+ eval_results.append(eval_result)
+ return name, eval_results
+
+
+def _remove_none(obj):
+ """
+ Recursively remove `None` values from a dict. Borrowed from: https://stackoverflow.com/a/20558778
+ """
+ if isinstance(obj, (list, tuple, set)):
+ return type(obj)(_remove_none(x) for x in obj if x is not None)
+ elif isinstance(obj, dict):
+ return type(obj)((_remove_none(k), _remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
+ else:
+ return obj
+
+
+def eval_results_to_model_index(model_name: str, eval_results: List[EvalResult]) -> List[Dict[str, Any]]:
+ """Takes in given model name and list of `huggingface_hub.EvalResult` and returns a
+ valid model-index that will be compatible with the format expected by the
+ Hugging Face Hub.
+
+ Args:
+ model_name (`str`):
+ Name of the model (ex. "my-cool-model"). This is used as the identifier
+ for the model on leaderboards like PapersWithCode.
+ eval_results (`List[EvalResult]`):
+ List of `huggingface_hub.EvalResult` objects containing the metrics to be
+ reported in the model-index.
+
+ Returns:
+ model_index (`List[Dict[str, Any]]`): The eval_results converted to a model-index.
+
+ Example:
+ ```python
+ >>> from huggingface_hub.repocard_data import eval_results_to_model_index, EvalResult
+ >>> # Define minimal eval_results
+ >>> eval_results = [
+ ... EvalResult(
+ ... task_type="image-classification", # Required
+ ... dataset_type="beans", # Required
+ ... dataset_name="Beans", # Required
+ ... metric_type="accuracy", # Required
+ ... metric_value=0.9, # Required
+ ... )
+ ... ]
+ >>> eval_results_to_model_index("my-cool-model", eval_results)
+ [{'name': 'my-cool-model', 'results': [{'task': {'type': 'image-classification'}, 'dataset': {'name': 'Beans', 'type': 'beans'}, 'metrics': [{'type': 'accuracy', 'value': 0.9}]}]}]
+
+ ```
+ """
+
+ # Metrics are reported on a unique task-and-dataset basis.
+ # Here, we make a map of those pairs and the associated EvalResults.
+ task_and_ds_types_map: Dict[Any, List[EvalResult]] = defaultdict(list)
+ for eval_result in eval_results:
+ task_and_ds_types_map[eval_result.unique_identifier].append(eval_result)
+
+ # Use the map from above to generate the model index data.
+ model_index_data = []
+ for results in task_and_ds_types_map.values():
+ # All items from `results` share same metadata
+ sample_result = results[0]
+ data = {
+ "task": {
+ "type": sample_result.task_type,
+ "name": sample_result.task_name,
+ },
+ "dataset": {
+ "name": sample_result.dataset_name,
+ "type": sample_result.dataset_type,
+ "config": sample_result.dataset_config,
+ "split": sample_result.dataset_split,
+ "revision": sample_result.dataset_revision,
+ "args": sample_result.dataset_args,
+ },
+ "metrics": [
+ {
+ "type": result.metric_type,
+ "value": result.metric_value,
+ "name": result.metric_name,
+ "config": result.metric_config,
+ "args": result.metric_args,
+ "verified": result.verified,
+ "verifyToken": result.verify_token,
+ }
+ for result in results
+ ],
+ }
+ if sample_result.source_url is not None:
+ source = {
+ "url": sample_result.source_url,
+ }
+ if sample_result.source_name is not None:
+ source["name"] = sample_result.source_name
+ data["source"] = source
+ model_index_data.append(data)
+
+ # TODO - Check if there cases where this list is longer than one?
+ # Finally, the model index itself is list of dicts.
+ model_index = [
+ {
+ "name": model_name,
+ "results": model_index_data,
+ }
+ ]
+ return _remove_none(model_index)
+
+
+def _to_unique_list(tags: Optional[List[str]]) -> Optional[List[str]]:
+ if tags is None:
+ return tags
+ unique_tags = [] # make tags unique + keep order explicitly
+ for tag in tags:
+ if tag not in unique_tags:
+ unique_tags.append(tag)
+ return unique_tags
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/repository.py b/.venv/lib/python3.10/site-packages/huggingface_hub/repository.py
new file mode 100644
index 0000000000000000000000000000000000000000..d7aff2eb66318803ee8c2fa4e3cccb29e0036801
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/repository.py
@@ -0,0 +1,1476 @@
+import atexit
+import os
+import re
+import subprocess
+import threading
+import time
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Callable, Dict, Iterator, List, Optional, Tuple, TypedDict, Union
+from urllib.parse import urlparse
+
+from huggingface_hub.constants import REPO_TYPES_URL_PREFIXES, REPOCARD_NAME
+from huggingface_hub.repocard import metadata_load, metadata_save
+
+from .hf_api import HfApi, repo_type_and_id_from_hf_id
+from .lfs import LFS_MULTIPART_UPLOAD_COMMAND
+from .utils import (
+ SoftTemporaryDirectory,
+ get_token,
+ logging,
+ run_subprocess,
+ tqdm,
+ validate_hf_hub_args,
+)
+from .utils._deprecation import _deprecate_method
+
+
+logger = logging.get_logger(__name__)
+
+
+class CommandInProgress:
+ """
+ Utility to follow commands launched asynchronously.
+ """
+
+ def __init__(
+ self,
+ title: str,
+ is_done_method: Callable,
+ status_method: Callable,
+ process: subprocess.Popen,
+ post_method: Optional[Callable] = None,
+ ):
+ self.title = title
+ self._is_done = is_done_method
+ self._status = status_method
+ self._process = process
+ self._stderr = ""
+ self._stdout = ""
+ self._post_method = post_method
+
+ @property
+ def is_done(self) -> bool:
+ """
+ Whether the process is done.
+ """
+ result = self._is_done()
+
+ if result and self._post_method is not None:
+ self._post_method()
+ self._post_method = None
+
+ return result
+
+ @property
+ def status(self) -> int:
+ """
+ The exit code/status of the current action. Will return `0` if the
+ command has completed successfully, and a number between 1 and 255 if
+ the process errored-out.
+
+ Will return -1 if the command is still ongoing.
+ """
+ return self._status()
+
+ @property
+ def failed(self) -> bool:
+ """
+ Whether the process errored-out.
+ """
+ return self.status > 0
+
+ @property
+ def stderr(self) -> str:
+ """
+ The current output message on the standard error.
+ """
+ if self._process.stderr is not None:
+ self._stderr += self._process.stderr.read()
+ return self._stderr
+
+ @property
+ def stdout(self) -> str:
+ """
+ The current output message on the standard output.
+ """
+ if self._process.stdout is not None:
+ self._stdout += self._process.stdout.read()
+ return self._stdout
+
+ def __repr__(self):
+ status = self.status
+
+ if status == -1:
+ status = "running"
+
+ return (
+ f"[{self.title} command, status code: {status},"
+ f" {'in progress.' if not self.is_done else 'finished.'} PID:"
+ f" {self._process.pid}]"
+ )
+
+
+def is_git_repo(folder: Union[str, Path]) -> bool:
+ """
+ Check if the folder is the root or part of a git repository
+
+ Args:
+ folder (`str`):
+ The folder in which to run the command.
+
+ Returns:
+ `bool`: `True` if the repository is part of a repository, `False`
+ otherwise.
+ """
+ folder_exists = os.path.exists(os.path.join(folder, ".git"))
+ git_branch = subprocess.run("git branch".split(), cwd=folder, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return folder_exists and git_branch.returncode == 0
+
+
+def is_local_clone(folder: Union[str, Path], remote_url: str) -> bool:
+ """
+ Check if the folder is a local clone of the remote_url
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+ remote_url (`str`):
+ The url of a git repository.
+
+ Returns:
+ `bool`: `True` if the repository is a local clone of the remote
+ repository specified, `False` otherwise.
+ """
+ if not is_git_repo(folder):
+ return False
+
+ remotes = run_subprocess("git remote -v", folder).stdout
+
+ # Remove token for the test with remotes.
+ remote_url = re.sub(r"https://.*@", "https://", remote_url)
+ remotes = [re.sub(r"https://.*@", "https://", remote) for remote in remotes.split()]
+ return remote_url in remotes
+
+
+def is_tracked_with_lfs(filename: Union[str, Path]) -> bool:
+ """
+ Check if the file passed is tracked with git-lfs.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is tracked with git-lfs, `False`
+ otherwise.
+ """
+ folder = Path(filename).parent
+ filename = Path(filename).name
+
+ try:
+ p = run_subprocess("git check-attr -a".split() + [filename], folder)
+ attributes = p.stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ if not is_git_repo(folder):
+ return False
+ else:
+ raise OSError(exc.stderr)
+
+ if len(attributes) == 0:
+ return False
+
+ found_lfs_tag = {"diff": False, "merge": False, "filter": False}
+
+ for attribute in attributes.split("\n"):
+ for tag in found_lfs_tag.keys():
+ if tag in attribute and "lfs" in attribute:
+ found_lfs_tag[tag] = True
+
+ return all(found_lfs_tag.values())
+
+
+def is_git_ignored(filename: Union[str, Path]) -> bool:
+ """
+ Check if file is git-ignored. Supports nested .gitignore files.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is ignored by `git`, `False`
+ otherwise.
+ """
+ folder = Path(filename).parent
+ filename = Path(filename).name
+
+ try:
+ p = run_subprocess("git check-ignore".split() + [filename], folder, check=False)
+ # Will return exit code 1 if not gitignored
+ is_ignored = not bool(p.returncode)
+ except subprocess.CalledProcessError as exc:
+ raise OSError(exc.stderr)
+
+ return is_ignored
+
+
+def is_binary_file(filename: Union[str, Path]) -> bool:
+ """
+ Check if file is a binary file.
+
+ Args:
+ filename (`str` or `Path`):
+ The filename to check.
+
+ Returns:
+ `bool`: `True` if the file passed is a binary file, `False` otherwise.
+ """
+ try:
+ with open(filename, "rb") as f:
+ content = f.read(10 * (1024**2)) # Read a maximum of 10MB
+
+ # Code sample taken from the following stack overflow thread
+ # https://stackoverflow.com/questions/898669/how-can-i-detect-if-a-file-is-binary-non-text-in-python/7392391#7392391
+ text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
+ return bool(content.translate(None, text_chars))
+ except UnicodeDecodeError:
+ return True
+
+
+def files_to_be_staged(pattern: str = ".", folder: Union[str, Path, None] = None) -> List[str]:
+ """
+ Returns a list of filenames that are to be staged.
+
+ Args:
+ pattern (`str` or `Path`):
+ The pattern of filenames to check. Put `.` to get all files.
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+
+ Returns:
+ `List[str]`: List of files that are to be staged.
+ """
+ try:
+ p = run_subprocess("git ls-files --exclude-standard -mo".split() + [pattern], folder)
+ if len(p.stdout.strip()):
+ files = p.stdout.strip().split("\n")
+ else:
+ files = []
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return files
+
+
+def is_tracked_upstream(folder: Union[str, Path]) -> bool:
+ """
+ Check if the current checked-out branch is tracked upstream.
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+
+ Returns:
+ `bool`: `True` if the current checked-out branch is tracked upstream,
+ `False` otherwise.
+ """
+ try:
+ run_subprocess("git rev-parse --symbolic-full-name --abbrev-ref @{u}", folder)
+ return True
+ except subprocess.CalledProcessError as exc:
+ if "HEAD" in exc.stderr:
+ raise OSError("No branch checked out")
+
+ return False
+
+
+def commits_to_push(folder: Union[str, Path], upstream: Optional[str] = None) -> int:
+ """
+ Check the number of commits that would be pushed upstream
+
+ Args:
+ folder (`str` or `Path`):
+ The folder in which to run the command.
+ upstream (`str`, *optional*):
+ The name of the upstream repository with which the comparison should be
+ made.
+
+ Returns:
+ `int`: Number of commits that would be pushed upstream were a `git
+ push` to proceed.
+ """
+ try:
+ result = run_subprocess(f"git cherry -v {upstream or ''}", folder)
+ return len(result.stdout.split("\n")) - 1
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+
+class PbarT(TypedDict):
+ # Used to store an opened progress bar in `_lfs_log_progress`
+ bar: tqdm
+ past_bytes: int
+
+
+@contextmanager
+def _lfs_log_progress():
+ """
+ This is a context manager that will log the Git LFS progress of cleaning,
+ smudging, pulling and pushing.
+ """
+
+ if logger.getEffectiveLevel() >= logging.ERROR:
+ try:
+ yield
+ except Exception:
+ pass
+ return
+
+ def output_progress(stopping_event: threading.Event):
+ """
+ To be launched as a separate thread with an event meaning it should stop
+ the tail.
+ """
+ # Key is tuple(state, filename), value is a dict(tqdm bar and a previous value)
+ pbars: Dict[Tuple[str, str], PbarT] = {}
+
+ def close_pbars():
+ for pbar in pbars.values():
+ pbar["bar"].update(pbar["bar"].total - pbar["past_bytes"])
+ pbar["bar"].refresh()
+ pbar["bar"].close()
+
+ def tail_file(filename) -> Iterator[str]:
+ """
+ Creates a generator to be iterated through, which will return each
+ line one by one. Will stop tailing the file if the stopping_event is
+ set.
+ """
+ with open(filename, "r") as file:
+ current_line = ""
+ while True:
+ if stopping_event.is_set():
+ close_pbars()
+ break
+
+ line_bit = file.readline()
+ if line_bit is not None and not len(line_bit.strip()) == 0:
+ current_line += line_bit
+ if current_line.endswith("\n"):
+ yield current_line
+ current_line = ""
+ else:
+ time.sleep(1)
+
+ # If the file isn't created yet, wait for a few seconds before trying again.
+ # Can be interrupted with the stopping_event.
+ while not os.path.exists(os.environ["GIT_LFS_PROGRESS"]):
+ if stopping_event.is_set():
+ close_pbars()
+ return
+
+ time.sleep(2)
+
+ for line in tail_file(os.environ["GIT_LFS_PROGRESS"]):
+ try:
+ state, file_progress, byte_progress, filename = line.split()
+ except ValueError as error:
+ # Try/except to ease debugging. See https://github.com/huggingface/huggingface_hub/issues/1373.
+ raise ValueError(f"Cannot unpack LFS progress line:\n{line}") from error
+ description = f"{state.capitalize()} file {filename}"
+
+ current_bytes, total_bytes = byte_progress.split("/")
+ current_bytes_int = int(current_bytes)
+ total_bytes_int = int(total_bytes)
+
+ pbar = pbars.get((state, filename))
+ if pbar is None:
+ # Initialize progress bar
+ pbars[(state, filename)] = {
+ "bar": tqdm(
+ desc=description,
+ initial=current_bytes_int,
+ total=total_bytes_int,
+ unit="B",
+ unit_scale=True,
+ unit_divisor=1024,
+ ),
+ "past_bytes": int(current_bytes),
+ }
+ else:
+ # Update progress bar
+ pbar["bar"].update(current_bytes_int - pbar["past_bytes"])
+ pbar["past_bytes"] = current_bytes_int
+
+ current_lfs_progress_value = os.environ.get("GIT_LFS_PROGRESS", "")
+
+ with SoftTemporaryDirectory() as tmpdir:
+ os.environ["GIT_LFS_PROGRESS"] = os.path.join(tmpdir, "lfs_progress")
+ logger.debug(f"Following progress in {os.environ['GIT_LFS_PROGRESS']}")
+
+ exit_event = threading.Event()
+ x = threading.Thread(target=output_progress, args=(exit_event,), daemon=True)
+ x.start()
+
+ try:
+ yield
+ finally:
+ exit_event.set()
+ x.join()
+
+ os.environ["GIT_LFS_PROGRESS"] = current_lfs_progress_value
+
+
+class Repository:
+ """
+ Helper class to wrap the git and git-lfs commands.
+
+ The aim is to facilitate interacting with huggingface.co hosted model or
+ dataset repos, though not a lot here (if any) is actually specific to
+ huggingface.co.
+
+
+
+ [`Repository`] is deprecated in favor of the http-based alternatives implemented in
+ [`HfApi`]. Given its large adoption in legacy code, the complete removal of
+ [`Repository`] will only happen in release `v1.0`. For more details, please read
+ https://huggingface.co/docs/huggingface_hub/concepts/git_vs_http.
+
+
+ """
+
+ command_queue: List[CommandInProgress]
+
+ @validate_hf_hub_args
+ @_deprecate_method(
+ version="1.0",
+ message=(
+ "Please prefer the http-based alternatives instead. Given its large adoption in legacy code, the complete"
+ " removal is only planned on next major release.\nFor more details, please read"
+ " https://huggingface.co/docs/huggingface_hub/concepts/git_vs_http."
+ ),
+ )
+ def __init__(
+ self,
+ local_dir: Union[str, Path],
+ clone_from: Optional[str] = None,
+ repo_type: Optional[str] = None,
+ token: Union[bool, str] = True,
+ git_user: Optional[str] = None,
+ git_email: Optional[str] = None,
+ revision: Optional[str] = None,
+ skip_lfs_files: bool = False,
+ client: Optional[HfApi] = None,
+ ):
+ """
+ Instantiate a local clone of a git repo.
+
+ If `clone_from` is set, the repo will be cloned from an existing remote repository.
+ If the remote repo does not exist, a `EnvironmentError` exception will be thrown.
+ Please create the remote repo first using [`create_repo`].
+
+ `Repository` uses the local git credentials by default. If explicitly set, the `token`
+ or the `git_user`/`git_email` pair will be used instead.
+
+ Args:
+ local_dir (`str` or `Path`):
+ path (e.g. `'my_trained_model/'`) to the local directory, where
+ the `Repository` will be initialized.
+ clone_from (`str`, *optional*):
+ Either a repository url or `repo_id`.
+ Example:
+ - `"https://huggingface.co/philschmid/playground-tests"`
+ - `"philschmid/playground-tests"`
+ repo_type (`str`, *optional*):
+ To set when cloning a repo from a repo_id. Default is model.
+ token (`bool` or `str`, *optional*):
+ A valid authentication token (see https://huggingface.co/settings/token).
+ If `None` or `True` and machine is logged in (through `huggingface-cli login`
+ or [`~huggingface_hub.login`]), token will be retrieved from the cache.
+ If `False`, token is not sent in the request header.
+ git_user (`str`, *optional*):
+ will override the `git config user.name` for committing and
+ pushing files to the hub.
+ git_email (`str`, *optional*):
+ will override the `git config user.email` for committing and
+ pushing files to the hub.
+ revision (`str`, *optional*):
+ Revision to checkout after initializing the repository. If the
+ revision doesn't exist, a branch will be created with that
+ revision name from the default branch's current HEAD.
+ skip_lfs_files (`bool`, *optional*, defaults to `False`):
+ whether to skip git-LFS files or not.
+ client (`HfApi`, *optional*):
+ Instance of [`HfApi`] to use when calling the HF Hub API. A new
+ instance will be created if this is left to `None`.
+
+ Raises:
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if the remote repository set in `clone_from` does not exist.
+ """
+ if isinstance(local_dir, Path):
+ local_dir = str(local_dir)
+ os.makedirs(local_dir, exist_ok=True)
+ self.local_dir = os.path.join(os.getcwd(), local_dir)
+ self._repo_type = repo_type
+ self.command_queue = []
+ self.skip_lfs_files = skip_lfs_files
+ self.client = client if client is not None else HfApi()
+
+ self.check_git_versions()
+
+ if isinstance(token, str):
+ self.huggingface_token: Optional[str] = token
+ elif token is False:
+ self.huggingface_token = None
+ else:
+ # if `True` -> explicit use of the cached token
+ # if `None` -> implicit use of the cached token
+ self.huggingface_token = get_token()
+
+ if clone_from is not None:
+ self.clone_from(repo_url=clone_from)
+ else:
+ if is_git_repo(self.local_dir):
+ logger.debug("[Repository] is a valid git repo")
+ else:
+ raise ValueError("If not specifying `clone_from`, you need to pass Repository a valid git clone.")
+
+ if self.huggingface_token is not None and (git_email is None or git_user is None):
+ user = self.client.whoami(self.huggingface_token)
+
+ if git_email is None:
+ git_email = user["email"]
+
+ if git_user is None:
+ git_user = user["fullname"]
+
+ if git_user is not None or git_email is not None:
+ self.git_config_username_and_email(git_user, git_email)
+
+ self.lfs_enable_largefiles()
+ self.git_credential_helper_store()
+
+ if revision is not None:
+ self.git_checkout(revision, create_branch_ok=True)
+
+ # This ensures that all commands exit before exiting the Python runtime.
+ # This will ensure all pushes register on the hub, even if other errors happen in subsequent operations.
+ atexit.register(self.wait_for_commands)
+
+ @property
+ def current_branch(self) -> str:
+ """
+ Returns the current checked out branch.
+
+ Returns:
+ `str`: Current checked out branch.
+ """
+ try:
+ result = run_subprocess("git rev-parse --abbrev-ref HEAD", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return result
+
+ def check_git_versions(self):
+ """
+ Checks that `git` and `git-lfs` can be run.
+
+ Raises:
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if `git` or `git-lfs` are not installed.
+ """
+ try:
+ git_version = run_subprocess("git --version", self.local_dir).stdout.strip()
+ except FileNotFoundError:
+ raise EnvironmentError("Looks like you do not have git installed, please install.")
+
+ try:
+ lfs_version = run_subprocess("git-lfs --version", self.local_dir).stdout.strip()
+ except FileNotFoundError:
+ raise EnvironmentError(
+ "Looks like you do not have git-lfs installed, please install."
+ " You can install from https://git-lfs.github.com/."
+ " Then run `git lfs install` (you only have to do this once)."
+ )
+ logger.info(git_version + "\n" + lfs_version)
+
+ @validate_hf_hub_args
+ def clone_from(self, repo_url: str, token: Union[bool, str, None] = None):
+ """
+ Clone from a remote. If the folder already exists, will try to clone the
+ repository within it.
+
+ If this folder is a git repository with linked history, will try to
+ update the repository.
+
+ Args:
+ repo_url (`str`):
+ The URL from which to clone the repository
+ token (`Union[str, bool]`, *optional*):
+ Whether to use the authentication token. It can be:
+ - a string which is the token itself
+ - `False`, which would not use the authentication token
+ - `True`, which would fetch the authentication token from the
+ local folder and use it (you should be logged in for this to
+ work).
+ - `None`, which would retrieve the value of
+ `self.huggingface_token`.
+
+
+
+ Raises the following error:
+
+ - [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ if an organization token (starts with "api_org") is passed. Use must use
+ your own personal access token (see https://hf.co/settings/tokens).
+
+ - [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ if you are trying to clone the repository in a non-empty folder, or if the
+ `git` operations raise errors.
+
+
+ """
+ token = (
+ token # str -> use it
+ if isinstance(token, str)
+ else (
+ None # `False` -> explicit no token
+ if token is False
+ else self.huggingface_token # `None` or `True` -> use default
+ )
+ )
+ if token is not None and token.startswith("api_org"):
+ raise ValueError(
+ "You must use your personal access token, not an Organization token"
+ " (see https://hf.co/settings/tokens)."
+ )
+
+ hub_url = self.client.endpoint
+ if hub_url in repo_url or ("http" not in repo_url and len(repo_url.split("/")) <= 2):
+ repo_type, namespace, repo_name = repo_type_and_id_from_hf_id(repo_url, hub_url=hub_url)
+ repo_id = f"{namespace}/{repo_name}" if namespace is not None else repo_name
+
+ if repo_type is not None:
+ self._repo_type = repo_type
+
+ repo_url = hub_url + "/"
+
+ if self._repo_type in REPO_TYPES_URL_PREFIXES:
+ repo_url += REPO_TYPES_URL_PREFIXES[self._repo_type]
+
+ if token is not None:
+ # Add token in git url when provided
+ scheme = urlparse(repo_url).scheme
+ repo_url = repo_url.replace(f"{scheme}://", f"{scheme}://user:{token}@")
+
+ repo_url += repo_id
+
+ # For error messages, it's cleaner to show the repo url without the token.
+ clean_repo_url = re.sub(r"(https?)://.*@", r"\1://", repo_url)
+ try:
+ run_subprocess("git lfs install", self.local_dir)
+
+ # checks if repository is initialized in a empty repository or in one with files
+ if len(os.listdir(self.local_dir)) == 0:
+ logger.warning(f"Cloning {clean_repo_url} into local empty directory.")
+
+ with _lfs_log_progress():
+ env = os.environ.copy()
+
+ if self.skip_lfs_files:
+ env.update({"GIT_LFS_SKIP_SMUDGE": "1"})
+
+ run_subprocess(
+ # 'git lfs clone' is deprecated (will display a warning in the terminal)
+ # but we still use it as it provides a nicer UX when downloading large
+ # files (shows progress).
+ f"{'git clone' if self.skip_lfs_files else 'git lfs clone'} {repo_url} .",
+ self.local_dir,
+ env=env,
+ )
+ else:
+ # Check if the folder is the root of a git repository
+ if not is_git_repo(self.local_dir):
+ raise EnvironmentError(
+ "Tried to clone a repository in a non-empty folder that isn't"
+ f" a git repository ('{self.local_dir}'). If you really want to"
+ f" do this, do it manually:\n cd {self.local_dir} && git init"
+ " && git remote add origin && git pull origin main\n or clone"
+ " repo to a new folder and move your existing files there"
+ " afterwards."
+ )
+
+ if is_local_clone(self.local_dir, repo_url):
+ logger.warning(
+ f"{self.local_dir} is already a clone of {clean_repo_url}."
+ " Make sure you pull the latest changes with"
+ " `repo.git_pull()`."
+ )
+ else:
+ output = run_subprocess("git remote get-url origin", self.local_dir, check=False)
+
+ error_msg = (
+ f"Tried to clone {clean_repo_url} in an unrelated git"
+ " repository.\nIf you believe this is an error, please add"
+ f" a remote with the following URL: {clean_repo_url}."
+ )
+ if output.returncode == 0:
+ clean_local_remote_url = re.sub(r"https://.*@", "https://", output.stdout)
+ error_msg += f"\nLocal path has its origin defined as: {clean_local_remote_url}"
+ raise EnvironmentError(error_msg)
+
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_config_username_and_email(self, git_user: Optional[str] = None, git_email: Optional[str] = None):
+ """
+ Sets git username and email (only in the current repo).
+
+ Args:
+ git_user (`str`, *optional*):
+ The username to register through `git`.
+ git_email (`str`, *optional*):
+ The email to register through `git`.
+ """
+ try:
+ if git_user is not None:
+ run_subprocess("git config user.name".split() + [git_user], self.local_dir)
+
+ if git_email is not None:
+ run_subprocess(f"git config user.email {git_email}".split(), self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_credential_helper_store(self):
+ """
+ Sets the git credential helper to `store`
+ """
+ try:
+ run_subprocess("git config credential.helper store", self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_head_hash(self) -> str:
+ """
+ Get commit sha on top of HEAD.
+
+ Returns:
+ `str`: The current checked out commit SHA.
+ """
+ try:
+ p = run_subprocess("git rev-parse HEAD", self.local_dir)
+ return p.stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_remote_url(self) -> str:
+ """
+ Get URL to origin remote.
+
+ Returns:
+ `str`: The URL of the `origin` remote.
+ """
+ try:
+ p = run_subprocess("git config --get remote.origin.url", self.local_dir)
+ url = p.stdout.strip()
+ # Strip basic auth info.
+ return re.sub(r"https://.*@", "https://", url)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_head_commit_url(self) -> str:
+ """
+ Get URL to last commit on HEAD. We assume it's been pushed, and the url
+ scheme is the same one as for GitHub or HuggingFace.
+
+ Returns:
+ `str`: The URL to the current checked-out commit.
+ """
+ sha = self.git_head_hash()
+ url = self.git_remote_url()
+ if url.endswith("/"):
+ url = url[:-1]
+ return f"{url}/commit/{sha}"
+
+ def list_deleted_files(self) -> List[str]:
+ """
+ Returns a list of the files that are deleted in the working directory or
+ index.
+
+ Returns:
+ `List[str]`: A list of files that have been deleted in the working
+ directory or index.
+ """
+ try:
+ git_status = run_subprocess("git status -s", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if len(git_status) == 0:
+ return []
+
+ # Receives a status like the following
+ # D .gitignore
+ # D new_file.json
+ # AD new_file1.json
+ # ?? new_file2.json
+ # ?? new_file4.json
+
+ # Strip each line of whitespaces
+ modified_files_statuses = [status.strip() for status in git_status.split("\n")]
+
+ # Only keep files that are deleted using the D prefix
+ deleted_files_statuses = [status for status in modified_files_statuses if "D" in status.split()[0]]
+
+ # Remove the D prefix and strip to keep only the relevant filename
+ deleted_files = [status.split()[-1].strip() for status in deleted_files_statuses]
+
+ return deleted_files
+
+ def lfs_track(self, patterns: Union[str, List[str]], filename: bool = False):
+ """
+ Tell git-lfs to track files according to a pattern.
+
+ Setting the `filename` argument to `True` will treat the arguments as
+ literal filenames, not as patterns. Any special glob characters in the
+ filename will be escaped when writing to the `.gitattributes` file.
+
+ Args:
+ patterns (`Union[str, List[str]]`):
+ The pattern, or list of patterns, to track with git-lfs.
+ filename (`bool`, *optional*, defaults to `False`):
+ Whether to use the patterns as literal filenames.
+ """
+ if isinstance(patterns, str):
+ patterns = [patterns]
+ try:
+ for pattern in patterns:
+ run_subprocess(
+ f"git lfs track {'--filename' if filename else ''} {pattern}",
+ self.local_dir,
+ )
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def lfs_untrack(self, patterns: Union[str, List[str]]):
+ """
+ Tell git-lfs to untrack those files.
+
+ Args:
+ patterns (`Union[str, List[str]]`):
+ The pattern, or list of patterns, to untrack with git-lfs.
+ """
+ if isinstance(patterns, str):
+ patterns = [patterns]
+ try:
+ for pattern in patterns:
+ run_subprocess("git lfs untrack".split() + [pattern], self.local_dir)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def lfs_enable_largefiles(self):
+ """
+ HF-specific. This enables upload support of files >5GB.
+ """
+ try:
+ lfs_config = "git config lfs.customtransfer.multipart"
+ run_subprocess(f"{lfs_config}.path huggingface-cli", self.local_dir)
+ run_subprocess(
+ f"{lfs_config}.args {LFS_MULTIPART_UPLOAD_COMMAND}",
+ self.local_dir,
+ )
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def auto_track_binary_files(self, pattern: str = ".") -> List[str]:
+ """
+ Automatically track binary files with git-lfs.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to track files that are binary.
+
+ Returns:
+ `List[str]`: List of filenames that are now tracked due to being
+ binary files
+ """
+ files_to_be_tracked_with_lfs = []
+
+ deleted_files = self.list_deleted_files()
+
+ for filename in files_to_be_staged(pattern, folder=self.local_dir):
+ if filename in deleted_files:
+ continue
+
+ path_to_file = os.path.join(os.getcwd(), self.local_dir, filename)
+
+ if not (is_tracked_with_lfs(path_to_file) or is_git_ignored(path_to_file)):
+ size_in_mb = os.path.getsize(path_to_file) / (1024 * 1024)
+
+ if size_in_mb >= 10:
+ logger.warning(
+ "Parsing a large file to check if binary or not. Tracking large"
+ " files using `repository.auto_track_large_files` is"
+ " recommended so as to not load the full file in memory."
+ )
+
+ is_binary = is_binary_file(path_to_file)
+
+ if is_binary:
+ self.lfs_track(filename)
+ files_to_be_tracked_with_lfs.append(filename)
+
+ # Cleanup the .gitattributes if files were deleted
+ self.lfs_untrack(deleted_files)
+
+ return files_to_be_tracked_with_lfs
+
+ def auto_track_large_files(self, pattern: str = ".") -> List[str]:
+ """
+ Automatically track large files (files that weigh more than 10MBs) with
+ git-lfs.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to track files that are above 10MBs.
+
+ Returns:
+ `List[str]`: List of filenames that are now tracked due to their
+ size.
+ """
+ files_to_be_tracked_with_lfs = []
+
+ deleted_files = self.list_deleted_files()
+
+ for filename in files_to_be_staged(pattern, folder=self.local_dir):
+ if filename in deleted_files:
+ continue
+
+ path_to_file = os.path.join(os.getcwd(), self.local_dir, filename)
+ size_in_mb = os.path.getsize(path_to_file) / (1024 * 1024)
+
+ if size_in_mb >= 10 and not is_tracked_with_lfs(path_to_file) and not is_git_ignored(path_to_file):
+ self.lfs_track(filename)
+ files_to_be_tracked_with_lfs.append(filename)
+
+ # Cleanup the .gitattributes if files were deleted
+ self.lfs_untrack(deleted_files)
+
+ return files_to_be_tracked_with_lfs
+
+ def lfs_prune(self, recent=False):
+ """
+ git lfs prune
+
+ Args:
+ recent (`bool`, *optional*, defaults to `False`):
+ Whether to prune files even if they were referenced by recent
+ commits. See the following
+ [link](https://github.com/git-lfs/git-lfs/blob/f3d43f0428a84fc4f1e5405b76b5a73ec2437e65/docs/man/git-lfs-prune.1.ronn#recent-files)
+ for more information.
+ """
+ try:
+ with _lfs_log_progress():
+ result = run_subprocess(f"git lfs prune {'--recent' if recent else ''}", self.local_dir)
+ logger.info(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_pull(self, rebase: bool = False, lfs: bool = False):
+ """
+ git pull
+
+ Args:
+ rebase (`bool`, *optional*, defaults to `False`):
+ Whether to rebase the current branch on top of the upstream
+ branch after fetching.
+ lfs (`bool`, *optional*, defaults to `False`):
+ Whether to fetch the LFS files too. This option only changes the
+ behavior when a repository was cloned without fetching the LFS
+ files; calling `repo.git_pull(lfs=True)` will then fetch the LFS
+ file from the remote repository.
+ """
+ command = "git pull" if not lfs else "git lfs pull"
+ if rebase:
+ command += " --rebase"
+ try:
+ with _lfs_log_progress():
+ result = run_subprocess(command, self.local_dir)
+ logger.info(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_add(self, pattern: str = ".", auto_lfs_track: bool = False):
+ """
+ git add
+
+ Setting the `auto_lfs_track` parameter to `True` will automatically
+ track files that are larger than 10MB with `git-lfs`.
+
+ Args:
+ pattern (`str`, *optional*, defaults to "."):
+ The pattern with which to add files to staging.
+ auto_lfs_track (`bool`, *optional*, defaults to `False`):
+ Whether to automatically track large and binary files with
+ git-lfs. Any file over 10MB in size, or in binary format, will
+ be automatically tracked.
+ """
+ if auto_lfs_track:
+ # Track files according to their size (>=10MB)
+ tracked_files = self.auto_track_large_files(pattern)
+
+ # Read the remaining files and track them if they're binary
+ tracked_files.extend(self.auto_track_binary_files(pattern))
+
+ if tracked_files:
+ logger.warning(
+ f"Adding files tracked by Git LFS: {tracked_files}. This may take a"
+ " bit of time if the files are large."
+ )
+
+ try:
+ result = run_subprocess("git add -v".split() + [pattern], self.local_dir)
+ logger.info(f"Adding to index:\n{result.stdout}\n")
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def git_commit(self, commit_message: str = "commit files to HF hub"):
+ """
+ git commit
+
+ Args:
+ commit_message (`str`, *optional*, defaults to "commit files to HF hub"):
+ The message attributed to the commit.
+ """
+ try:
+ result = run_subprocess("git commit -v -m".split() + [commit_message], self.local_dir)
+ logger.info(f"Committed:\n{result.stdout}\n")
+ except subprocess.CalledProcessError as exc:
+ if len(exc.stderr) > 0:
+ raise EnvironmentError(exc.stderr)
+ else:
+ raise EnvironmentError(exc.stdout)
+
+ def git_push(
+ self,
+ upstream: Optional[str] = None,
+ blocking: bool = True,
+ auto_lfs_prune: bool = False,
+ ) -> Union[str, Tuple[str, CommandInProgress]]:
+ """
+ git push
+
+ If used without setting `blocking`, will return url to commit on remote
+ repo. If used with `blocking=True`, will return a tuple containing the
+ url to commit and the command object to follow for information about the
+ process.
+
+ Args:
+ upstream (`str`, *optional*):
+ Upstream to which this should push. If not specified, will push
+ to the lastly defined upstream or to the default one (`origin
+ main`).
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the push has
+ finished. Setting this to `False` will return an
+ `CommandInProgress` object which has an `is_done` property. This
+ property will be set to `True` when the push is finished.
+ auto_lfs_prune (`bool`, *optional*, defaults to `False`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+ """
+ command = "git push"
+
+ if upstream:
+ command += f" --set-upstream {upstream}"
+
+ number_of_commits = commits_to_push(self.local_dir, upstream)
+
+ if number_of_commits > 1:
+ logger.warning(f"Several commits ({number_of_commits}) will be pushed upstream.")
+ if blocking:
+ logger.warning("The progress bars may be unreliable.")
+
+ try:
+ with _lfs_log_progress():
+ process = subprocess.Popen(
+ command.split(),
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ encoding="utf-8",
+ cwd=self.local_dir,
+ )
+
+ if blocking:
+ stdout, stderr = process.communicate()
+ return_code = process.poll()
+ process.kill()
+
+ if len(stderr):
+ logger.warning(stderr)
+
+ if return_code:
+ raise subprocess.CalledProcessError(return_code, process.args, output=stdout, stderr=stderr)
+
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if not blocking:
+
+ def status_method():
+ status = process.poll()
+ if status is None:
+ return -1
+ else:
+ return status
+
+ command_in_progress = CommandInProgress(
+ "push",
+ is_done_method=lambda: process.poll() is not None,
+ status_method=status_method,
+ process=process,
+ post_method=self.lfs_prune if auto_lfs_prune else None,
+ )
+
+ self.command_queue.append(command_in_progress)
+
+ return self.git_head_commit_url(), command_in_progress
+
+ if auto_lfs_prune:
+ self.lfs_prune()
+
+ return self.git_head_commit_url()
+
+ def git_checkout(self, revision: str, create_branch_ok: bool = False):
+ """
+ git checkout a given revision
+
+ Specifying `create_branch_ok` to `True` will create the branch to the
+ given revision if that revision doesn't exist.
+
+ Args:
+ revision (`str`):
+ The revision to checkout.
+ create_branch_ok (`str`, *optional*, defaults to `False`):
+ Whether creating a branch named with the `revision` passed at
+ the current checked-out reference if `revision` isn't an
+ existing revision is allowed.
+ """
+ try:
+ result = run_subprocess(f"git checkout {revision}", self.local_dir)
+ logger.warning(f"Checked out {revision} from {self.current_branch}.")
+ logger.warning(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ if not create_branch_ok:
+ raise EnvironmentError(exc.stderr)
+ else:
+ try:
+ result = run_subprocess(f"git checkout -b {revision}", self.local_dir)
+ logger.warning(
+ f"Revision `{revision}` does not exist. Created and checked out branch `{revision}`."
+ )
+ logger.warning(result.stdout)
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def tag_exists(self, tag_name: str, remote: Optional[str] = None) -> bool:
+ """
+ Check if a tag exists or not.
+
+ Args:
+ tag_name (`str`):
+ The name of the tag to check.
+ remote (`str`, *optional*):
+ Whether to check if the tag exists on a remote. This parameter
+ should be the identifier of the remote.
+
+ Returns:
+ `bool`: Whether the tag exists.
+ """
+ if remote:
+ try:
+ result = run_subprocess(f"git ls-remote origin refs/tags/{tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return len(result) != 0
+ else:
+ try:
+ git_tags = run_subprocess("git tag", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ git_tags = git_tags.split("\n")
+ return tag_name in git_tags
+
+ def delete_tag(self, tag_name: str, remote: Optional[str] = None) -> bool:
+ """
+ Delete a tag, both local and remote, if it exists
+
+ Args:
+ tag_name (`str`):
+ The tag name to delete.
+ remote (`str`, *optional*):
+ The remote on which to delete the tag.
+
+ Returns:
+ `bool`: `True` if deleted, `False` if the tag didn't exist.
+ If remote is not passed, will just be updated locally
+ """
+ delete_locally = True
+ delete_remotely = True
+
+ if not self.tag_exists(tag_name):
+ delete_locally = False
+
+ if not self.tag_exists(tag_name, remote=remote):
+ delete_remotely = False
+
+ if delete_locally:
+ try:
+ run_subprocess(["git", "tag", "-d", tag_name], self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if remote and delete_remotely:
+ try:
+ run_subprocess(f"git push {remote} --delete {tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return True
+
+ def add_tag(self, tag_name: str, message: Optional[str] = None, remote: Optional[str] = None):
+ """
+ Add a tag at the current head and push it
+
+ If remote is None, will just be updated locally
+
+ If no message is provided, the tag will be lightweight. if a message is
+ provided, the tag will be annotated.
+
+ Args:
+ tag_name (`str`):
+ The name of the tag to be added.
+ message (`str`, *optional*):
+ The message that accompanies the tag. The tag will turn into an
+ annotated tag if a message is passed.
+ remote (`str`, *optional*):
+ The remote on which to add the tag.
+ """
+ if message:
+ tag_args = ["git", "tag", "-a", tag_name, "-m", message]
+ else:
+ tag_args = ["git", "tag", tag_name]
+
+ try:
+ run_subprocess(tag_args, self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ if remote:
+ try:
+ run_subprocess(f"git push {remote} {tag_name}", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ def is_repo_clean(self) -> bool:
+ """
+ Return whether or not the git status is clean or not
+
+ Returns:
+ `bool`: `True` if the git status is clean, `False` otherwise.
+ """
+ try:
+ git_status = run_subprocess("git status --porcelain", self.local_dir).stdout.strip()
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+ return len(git_status) == 0
+
+ def push_to_hub(
+ self,
+ commit_message: str = "commit files to HF hub",
+ blocking: bool = True,
+ clean_ok: bool = True,
+ auto_lfs_prune: bool = False,
+ ) -> Union[None, str, Tuple[str, CommandInProgress]]:
+ """
+ Helper to add, commit, and push files to remote repository on the
+ HuggingFace Hub. Will automatically track large files (>10MB).
+
+ Args:
+ commit_message (`str`):
+ Message to use for the commit.
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the `git push` has
+ finished.
+ clean_ok (`bool`, *optional*, defaults to `True`):
+ If True, this function will return None if the repo is
+ untouched. Default behavior is to fail because the git command
+ fails.
+ auto_lfs_prune (`bool`, *optional*, defaults to `False`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+ """
+ if clean_ok and self.is_repo_clean():
+ logger.info("Repo currently clean. Ignoring push_to_hub")
+ return None
+ self.git_add(auto_lfs_track=True)
+ self.git_commit(commit_message)
+ return self.git_push(
+ upstream=f"origin {self.current_branch}",
+ blocking=blocking,
+ auto_lfs_prune=auto_lfs_prune,
+ )
+
+ @contextmanager
+ def commit(
+ self,
+ commit_message: str,
+ branch: Optional[str] = None,
+ track_large_files: bool = True,
+ blocking: bool = True,
+ auto_lfs_prune: bool = False,
+ ):
+ """
+ Context manager utility to handle committing to a repository. This
+ automatically tracks large files (>10Mb) with git-lfs. Set the
+ `track_large_files` argument to `False` if you wish to ignore that
+ behavior.
+
+ Args:
+ commit_message (`str`):
+ Message to use for the commit.
+ branch (`str`, *optional*):
+ The branch on which the commit will appear. This branch will be
+ checked-out before any operation.
+ track_large_files (`bool`, *optional*, defaults to `True`):
+ Whether to automatically track large files or not. Will do so by
+ default.
+ blocking (`bool`, *optional*, defaults to `True`):
+ Whether the function should return only when the `git push` has
+ finished.
+ auto_lfs_prune (`bool`, defaults to `True`):
+ Whether to automatically prune files once they have been pushed
+ to the remote.
+
+ Examples:
+
+ ```python
+ >>> with Repository(
+ ... "text-files",
+ ... clone_from="/text-files",
+ ... token=True,
+ >>> ).commit("My first file :)"):
+ ... with open("file.txt", "w+") as f:
+ ... f.write(json.dumps({"hey": 8}))
+
+ >>> import torch
+
+ >>> model = torch.nn.Transformer()
+ >>> with Repository(
+ ... "torch-model",
+ ... clone_from="/torch-model",
+ ... token=True,
+ >>> ).commit("My cool model :)"):
+ ... torch.save(model.state_dict(), "model.pt")
+ ```
+
+ """
+
+ files_to_stage = files_to_be_staged(".", folder=self.local_dir)
+
+ if len(files_to_stage):
+ files_in_msg = str(files_to_stage[:5])[:-1] + ", ...]" if len(files_to_stage) > 5 else str(files_to_stage)
+ logger.error(
+ "There exists some updated files in the local repository that are not"
+ f" committed: {files_in_msg}. This may lead to errors if checking out"
+ " a branch. These files and their modifications will be added to the"
+ " current commit."
+ )
+
+ if branch is not None:
+ self.git_checkout(branch, create_branch_ok=True)
+
+ if is_tracked_upstream(self.local_dir):
+ logger.warning("Pulling changes ...")
+ self.git_pull(rebase=True)
+ else:
+ logger.warning(f"The current branch has no upstream branch. Will push to 'origin {self.current_branch}'")
+
+ current_working_directory = os.getcwd()
+ os.chdir(os.path.join(current_working_directory, self.local_dir))
+
+ try:
+ yield self
+ finally:
+ self.git_add(auto_lfs_track=track_large_files)
+
+ try:
+ self.git_commit(commit_message)
+ except OSError as e:
+ # If no changes are detected, there is nothing to commit.
+ if "nothing to commit" not in str(e):
+ raise e
+
+ try:
+ self.git_push(
+ upstream=f"origin {self.current_branch}",
+ blocking=blocking,
+ auto_lfs_prune=auto_lfs_prune,
+ )
+ except OSError as e:
+ # If no changes are detected, there is nothing to commit.
+ if "could not read Username" in str(e):
+ raise OSError("Couldn't authenticate user for push. Did you set `token` to `True`?") from e
+ else:
+ raise e
+
+ os.chdir(current_working_directory)
+
+ def repocard_metadata_load(self) -> Optional[Dict]:
+ filepath = os.path.join(self.local_dir, REPOCARD_NAME)
+ if os.path.isfile(filepath):
+ return metadata_load(filepath)
+ return None
+
+ def repocard_metadata_save(self, data: Dict) -> None:
+ return metadata_save(os.path.join(self.local_dir, REPOCARD_NAME), data)
+
+ @property
+ def commands_failed(self):
+ """
+ Returns the asynchronous commands that failed.
+ """
+ return [c for c in self.command_queue if c.status > 0]
+
+ @property
+ def commands_in_progress(self):
+ """
+ Returns the asynchronous commands that are currently in progress.
+ """
+ return [c for c in self.command_queue if not c.is_done]
+
+ def wait_for_commands(self):
+ """
+ Blocking method: blocks all subsequent execution until all commands have
+ been processed.
+ """
+ index = 0
+ for command_failed in self.commands_failed:
+ logger.error(f"The {command_failed.title} command with PID {command_failed._process.pid} failed.")
+ logger.error(command_failed.stderr)
+
+ while self.commands_in_progress:
+ if index % 10 == 0:
+ logger.warning(
+ f"Waiting for the following commands to finish before shutting down: {self.commands_in_progress}."
+ )
+
+ index += 1
+
+ time.sleep(1)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bb6c2d0a1556d8119d3b400a9adcff73b34e6e5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/__init__.py
@@ -0,0 +1,20 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ruff: noqa: F401
+"""Contains helpers to serialize tensors."""
+
+from ._base import StateDictSplit, split_state_dict_into_shards_factory
+from ._numpy import split_numpy_state_dict_into_shards
+from ._tensorflow import split_tf_state_dict_into_shards
+from ._torch import split_torch_state_dict_into_shards
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_base.py b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7f7bba89263d31d5facb1ebed66c5f701dba973
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_base.py
@@ -0,0 +1,169 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains helpers to split tensors into shards."""
+
+from dataclasses import dataclass, field
+from typing import Any, Callable, Dict, List, Optional, TypeVar
+
+from .. import logging
+
+
+TensorT = TypeVar("TensorT")
+TensorSizeFn_T = Callable[[TensorT], int]
+StorageIDFn_T = Callable[[TensorT], Optional[Any]]
+
+MAX_SHARD_SIZE = 5_000_000_000 # 5GB
+FILENAME_PATTERN = "model{suffix}.safetensors"
+
+logger = logging.get_logger(__file__)
+
+
+@dataclass
+class StateDictSplit:
+ is_sharded: bool = field(init=False)
+ metadata: Dict[str, Any]
+ filename_to_tensors: Dict[str, List[str]]
+ tensor_to_filename: Dict[str, str]
+
+ def __post_init__(self):
+ self.is_sharded = len(self.filename_to_tensors) > 1
+
+
+def split_state_dict_into_shards_factory(
+ state_dict: Dict[str, TensorT],
+ *,
+ get_tensor_size: TensorSizeFn_T,
+ get_storage_id: StorageIDFn_T = lambda tensor: None,
+ filename_pattern: str = FILENAME_PATTERN,
+ max_shard_size: int = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, Tensor]`):
+ The state dictionary to save.
+ get_tensor_size (`Callable[[Tensor], int]`):
+ A function that returns the size of a tensor in bytes.
+ get_storage_id (`Callable[[Tensor], Optional[Any]]`, *optional*):
+ A function that returns a unique identifier to a tensor storage. Multiple different tensors can share the
+ same underlying storage. This identifier is guaranteed to be unique and constant for this tensor's storage
+ during its lifetime. Two tensor storages with non-overlapping lifetimes may have the same id.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+ """
+ storage_id_to_tensors: Dict[Any, List[str]] = {}
+
+ shard_list: List[Dict[str, TensorT]] = []
+ current_shard: Dict[str, TensorT] = {}
+ current_shard_size = 0
+ total_size = 0
+
+ for key, tensor in state_dict.items():
+ # when bnb serialization is used the weights in the state dict can be strings
+ # check: https://github.com/huggingface/transformers/pull/24416 for more details
+ if isinstance(tensor, str):
+ logger.info("Skipping tensor %s as it is a string (bnb serialization)", key)
+ continue
+
+ # If a `tensor` shares the same underlying storage as another tensor, we put `tensor` in the same `block`
+ storage_id = get_storage_id(tensor)
+ if storage_id is not None:
+ if storage_id in storage_id_to_tensors:
+ # We skip this tensor for now and will reassign to correct shard later
+ storage_id_to_tensors[storage_id].append(key)
+ continue
+ else:
+ # This is the first tensor with this storage_id, we create a new entry
+ # in the storage_id_to_tensors dict => we will assign the shard id later
+ storage_id_to_tensors[storage_id] = [key]
+
+ # Compute tensor size
+ tensor_size = get_tensor_size(tensor)
+
+ # If this tensor is bigger than the maximal size, we put it in its own shard
+ if tensor_size > max_shard_size:
+ total_size += tensor_size
+ shard_list.append({key: tensor})
+ continue
+
+ # If this tensor is going to tip up over the maximal size, we split.
+ # Current shard already has some tensors, we add it to the list of shards and create a new one.
+ if current_shard_size + tensor_size > max_shard_size:
+ shard_list.append(current_shard)
+ current_shard = {}
+ current_shard_size = 0
+
+ # Add the tensor to the current shard
+ current_shard[key] = tensor
+ current_shard_size += tensor_size
+ total_size += tensor_size
+
+ # Add the last shard
+ if len(current_shard) > 0:
+ shard_list.append(current_shard)
+ nb_shards = len(shard_list)
+
+ # Loop over the tensors that share the same storage and assign them together
+ for storage_id, keys in storage_id_to_tensors.items():
+ # Let's try to find the shard where the first tensor of this storage is and put all tensors in the same shard
+ for shard in shard_list:
+ if keys[0] in shard:
+ for key in keys:
+ shard[key] = state_dict[key]
+ break
+
+ # If we only have one shard, we return it => no need to build the index
+ if nb_shards == 1:
+ filename = filename_pattern.format(suffix="")
+ return StateDictSplit(
+ metadata={"total_size": total_size},
+ filename_to_tensors={filename: list(state_dict.keys())},
+ tensor_to_filename={key: filename for key in state_dict.keys()},
+ )
+
+ # Now that each tensor is assigned to a shard, let's assign a filename to each shard
+ tensor_name_to_filename = {}
+ filename_to_tensors = {}
+ for idx, shard in enumerate(shard_list):
+ filename = filename_pattern.format(suffix=f"-{idx+1:05d}-of-{nb_shards:05d}")
+ for key in shard:
+ tensor_name_to_filename[key] = filename
+ filename_to_tensors[filename] = list(shard.keys())
+
+ # Build the index and return
+ return StateDictSplit(
+ metadata={"total_size": total_size},
+ filename_to_tensors=filename_to_tensors,
+ tensor_to_filename=tensor_name_to_filename,
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_numpy.py b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_numpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..214c77d9acde2a14069f403ed337e6c8c57047ad
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_numpy.py
@@ -0,0 +1,68 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains numpy-specific helpers."""
+
+from typing import TYPE_CHECKING, Dict
+
+from ._base import FILENAME_PATTERN, MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory
+
+
+if TYPE_CHECKING:
+ import numpy as np
+
+
+def split_numpy_state_dict_into_shards(
+ state_dict: Dict[str, "np.ndarray"],
+ *,
+ filename_pattern: str = FILENAME_PATTERN,
+ max_shard_size: int = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, np.ndarray]`):
+ The state dictionary to save.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+ """
+ return split_state_dict_into_shards_factory(
+ state_dict,
+ max_shard_size=max_shard_size,
+ filename_pattern=filename_pattern,
+ get_tensor_size=get_tensor_size,
+ )
+
+
+def get_tensor_size(tensor: "np.ndarray") -> int:
+ return tensor.nbytes
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_tensorflow.py b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_tensorflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8d752c083063d3e9772b69982e8f979fbda53ea
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_tensorflow.py
@@ -0,0 +1,94 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains tensorflow-specific helpers."""
+
+import math
+import re
+from typing import TYPE_CHECKING, Dict
+
+from ._base import MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory
+
+
+if TYPE_CHECKING:
+ import tensorflow as tf
+
+
+def split_tf_state_dict_into_shards(
+ state_dict: Dict[str, "tf.Tensor"],
+ *,
+ filename_pattern: str = "tf_model{suffix}.h5",
+ max_shard_size: int = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, Tensor]`):
+ The state dictionary to save.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"tf_model{suffix}.h5"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+ """
+ return split_state_dict_into_shards_factory(
+ state_dict,
+ max_shard_size=max_shard_size,
+ filename_pattern=filename_pattern,
+ get_tensor_size=get_tensor_size,
+ )
+
+
+def get_tensor_size(tensor: "tf.Tensor") -> int:
+ # Return `math.ceil` since dtype byte size can be a float (e.g., 0.125 for tf.bool).
+ # Better to overestimate than underestimate.
+ return math.ceil(tensor.numpy().size * _dtype_byte_size_tf(tensor.dtype))
+
+
+def _dtype_byte_size_tf(dtype) -> float:
+ """
+ Returns the size (in bytes) occupied by one parameter of type `dtype`.
+ Taken from https://github.com/huggingface/transformers/blob/74d9d0cebb0263a3f8ab9c280569170cc74651d0/src/transformers/modeling_tf_utils.py#L608.
+ NOTE: why not `tensor.numpy().nbytes`?
+ Example:
+ ```py
+ >>> _dtype_byte_size(tf.float32)
+ 4
+ ```
+ """
+ import tensorflow as tf
+
+ if dtype == tf.bool:
+ return 1 / 8
+ bit_search = re.search(r"[^\d](\d+)$", dtype.name)
+ if bit_search is None:
+ raise ValueError(f"`dtype` is not a valid dtype: {dtype}.")
+ bit_size = int(bit_search.groups()[0])
+ return bit_size // 8
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_torch.py b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_torch.py
new file mode 100644
index 0000000000000000000000000000000000000000..00ab7e2c80d7a8fde928588c284213fca2100cf3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/serialization/_torch.py
@@ -0,0 +1,200 @@
+# Copyright 2024 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains pytorch-specific helpers."""
+
+import importlib
+from functools import lru_cache
+from typing import TYPE_CHECKING, Dict, Tuple
+
+from ._base import FILENAME_PATTERN, MAX_SHARD_SIZE, StateDictSplit, split_state_dict_into_shards_factory
+
+
+if TYPE_CHECKING:
+ import torch
+
+
+def split_torch_state_dict_into_shards(
+ state_dict: Dict[str, "torch.Tensor"],
+ *,
+ filename_pattern: str = FILENAME_PATTERN,
+ max_shard_size: int = MAX_SHARD_SIZE,
+) -> StateDictSplit:
+ """
+ Split a model state dictionary in shards so that each shard is smaller than a given size.
+
+ The shards are determined by iterating through the `state_dict` in the order of its keys. There is no optimization
+ made to make each shard as close as possible to the maximum size passed. For example, if the limit is 10GB and we
+ have tensors of sizes [6GB, 6GB, 2GB, 6GB, 2GB, 2GB] they will get sharded as [6GB], [6+2GB], [6+2+2GB] and not
+ [6+2+2GB], [6+2GB], [6GB].
+
+
+
+ If one of the model's tensor is bigger than `max_shard_size`, it will end up in its own shard which will have a
+ size greater than `max_shard_size`.
+
+
+
+ Args:
+ state_dict (`Dict[str, torch.Tensor]`):
+ The state dictionary to save.
+ filename_pattern (`str`, *optional*):
+ The pattern to generate the files names in which the model will be saved. Pattern must be a string that
+ can be formatted with `filename_pattern.format(suffix=...)` and must contain the keyword `suffix`
+ Defaults to `"model{suffix}.safetensors"`.
+ max_shard_size (`int` or `str`, *optional*):
+ The maximum size of each shard, in bytes. Defaults to 5GB.
+
+ Returns:
+ [`StateDictSplit`]: A `StateDictSplit` object containing the shards and the index to retrieve them.
+
+ Example:
+ ```py
+ >>> import json
+ >>> import os
+ >>> from safetensors.torch import save_file as safe_save_file
+ >>> from huggingface_hub import split_torch_state_dict_into_shards
+
+ >>> def save_state_dict(state_dict: Dict[str, torch.Tensor], save_directory: str):
+ ... state_dict_split = split_torch_state_dict_into_shards(state_dict)
+ ... for filename, tensors in state_dict_split.filename_to_tensors.values():
+ ... shard = {tensor: state_dict[tensor] for tensor in tensors}
+ ... safe_save_file(
+ ... shard,
+ ... os.path.join(save_directory, filename),
+ ... metadata={"format": "pt"},
+ ... )
+ ... if state_dict_split.is_sharded:
+ ... index = {
+ ... "metadata": state_dict_split.metadata,
+ ... "weight_map": state_dict_split.tensor_to_filename,
+ ... }
+ ... with open(os.path.join(save_directory, "model.safetensors.index.json"), "w") as f:
+ ... f.write(json.dumps(index, indent=2))
+ ```
+ """
+ return split_state_dict_into_shards_factory(
+ state_dict,
+ max_shard_size=max_shard_size,
+ filename_pattern=filename_pattern,
+ get_tensor_size=get_tensor_size,
+ get_storage_id=get_storage_id,
+ )
+
+
+def get_storage_id(tensor: "torch.Tensor") -> Tuple["torch.device", int, int]:
+ """
+ Return unique identifier to a tensor storage.
+
+ Multiple different tensors can share the same underlying storage. For
+ example, "meta" tensors all share the same storage, and thus their identifier will all be equal. This identifier is
+ guaranteed to be unique and constant for this tensor's storage during its lifetime. Two tensor storages with
+ non-overlapping lifetimes may have the same id.
+
+ Taken from https://github.com/huggingface/transformers/blob/1ecf5f7c982d761b4daaa96719d162c324187c64/src/transformers/pytorch_utils.py#L278.
+ """
+ if tensor.device.type == "xla" and is_torch_tpu_available():
+ # NOTE: xla tensors dont have storage
+ # use some other unique id to distinguish.
+ # this is a XLA tensor, it must be created using torch_xla's
+ # device. So the following import is safe:
+ import torch_xla
+
+ unique_id = torch_xla._XLAC._xla_get_tensor_id(tensor)
+ else:
+ unique_id = storage_ptr(tensor)
+
+ return tensor.device, unique_id, get_storage_size(tensor)
+
+
+def get_tensor_size(tensor: "torch.Tensor") -> int:
+ return tensor.numel() * tensor.element_size()
+
+
+@lru_cache()
+def is_torch_tpu_available(check_device=True):
+ """
+ Checks if `torch_xla` is installed and potentially if a TPU is in the environment
+
+ Taken from https://github.com/huggingface/transformers/blob/1ecf5f7c982d761b4daaa96719d162c324187c64/src/transformers/utils/import_utils.py#L463.
+ """
+ if importlib.util.find_spec("torch_xla") is not None:
+ if check_device:
+ # We need to check if `xla_device` can be found, will raise a RuntimeError if not
+ try:
+ import torch_xla.core.xla_model as xm
+
+ _ = xm.xla_device()
+ return True
+ except RuntimeError:
+ return False
+ return True
+ return False
+
+
+def storage_ptr(tensor: "torch.Tensor") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/08db34094e9e59e2f9218f2df133b7b4aaff5a99/bindings/python/py_src/safetensors/torch.py#L11C1-L20C21.
+ """
+ try:
+ return tensor.untyped_storage().data_ptr()
+ except Exception:
+ # Fallback for torch==1.10
+ try:
+ return tensor.storage().data_ptr()
+ except NotImplementedError:
+ # Fallback for meta storage
+ return 0
+
+
+def get_storage_size(tensor: "torch.Tensor") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/08db34094e9e59e2f9218f2df133b7b4aaff5a99/bindings/python/py_src/safetensors/torch.py#L31C1-L41C59
+ """
+ try:
+ return tensor.untyped_storage().nbytes()
+ except AttributeError:
+ # Fallback for torch==1.10
+ try:
+ return tensor.storage().size() * _get_dtype_size(tensor.dtype)
+ except NotImplementedError:
+ # Fallback for meta storage
+ # On torch >=2.0 this is the tensor size
+ return tensor.nelement() * _get_dtype_size(tensor.dtype)
+
+
+@lru_cache()
+def _get_dtype_size(dtype: "torch.dtype") -> int:
+ """
+ Taken from https://github.com/huggingface/safetensors/blob/08db34094e9e59e2f9218f2df133b7b4aaff5a99/bindings/python/py_src/safetensors/torch.py#L344
+ """
+ import torch
+
+ # torch.float8 formats require 2.1; we do not support these dtypes on earlier versions
+ _float8_e4m3fn = getattr(torch, "float8_e4m3fn", None)
+ _float8_e5m2 = getattr(torch, "float8_e5m2", None)
+ _SIZE = {
+ torch.int64: 8,
+ torch.float32: 4,
+ torch.int32: 4,
+ torch.bfloat16: 2,
+ torch.float16: 2,
+ torch.int16: 2,
+ torch.uint8: 1,
+ torch.int8: 1,
+ torch.bool: 1,
+ torch.float64: 8,
+ _float8_e4m3fn: 1,
+ _float8_e5m2: 1,
+ }
+ return _SIZE[dtype]
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/templates/datasetcard_template.md b/.venv/lib/python3.10/site-packages/huggingface_hub/templates/datasetcard_template.md
new file mode 100644
index 0000000000000000000000000000000000000000..9af29ebbed93653ec74a8952e314e7554323ef15
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/templates/datasetcard_template.md
@@ -0,0 +1,143 @@
+---
+# For reference on dataset card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/datasetcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/datasets-cards
+{{ card_data }}
+---
+
+# Dataset Card for {{ pretty_name | default("Dataset Name", true) }}
+
+
+
+{{ dataset_summary | default("", true) }}
+
+## Dataset Details
+
+### Dataset Description
+
+
+
+{{ dataset_description | default("", true) }}
+
+- **Curated by:** {{ curators | default("[More Information Needed]", true)}}
+- **Funded by [optional]:** {{ funded_by | default("[More Information Needed]", true)}}
+- **Shared by [optional]:** {{ shared_by | default("[More Information Needed]", true)}}
+- **Language(s) (NLP):** {{ language | default("[More Information Needed]", true)}}
+- **License:** {{ license | default("[More Information Needed]", true)}}
+
+### Dataset Sources [optional]
+
+
+
+- **Repository:** {{ repo | default("[More Information Needed]", true)}}
+- **Paper [optional]:** {{ paper | default("[More Information Needed]", true)}}
+- **Demo [optional]:** {{ demo | default("[More Information Needed]", true)}}
+
+## Uses
+
+
+
+### Direct Use
+
+
+
+{{ direct_use | default("[More Information Needed]", true)}}
+
+### Out-of-Scope Use
+
+
+
+{{ out_of_scope_use | default("[More Information Needed]", true)}}
+
+## Dataset Structure
+
+
+
+{{ dataset_structure | default("[More Information Needed]", true)}}
+
+## Dataset Creation
+
+### Curation Rationale
+
+
+
+{{ curation_rationale_section | default("[More Information Needed]", true)}}
+
+### Source Data
+
+
+
+#### Data Collection and Processing
+
+
+
+{{ data_collection_and_processing_section | default("[More Information Needed]", true)}}
+
+#### Who are the source data producers?
+
+
+
+{{ source_data_producers_section | default("[More Information Needed]", true)}}
+
+### Annotations [optional]
+
+
+
+#### Annotation process
+
+
+
+{{ annotation_process_section | default("[More Information Needed]", true)}}
+
+#### Who are the annotators?
+
+
+
+{{ who_are_annotators_section | default("[More Information Needed]", true)}}
+
+#### Personal and Sensitive Information
+
+
+
+{{ personal_and_sensitive_information | default("[More Information Needed]", true)}}
+
+## Bias, Risks, and Limitations
+
+
+
+{{ bias_risks_limitations | default("[More Information Needed]", true)}}
+
+### Recommendations
+
+
+
+{{ bias_recommendations | default("Users should be made aware of the risks, biases and limitations of the dataset. More information needed for further recommendations.", true)}}
+
+## Citation [optional]
+
+
+
+**BibTeX:**
+
+{{ citation_bibtex | default("[More Information Needed]", true)}}
+
+**APA:**
+
+{{ citation_apa | default("[More Information Needed]", true)}}
+
+## Glossary [optional]
+
+
+
+{{ glossary | default("[More Information Needed]", true)}}
+
+## More Information [optional]
+
+{{ more_information | default("[More Information Needed]", true)}}
+
+## Dataset Card Authors [optional]
+
+{{ dataset_card_authors | default("[More Information Needed]", true)}}
+
+## Dataset Card Contact
+
+{{ dataset_card_contact | default("[More Information Needed]", true)}}
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/templates/modelcard_template.md b/.venv/lib/python3.10/site-packages/huggingface_hub/templates/modelcard_template.md
new file mode 100644
index 0000000000000000000000000000000000000000..79ca15e4547debac763b390ef8e4b715e6f6403f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/templates/modelcard_template.md
@@ -0,0 +1,200 @@
+---
+# For reference on model card metadata, see the spec: https://github.com/huggingface/hub-docs/blob/main/modelcard.md?plain=1
+# Doc / guide: https://huggingface.co/docs/hub/model-cards
+{{ card_data }}
+---
+
+# Model Card for {{ model_id | default("Model ID", true) }}
+
+
+
+{{ model_summary | default("", true) }}
+
+## Model Details
+
+### Model Description
+
+
+
+{{ model_description | default("", true) }}
+
+- **Developed by:** {{ developers | default("[More Information Needed]", true)}}
+- **Funded by [optional]:** {{ funded_by | default("[More Information Needed]", true)}}
+- **Shared by [optional]:** {{ shared_by | default("[More Information Needed]", true)}}
+- **Model type:** {{ model_type | default("[More Information Needed]", true)}}
+- **Language(s) (NLP):** {{ language | default("[More Information Needed]", true)}}
+- **License:** {{ license | default("[More Information Needed]", true)}}
+- **Finetuned from model [optional]:** {{ base_model | default("[More Information Needed]", true)}}
+
+### Model Sources [optional]
+
+
+
+- **Repository:** {{ repo | default("[More Information Needed]", true)}}
+- **Paper [optional]:** {{ paper | default("[More Information Needed]", true)}}
+- **Demo [optional]:** {{ demo | default("[More Information Needed]", true)}}
+
+## Uses
+
+
+
+### Direct Use
+
+
+
+{{ direct_use | default("[More Information Needed]", true)}}
+
+### Downstream Use [optional]
+
+
+
+{{ downstream_use | default("[More Information Needed]", true)}}
+
+### Out-of-Scope Use
+
+
+
+{{ out_of_scope_use | default("[More Information Needed]", true)}}
+
+## Bias, Risks, and Limitations
+
+
+
+{{ bias_risks_limitations | default("[More Information Needed]", true)}}
+
+### Recommendations
+
+
+
+{{ bias_recommendations | default("Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.", true)}}
+
+## How to Get Started with the Model
+
+Use the code below to get started with the model.
+
+{{ get_started_code | default("[More Information Needed]", true)}}
+
+## Training Details
+
+### Training Data
+
+
+
+{{ training_data | default("[More Information Needed]", true)}}
+
+### Training Procedure
+
+
+
+#### Preprocessing [optional]
+
+{{ preprocessing | default("[More Information Needed]", true)}}
+
+
+#### Training Hyperparameters
+
+- **Training regime:** {{ training_regime | default("[More Information Needed]", true)}}
+
+#### Speeds, Sizes, Times [optional]
+
+
+
+{{ speeds_sizes_times | default("[More Information Needed]", true)}}
+
+## Evaluation
+
+
+
+### Testing Data, Factors & Metrics
+
+#### Testing Data
+
+
+
+{{ testing_data | default("[More Information Needed]", true)}}
+
+#### Factors
+
+
+
+{{ testing_factors | default("[More Information Needed]", true)}}
+
+#### Metrics
+
+
+
+{{ testing_metrics | default("[More Information Needed]", true)}}
+
+### Results
+
+{{ results | default("[More Information Needed]", true)}}
+
+#### Summary
+
+{{ results_summary | default("", true) }}
+
+## Model Examination [optional]
+
+
+
+{{ model_examination | default("[More Information Needed]", true)}}
+
+## Environmental Impact
+
+
+
+Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
+
+- **Hardware Type:** {{ hardware_type | default("[More Information Needed]", true)}}
+- **Hours used:** {{ hours_used | default("[More Information Needed]", true)}}
+- **Cloud Provider:** {{ cloud_provider | default("[More Information Needed]", true)}}
+- **Compute Region:** {{ cloud_region | default("[More Information Needed]", true)}}
+- **Carbon Emitted:** {{ co2_emitted | default("[More Information Needed]", true)}}
+
+## Technical Specifications [optional]
+
+### Model Architecture and Objective
+
+{{ model_specs | default("[More Information Needed]", true)}}
+
+### Compute Infrastructure
+
+{{ compute_infrastructure | default("[More Information Needed]", true)}}
+
+#### Hardware
+
+{{ hardware_requirements | default("[More Information Needed]", true)}}
+
+#### Software
+
+{{ software | default("[More Information Needed]", true)}}
+
+## Citation [optional]
+
+
+
+**BibTeX:**
+
+{{ citation_bibtex | default("[More Information Needed]", true)}}
+
+**APA:**
+
+{{ citation_apa | default("[More Information Needed]", true)}}
+
+## Glossary [optional]
+
+
+
+{{ glossary | default("[More Information Needed]", true)}}
+
+## More Information [optional]
+
+{{ more_information | default("[More Information Needed]", true)}}
+
+## Model Card Authors [optional]
+
+{{ model_card_authors | default("[More Information Needed]", true)}}
+
+## Model Card Contact
+
+{{ model_card_contact | default("[More Information Needed]", true)}}
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__init__.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..97c236ecec3233c863facf2207a0038cb173bcea
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__init__.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# ruff: noqa: F401
+
+from . import tqdm as _tqdm # _tqdm is the module
+from ._cache_assets import cached_assets_path
+from ._cache_manager import (
+ CachedFileInfo,
+ CachedRepoInfo,
+ CachedRevisionInfo,
+ CacheNotFound,
+ CorruptedCacheException,
+ DeleteCacheStrategy,
+ HFCacheInfo,
+ scan_cache_dir,
+)
+from ._chunk_utils import chunk_iterable
+from ._datetime import parse_datetime
+from ._errors import (
+ BadRequestError,
+ DisabledRepoError,
+ EntryNotFoundError,
+ FileMetadataError,
+ GatedRepoError,
+ HfHubHTTPError,
+ LocalEntryNotFoundError,
+ RepositoryNotFoundError,
+ RevisionNotFoundError,
+ hf_raise_for_status,
+)
+from ._experimental import experimental
+from ._fixes import SoftTemporaryDirectory, WeakFileLock, yaml_dump
+from ._git_credential import list_credential_helpers, set_git_credential, unset_git_credential
+from ._headers import LocalTokenNotFoundError, build_hf_headers, get_token_to_send
+from ._hf_folder import HfFolder
+from ._http import (
+ OfflineModeIsEnabled,
+ configure_http_backend,
+ fix_hf_endpoint_in_url,
+ get_session,
+ http_backoff,
+ reset_sessions,
+)
+from ._pagination import paginate
+from ._paths import IGNORE_GIT_FOLDER_PATTERNS, filter_repo_objects
+from ._runtime import (
+ dump_environment_info,
+ get_aiohttp_version,
+ get_fastai_version,
+ get_fastcore_version,
+ get_gradio_version,
+ get_graphviz_version,
+ get_hf_hub_version,
+ get_hf_transfer_version,
+ get_jinja_version,
+ get_minijinja_version,
+ get_numpy_version,
+ get_pillow_version,
+ get_pydantic_version,
+ get_pydot_version,
+ get_python_version,
+ get_tensorboard_version,
+ get_tf_version,
+ get_torch_version,
+ is_aiohttp_available,
+ is_fastai_available,
+ is_fastcore_available,
+ is_google_colab,
+ is_gradio_available,
+ is_graphviz_available,
+ is_hf_transfer_available,
+ is_jinja_available,
+ is_minijinja_available,
+ is_notebook,
+ is_numpy_available,
+ is_package_available,
+ is_pillow_available,
+ is_pydantic_available,
+ is_pydot_available,
+ is_safetensors_available,
+ is_tensorboard_available,
+ is_tf_available,
+ is_torch_available,
+)
+from ._safetensors import (
+ NotASafetensorsRepoError,
+ SafetensorsFileMetadata,
+ SafetensorsParsingError,
+ SafetensorsRepoMetadata,
+ TensorInfo,
+)
+from ._subprocess import capture_output, run_interactive_subprocess, run_subprocess
+from ._telemetry import send_telemetry
+from ._token import get_token
+from ._typing import is_jsonable
+from ._validators import (
+ HFValidationError,
+ smoothly_deprecate_use_auth_token,
+ validate_hf_hub_args,
+ validate_repo_id,
+)
+from .tqdm import (
+ are_progress_bars_disabled,
+ disable_progress_bars,
+ enable_progress_bars,
+ tqdm,
+ tqdm_stream_file,
+)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b02e23e0de16f28df05401ec7d6884435880034b
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_assets.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_assets.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f55bf65a5383d2adab8fab7ff722c16ee210ae8b
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_assets.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_manager.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_manager.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..706df4717e060ca50fa9297dae7a39843fed33c1
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_cache_manager.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_chunk_utils.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_chunk_utils.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6d0baab8f3f2540ecd47a8d39a6ef1fc695a7f59
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_chunk_utils.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_datetime.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_datetime.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c6eeee1fc74a03a399f300fe6cd0b3cf0a3e9b73
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_datetime.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_deprecation.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_deprecation.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..98a5effda854ee29c6cb6f966b10534a643f22de
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_deprecation.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_errors.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_errors.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0e03c7c3cc2a9a1668129a2ffab5e970d1f75b71
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_errors.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_experimental.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_experimental.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4beaf4718603727a7e9c15bedec8701e4c822a44
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_experimental.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_fixes.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_fixes.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..08172ceeb0b7ef424b3e6337837ca4d345d5b7d4
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_fixes.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_git_credential.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_git_credential.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9523bbd5e6af61fee6daf9dcd7de3bcc6f8b9a9d
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_git_credential.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_headers.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_headers.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6e26665cb42e3d8c8fe97c1b385f3a1a56af6f21
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_headers.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_hf_folder.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_hf_folder.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..209d55a6ac01ce7d8b4028b7937a08bfe385c902
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_hf_folder.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_http.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_http.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..79f18632325d0adbd32fb9d0b7c76a561329d17c
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_http.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_pagination.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_pagination.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2a03cbb1e6f335a391ebdf8e31ae7c593280a5d0
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_pagination.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_paths.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_paths.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3ccc5d0bd6598322363178b1a54fd8f6201e0651
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_paths.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_runtime.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_runtime.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2a8f72c8996711498e021facfbc94e8dd1b411f4
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_runtime.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_safetensors.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_safetensors.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ea188ffc1093b4c58d082c97d1f6334211bf0c6f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_safetensors.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_subprocess.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_subprocess.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..875fb4a439313573a8328a13ebe17dbed6344a00
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_subprocess.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_telemetry.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_telemetry.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c8851a23f4703026dc9af8c971e2e6a4067a01a5
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_telemetry.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_token.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_token.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..93239895d1925f8b8e8c3ee2578d596484ad3d70
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_token.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_typing.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_typing.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8f7dfa15910fb2f7133f43e39fa516c8dd449284
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_typing.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_validators.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_validators.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2b5af89efcf677fa1b32d1982c99b6d09b518251
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/_validators.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/endpoint_helpers.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/endpoint_helpers.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..31dcca430ce1e0f079f4d6ca71be141a92a627af
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/endpoint_helpers.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/insecure_hashlib.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/insecure_hashlib.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..451e9023b6c0b27284780800fa3c26c68e296d7c
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/insecure_hashlib.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/logging.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/logging.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7ef31d0e1d52291cd03ba7190741b0e197fd419e
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/logging.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/sha.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/sha.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ca16aa43a72cdf275f3101501a24dee9a6e941e8
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/sha.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/tqdm.cpython-310.pyc b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/tqdm.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c83e5808ae2df155fadf22a63b3e86f2b937c1ba
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/__pycache__/tqdm.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_assets.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_assets.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5d435df9b0bb0c67c0bcb5ef65711e9aef367f6
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_assets.py
@@ -0,0 +1,135 @@
+# coding=utf-8
+# Copyright 2019-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from pathlib import Path
+from typing import Union
+
+from ..constants import HF_ASSETS_CACHE
+
+
+def cached_assets_path(
+ library_name: str,
+ namespace: str = "default",
+ subfolder: str = "default",
+ *,
+ assets_dir: Union[str, Path, None] = None,
+):
+ """Return a folder path to cache arbitrary files.
+
+ `huggingface_hub` provides a canonical folder path to store assets. This is the
+ recommended way to integrate cache in a downstream library as it will benefit from
+ the builtins tools to scan and delete the cache properly.
+
+ The distinction is made between files cached from the Hub and assets. Files from the
+ Hub are cached in a git-aware manner and entirely managed by `huggingface_hub`. See
+ [related documentation](https://huggingface.co/docs/huggingface_hub/how-to-cache).
+ All other files that a downstream library caches are considered to be "assets"
+ (files downloaded from external sources, extracted from a .tar archive, preprocessed
+ for training,...).
+
+ Once the folder path is generated, it is guaranteed to exist and to be a directory.
+ The path is based on 3 levels of depth: the library name, a namespace and a
+ subfolder. Those 3 levels grants flexibility while allowing `huggingface_hub` to
+ expect folders when scanning/deleting parts of the assets cache. Within a library,
+ it is expected that all namespaces share the same subset of subfolder names but this
+ is not a mandatory rule. The downstream library has then full control on which file
+ structure to adopt within its cache. Namespace and subfolder are optional (would
+ default to a `"default/"` subfolder) but library name is mandatory as we want every
+ downstream library to manage its own cache.
+
+ Expected tree:
+ ```text
+ assets/
+ └── datasets/
+ │ ├── SQuAD/
+ │ │ ├── downloaded/
+ │ │ ├── extracted/
+ │ │ └── processed/
+ │ ├── Helsinki-NLP--tatoeba_mt/
+ │ ├── downloaded/
+ │ ├── extracted/
+ │ └── processed/
+ └── transformers/
+ ├── default/
+ │ ├── something/
+ ├── bert-base-cased/
+ │ ├── default/
+ │ └── training/
+ hub/
+ └── models--julien-c--EsperBERTo-small/
+ ├── blobs/
+ │ ├── (...)
+ │ ├── (...)
+ ├── refs/
+ │ └── (...)
+ └── [ 128] snapshots/
+ ├── 2439f60ef33a0d46d85da5001d52aeda5b00ce9f/
+ │ ├── (...)
+ └── bbc77c8132af1cc5cf678da3f1ddf2de43606d48/
+ └── (...)
+ ```
+
+
+ Args:
+ library_name (`str`):
+ Name of the library that will manage the cache folder. Example: `"dataset"`.
+ namespace (`str`, *optional*, defaults to "default"):
+ Namespace to which the data belongs. Example: `"SQuAD"`.
+ subfolder (`str`, *optional*, defaults to "default"):
+ Subfolder in which the data will be stored. Example: `extracted`.
+ assets_dir (`str`, `Path`, *optional*):
+ Path to the folder where assets are cached. This must not be the same folder
+ where Hub files are cached. Defaults to `HF_HOME / "assets"` if not provided.
+ Can also be set with `HF_ASSETS_CACHE` environment variable.
+
+ Returns:
+ Path to the cache folder (`Path`).
+
+ Example:
+ ```py
+ >>> from huggingface_hub import cached_assets_path
+
+ >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="download")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/download')
+
+ >>> cached_assets_path(library_name="datasets", namespace="SQuAD", subfolder="extracted")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/SQuAD/extracted')
+
+ >>> cached_assets_path(library_name="datasets", namespace="Helsinki-NLP/tatoeba_mt")
+ PosixPath('/home/wauplin/.cache/huggingface/extra/datasets/Helsinki-NLP--tatoeba_mt/default')
+
+ >>> cached_assets_path(library_name="datasets", assets_dir="/tmp/tmp123456")
+ PosixPath('/tmp/tmp123456/datasets/default/default')
+ ```
+ """
+ # Resolve assets_dir
+ if assets_dir is None:
+ assets_dir = HF_ASSETS_CACHE
+ assets_dir = Path(assets_dir).expanduser().resolve()
+
+ # Avoid names that could create path issues
+ for part in (" ", "/", "\\"):
+ library_name = library_name.replace(part, "--")
+ namespace = namespace.replace(part, "--")
+ subfolder = subfolder.replace(part, "--")
+
+ # Path to subfolder is created
+ path = assets_dir / library_name / namespace / subfolder
+ try:
+ path.mkdir(exist_ok=True, parents=True)
+ except (FileExistsError, NotADirectoryError):
+ raise ValueError(f"Corrupted assets folder: cannot create directory because of an existing file ({path}).")
+
+ # Return
+ return path
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_manager.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..78ce8fdadc58ac3112324be25c02cbb56637f86e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_cache_manager.py
@@ -0,0 +1,813 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to manage the HF cache directory."""
+
+import os
+import shutil
+import time
+from collections import defaultdict
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Dict, FrozenSet, List, Literal, Optional, Set, Union
+
+from ..constants import HF_HUB_CACHE
+from . import logging
+
+
+logger = logging.get_logger(__name__)
+
+REPO_TYPE_T = Literal["model", "dataset", "space"]
+
+# List of OS-created helper files that need to be ignored
+FILES_TO_IGNORE = [".DS_Store"]
+
+
+class CacheNotFound(Exception):
+ """Exception thrown when the Huggingface cache is not found."""
+
+ cache_dir: Union[str, Path]
+
+ def __init__(self, msg: str, cache_dir: Union[str, Path], *args, **kwargs):
+ super().__init__(msg, *args, **kwargs)
+ self.cache_dir = cache_dir
+
+
+class CorruptedCacheException(Exception):
+ """Exception for any unexpected structure in the Huggingface cache-system."""
+
+
+@dataclass(frozen=True)
+class CachedFileInfo:
+ """Frozen data structure holding information about a single cached file.
+
+ Args:
+ file_name (`str`):
+ Name of the file. Example: `config.json`.
+ file_path (`Path`):
+ Path of the file in the `snapshots` directory. The file path is a symlink
+ referring to a blob in the `blobs` folder.
+ blob_path (`Path`):
+ Path of the blob file. This is equivalent to `file_path.resolve()`.
+ size_on_disk (`int`):
+ Size of the blob file in bytes.
+ blob_last_accessed (`float`):
+ Timestamp of the last time the blob file has been accessed (from any
+ revision).
+ blob_last_modified (`float`):
+ Timestamp of the last time the blob file has been modified/created.
+
+
+
+ `blob_last_accessed` and `blob_last_modified` reliability can depend on the OS you
+ are using. See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
+ for more details.
+
+
+ """
+
+ file_name: str
+ file_path: Path
+ blob_path: Path
+ size_on_disk: int
+
+ blob_last_accessed: float
+ blob_last_modified: float
+
+ @property
+ def blob_last_accessed_str(self) -> str:
+ """
+ (property) Timestamp of the last time the blob file has been accessed (from any
+ revision), returned as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.blob_last_accessed)
+
+ @property
+ def blob_last_modified_str(self) -> str:
+ """
+ (property) Timestamp of the last time the blob file has been modified, returned
+ as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.blob_last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Size of the blob file as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+
+@dataclass(frozen=True)
+class CachedRevisionInfo:
+ """Frozen data structure holding information about a revision.
+
+ A revision correspond to a folder in the `snapshots` folder and is populated with
+ the exact tree structure as the repo on the Hub but contains only symlinks. A
+ revision can be either referenced by 1 or more `refs` or be "detached" (no refs).
+
+ Args:
+ commit_hash (`str`):
+ Hash of the revision (unique).
+ Example: `"9338f7b671827df886678df2bdd7cc7b4f36dffd"`.
+ snapshot_path (`Path`):
+ Path to the revision directory in the `snapshots` folder. It contains the
+ exact tree structure as the repo on the Hub.
+ files: (`FrozenSet[CachedFileInfo]`):
+ Set of [`~CachedFileInfo`] describing all files contained in the snapshot.
+ refs (`FrozenSet[str]`):
+ Set of `refs` pointing to this revision. If the revision has no `refs`, it
+ is considered detached.
+ Example: `{"main", "2.4.0"}` or `{"refs/pr/1"}`.
+ size_on_disk (`int`):
+ Sum of the blob file sizes that are symlink-ed by the revision.
+ last_modified (`float`):
+ Timestamp of the last time the revision has been created/modified.
+
+
+
+ `last_accessed` cannot be determined correctly on a single revision as blob files
+ are shared across revisions.
+
+
+
+
+
+ `size_on_disk` is not necessarily the sum of all file sizes because of possible
+ duplicated files. Besides, only blobs are taken into account, not the (negligible)
+ size of folders and symlinks.
+
+
+ """
+
+ commit_hash: str
+ snapshot_path: Path
+ size_on_disk: int
+ files: FrozenSet[CachedFileInfo]
+ refs: FrozenSet[str]
+
+ last_modified: float
+
+ @property
+ def last_modified_str(self) -> str:
+ """
+ (property) Timestamp of the last time the revision has been modified, returned
+ as a human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of the blob file sizes as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ @property
+ def nb_files(self) -> int:
+ """
+ (property) Total number of files in the revision.
+ """
+ return len(self.files)
+
+
+@dataclass(frozen=True)
+class CachedRepoInfo:
+ """Frozen data structure holding information about a cached repository.
+
+ Args:
+ repo_id (`str`):
+ Repo id of the repo on the Hub. Example: `"google/fleurs"`.
+ repo_type (`Literal["dataset", "model", "space"]`):
+ Type of the cached repo.
+ repo_path (`Path`):
+ Local path to the cached repo.
+ size_on_disk (`int`):
+ Sum of the blob file sizes in the cached repo.
+ nb_files (`int`):
+ Total number of blob files in the cached repo.
+ revisions (`FrozenSet[CachedRevisionInfo]`):
+ Set of [`~CachedRevisionInfo`] describing all revisions cached in the repo.
+ last_accessed (`float`):
+ Timestamp of the last time a blob file of the repo has been accessed.
+ last_modified (`float`):
+ Timestamp of the last time a blob file of the repo has been modified/created.
+
+
+
+ `size_on_disk` is not necessarily the sum of all revisions sizes because of
+ duplicated files. Besides, only blobs are taken into account, not the (negligible)
+ size of folders and symlinks.
+
+
+
+
+
+ `last_accessed` and `last_modified` reliability can depend on the OS you are using.
+ See [python documentation](https://docs.python.org/3/library/os.html#os.stat_result)
+ for more details.
+
+
+ """
+
+ repo_id: str
+ repo_type: REPO_TYPE_T
+ repo_path: Path
+ size_on_disk: int
+ nb_files: int
+ revisions: FrozenSet[CachedRevisionInfo]
+
+ last_accessed: float
+ last_modified: float
+
+ @property
+ def last_accessed_str(self) -> str:
+ """
+ (property) Last time a blob file of the repo has been accessed, returned as a
+ human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_accessed)
+
+ @property
+ def last_modified_str(self) -> str:
+ """
+ (property) Last time a blob file of the repo has been modified, returned as a
+ human-readable string.
+
+ Example: "2 weeks ago".
+ """
+ return _format_timesince(self.last_modified)
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of the blob file sizes as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ @property
+ def refs(self) -> Dict[str, CachedRevisionInfo]:
+ """
+ (property) Mapping between `refs` and revision data structures.
+ """
+ return {ref: revision for revision in self.revisions for ref in revision.refs}
+
+
+@dataclass(frozen=True)
+class DeleteCacheStrategy:
+ """Frozen data structure holding the strategy to delete cached revisions.
+
+ This object is not meant to be instantiated programmatically but to be returned by
+ [`~utils.HFCacheInfo.delete_revisions`]. See documentation for usage example.
+
+ Args:
+ expected_freed_size (`float`):
+ Expected freed size once strategy is executed.
+ blobs (`FrozenSet[Path]`):
+ Set of blob file paths to be deleted.
+ refs (`FrozenSet[Path]`):
+ Set of reference file paths to be deleted.
+ repos (`FrozenSet[Path]`):
+ Set of entire repo paths to be deleted.
+ snapshots (`FrozenSet[Path]`):
+ Set of snapshots to be deleted (directory of symlinks).
+ """
+
+ expected_freed_size: int
+ blobs: FrozenSet[Path]
+ refs: FrozenSet[Path]
+ repos: FrozenSet[Path]
+ snapshots: FrozenSet[Path]
+
+ @property
+ def expected_freed_size_str(self) -> str:
+ """
+ (property) Expected size that will be freed as a human-readable string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.expected_freed_size)
+
+ def execute(self) -> None:
+ """Execute the defined strategy.
+
+
+
+ If this method is interrupted, the cache might get corrupted. Deletion order is
+ implemented so that references and symlinks are deleted before the actual blob
+ files.
+
+
+
+
+
+ This method is irreversible. If executed, cached files are erased and must be
+ downloaded again.
+
+
+ """
+ # Deletion order matters. Blobs are deleted in last so that the user can't end
+ # up in a state where a `ref`` refers to a missing snapshot or a snapshot
+ # symlink refers to a deleted blob.
+
+ # Delete entire repos
+ for path in self.repos:
+ _try_delete_path(path, path_type="repo")
+
+ # Delete snapshot directories
+ for path in self.snapshots:
+ _try_delete_path(path, path_type="snapshot")
+
+ # Delete refs files
+ for path in self.refs:
+ _try_delete_path(path, path_type="ref")
+
+ # Delete blob files
+ for path in self.blobs:
+ _try_delete_path(path, path_type="blob")
+
+ logger.info(f"Cache deletion done. Saved {self.expected_freed_size_str}.")
+
+
+@dataclass(frozen=True)
+class HFCacheInfo:
+ """Frozen data structure holding information about the entire cache-system.
+
+ This data structure is returned by [`scan_cache_dir`] and is immutable.
+
+ Args:
+ size_on_disk (`int`):
+ Sum of all valid repo sizes in the cache-system.
+ repos (`FrozenSet[CachedRepoInfo]`):
+ Set of [`~CachedRepoInfo`] describing all valid cached repos found on the
+ cache-system while scanning.
+ warnings (`List[CorruptedCacheException]`):
+ List of [`~CorruptedCacheException`] that occurred while scanning the cache.
+ Those exceptions are captured so that the scan can continue. Corrupted repos
+ are skipped from the scan.
+
+
+
+ Here `size_on_disk` is equal to the sum of all repo sizes (only blobs). However if
+ some cached repos are corrupted, their sizes are not taken into account.
+
+
+ """
+
+ size_on_disk: int
+ repos: FrozenSet[CachedRepoInfo]
+ warnings: List[CorruptedCacheException]
+
+ @property
+ def size_on_disk_str(self) -> str:
+ """
+ (property) Sum of all valid repo sizes in the cache-system as a human-readable
+ string.
+
+ Example: "42.2K".
+ """
+ return _format_size(self.size_on_disk)
+
+ def delete_revisions(self, *revisions: str) -> DeleteCacheStrategy:
+ """Prepare the strategy to delete one or more revisions cached locally.
+
+ Input revisions can be any revision hash. If a revision hash is not found in the
+ local cache, a warning is thrown but no error is raised. Revisions can be from
+ different cached repos since hashes are unique across repos,
+
+ Examples:
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+ >>> cache_info = scan_cache_dir()
+ >>> delete_strategy = cache_info.delete_revisions(
+ ... "81fd1d6e7847c99f5862c9fb81387956d99ec7aa"
+ ... )
+ >>> print(f"Will free {delete_strategy.expected_freed_size_str}.")
+ Will free 7.9K.
+ >>> delete_strategy.execute()
+ Cache deletion done. Saved 7.9K.
+ ```
+
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+ >>> scan_cache_dir().delete_revisions(
+ ... "81fd1d6e7847c99f5862c9fb81387956d99ec7aa",
+ ... "e2983b237dccf3ab4937c97fa717319a9ca1a96d",
+ ... "6c0e6080953db56375760c0471a8c5f2929baf11",
+ ... ).execute()
+ Cache deletion done. Saved 8.6G.
+ ```
+
+
+
+ `delete_revisions` returns a [`~utils.DeleteCacheStrategy`] object that needs to
+ be executed. The [`~utils.DeleteCacheStrategy`] is not meant to be modified but
+ allows having a dry run before actually executing the deletion.
+
+
+ """
+ hashes_to_delete: Set[str] = set(revisions)
+
+ repos_with_revisions: Dict[CachedRepoInfo, Set[CachedRevisionInfo]] = defaultdict(set)
+
+ for repo in self.repos:
+ for revision in repo.revisions:
+ if revision.commit_hash in hashes_to_delete:
+ repos_with_revisions[repo].add(revision)
+ hashes_to_delete.remove(revision.commit_hash)
+
+ if len(hashes_to_delete) > 0:
+ logger.warning(f"Revision(s) not found - cannot delete them: {', '.join(hashes_to_delete)}")
+
+ delete_strategy_blobs: Set[Path] = set()
+ delete_strategy_refs: Set[Path] = set()
+ delete_strategy_repos: Set[Path] = set()
+ delete_strategy_snapshots: Set[Path] = set()
+ delete_strategy_expected_freed_size = 0
+
+ for affected_repo, revisions_to_delete in repos_with_revisions.items():
+ other_revisions = affected_repo.revisions - revisions_to_delete
+
+ # If no other revisions, it means all revisions are deleted
+ # -> delete the entire cached repo
+ if len(other_revisions) == 0:
+ delete_strategy_repos.add(affected_repo.repo_path)
+ delete_strategy_expected_freed_size += affected_repo.size_on_disk
+ continue
+
+ # Some revisions of the repo will be deleted but not all. We need to filter
+ # which blob files will not be linked anymore.
+ for revision_to_delete in revisions_to_delete:
+ # Snapshot dir
+ delete_strategy_snapshots.add(revision_to_delete.snapshot_path)
+
+ # Refs dir
+ for ref in revision_to_delete.refs:
+ delete_strategy_refs.add(affected_repo.repo_path / "refs" / ref)
+
+ # Blobs dir
+ for file in revision_to_delete.files:
+ if file.blob_path not in delete_strategy_blobs:
+ is_file_alone = True
+ for revision in other_revisions:
+ for rev_file in revision.files:
+ if file.blob_path == rev_file.blob_path:
+ is_file_alone = False
+ break
+ if not is_file_alone:
+ break
+
+ # Blob file not referenced by remaining revisions -> delete
+ if is_file_alone:
+ delete_strategy_blobs.add(file.blob_path)
+ delete_strategy_expected_freed_size += file.size_on_disk
+
+ # Return the strategy instead of executing it.
+ return DeleteCacheStrategy(
+ blobs=frozenset(delete_strategy_blobs),
+ refs=frozenset(delete_strategy_refs),
+ repos=frozenset(delete_strategy_repos),
+ snapshots=frozenset(delete_strategy_snapshots),
+ expected_freed_size=delete_strategy_expected_freed_size,
+ )
+
+
+def scan_cache_dir(cache_dir: Optional[Union[str, Path]] = None) -> HFCacheInfo:
+ """Scan the entire HF cache-system and return a [`~HFCacheInfo`] structure.
+
+ Use `scan_cache_dir` in order to programmatically scan your cache-system. The cache
+ will be scanned repo by repo. If a repo is corrupted, a [`~CorruptedCacheException`]
+ will be thrown internally but captured and returned in the [`~HFCacheInfo`]
+ structure. Only valid repos get a proper report.
+
+ ```py
+ >>> from huggingface_hub import scan_cache_dir
+
+ >>> hf_cache_info = scan_cache_dir()
+ HFCacheInfo(
+ size_on_disk=3398085269,
+ repos=frozenset({
+ CachedRepoInfo(
+ repo_id='t5-small',
+ repo_type='model',
+ repo_path=PosixPath(...),
+ size_on_disk=970726914,
+ nb_files=11,
+ revisions=frozenset({
+ CachedRevisionInfo(
+ commit_hash='d78aea13fa7ecd06c29e3e46195d6341255065d5',
+ size_on_disk=970726339,
+ snapshot_path=PosixPath(...),
+ files=frozenset({
+ CachedFileInfo(
+ file_name='config.json',
+ size_on_disk=1197
+ file_path=PosixPath(...),
+ blob_path=PosixPath(...),
+ ),
+ CachedFileInfo(...),
+ ...
+ }),
+ ),
+ CachedRevisionInfo(...),
+ ...
+ }),
+ ),
+ CachedRepoInfo(...),
+ ...
+ }),
+ warnings=[
+ CorruptedCacheException("Snapshots dir doesn't exist in cached repo: ..."),
+ CorruptedCacheException(...),
+ ...
+ ],
+ )
+ ```
+
+ You can also print a detailed report directly from the `huggingface-cli` using:
+ ```text
+ > huggingface-cli scan-cache
+ REPO ID REPO TYPE SIZE ON DISK NB FILES REFS LOCAL PATH
+ --------------------------- --------- ------------ -------- ------------------- -------------------------------------------------------------------------
+ glue dataset 116.3K 15 1.17.0, main, 2.4.0 /Users/lucain/.cache/huggingface/hub/datasets--glue
+ google/fleurs dataset 64.9M 6 main, refs/pr/1 /Users/lucain/.cache/huggingface/hub/datasets--google--fleurs
+ Jean-Baptiste/camembert-ner model 441.0M 7 main /Users/lucain/.cache/huggingface/hub/models--Jean-Baptiste--camembert-ner
+ bert-base-cased model 1.9G 13 main /Users/lucain/.cache/huggingface/hub/models--bert-base-cased
+ t5-base model 10.1K 3 main /Users/lucain/.cache/huggingface/hub/models--t5-base
+ t5-small model 970.7M 11 refs/pr/1, main /Users/lucain/.cache/huggingface/hub/models--t5-small
+
+ Done in 0.0s. Scanned 6 repo(s) for a total of 3.4G.
+ Got 1 warning(s) while scanning. Use -vvv to print details.
+ ```
+
+ Args:
+ cache_dir (`str` or `Path`, `optional`):
+ Cache directory to cache. Defaults to the default HF cache directory.
+
+
+
+ Raises:
+
+ `CacheNotFound`
+ If the cache directory does not exist.
+
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If the cache directory is a file, instead of a directory.
+
+
+
+ Returns: a [`~HFCacheInfo`] object.
+ """
+ if cache_dir is None:
+ cache_dir = HF_HUB_CACHE
+
+ cache_dir = Path(cache_dir).expanduser().resolve()
+ if not cache_dir.exists():
+ raise CacheNotFound(
+ f"Cache directory not found: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable.",
+ cache_dir=cache_dir,
+ )
+
+ if cache_dir.is_file():
+ raise ValueError(
+ f"Scan cache expects a directory but found a file: {cache_dir}. Please use `cache_dir` argument or set `HF_HUB_CACHE` environment variable."
+ )
+
+ repos: Set[CachedRepoInfo] = set()
+ warnings: List[CorruptedCacheException] = []
+ for repo_path in cache_dir.iterdir():
+ if repo_path.name == ".locks": # skip './.locks/' folder
+ continue
+ try:
+ repos.add(_scan_cached_repo(repo_path))
+ except CorruptedCacheException as e:
+ warnings.append(e)
+
+ return HFCacheInfo(
+ repos=frozenset(repos),
+ size_on_disk=sum(repo.size_on_disk for repo in repos),
+ warnings=warnings,
+ )
+
+
+def _scan_cached_repo(repo_path: Path) -> CachedRepoInfo:
+ """Scan a single cache repo and return information about it.
+
+ Any unexpected behavior will raise a [`~CorruptedCacheException`].
+ """
+ if not repo_path.is_dir():
+ raise CorruptedCacheException(f"Repo path is not a directory: {repo_path}")
+
+ if "--" not in repo_path.name:
+ raise CorruptedCacheException(f"Repo path is not a valid HuggingFace cache directory: {repo_path}")
+
+ repo_type, repo_id = repo_path.name.split("--", maxsplit=1)
+ repo_type = repo_type[:-1] # "models" -> "model"
+ repo_id = repo_id.replace("--", "/") # google/fleurs -> "google/fleurs"
+
+ if repo_type not in {"dataset", "model", "space"}:
+ raise CorruptedCacheException(
+ f"Repo type must be `dataset`, `model` or `space`, found `{repo_type}` ({repo_path})."
+ )
+
+ blob_stats: Dict[Path, os.stat_result] = {} # Key is blob_path, value is blob stats
+
+ snapshots_path = repo_path / "snapshots"
+ refs_path = repo_path / "refs"
+
+ if not snapshots_path.exists() or not snapshots_path.is_dir():
+ raise CorruptedCacheException(f"Snapshots dir doesn't exist in cached repo: {snapshots_path}")
+
+ # Scan over `refs` directory
+
+ # key is revision hash, value is set of refs
+ refs_by_hash: Dict[str, Set[str]] = defaultdict(set)
+ if refs_path.exists():
+ # Example of `refs` directory
+ # ── refs
+ # ├── main
+ # └── refs
+ # └── pr
+ # └── 1
+ if refs_path.is_file():
+ raise CorruptedCacheException(f"Refs directory cannot be a file: {refs_path}")
+
+ for ref_path in refs_path.glob("**/*"):
+ # glob("**/*") iterates over all files and directories -> skip directories
+ if ref_path.is_dir():
+ continue
+
+ ref_name = str(ref_path.relative_to(refs_path))
+ with ref_path.open() as f:
+ commit_hash = f.read()
+
+ refs_by_hash[commit_hash].add(ref_name)
+
+ # Scan snapshots directory
+ cached_revisions: Set[CachedRevisionInfo] = set()
+ for revision_path in snapshots_path.iterdir():
+ # Ignore OS-created helper files
+ if revision_path.name in FILES_TO_IGNORE:
+ continue
+ if revision_path.is_file():
+ raise CorruptedCacheException(f"Snapshots folder corrupted. Found a file: {revision_path}")
+
+ cached_files = set()
+ for file_path in revision_path.glob("**/*"):
+ # glob("**/*") iterates over all files and directories -> skip directories
+ if file_path.is_dir():
+ continue
+
+ blob_path = Path(file_path).resolve()
+ if not blob_path.exists():
+ raise CorruptedCacheException(f"Blob missing (broken symlink): {blob_path}")
+
+ if blob_path not in blob_stats:
+ blob_stats[blob_path] = blob_path.stat()
+
+ cached_files.add(
+ CachedFileInfo(
+ file_name=file_path.name,
+ file_path=file_path,
+ size_on_disk=blob_stats[blob_path].st_size,
+ blob_path=blob_path,
+ blob_last_accessed=blob_stats[blob_path].st_atime,
+ blob_last_modified=blob_stats[blob_path].st_mtime,
+ )
+ )
+
+ # Last modified is either the last modified blob file or the revision folder
+ # itself if it is empty
+ if len(cached_files) > 0:
+ revision_last_modified = max(blob_stats[file.blob_path].st_mtime for file in cached_files)
+ else:
+ revision_last_modified = revision_path.stat().st_mtime
+
+ cached_revisions.add(
+ CachedRevisionInfo(
+ commit_hash=revision_path.name,
+ files=frozenset(cached_files),
+ refs=frozenset(refs_by_hash.pop(revision_path.name, set())),
+ size_on_disk=sum(
+ blob_stats[blob_path].st_size for blob_path in set(file.blob_path for file in cached_files)
+ ),
+ snapshot_path=revision_path,
+ last_modified=revision_last_modified,
+ )
+ )
+
+ # Check that all refs referred to an existing revision
+ if len(refs_by_hash) > 0:
+ raise CorruptedCacheException(
+ f"Reference(s) refer to missing commit hashes: {dict(refs_by_hash)} ({repo_path})."
+ )
+
+ # Last modified is either the last modified blob file or the repo folder itself if
+ # no blob files has been found. Same for last accessed.
+ if len(blob_stats) > 0:
+ repo_last_accessed = max(stat.st_atime for stat in blob_stats.values())
+ repo_last_modified = max(stat.st_mtime for stat in blob_stats.values())
+ else:
+ repo_stats = repo_path.stat()
+ repo_last_accessed = repo_stats.st_atime
+ repo_last_modified = repo_stats.st_mtime
+
+ # Build and return frozen structure
+ return CachedRepoInfo(
+ nb_files=len(blob_stats),
+ repo_id=repo_id,
+ repo_path=repo_path,
+ repo_type=repo_type, # type: ignore
+ revisions=frozenset(cached_revisions),
+ size_on_disk=sum(stat.st_size for stat in blob_stats.values()),
+ last_accessed=repo_last_accessed,
+ last_modified=repo_last_modified,
+ )
+
+
+def _format_size(num: int) -> str:
+ """Format size in bytes into a human-readable string.
+
+ Taken from https://stackoverflow.com/a/1094933
+ """
+ num_f = float(num)
+ for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
+ if abs(num_f) < 1000.0:
+ return f"{num_f:3.1f}{unit}"
+ num_f /= 1000.0
+ return f"{num_f:.1f}Y"
+
+
+_TIMESINCE_CHUNKS = (
+ # Label, divider, max value
+ ("second", 1, 60),
+ ("minute", 60, 60),
+ ("hour", 60 * 60, 24),
+ ("day", 60 * 60 * 24, 6),
+ ("week", 60 * 60 * 24 * 7, 6),
+ ("month", 60 * 60 * 24 * 30, 11),
+ ("year", 60 * 60 * 24 * 365, None),
+)
+
+
+def _format_timesince(ts: float) -> str:
+ """Format timestamp in seconds into a human-readable string, relative to now.
+
+ Vaguely inspired by Django's `timesince` formatter.
+ """
+ delta = time.time() - ts
+ if delta < 20:
+ return "a few seconds ago"
+ for label, divider, max_value in _TIMESINCE_CHUNKS: # noqa: B007
+ value = round(delta / divider)
+ if max_value is not None and value <= max_value:
+ break
+ return f"{value} {label}{'s' if value > 1 else ''} ago"
+
+
+def _try_delete_path(path: Path, path_type: str) -> None:
+ """Try to delete a local file or folder.
+
+ If the path does not exists, error is logged as a warning and then ignored.
+
+ Args:
+ path (`Path`)
+ Path to delete. Can be a file or a folder.
+ path_type (`str`)
+ What path are we deleting ? Only for logging purposes. Example: "snapshot".
+ """
+ logger.info(f"Delete {path_type}: {path}")
+ try:
+ if path.is_file():
+ os.remove(path)
+ else:
+ shutil.rmtree(path)
+ except FileNotFoundError:
+ logger.warning(f"Couldn't delete {path_type}: file not found ({path})", exc_info=True)
+ except PermissionError:
+ logger.warning(f"Couldn't delete {path_type}: permission denied ({path})", exc_info=True)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_chunk_utils.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_chunk_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0af032ae6a68f03676ad7fdb8e483248d9853f8
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_chunk_utils.py
@@ -0,0 +1,65 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains a utility to iterate by chunks over an iterator."""
+
+import itertools
+from typing import Iterable, TypeVar
+
+
+T = TypeVar("T")
+
+
+def chunk_iterable(iterable: Iterable[T], chunk_size: int) -> Iterable[Iterable[T]]:
+ """Iterates over an iterator chunk by chunk.
+
+ Taken from https://stackoverflow.com/a/8998040.
+ See also https://github.com/huggingface/huggingface_hub/pull/920#discussion_r938793088.
+
+ Args:
+ iterable (`Iterable`):
+ The iterable on which we want to iterate.
+ chunk_size (`int`):
+ Size of the chunks. Must be a strictly positive integer (e.g. >0).
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.utils import chunk_iterable
+
+ >>> for items in chunk_iterable(range(17), chunk_size=8):
+ ... print(items)
+ # [0, 1, 2, 3, 4, 5, 6, 7]
+ # [8, 9, 10, 11, 12, 13, 14, 15]
+ # [16] # smaller last chunk
+ ```
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If `chunk_size` <= 0.
+
+
+ The last chunk can be smaller than `chunk_size`.
+
+ """
+ if not isinstance(chunk_size, int) or chunk_size <= 0:
+ raise ValueError("`chunk_size` must be a strictly positive integer (>0).")
+
+ iterator = iter(iterable)
+ while True:
+ try:
+ next_item = next(iterator)
+ except StopIteration:
+ return
+ yield itertools.chain((next_item,), itertools.islice(iterator, chunk_size - 1))
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_datetime.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..e544884b8793d8d409303cafd34586523fc3fb1c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_datetime.py
@@ -0,0 +1,62 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle datetimes in Huggingface Hub."""
+
+from datetime import datetime, timezone
+
+
+def parse_datetime(date_string: str) -> datetime:
+ """
+ Parses a date_string returned from the server to a datetime object.
+
+ This parser is a weak-parser is the sense that it handles only a single format of
+ date_string. It is expected that the server format will never change. The
+ implementation depends only on the standard lib to avoid an external dependency
+ (python-dateutil). See full discussion about this decision on PR:
+ https://github.com/huggingface/huggingface_hub/pull/999.
+
+ Example:
+ ```py
+ > parse_datetime('2022-08-19T07:19:38.123Z')
+ datetime.datetime(2022, 8, 19, 7, 19, 38, 123000, tzinfo=timezone.utc)
+ ```
+
+ Args:
+ date_string (`str`):
+ A string representing a datetime returned by the Hub server.
+ String is expected to follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern.
+
+ Returns:
+ A python datetime object.
+
+ Raises:
+ :class:`ValueError`:
+ If `date_string` cannot be parsed.
+ """
+ try:
+ # Datetime ending with a Z means "UTC". We parse the date and then explicitly
+ # set the timezone to UTC.
+ # See https://en.wikipedia.org/wiki/ISO_8601#Coordinated_Universal_Time_(UTC)
+ # Taken from https://stackoverflow.com/a/3168394.
+ if len(date_string) == 30:
+ # Means timezoned-timestamp with nanoseconds precision. We need to truncate the last 3 digits.
+ date_string = date_string[:-4] + "Z"
+ dt = datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ")
+ return dt.replace(tzinfo=timezone.utc) # Set explicit timezone
+ except ValueError as e:
+ raise ValueError(
+ f"Cannot parse '{date_string}' as a datetime. Date string is expected to"
+ " follow '%Y-%m-%dT%H:%M:%S.%fZ' pattern."
+ ) from e
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_deprecation.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cb8d6e418c76accd1ecd61158b4bdd265e12f71
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_deprecation.py
@@ -0,0 +1,136 @@
+import warnings
+from functools import wraps
+from inspect import Parameter, signature
+from typing import Iterable, Optional
+
+
+def _deprecate_positional_args(*, version: str):
+ """Decorator for methods that issues warnings for positional arguments.
+ Using the keyword-only argument syntax in pep 3102, arguments after the
+ * will issue a warning when passed as a positional argument.
+
+ Args:
+ version (`str`):
+ The version when positional arguments will result in error.
+ """
+
+ def _inner_deprecate_positional_args(f):
+ sig = signature(f)
+ kwonly_args = []
+ all_args = []
+ for name, param in sig.parameters.items():
+ if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
+ all_args.append(name)
+ elif param.kind == Parameter.KEYWORD_ONLY:
+ kwonly_args.append(name)
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ extra_args = len(args) - len(all_args)
+ if extra_args <= 0:
+ return f(*args, **kwargs)
+ # extra_args > 0
+ args_msg = [
+ f"{name}='{arg}'" if isinstance(arg, str) else f"{name}={arg}"
+ for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:])
+ ]
+ args_msg = ", ".join(args_msg)
+ warnings.warn(
+ f"Deprecated positional argument(s) used in '{f.__name__}': pass"
+ f" {args_msg} as keyword args. From version {version} passing these"
+ " as positional arguments will result in an error,",
+ FutureWarning,
+ )
+ kwargs.update(zip(sig.parameters, args))
+ return f(**kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_positional_args
+
+
+def _deprecate_arguments(
+ *,
+ version: str,
+ deprecated_args: Iterable[str],
+ custom_message: Optional[str] = None,
+):
+ """Decorator to issue warnings when using deprecated arguments.
+
+ TODO: could be useful to be able to set a custom error message.
+
+ Args:
+ version (`str`):
+ The version when deprecated arguments will result in error.
+ deprecated_args (`List[str]`):
+ List of the arguments to be deprecated.
+ custom_message (`str`, *optional*):
+ Warning message that is raised. If not passed, a default warning message
+ will be created.
+ """
+
+ def _inner_deprecate_positional_args(f):
+ sig = signature(f)
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ # Check for used deprecated arguments
+ used_deprecated_args = []
+ for _, parameter in zip(args, sig.parameters.values()):
+ if parameter.name in deprecated_args:
+ used_deprecated_args.append(parameter.name)
+ for kwarg_name, kwarg_value in kwargs.items():
+ if (
+ # If argument is deprecated but still used
+ kwarg_name in deprecated_args
+ # And then the value is not the default value
+ and kwarg_value != sig.parameters[kwarg_name].default
+ ):
+ used_deprecated_args.append(kwarg_name)
+
+ # Warn and proceed
+ if len(used_deprecated_args) > 0:
+ message = (
+ f"Deprecated argument(s) used in '{f.__name__}':"
+ f" {', '.join(used_deprecated_args)}. Will not be supported from"
+ f" version '{version}'."
+ )
+ if custom_message is not None:
+ message += "\n\n" + custom_message
+ warnings.warn(message, FutureWarning)
+ return f(*args, **kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_positional_args
+
+
+def _deprecate_method(*, version: str, message: Optional[str] = None):
+ """Decorator to issue warnings when using a deprecated method.
+
+ Args:
+ version (`str`):
+ The version when deprecated arguments will result in error.
+ message (`str`, *optional*):
+ Warning message that is raised. If not passed, a default warning message
+ will be created.
+ """
+
+ def _inner_deprecate_method(f):
+ name = f.__name__
+ if name == "__init__":
+ name = f.__qualname__.split(".")[0] # class name instead of method name
+
+ @wraps(f)
+ def inner_f(*args, **kwargs):
+ warning_message = (
+ f"'{name}' (from '{f.__module__}') is deprecated and will be removed from version '{version}'."
+ )
+ if message is not None:
+ warning_message += " " + message
+ warnings.warn(warning_message, FutureWarning)
+ return f(*args, **kwargs)
+
+ return inner_f
+
+ return _inner_deprecate_method
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_errors.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_errors.py
new file mode 100644
index 0000000000000000000000000000000000000000..70e97f0c24101243bce003f0ca55d852c02a48cd
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_errors.py
@@ -0,0 +1,397 @@
+import re
+from typing import Optional
+
+from requests import HTTPError, Response
+
+from ._fixes import JSONDecodeError
+
+
+REPO_API_REGEX = re.compile(
+ r"""
+ # staging or production endpoint
+ ^https://[^/]+
+ (
+ # on /api/repo_type/repo_id
+ /api/(models|datasets|spaces)/(.+)
+ |
+ # or /repo_id/resolve/revision/...
+ /(.+)/resolve/(.+)
+ )
+ """,
+ flags=re.VERBOSE,
+)
+
+
+class FileMetadataError(OSError):
+ """Error triggered when the metadata of a file on the Hub cannot be retrieved (missing ETag or commit_hash).
+
+ Inherits from `OSError` for backward compatibility.
+ """
+
+
+class HfHubHTTPError(HTTPError):
+ """
+ HTTPError to inherit from for any custom HTTP Error raised in HF Hub.
+
+ Any HTTPError is converted at least into a `HfHubHTTPError`. If some information is
+ sent back by the server, it will be added to the error message.
+
+ Added details:
+ - Request id from "X-Request-Id" header if exists.
+ - Server error message from the header "X-Error-Message".
+ - Server error message if we can found one in the response body.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError
+
+ response = get_session().post(...)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ print(str(e)) # formatted message
+ e.request_id, e.server_message # details returned by server
+
+ # Complete the error message with additional information once it's raised
+ e.append_to_message("\n`create_commit` expects the repository to exist.")
+ raise
+ ```
+ """
+
+ request_id: Optional[str] = None
+ server_message: Optional[str] = None
+
+ def __init__(self, message: str, response: Optional[Response] = None):
+ # Parse server information if any.
+ if response is not None:
+ self.request_id = response.headers.get("X-Request-Id")
+ try:
+ server_data = response.json()
+ except JSONDecodeError:
+ server_data = {}
+
+ # Retrieve server error message from multiple sources
+ server_message_from_headers = response.headers.get("X-Error-Message")
+ server_message_from_body = server_data.get("error")
+ server_multiple_messages_from_body = "\n".join(
+ error["message"] for error in server_data.get("errors", []) if "message" in error
+ )
+
+ # Concatenate error messages
+ _server_message = ""
+ if server_message_from_headers is not None: # from headers
+ _server_message += server_message_from_headers + "\n"
+ if server_message_from_body is not None: # from body "error"
+ if isinstance(server_message_from_body, list):
+ server_message_from_body = "\n".join(server_message_from_body)
+ if server_message_from_body not in _server_message:
+ _server_message += server_message_from_body + "\n"
+ if server_multiple_messages_from_body is not None: # from body "errors"
+ if server_multiple_messages_from_body not in _server_message:
+ _server_message += server_multiple_messages_from_body + "\n"
+ _server_message = _server_message.strip()
+
+ # Set message to `HfHubHTTPError` (if any)
+ if _server_message != "":
+ self.server_message = _server_message
+
+ super().__init__(
+ _format_error_message(
+ message,
+ request_id=self.request_id,
+ server_message=self.server_message,
+ ),
+ response=response, # type: ignore
+ request=response.request if response is not None else None, # type: ignore
+ )
+
+ def append_to_message(self, additional_message: str) -> None:
+ """Append additional information to the `HfHubHTTPError` initial message."""
+ self.args = (self.args[0] + additional_message,) + self.args[1:]
+
+
+class RepositoryNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with an invalid repository name, or
+ with a private repo name the user does not have access to.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import model_info
+ >>> model_info("")
+ (...)
+ huggingface_hub.utils._errors.RepositoryNotFoundError: 401 Client Error. (Request ID: PvMw_VjBMjVdMz53WKIzP)
+
+ Repository Not Found for url: https://huggingface.co/api/models/%3Cnon_existent_repository%3E.
+ Please make sure you specified the correct `repo_id` and `repo_type`.
+ If the repo is private, make sure you are authenticated.
+ Invalid username or password.
+ ```
+ """
+
+
+class GatedRepoError(RepositoryNotFoundError):
+ """
+ Raised when trying to access a gated repository for which the user is not on the
+ authorized list.
+
+ Note: derives from `RepositoryNotFoundError` to ensure backward compatibility.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import model_info
+ >>> model_info("")
+ (...)
+ huggingface_hub.utils._errors.GatedRepoError: 403 Client Error. (Request ID: ViT1Bf7O_026LGSQuVqfa)
+
+ Cannot access gated repo for url https://huggingface.co/api/models/ardent-figment/gated-model.
+ Access to model ardent-figment/gated-model is restricted and you are not in the authorized list.
+ Visit https://huggingface.co/ardent-figment/gated-model to ask for access.
+ ```
+ """
+
+
+class DisabledRepoError(HfHubHTTPError):
+ """
+ Raised when trying to access a repository that has been disabled by its author.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import dataset_info
+ >>> dataset_info("laion/laion-art")
+ (...)
+ huggingface_hub.utils._errors.DisabledRepoError: 403 Client Error. (Request ID: Root=1-659fc3fa-3031673e0f92c71a2260dbe2;bc6f4dfb-b30a-4862-af0a-5cfe827610d8)
+
+ Cannot access repository for url https://huggingface.co/api/datasets/laion/laion-art.
+ Access to this resource is disabled.
+ ```
+ """
+
+
+class RevisionNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with a valid repository but an invalid
+ revision.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', 'config.json', revision='')
+ (...)
+ huggingface_hub.utils._errors.RevisionNotFoundError: 404 Client Error. (Request ID: Mwhe_c3Kt650GcdKEFomX)
+
+ Revision Not Found for url: https://huggingface.co/bert-base-cased/resolve/%3Cnon-existent-revision%3E/config.json.
+ ```
+ """
+
+
+class EntryNotFoundError(HfHubHTTPError):
+ """
+ Raised when trying to access a hf.co URL with a valid repository and revision
+ but an invalid filename.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', '')
+ (...)
+ huggingface_hub.utils._errors.EntryNotFoundError: 404 Client Error. (Request ID: 53pNl6M0MxsnG5Sw8JA6x)
+
+ Entry Not Found for url: https://huggingface.co/bert-base-cased/resolve/main/%3Cnon-existent-file%3E.
+ ```
+ """
+
+
+class LocalEntryNotFoundError(EntryNotFoundError, FileNotFoundError, ValueError):
+ """
+ Raised when trying to access a file or snapshot that is not on the disk when network is
+ disabled or unavailable (connection issue). The entry may exist on the Hub.
+
+ Note: `ValueError` type is to ensure backward compatibility.
+ Note: `LocalEntryNotFoundError` derives from `HTTPError` because of `EntryNotFoundError`
+ even when it is not a network issue.
+
+ Example:
+
+ ```py
+ >>> from huggingface_hub import hf_hub_download
+ >>> hf_hub_download('bert-base-cased', '', local_files_only=True)
+ (...)
+ huggingface_hub.utils._errors.LocalEntryNotFoundError: Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable hf.co look-ups and downloads online, set 'local_files_only' to False.
+ ```
+ """
+
+ def __init__(self, message: str):
+ super().__init__(message, response=None)
+
+
+class BadRequestError(HfHubHTTPError, ValueError):
+ """
+ Raised by `hf_raise_for_status` when the server returns a HTTP 400 error.
+
+ Example:
+
+ ```py
+ >>> resp = requests.post("hf.co/api/check", ...)
+ >>> hf_raise_for_status(resp, endpoint_name="check")
+ huggingface_hub.utils._errors.BadRequestError: Bad request for check endpoint: {details} (Request ID: XXX)
+ ```
+ """
+
+
+def hf_raise_for_status(response: Response, endpoint_name: Optional[str] = None) -> None:
+ """
+ Internal version of `response.raise_for_status()` that will refine a
+ potential HTTPError. Raised exception will be an instance of `HfHubHTTPError`.
+
+ This helper is meant to be the unique method to raise_for_status when making a call
+ to the Hugging Face Hub.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError
+
+ response = get_session().post(...)
+ try:
+ hf_raise_for_status(response)
+ except HfHubHTTPError as e:
+ print(str(e)) # formatted message
+ e.request_id, e.server_message # details returned by server
+
+ # Complete the error message with additional information once it's raised
+ e.append_to_message("\n`create_commit` expects the repository to exist.")
+ raise
+ ```
+
+ Args:
+ response (`Response`):
+ Response from the server.
+ endpoint_name (`str`, *optional*):
+ Name of the endpoint that has been called. If provided, the error message
+ will be more complete.
+
+
+
+ Raises when the request has failed:
+
+ - [`~utils.RepositoryNotFoundError`]
+ If the repository to download from cannot be found. This may be because it
+ doesn't exist, because `repo_type` is not set correctly, or because the repo
+ is `private` and you do not have access.
+ - [`~utils.GatedRepoError`]
+ If the repository exists but is gated and the user is not on the authorized
+ list.
+ - [`~utils.RevisionNotFoundError`]
+ If the repository exists but the revision couldn't be find.
+ - [`~utils.EntryNotFoundError`]
+ If the repository exists but the entry (e.g. the requested file) couldn't be
+ find.
+ - [`~utils.BadRequestError`]
+ If request failed with a HTTP 400 BadRequest error.
+ - [`~utils.HfHubHTTPError`]
+ If request failed for a reason not listed above.
+
+
+ """
+ try:
+ response.raise_for_status()
+ except HTTPError as e:
+ error_code = response.headers.get("X-Error-Code")
+ error_message = response.headers.get("X-Error-Message")
+
+ if error_code == "RevisionNotFound":
+ message = f"{response.status_code} Client Error." + "\n\n" + f"Revision Not Found for url: {response.url}."
+ raise RevisionNotFoundError(message, response) from e
+
+ elif error_code == "EntryNotFound":
+ message = f"{response.status_code} Client Error." + "\n\n" + f"Entry Not Found for url: {response.url}."
+ raise EntryNotFoundError(message, response) from e
+
+ elif error_code == "GatedRepo":
+ message = (
+ f"{response.status_code} Client Error." + "\n\n" + f"Cannot access gated repo for url {response.url}."
+ )
+ raise GatedRepoError(message, response) from e
+
+ elif error_message == "Access to this resource is disabled.":
+ message = (
+ f"{response.status_code} Client Error."
+ + "\n\n"
+ + f"Cannot access repository for url {response.url}."
+ + "\n"
+ + "Access to this resource is disabled."
+ )
+ raise DisabledRepoError(message, response) from e
+
+ elif error_code == "RepoNotFound" or (
+ response.status_code == 401
+ and response.request is not None
+ and response.request.url is not None
+ and REPO_API_REGEX.search(response.request.url) is not None
+ ):
+ # 401 is misleading as it is returned for:
+ # - private and gated repos if user is not authenticated
+ # - missing repos
+ # => for now, we process them as `RepoNotFound` anyway.
+ # See https://gist.github.com/Wauplin/46c27ad266b15998ce56a6603796f0b9
+ message = (
+ f"{response.status_code} Client Error."
+ + "\n\n"
+ + f"Repository Not Found for url: {response.url}."
+ + "\nPlease make sure you specified the correct `repo_id` and"
+ " `repo_type`.\nIf you are trying to access a private or gated repo,"
+ " make sure you are authenticated."
+ )
+ raise RepositoryNotFoundError(message, response) from e
+
+ elif response.status_code == 400:
+ message = (
+ f"\n\nBad request for {endpoint_name} endpoint:" if endpoint_name is not None else "\n\nBad request:"
+ )
+ raise BadRequestError(message, response=response) from e
+
+ elif response.status_code == 403:
+ message = (
+ f"\n\n{response.status_code} Forbidden: {error_message}."
+ + f"\nCannot access content at: {response.url}."
+ + "\nIf you are trying to create or update content,"
+ + "make sure you have a token with the `write` role."
+ )
+ raise HfHubHTTPError(message, response=response) from e
+
+ # Convert `HTTPError` into a `HfHubHTTPError` to display request information
+ # as well (request id and/or server error message)
+ raise HfHubHTTPError(str(e), response=response) from e
+
+
+def _format_error_message(message: str, request_id: Optional[str], server_message: Optional[str]) -> str:
+ """
+ Format the `HfHubHTTPError` error message based on initial message and information
+ returned by the server.
+
+ Used when initializing `HfHubHTTPError`.
+ """
+ # Add message from response body
+ if server_message is not None and len(server_message) > 0 and server_message.lower() not in message.lower():
+ if "\n\n" in message:
+ message += "\n" + server_message
+ else:
+ message += "\n\n" + server_message
+
+ # Add Request ID
+ if request_id is not None and str(request_id).lower() not in message.lower():
+ request_id_message = f" (Request ID: {request_id})"
+ if "\n" in message:
+ newline_index = message.index("\n")
+ message = message[:newline_index] + request_id_message + message[newline_index:]
+ else:
+ message += request_id_message
+
+ return message
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_experimental.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_experimental.py
new file mode 100644
index 0000000000000000000000000000000000000000..34141eba09123c06fbca55c929a19a0264e5788e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_experimental.py
@@ -0,0 +1,66 @@
+# coding=utf-8
+# Copyright 2023-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to flag a feature as "experimental" in Huggingface Hub."""
+
+import warnings
+from functools import wraps
+from typing import Callable
+
+from .. import constants
+
+
+def experimental(fn: Callable) -> Callable:
+ """Decorator to flag a feature as experimental.
+
+ An experimental feature trigger a warning when used as it might be subject to breaking changes in the future.
+ Warnings can be disabled by setting the environment variable `HF_EXPERIMENTAL_WARNING` to `0`.
+
+ Args:
+ fn (`Callable`):
+ The function to flag as experimental.
+
+ Returns:
+ `Callable`: The decorated function.
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub.utils import experimental
+
+ >>> @experimental
+ ... def my_function():
+ ... print("Hello world!")
+
+ >>> my_function()
+ UserWarning: 'my_function' is experimental and might be subject to breaking changes in the future. You can disable
+ this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment variable.
+ Hello world!
+ ```
+ """
+ # For classes, put the "experimental" around the "__new__" method => __new__ will be removed in warning message
+ name = fn.__qualname__[: -len(".__new__")] if fn.__qualname__.endswith(".__new__") else fn.__qualname__
+
+ @wraps(fn)
+ def _inner_fn(*args, **kwargs):
+ if not constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING:
+ warnings.warn(
+ f"'{name}' is experimental and might be subject to breaking changes in the future."
+ " You can disable this warning by setting `HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1` as environment"
+ " variable.",
+ UserWarning,
+ )
+ return fn(*args, **kwargs)
+
+ return _inner_fn
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_fixes.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_fixes.py
new file mode 100644
index 0000000000000000000000000000000000000000..1edcbc1eeedc8d89a8c9b9ff8a4cbe4371ce2576
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_fixes.py
@@ -0,0 +1,93 @@
+# JSONDecodeError was introduced in requests=2.27 released in 2022.
+# This allows us to support older requests for users
+# More information: https://github.com/psf/requests/pull/5856
+try:
+ from requests import JSONDecodeError # type: ignore # noqa: F401
+except ImportError:
+ try:
+ from simplejson import JSONDecodeError # type: ignore # noqa: F401
+ except ImportError:
+ from json import JSONDecodeError # type: ignore # noqa: F401
+import contextlib
+import os
+import shutil
+import stat
+import tempfile
+from functools import partial
+from pathlib import Path
+from typing import Callable, Generator, Optional, Union
+
+import yaml
+from filelock import BaseFileLock, FileLock
+
+
+# Wrap `yaml.dump` to set `allow_unicode=True` by default.
+#
+# Example:
+# ```py
+# >>> yaml.dump({"emoji": "👀", "some unicode": "日本か"})
+# 'emoji: "\\U0001F440"\nsome unicode: "\\u65E5\\u672C\\u304B"\n'
+#
+# >>> yaml_dump({"emoji": "👀", "some unicode": "日本か"})
+# 'emoji: "👀"\nsome unicode: "日本か"\n'
+# ```
+yaml_dump: Callable[..., str] = partial(yaml.dump, stream=None, allow_unicode=True) # type: ignore
+
+
+@contextlib.contextmanager
+def SoftTemporaryDirectory(
+ suffix: Optional[str] = None,
+ prefix: Optional[str] = None,
+ dir: Optional[Union[Path, str]] = None,
+ **kwargs,
+) -> Generator[Path, None, None]:
+ """
+ Context manager to create a temporary directory and safely delete it.
+
+ If tmp directory cannot be deleted normally, we set the WRITE permission and retry.
+ If cleanup still fails, we give up but don't raise an exception. This is equivalent
+ to `tempfile.TemporaryDirectory(..., ignore_cleanup_errors=True)` introduced in
+ Python 3.10.
+
+ See https://www.scivision.dev/python-tempfile-permission-error-windows/.
+ """
+ tmpdir = tempfile.TemporaryDirectory(prefix=prefix, suffix=suffix, dir=dir, **kwargs)
+ yield Path(tmpdir.name).resolve()
+
+ try:
+ # First once with normal cleanup
+ shutil.rmtree(tmpdir.name)
+ except Exception:
+ # If failed, try to set write permission and retry
+ try:
+ shutil.rmtree(tmpdir.name, onerror=_set_write_permission_and_retry)
+ except Exception:
+ pass
+
+ # And finally, cleanup the tmpdir.
+ # If it fails again, give up but do not throw error
+ try:
+ tmpdir.cleanup()
+ except Exception:
+ pass
+
+
+def _set_write_permission_and_retry(func, path, excinfo):
+ os.chmod(path, stat.S_IWRITE)
+ func(path)
+
+
+@contextlib.contextmanager
+def WeakFileLock(lock_file: Union[str, Path]) -> Generator[BaseFileLock, None, None]:
+ lock = FileLock(lock_file)
+ lock.acquire()
+
+ yield lock
+
+ try:
+ return lock.release()
+ except OSError:
+ try:
+ Path(lock_file).unlink()
+ except OSError:
+ pass
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_git_credential.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_git_credential.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8ed77f4e49ca88ff4fa9aba48cbf00195036013
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_git_credential.py
@@ -0,0 +1,121 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to manage Git credentials."""
+
+import re
+import subprocess
+from typing import List, Optional
+
+from ..constants import ENDPOINT
+from ._subprocess import run_interactive_subprocess, run_subprocess
+
+
+GIT_CREDENTIAL_REGEX = re.compile(
+ r"""
+ ^\s* # start of line
+ credential\.helper # credential.helper value
+ \s*=\s* # separator
+ (\w+) # the helper name (group 1)
+ (\s|$) # whitespace or end of line
+ """,
+ flags=re.MULTILINE | re.IGNORECASE | re.VERBOSE,
+)
+
+
+def list_credential_helpers(folder: Optional[str] = None) -> List[str]:
+ """Return the list of git credential helpers configured.
+
+ See https://git-scm.com/docs/gitcredentials.
+
+ Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
+ Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ try:
+ output = run_subprocess("git config --list", folder=folder).stdout
+ parsed = _parse_credential_output(output)
+ return parsed
+ except subprocess.CalledProcessError as exc:
+ raise EnvironmentError(exc.stderr)
+
+
+def set_git_credential(token: str, username: str = "hf_user", folder: Optional[str] = None) -> None:
+ """Save a username/token pair in git credential for HF Hub registry.
+
+ Credentials are saved in all configured helpers (store, cache, macOS keychain,...).
+ Calls "`git credential approve`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ username (`str`, defaults to `"hf_user"`):
+ A git username. Defaults to `"hf_user"`, the default user used in the Hub.
+ token (`str`, defaults to `"hf_user"`):
+ A git password. In practice, the User Access Token for the Hub.
+ See https://huggingface.co/settings/tokens.
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ with run_interactive_subprocess("git credential approve", folder=folder) as (
+ stdin,
+ _,
+ ):
+ stdin.write(f"url={ENDPOINT}\nusername={username.lower()}\npassword={token}\n\n")
+ stdin.flush()
+
+
+def unset_git_credential(username: str = "hf_user", folder: Optional[str] = None) -> None:
+ """Erase credentials from git credential for HF Hub registry.
+
+ Credentials are erased from the configured helpers (store, cache, macOS
+ keychain,...), if any. If `username` is not provided, any credential configured for
+ HF Hub endpoint is erased.
+ Calls "`git credential erase`" internally. See https://git-scm.com/docs/git-credential.
+
+ Args:
+ username (`str`, defaults to `"hf_user"`):
+ A git username. Defaults to `"hf_user"`, the default user used in the Hub.
+ folder (`str`, *optional*):
+ The folder in which to check the configured helpers.
+ """
+ with run_interactive_subprocess("git credential reject", folder=folder) as (
+ stdin,
+ _,
+ ):
+ standard_input = f"url={ENDPOINT}\n"
+ if username is not None:
+ standard_input += f"username={username.lower()}\n"
+ standard_input += "\n"
+
+ stdin.write(standard_input)
+ stdin.flush()
+
+
+def _parse_credential_output(output: str) -> List[str]:
+ """Parse the output of `git credential fill` to extract the password.
+
+ Args:
+ output (`str`):
+ The output of `git credential fill`.
+ """
+ # NOTE: If user has set an helper for a custom URL, it will not we caught here.
+ # Example: `credential.https://huggingface.co.helper=store`
+ # See: https://github.com/huggingface/huggingface_hub/pull/1138#discussion_r1013324508
+ return sorted( # Sort for nice printing
+ set( # Might have some duplicates
+ match[0] for match in GIT_CREDENTIAL_REGEX.findall(output)
+ )
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_headers.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_headers.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdcaf06e9d19de202a6e84b8bde212d4482d1b07
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_headers.py
@@ -0,0 +1,241 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle headers to send in calls to Huggingface Hub."""
+
+from typing import Dict, Optional, Union
+
+from .. import constants
+from ._runtime import (
+ get_fastai_version,
+ get_fastcore_version,
+ get_hf_hub_version,
+ get_python_version,
+ get_tf_version,
+ get_torch_version,
+ is_fastai_available,
+ is_fastcore_available,
+ is_tf_available,
+ is_torch_available,
+)
+from ._token import get_token
+from ._validators import validate_hf_hub_args
+
+
+class LocalTokenNotFoundError(EnvironmentError):
+ """Raised if local token is required but not found."""
+
+
+@validate_hf_hub_args
+def build_hf_headers(
+ *,
+ token: Optional[Union[bool, str]] = None,
+ is_write_action: bool = False,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+ headers: Optional[Dict[str, str]] = None,
+) -> Dict[str, str]:
+ """
+ Build headers dictionary to send in a HF Hub call.
+
+ By default, authorization token is always provided either from argument (explicit
+ use) or retrieved from the cache (implicit use). To explicitly avoid sending the
+ token to the Hub, set `token=False` or set the `HF_HUB_DISABLE_IMPLICIT_TOKEN`
+ environment variable.
+
+ In case of an API call that requires write access, an error is thrown if token is
+ `None` or token is an organization token (starting with `"api_org***"`).
+
+ In addition to the auth header, a user-agent is added to provide information about
+ the installed packages (versions of python, huggingface_hub, torch, tensorflow,
+ fastai and fastcore).
+
+ Args:
+ token (`str`, `bool`, *optional*):
+ The token to be sent in authorization header for the Hub call:
+ - if a string, it is used as the Hugging Face token
+ - if `True`, the token is read from the machine (cache or env variable)
+ - if `False`, authorization header is not set
+ - if `None`, the token is read from the machine only except if
+ `HF_HUB_DISABLE_IMPLICIT_TOKEN` env variable is set.
+ is_write_action (`bool`, default to `False`):
+ Set to True if the API call requires a write access. If `True`, the token
+ will be validated (cannot be `None`, cannot start by `"api_org***"`).
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to
+ the user-agent header.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added
+ to the user-agent header.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will
+ be completed with information about the installed packages.
+ headers (`dict`, *optional*):
+ Additional headers to include in the request. Those headers take precedence
+ over the ones generated by this function.
+
+ Returns:
+ A `Dict` of headers to pass in your API call.
+
+ Example:
+ ```py
+ >>> build_hf_headers(token="hf_***") # explicit token
+ {"authorization": "Bearer hf_***", "user-agent": ""}
+
+ >>> build_hf_headers(token=True) # explicitly use cached token
+ {"authorization": "Bearer hf_***",...}
+
+ >>> build_hf_headers(token=False) # explicitly don't use cached token
+ {"user-agent": ...}
+
+ >>> build_hf_headers() # implicit use of the cached token
+ {"authorization": "Bearer hf_***",...}
+
+ # HF_HUB_DISABLE_IMPLICIT_TOKEN=True # to set as env variable
+ >>> build_hf_headers() # token is not sent
+ {"user-agent": ...}
+
+ >>> build_hf_headers(token="api_org_***", is_write_action=True)
+ ValueError: You must use your personal account token for write-access methods.
+
+ >>> build_hf_headers(library_name="transformers", library_version="1.2.3")
+ {"authorization": ..., "user-agent": "transformers/1.2.3; hf_hub/0.10.2; python/3.10.4; tensorflow/1.55"}
+ ```
+
+ Raises:
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If organization token is passed and "write" access is required.
+ [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError)
+ If "write" access is required but token is not passed and not saved locally.
+ [`EnvironmentError`](https://docs.python.org/3/library/exceptions.html#EnvironmentError)
+ If `token=True` but token is not saved locally.
+ """
+ # Get auth token to send
+ token_to_send = get_token_to_send(token)
+ _validate_token_to_send(token_to_send, is_write_action=is_write_action)
+
+ # Combine headers
+ hf_headers = {
+ "user-agent": _http_user_agent(
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ )
+ }
+ if token_to_send is not None:
+ hf_headers["authorization"] = f"Bearer {token_to_send}"
+ if headers is not None:
+ hf_headers.update(headers)
+ return hf_headers
+
+
+def get_token_to_send(token: Optional[Union[bool, str]]) -> Optional[str]:
+ """Select the token to send from either `token` or the cache."""
+ # Case token is explicitly provided
+ if isinstance(token, str):
+ return token
+
+ # Case token is explicitly forbidden
+ if token is False:
+ return None
+
+ # Token is not provided: we get it from local cache
+ cached_token = get_token()
+
+ # Case token is explicitly required
+ if token is True:
+ if cached_token is None:
+ raise LocalTokenNotFoundError(
+ "Token is required (`token=True`), but no token found. You"
+ " need to provide a token or be logged in to Hugging Face with"
+ " `huggingface-cli login` or `huggingface_hub.login`. See"
+ " https://huggingface.co/settings/tokens."
+ )
+ return cached_token
+
+ # Case implicit use of the token is forbidden by env variable
+ if constants.HF_HUB_DISABLE_IMPLICIT_TOKEN:
+ return None
+
+ # Otherwise: we use the cached token as the user has not explicitly forbidden it
+ return cached_token
+
+
+def _validate_token_to_send(token: Optional[str], is_write_action: bool) -> None:
+ if is_write_action:
+ if token is None:
+ raise ValueError(
+ "Token is required (write-access action) but no token found. You need"
+ " to provide a token or be logged in to Hugging Face with"
+ " `huggingface-cli login` or `huggingface_hub.login`. See"
+ " https://huggingface.co/settings/tokens."
+ )
+ if token.startswith("api_org"):
+ raise ValueError(
+ "You must use your personal account token for write-access methods. To"
+ " generate a write-access token, go to"
+ " https://huggingface.co/settings/tokens"
+ )
+
+
+def _http_user_agent(
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> str:
+ """Format a user-agent string containing information about the installed packages.
+
+ Args:
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string.
+
+ Returns:
+ The formatted user-agent string.
+ """
+ if library_name is not None:
+ ua = f"{library_name}/{library_version}"
+ else:
+ ua = "unknown/None"
+ ua += f"; hf_hub/{get_hf_hub_version()}"
+ ua += f"; python/{get_python_version()}"
+
+ if not constants.HF_HUB_DISABLE_TELEMETRY:
+ if is_torch_available():
+ ua += f"; torch/{get_torch_version()}"
+ if is_tf_available():
+ ua += f"; tensorflow/{get_tf_version()}"
+ if is_fastai_available():
+ ua += f"; fastai/{get_fastai_version()}"
+ if is_fastcore_available():
+ ua += f"; fastcore/{get_fastcore_version()}"
+
+ if isinstance(user_agent, dict):
+ ua += "; " + "; ".join(f"{k}/{v}" for k, v in user_agent.items())
+ elif isinstance(user_agent, str):
+ ua += "; " + user_agent
+
+ return _deduplicate_user_agent(ua)
+
+
+def _deduplicate_user_agent(user_agent: str) -> str:
+ """Deduplicate redundant information in the generated user-agent."""
+ # Split around ";" > Strip whitespaces > Store as dict keys (ensure unicity) > format back as string
+ # Order is implicitly preserved by dictionary structure (see https://stackoverflow.com/a/53657523).
+ return "; ".join({key.strip(): None for key in user_agent.split(";")}.keys())
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_hf_folder.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_hf_folder.py
new file mode 100644
index 0000000000000000000000000000000000000000..502b22658b44d2221b535cbd943348bb93213245
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_hf_folder.py
@@ -0,0 +1,96 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contain helper class to retrieve/store token from/to local cache."""
+
+import warnings
+from pathlib import Path
+from typing import Optional
+
+from .. import constants
+from ._token import get_token
+
+
+class HfFolder:
+ path_token = Path(constants.HF_TOKEN_PATH)
+ # Private attribute. Will be removed in v0.15
+ _old_path_token = Path(constants._OLD_HF_TOKEN_PATH)
+
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.login` instead.")
+ @classmethod
+ def save_token(cls, token: str) -> None:
+ """
+ Save token, creating folder as needed.
+
+ Token is saved in the huggingface home folder. You can configure it by setting
+ the `HF_HOME` environment variable.
+
+ Args:
+ token (`str`):
+ The token to save to the [`HfFolder`]
+ """
+ cls.path_token.parent.mkdir(parents=True, exist_ok=True)
+ cls.path_token.write_text(token)
+
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.get_token` instead.")
+ @classmethod
+ def get_token(cls) -> Optional[str]:
+ """
+ Get token or None if not existent.
+
+ This method is deprecated in favor of [`huggingface_hub.get_token`] but is kept for backward compatibility.
+ Its behavior is the same as [`huggingface_hub.get_token`].
+
+ Returns:
+ `str` or `None`: The token, `None` if it doesn't exist.
+ """
+ # 0. Check if token exist in old path but not new location
+ try:
+ cls._copy_to_new_path_and_warn()
+ except Exception: # if not possible (e.g. PermissionError), do not raise
+ pass
+
+ return get_token()
+
+ # TODO: deprecate when adapted in transformers/datasets/gradio
+ # @_deprecate_method(version="1.0", message="Use `huggingface_hub.logout` instead.")
+ @classmethod
+ def delete_token(cls) -> None:
+ """
+ Deletes the token from storage. Does not fail if token does not exist.
+ """
+ try:
+ cls.path_token.unlink()
+ except FileNotFoundError:
+ pass
+
+ try:
+ cls._old_path_token.unlink()
+ except FileNotFoundError:
+ pass
+
+ @classmethod
+ def _copy_to_new_path_and_warn(cls):
+ if cls._old_path_token.exists() and not cls.path_token.exists():
+ cls.save_token(cls._old_path_token.read_text())
+ warnings.warn(
+ f"A token has been found in `{cls._old_path_token}`. This is the old"
+ " path where tokens were stored. The new location is"
+ f" `{cls.path_token}` which is configurable using `HF_HOME` environment"
+ " variable. Your token has been copied to this new location. You can"
+ " now safely delete the old token file manually or use"
+ " `huggingface-cli logout`."
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_http.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_http.py
new file mode 100644
index 0000000000000000000000000000000000000000..081d84a4f455e5b47c2f0f6483550dc1c8ac7a36
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_http.py
@@ -0,0 +1,321 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle HTTP requests in Huggingface Hub."""
+
+import io
+import os
+import threading
+import time
+import uuid
+from functools import lru_cache
+from http import HTTPStatus
+from typing import Callable, Optional, Tuple, Type, Union
+
+import requests
+from requests import Response
+from requests.adapters import HTTPAdapter
+from requests.models import PreparedRequest
+
+from .. import constants
+from . import logging
+from ._typing import HTTP_METHOD_T
+
+
+logger = logging.get_logger(__name__)
+
+# Both headers are used by the Hub to debug failed requests.
+# `X_AMZN_TRACE_ID` is better as it also works to debug on Cloudfront and ALB.
+# If `X_AMZN_TRACE_ID` is set, the Hub will use it as well.
+X_AMZN_TRACE_ID = "X-Amzn-Trace-Id"
+X_REQUEST_ID = "x-request-id"
+
+
+class OfflineModeIsEnabled(ConnectionError):
+ """Raised when a request is made but `HF_HUB_OFFLINE=1` is set as environment variable."""
+
+
+class UniqueRequestIdAdapter(HTTPAdapter):
+ X_AMZN_TRACE_ID = "X-Amzn-Trace-Id"
+
+ def add_headers(self, request, **kwargs):
+ super().add_headers(request, **kwargs)
+
+ # Add random request ID => easier for server-side debug
+ if X_AMZN_TRACE_ID not in request.headers:
+ request.headers[X_AMZN_TRACE_ID] = request.headers.get(X_REQUEST_ID) or str(uuid.uuid4())
+
+ # Add debug log
+ has_token = str(request.headers.get("authorization", "")).startswith("Bearer hf_")
+ logger.debug(
+ f"Request {request.headers[X_AMZN_TRACE_ID]}: {request.method} {request.url} (authenticated: {has_token})"
+ )
+
+ def send(self, request: PreparedRequest, *args, **kwargs) -> Response:
+ """Catch any RequestException to append request id to the error message for debugging."""
+ try:
+ return super().send(request, *args, **kwargs)
+ except requests.RequestException as e:
+ request_id = request.headers.get(X_AMZN_TRACE_ID)
+ if request_id is not None:
+ # Taken from https://stackoverflow.com/a/58270258
+ e.args = (*e.args, f"(Request ID: {request_id})")
+ raise
+
+
+class OfflineAdapter(HTTPAdapter):
+ def send(self, request: PreparedRequest, *args, **kwargs) -> Response:
+ raise OfflineModeIsEnabled(
+ f"Cannot reach {request.url}: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable."
+ )
+
+
+def _default_backend_factory() -> requests.Session:
+ session = requests.Session()
+ if constants.HF_HUB_OFFLINE:
+ session.mount("http://", OfflineAdapter())
+ session.mount("https://", OfflineAdapter())
+ else:
+ session.mount("http://", UniqueRequestIdAdapter())
+ session.mount("https://", UniqueRequestIdAdapter())
+ return session
+
+
+BACKEND_FACTORY_T = Callable[[], requests.Session]
+_GLOBAL_BACKEND_FACTORY: BACKEND_FACTORY_T = _default_backend_factory
+
+
+def configure_http_backend(backend_factory: BACKEND_FACTORY_T = _default_backend_factory) -> None:
+ """
+ Configure the HTTP backend by providing a `backend_factory`. Any HTTP calls made by `huggingface_hub` will use a
+ Session object instantiated by this factory. This can be useful if you are running your scripts in a specific
+ environment requiring custom configuration (e.g. custom proxy or certifications).
+
+ Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
+ `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
+ set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
+ calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.
+
+ See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub import configure_http_backend, get_session
+
+ # Create a factory function that returns a Session with configured proxies
+ def backend_factory() -> requests.Session:
+ session = requests.Session()
+ session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
+ return session
+
+ # Set it as the default session factory
+ configure_http_backend(backend_factory=backend_factory)
+
+ # In practice, this is mostly done internally in `huggingface_hub`
+ session = get_session()
+ ```
+ """
+ global _GLOBAL_BACKEND_FACTORY
+ _GLOBAL_BACKEND_FACTORY = backend_factory
+ reset_sessions()
+
+
+def get_session() -> requests.Session:
+ """
+ Get a `requests.Session` object, using the session factory from the user.
+
+ Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
+ `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
+ set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
+ calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.
+
+ See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.
+
+ Example:
+ ```py
+ import requests
+ from huggingface_hub import configure_http_backend, get_session
+
+ # Create a factory function that returns a Session with configured proxies
+ def backend_factory() -> requests.Session:
+ session = requests.Session()
+ session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
+ return session
+
+ # Set it as the default session factory
+ configure_http_backend(backend_factory=backend_factory)
+
+ # In practice, this is mostly done internally in `huggingface_hub`
+ session = get_session()
+ ```
+ """
+ return _get_session_from_cache(process_id=os.getpid(), thread_id=threading.get_ident())
+
+
+def reset_sessions() -> None:
+ """Reset the cache of sessions.
+
+ Mostly used internally when sessions are reconfigured or an SSLError is raised.
+ See [`configure_http_backend`] for more details.
+ """
+ _get_session_from_cache.cache_clear()
+
+
+@lru_cache
+def _get_session_from_cache(process_id: int, thread_id: int) -> requests.Session:
+ """
+ Create a new session per thread using global factory. Using LRU cache (maxsize 128) to avoid memory leaks when
+ using thousands of threads. Cache is cleared when `configure_http_backend` is called.
+ """
+ return _GLOBAL_BACKEND_FACTORY()
+
+
+def http_backoff(
+ method: HTTP_METHOD_T,
+ url: str,
+ *,
+ max_retries: int = 5,
+ base_wait_time: float = 1,
+ max_wait_time: float = 8,
+ retry_on_exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]] = (
+ requests.Timeout,
+ requests.ConnectionError,
+ ),
+ retry_on_status_codes: Union[int, Tuple[int, ...]] = HTTPStatus.SERVICE_UNAVAILABLE,
+ **kwargs,
+) -> Response:
+ """Wrapper around requests to retry calls on an endpoint, with exponential backoff.
+
+ Endpoint call is retried on exceptions (ex: connection timeout, proxy error,...)
+ and/or on specific status codes (ex: service unavailable). If the call failed more
+ than `max_retries`, the exception is thrown or `raise_for_status` is called on the
+ response object.
+
+ Re-implement mechanisms from the `backoff` library to avoid adding an external
+ dependencies to `hugging_face_hub`. See https://github.com/litl/backoff.
+
+ Args:
+ method (`Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]`):
+ HTTP method to perform.
+ url (`str`):
+ The URL of the resource to fetch.
+ max_retries (`int`, *optional*, defaults to `5`):
+ Maximum number of retries, defaults to 5 (no retries).
+ base_wait_time (`float`, *optional*, defaults to `1`):
+ Duration (in seconds) to wait before retrying the first time.
+ Wait time between retries then grows exponentially, capped by
+ `max_wait_time`.
+ max_wait_time (`float`, *optional*, defaults to `8`):
+ Maximum duration (in seconds) to wait before retrying.
+ retry_on_exceptions (`Type[Exception]` or `Tuple[Type[Exception]]`, *optional*):
+ Define which exceptions must be caught to retry the request. Can be a single type or a tuple of types.
+ By default, retry on `requests.Timeout` and `requests.ConnectionError`.
+ retry_on_status_codes (`int` or `Tuple[int]`, *optional*, defaults to `503`):
+ Define on which status codes the request must be retried. By default, only
+ HTTP 503 Service Unavailable is retried.
+ **kwargs (`dict`, *optional*):
+ kwargs to pass to `requests.request`.
+
+ Example:
+ ```
+ >>> from huggingface_hub.utils import http_backoff
+
+ # Same usage as "requests.request".
+ >>> response = http_backoff("GET", "https://www.google.com")
+ >>> response.raise_for_status()
+
+ # If you expect a Gateway Timeout from time to time
+ >>> http_backoff("PUT", upload_url, data=data, retry_on_status_codes=504)
+ >>> response.raise_for_status()
+ ```
+
+
+
+ When using `requests` it is possible to stream data by passing an iterator to the
+ `data` argument. On http backoff this is a problem as the iterator is not reset
+ after a failed call. This issue is mitigated for file objects or any IO streams
+ by saving the initial position of the cursor (with `data.tell()`) and resetting the
+ cursor between each call (with `data.seek()`). For arbitrary iterators, http backoff
+ will fail. If this is a hard constraint for you, please let us know by opening an
+ issue on [Github](https://github.com/huggingface/huggingface_hub).
+
+
+ """
+ if isinstance(retry_on_exceptions, type): # Tuple from single exception type
+ retry_on_exceptions = (retry_on_exceptions,)
+
+ if isinstance(retry_on_status_codes, int): # Tuple from single status code
+ retry_on_status_codes = (retry_on_status_codes,)
+
+ nb_tries = 0
+ sleep_time = base_wait_time
+
+ # If `data` is used and is a file object (or any IO), it will be consumed on the
+ # first HTTP request. We need to save the initial position so that the full content
+ # of the file is re-sent on http backoff. See warning tip in docstring.
+ io_obj_initial_pos = None
+ if "data" in kwargs and isinstance(kwargs["data"], io.IOBase):
+ io_obj_initial_pos = kwargs["data"].tell()
+
+ session = get_session()
+ while True:
+ nb_tries += 1
+ try:
+ # If `data` is used and is a file object (or any IO), set back cursor to
+ # initial position.
+ if io_obj_initial_pos is not None:
+ kwargs["data"].seek(io_obj_initial_pos)
+
+ # Perform request and return if status_code is not in the retry list.
+ response = session.request(method=method, url=url, **kwargs)
+ if response.status_code not in retry_on_status_codes:
+ return response
+
+ # Wrong status code returned (HTTP 503 for instance)
+ logger.warning(f"HTTP Error {response.status_code} thrown while requesting {method} {url}")
+ if nb_tries > max_retries:
+ response.raise_for_status() # Will raise uncaught exception
+ # We return response to avoid infinite loop in the corner case where the
+ # user ask for retry on a status code that doesn't raise_for_status.
+ return response
+
+ except retry_on_exceptions as err:
+ logger.warning(f"'{err}' thrown while requesting {method} {url}")
+
+ if isinstance(err, requests.ConnectionError):
+ reset_sessions() # In case of SSLError it's best to reset the shared requests.Session objects
+
+ if nb_tries > max_retries:
+ raise err
+
+ # Sleep for X seconds
+ logger.warning(f"Retrying in {sleep_time}s [Retry {nb_tries}/{max_retries}].")
+ time.sleep(sleep_time)
+
+ # Update sleep time for next retry
+ sleep_time = min(max_wait_time, sleep_time * 2) # Exponential backoff
+
+
+def fix_hf_endpoint_in_url(url: str, endpoint: Optional[str]) -> str:
+ """Replace the default endpoint in a URL by a custom one.
+
+ This is useful when using a proxy and the Hugging Face Hub returns a URL with the default endpoint.
+ """
+ endpoint = endpoint or constants.ENDPOINT
+ # check if a proxy has been set => if yes, update the returned URL to use the proxy
+ if endpoint not in (None, constants._HF_DEFAULT_ENDPOINT, constants._HF_DEFAULT_STAGING_ENDPOINT):
+ url = url.replace(constants._HF_DEFAULT_ENDPOINT, endpoint)
+ url = url.replace(constants._HF_DEFAULT_STAGING_ENDPOINT, endpoint)
+ return url
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_pagination.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_pagination.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7ab4fe7cba9bd13f01d9c81854a00fd30b7f0d9
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_pagination.py
@@ -0,0 +1,52 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle pagination on Huggingface Hub."""
+
+from typing import Dict, Iterable, Optional
+
+import requests
+
+from . import get_session, hf_raise_for_status, logging
+
+
+logger = logging.get_logger(__name__)
+
+
+def paginate(path: str, params: Dict, headers: Dict) -> Iterable:
+ """Fetch a list of models/datasets/spaces and paginate through results.
+
+ This is using the same "Link" header format as GitHub.
+ See:
+ - https://requests.readthedocs.io/en/latest/api/#requests.Response.links
+ - https://docs.github.com/en/rest/guides/traversing-with-pagination#link-header
+ """
+ session = get_session()
+ r = session.get(path, params=params, headers=headers)
+ hf_raise_for_status(r)
+ yield from r.json()
+
+ # Follow pages
+ # Next link already contains query params
+ next_page = _get_next_page(r)
+ while next_page is not None:
+ logger.debug(f"Pagination detected. Requesting next page: {next_page}")
+ r = session.get(next_page, headers=headers)
+ hf_raise_for_status(r)
+ yield from r.json()
+ next_page = _get_next_page(r)
+
+
+def _get_next_page(response: requests.Response) -> Optional[str]:
+ return response.links.get("next", {}).get("url")
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_paths.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_paths.py
new file mode 100644
index 0000000000000000000000000000000000000000..411d7d52bb3edc2be948d85267e21ce2be91d460
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_paths.py
@@ -0,0 +1,118 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to handle paths in Huggingface Hub."""
+
+from fnmatch import fnmatch
+from pathlib import Path
+from typing import Callable, Generator, Iterable, List, Optional, TypeVar, Union
+
+
+T = TypeVar("T")
+
+IGNORE_GIT_FOLDER_PATTERNS = [".git", ".git/*", "*/.git", "**/.git/**"]
+
+
+def filter_repo_objects(
+ items: Iterable[T],
+ *,
+ allow_patterns: Optional[Union[List[str], str]] = None,
+ ignore_patterns: Optional[Union[List[str], str]] = None,
+ key: Optional[Callable[[T], str]] = None,
+) -> Generator[T, None, None]:
+ """Filter repo objects based on an allowlist and a denylist.
+
+ Input must be a list of paths (`str` or `Path`) or a list of arbitrary objects.
+ In the later case, `key` must be provided and specifies a function of one argument
+ that is used to extract a path from each element in iterable.
+
+ Patterns are Unix shell-style wildcards which are NOT regular expressions. See
+ https://docs.python.org/3/library/fnmatch.html for more details.
+
+ Args:
+ items (`Iterable`):
+ List of items to filter.
+ allow_patterns (`str` or `List[str]`, *optional*):
+ Patterns constituting the allowlist. If provided, item paths must match at
+ least one pattern from the allowlist.
+ ignore_patterns (`str` or `List[str]`, *optional*):
+ Patterns constituting the denylist. If provided, item paths must not match
+ any patterns from the denylist.
+ key (`Callable[[T], str]`, *optional*):
+ Single-argument function to extract a path from each item. If not provided,
+ the `items` must already be `str` or `Path`.
+
+ Returns:
+ Filtered list of objects, as a generator.
+
+ Raises:
+ :class:`ValueError`:
+ If `key` is not provided and items are not `str` or `Path`.
+
+ Example usage with paths:
+ ```python
+ >>> # Filter only PDFs that are not hidden.
+ >>> list(filter_repo_objects(
+ ... ["aaa.PDF", "bbb.jpg", ".ccc.pdf", ".ddd.png"],
+ ... allow_patterns=["*.pdf"],
+ ... ignore_patterns=[".*"],
+ ... ))
+ ["aaa.pdf"]
+ ```
+
+ Example usage with objects:
+ ```python
+ >>> list(filter_repo_objects(
+ ... [
+ ... CommitOperationAdd(path_or_fileobj="/tmp/aaa.pdf", path_in_repo="aaa.pdf")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/bbb.jpg", path_in_repo="bbb.jpg")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/.ccc.pdf", path_in_repo=".ccc.pdf")
+ ... CommitOperationAdd(path_or_fileobj="/tmp/.ddd.png", path_in_repo=".ddd.png")
+ ... ],
+ ... allow_patterns=["*.pdf"],
+ ... ignore_patterns=[".*"],
+ ... key=lambda x: x.repo_in_path
+ ... ))
+ [CommitOperationAdd(path_or_fileobj="/tmp/aaa.pdf", path_in_repo="aaa.pdf")]
+ ```
+ """
+ if isinstance(allow_patterns, str):
+ allow_patterns = [allow_patterns]
+
+ if isinstance(ignore_patterns, str):
+ ignore_patterns = [ignore_patterns]
+
+ if key is None:
+
+ def _identity(item: T) -> str:
+ if isinstance(item, str):
+ return item
+ if isinstance(item, Path):
+ return str(item)
+ raise ValueError(f"Please provide `key` argument in `filter_repo_objects`: `{item}` is not a string.")
+
+ key = _identity # Items must be `str` or `Path`, otherwise raise ValueError
+
+ for item in items:
+ path = key(item)
+
+ # Skip if there's an allowlist and path doesn't match any
+ if allow_patterns is not None and not any(fnmatch(path, r) for r in allow_patterns):
+ continue
+
+ # Skip if there's a denylist and path matches any
+ if ignore_patterns is not None and any(fnmatch(path, r) for r in ignore_patterns):
+ continue
+
+ yield item
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_runtime.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_runtime.py
new file mode 100644
index 0000000000000000000000000000000000000000..21f852736c841be1caa747f8dc6c8657c0e3d8f2
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_runtime.py
@@ -0,0 +1,372 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Check presence of installed packages at runtime."""
+
+import importlib.metadata
+import platform
+import sys
+import warnings
+from typing import Any, Dict
+
+from .. import __version__, constants
+
+
+_PY_VERSION: str = sys.version.split()[0].rstrip("+")
+
+_package_versions = {}
+
+_CANDIDATES = {
+ "aiohttp": {"aiohttp"},
+ "fastai": {"fastai"},
+ "fastcore": {"fastcore"},
+ "gradio": {"gradio"},
+ "graphviz": {"graphviz"},
+ "hf_transfer": {"hf_transfer"},
+ "jinja": {"Jinja2"},
+ "keras": {"keras"},
+ "minijinja": {"minijinja"},
+ "numpy": {"numpy"},
+ "pillow": {"Pillow"},
+ "pydantic": {"pydantic"},
+ "pydot": {"pydot"},
+ "safetensors": {"safetensors"},
+ "tensorboard": {"tensorboardX"},
+ "tensorflow": (
+ "tensorflow",
+ "tensorflow-cpu",
+ "tensorflow-gpu",
+ "tf-nightly",
+ "tf-nightly-cpu",
+ "tf-nightly-gpu",
+ "intel-tensorflow",
+ "intel-tensorflow-avx512",
+ "tensorflow-rocm",
+ "tensorflow-macos",
+ ),
+ "torch": {"torch"},
+}
+
+# Check once at runtime
+for candidate_name, package_names in _CANDIDATES.items():
+ _package_versions[candidate_name] = "N/A"
+ for name in package_names:
+ try:
+ _package_versions[candidate_name] = importlib.metadata.version(name)
+ break
+ except importlib.metadata.PackageNotFoundError:
+ pass
+
+
+def _get_version(package_name: str) -> str:
+ return _package_versions.get(package_name, "N/A")
+
+
+def is_package_available(package_name: str) -> bool:
+ return _get_version(package_name) != "N/A"
+
+
+# Python
+def get_python_version() -> str:
+ return _PY_VERSION
+
+
+# Huggingface Hub
+def get_hf_hub_version() -> str:
+ return __version__
+
+
+# aiohttp
+def is_aiohttp_available() -> bool:
+ return is_package_available("aiohttp")
+
+
+def get_aiohttp_version() -> str:
+ return _get_version("aiohttp")
+
+
+# FastAI
+def is_fastai_available() -> bool:
+ return is_package_available("fastai")
+
+
+def get_fastai_version() -> str:
+ return _get_version("fastai")
+
+
+# Fastcore
+def is_fastcore_available() -> bool:
+ return is_package_available("fastcore")
+
+
+def get_fastcore_version() -> str:
+ return _get_version("fastcore")
+
+
+# FastAI
+def is_gradio_available() -> bool:
+ return is_package_available("gradio")
+
+
+def get_gradio_version() -> str:
+ return _get_version("gradio")
+
+
+# Graphviz
+def is_graphviz_available() -> bool:
+ return is_package_available("graphviz")
+
+
+def get_graphviz_version() -> str:
+ return _get_version("graphviz")
+
+
+# hf_transfer
+def is_hf_transfer_available() -> bool:
+ return is_package_available("hf_transfer")
+
+
+def get_hf_transfer_version() -> str:
+ return _get_version("hf_transfer")
+
+
+# keras
+def is_keras_available() -> bool:
+ return is_package_available("keras")
+
+
+def get_keras_version() -> str:
+ return _get_version("keras")
+
+
+# Minijinja
+def is_minijinja_available() -> bool:
+ return is_package_available("minijinja")
+
+
+def get_minijinja_version() -> str:
+ return _get_version("minijinja")
+
+
+# Numpy
+def is_numpy_available() -> bool:
+ return is_package_available("numpy")
+
+
+def get_numpy_version() -> str:
+ return _get_version("numpy")
+
+
+# Jinja
+def is_jinja_available() -> bool:
+ return is_package_available("jinja")
+
+
+def get_jinja_version() -> str:
+ return _get_version("jinja")
+
+
+# Pillow
+def is_pillow_available() -> bool:
+ return is_package_available("pillow")
+
+
+def get_pillow_version() -> str:
+ return _get_version("pillow")
+
+
+# Pydantic
+def is_pydantic_available() -> bool:
+ if not is_package_available("pydantic"):
+ return False
+ # For Pydantic, we add an extra check to test whether it is correctly installed or not. If both pydantic 2.x and
+ # typing_extensions<=4.5.0 are installed, then pydantic will fail at import time. This should not happen when
+ # it is installed with `pip install huggingface_hub[inference]` but it can happen when it is installed manually
+ # by the user in an environment that we don't control.
+ #
+ # Usually we won't need to do this kind of check on optional dependencies. However, pydantic is a special case
+ # as it is automatically imported when doing `from huggingface_hub import ...` even if the user doesn't use it.
+ #
+ # See https://github.com/huggingface/huggingface_hub/pull/1829 for more details.
+ try:
+ from pydantic import validator # noqa: F401
+ except ImportError:
+ # Example: "ImportError: cannot import name 'TypeAliasType' from 'typing_extensions'"
+ warnings.warn(
+ "Pydantic is installed but cannot be imported. Please check your installation. `huggingface_hub` will "
+ "default to not using Pydantic. Error message: '{e}'"
+ )
+ return False
+ return True
+
+
+def get_pydantic_version() -> str:
+ return _get_version("pydantic")
+
+
+# Pydot
+def is_pydot_available() -> bool:
+ return is_package_available("pydot")
+
+
+def get_pydot_version() -> str:
+ return _get_version("pydot")
+
+
+# Tensorboard
+def is_tensorboard_available() -> bool:
+ return is_package_available("tensorboard")
+
+
+def get_tensorboard_version() -> str:
+ return _get_version("tensorboard")
+
+
+# Tensorflow
+def is_tf_available() -> bool:
+ return is_package_available("tensorflow")
+
+
+def get_tf_version() -> str:
+ return _get_version("tensorflow")
+
+
+# Torch
+def is_torch_available() -> bool:
+ return is_package_available("torch")
+
+
+def get_torch_version() -> str:
+ return _get_version("torch")
+
+
+# Safetensors
+def is_safetensors_available() -> bool:
+ return is_package_available("safetensors")
+
+
+# Shell-related helpers
+try:
+ # Set to `True` if script is running in a Google Colab notebook.
+ # If running in Google Colab, git credential store is set globally which makes the
+ # warning disappear. See https://github.com/huggingface/huggingface_hub/issues/1043
+ #
+ # Taken from https://stackoverflow.com/a/63519730.
+ _is_google_colab = "google.colab" in str(get_ipython()) # type: ignore # noqa: F821
+except NameError:
+ _is_google_colab = False
+
+
+def is_notebook() -> bool:
+ """Return `True` if code is executed in a notebook (Jupyter, Colab, QTconsole).
+
+ Taken from https://stackoverflow.com/a/39662359.
+ Adapted to make it work with Google colab as well.
+ """
+ try:
+ shell_class = get_ipython().__class__ # type: ignore # noqa: F821
+ for parent_class in shell_class.__mro__: # e.g. "is subclass of"
+ if parent_class.__name__ == "ZMQInteractiveShell":
+ return True # Jupyter notebook, Google colab or qtconsole
+ return False
+ except NameError:
+ return False # Probably standard Python interpreter
+
+
+def is_google_colab() -> bool:
+ """Return `True` if code is executed in a Google colab.
+
+ Taken from https://stackoverflow.com/a/63519730.
+ """
+ return _is_google_colab
+
+
+def dump_environment_info() -> Dict[str, Any]:
+ """Dump information about the machine to help debugging issues.
+
+ Similar helper exist in:
+ - `datasets` (https://github.com/huggingface/datasets/blob/main/src/datasets/commands/env.py)
+ - `diffusers` (https://github.com/huggingface/diffusers/blob/main/src/diffusers/commands/env.py)
+ - `transformers` (https://github.com/huggingface/transformers/blob/main/src/transformers/commands/env.py)
+ """
+ from huggingface_hub import get_token, whoami
+ from huggingface_hub.utils import list_credential_helpers
+
+ token = get_token()
+
+ # Generic machine info
+ info: Dict[str, Any] = {
+ "huggingface_hub version": get_hf_hub_version(),
+ "Platform": platform.platform(),
+ "Python version": get_python_version(),
+ }
+
+ # Interpreter info
+ try:
+ shell_class = get_ipython().__class__ # type: ignore # noqa: F821
+ info["Running in iPython ?"] = "Yes"
+ info["iPython shell"] = shell_class.__name__
+ except NameError:
+ info["Running in iPython ?"] = "No"
+ info["Running in notebook ?"] = "Yes" if is_notebook() else "No"
+ info["Running in Google Colab ?"] = "Yes" if is_google_colab() else "No"
+
+ # Login info
+ info["Token path ?"] = constants.HF_TOKEN_PATH
+ info["Has saved token ?"] = token is not None
+ if token is not None:
+ try:
+ info["Who am I ?"] = whoami()["name"]
+ except Exception:
+ pass
+
+ try:
+ info["Configured git credential helpers"] = ", ".join(list_credential_helpers())
+ except Exception:
+ pass
+
+ # Installed dependencies
+ info["FastAI"] = get_fastai_version()
+ info["Tensorflow"] = get_tf_version()
+ info["Torch"] = get_torch_version()
+ info["Jinja2"] = get_jinja_version()
+ info["Graphviz"] = get_graphviz_version()
+ info["keras"] = get_keras_version()
+ info["Pydot"] = get_pydot_version()
+ info["Pillow"] = get_pillow_version()
+ info["hf_transfer"] = get_hf_transfer_version()
+ info["gradio"] = get_gradio_version()
+ info["tensorboard"] = get_tensorboard_version()
+ info["numpy"] = get_numpy_version()
+ info["pydantic"] = get_pydantic_version()
+ info["aiohttp"] = get_aiohttp_version()
+
+ # Environment variables
+ info["ENDPOINT"] = constants.ENDPOINT
+ info["HF_HUB_CACHE"] = constants.HF_HUB_CACHE
+ info["HF_ASSETS_CACHE"] = constants.HF_ASSETS_CACHE
+ info["HF_TOKEN_PATH"] = constants.HF_TOKEN_PATH
+ info["HF_HUB_OFFLINE"] = constants.HF_HUB_OFFLINE
+ info["HF_HUB_DISABLE_TELEMETRY"] = constants.HF_HUB_DISABLE_TELEMETRY
+ info["HF_HUB_DISABLE_PROGRESS_BARS"] = constants.HF_HUB_DISABLE_PROGRESS_BARS
+ info["HF_HUB_DISABLE_SYMLINKS_WARNING"] = constants.HF_HUB_DISABLE_SYMLINKS_WARNING
+ info["HF_HUB_DISABLE_EXPERIMENTAL_WARNING"] = constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING
+ info["HF_HUB_DISABLE_IMPLICIT_TOKEN"] = constants.HF_HUB_DISABLE_IMPLICIT_TOKEN
+ info["HF_HUB_ENABLE_HF_TRANSFER"] = constants.HF_HUB_ENABLE_HF_TRANSFER
+ info["HF_HUB_ETAG_TIMEOUT"] = constants.HF_HUB_ETAG_TIMEOUT
+ info["HF_HUB_DOWNLOAD_TIMEOUT"] = constants.HF_HUB_DOWNLOAD_TIMEOUT
+
+ print("\nCopy-and-paste the text below in your GitHub issue.\n")
+ print("\n".join([f"- {prop}: {val}" for prop, val in info.items()]) + "\n")
+ return info
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_safetensors.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_safetensors.py
new file mode 100644
index 0000000000000000000000000000000000000000..d37e8f76fee25976d48ad591fe8afb277ae6ef38
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_safetensors.py
@@ -0,0 +1,124 @@
+import functools
+import operator
+from collections import defaultdict
+from dataclasses import dataclass, field
+from typing import Dict, List, Literal, Optional, Tuple
+
+
+FILENAME_T = str
+TENSOR_NAME_T = str
+DTYPE_T = Literal["F64", "F32", "F16", "BF16", "I64", "I32", "I16", "I8", "U8", "BOOL"]
+
+
+class SafetensorsParsingError(Exception):
+ """Raised when failing to parse a safetensors file metadata.
+
+ This can be the case if the file is not a safetensors file or does not respect the specification.
+ """
+
+
+class NotASafetensorsRepoError(Exception):
+ """Raised when a repo is not a Safetensors repo i.e. doesn't have either a `model.safetensors` or a
+ `model.safetensors.index.json` file.
+ """
+
+
+@dataclass
+class TensorInfo:
+ """Information about a tensor.
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ dtype (`str`):
+ The data type of the tensor ("F64", "F32", "F16", "BF16", "I64", "I32", "I16", "I8", "U8", "BOOL").
+ shape (`List[int]`):
+ The shape of the tensor.
+ data_offsets (`Tuple[int, int]`):
+ The offsets of the data in the file as a tuple `[BEGIN, END]`.
+ parameter_count (`int`):
+ The number of parameters in the tensor.
+ """
+
+ dtype: DTYPE_T
+ shape: List[int]
+ data_offsets: Tuple[int, int]
+ parameter_count: int = field(init=False)
+
+ def __post_init__(self) -> None:
+ # Taken from https://stackoverflow.com/a/13840436
+ try:
+ self.parameter_count = functools.reduce(operator.mul, self.shape)
+ except TypeError:
+ self.parameter_count = 1 # scalar value has no shape
+
+
+@dataclass
+class SafetensorsFileMetadata:
+ """Metadata for a Safetensors file hosted on the Hub.
+
+ This class is returned by [`parse_safetensors_file_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ metadata (`Dict`):
+ The metadata contained in the file.
+ tensors (`Dict[str, TensorInfo]`):
+ A map of all tensors. Keys are tensor names and values are information about the corresponding tensor, as a
+ [`TensorInfo`] object.
+ parameter_count (`Dict[str, int]`):
+ A map of the number of parameters per data type. Keys are data types and values are the number of parameters
+ of that data type.
+ """
+
+ metadata: Dict[str, str]
+ tensors: Dict[TENSOR_NAME_T, TensorInfo]
+ parameter_count: Dict[DTYPE_T, int] = field(init=False)
+
+ def __post_init__(self) -> None:
+ parameter_count: Dict[DTYPE_T, int] = defaultdict(int)
+ for tensor in self.tensors.values():
+ parameter_count[tensor.dtype] += tensor.parameter_count
+ self.parameter_count = dict(parameter_count)
+
+
+@dataclass
+class SafetensorsRepoMetadata:
+ """Metadata for a Safetensors repo.
+
+ A repo is considered to be a Safetensors repo if it contains either a 'model.safetensors' weight file (non-shared
+ model) or a 'model.safetensors.index.json' index file (sharded model) at its root.
+
+ This class is returned by [`get_safetensors_metadata`].
+
+ For more details regarding the safetensors format, check out https://huggingface.co/docs/safetensors/index#format.
+
+ Attributes:
+ metadata (`Dict`, *optional*):
+ The metadata contained in the 'model.safetensors.index.json' file, if it exists. Only populated for sharded
+ models.
+ sharded (`bool`):
+ Whether the repo contains a sharded model or not.
+ weight_map (`Dict[str, str]`):
+ A map of all weights. Keys are tensor names and values are filenames of the files containing the tensors.
+ files_metadata (`Dict[str, SafetensorsFileMetadata]`):
+ A map of all files metadata. Keys are filenames and values are the metadata of the corresponding file, as
+ a [`SafetensorsFileMetadata`] object.
+ parameter_count (`Dict[str, int]`):
+ A map of the number of parameters per data type. Keys are data types and values are the number of parameters
+ of that data type.
+ """
+
+ metadata: Optional[Dict]
+ sharded: bool
+ weight_map: Dict[TENSOR_NAME_T, FILENAME_T] # tensor name -> filename
+ files_metadata: Dict[FILENAME_T, SafetensorsFileMetadata] # filename -> metadata
+ parameter_count: Dict[DTYPE_T, int] = field(init=False)
+
+ def __post_init__(self) -> None:
+ parameter_count: Dict[DTYPE_T, int] = defaultdict(int)
+ for file_metadata in self.files_metadata.values():
+ for dtype, nb_parameters_ in file_metadata.parameter_count.items():
+ parameter_count[dtype] += nb_parameters_
+ self.parameter_count = dict(parameter_count)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_subprocess.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_subprocess.py
new file mode 100644
index 0000000000000000000000000000000000000000..a09e0d58868ecd699ea3a3e503a8a702d25c8ea5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_subprocess.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+"""Contains utilities to easily handle subprocesses in `huggingface_hub`."""
+
+import os
+import subprocess
+import sys
+from contextlib import contextmanager
+from io import StringIO
+from pathlib import Path
+from typing import IO, Generator, List, Optional, Tuple, Union
+
+from .logging import get_logger
+
+
+logger = get_logger(__name__)
+
+
+@contextmanager
+def capture_output() -> Generator[StringIO, None, None]:
+ """Capture output that is printed to terminal.
+
+ Taken from https://stackoverflow.com/a/34738440
+
+ Example:
+ ```py
+ >>> with capture_output() as output:
+ ... print("hello world")
+ >>> assert output.getvalue() == "hello world\n"
+ ```
+ """
+ output = StringIO()
+ previous_output = sys.stdout
+ sys.stdout = output
+ yield output
+ sys.stdout = previous_output
+
+
+def run_subprocess(
+ command: Union[str, List[str]],
+ folder: Optional[Union[str, Path]] = None,
+ check=True,
+ **kwargs,
+) -> subprocess.CompletedProcess:
+ """
+ Method to run subprocesses. Calling this will capture the `stderr` and `stdout`,
+ please call `subprocess.run` manually in case you would like for them not to
+ be captured.
+
+ Args:
+ command (`str` or `List[str]`):
+ The command to execute as a string or list of strings.
+ folder (`str`, *optional*):
+ The folder in which to run the command. Defaults to current working
+ directory (from `os.getcwd()`).
+ check (`bool`, *optional*, defaults to `True`):
+ Setting `check` to `True` will raise a `subprocess.CalledProcessError`
+ when the subprocess has a non-zero exit code.
+ kwargs (`Dict[str]`):
+ Keyword arguments to be passed to the `subprocess.run` underlying command.
+
+ Returns:
+ `subprocess.CompletedProcess`: The completed process.
+ """
+ if isinstance(command, str):
+ command = command.split()
+
+ if isinstance(folder, Path):
+ folder = str(folder)
+
+ return subprocess.run(
+ command,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ check=check,
+ encoding="utf-8",
+ errors="replace", # if not utf-8, replace char by �
+ cwd=folder or os.getcwd(),
+ **kwargs,
+ )
+
+
+@contextmanager
+def run_interactive_subprocess(
+ command: Union[str, List[str]],
+ folder: Optional[Union[str, Path]] = None,
+ **kwargs,
+) -> Generator[Tuple[IO[str], IO[str]], None, None]:
+ """Run a subprocess in an interactive mode in a context manager.
+
+ Args:
+ command (`str` or `List[str]`):
+ The command to execute as a string or list of strings.
+ folder (`str`, *optional*):
+ The folder in which to run the command. Defaults to current working
+ directory (from `os.getcwd()`).
+ kwargs (`Dict[str]`):
+ Keyword arguments to be passed to the `subprocess.run` underlying command.
+
+ Returns:
+ `Tuple[IO[str], IO[str]]`: A tuple with `stdin` and `stdout` to interact
+ with the process (input and output are utf-8 encoded).
+
+ Example:
+ ```python
+ with _interactive_subprocess("git credential-store get") as (stdin, stdout):
+ # Write to stdin
+ stdin.write("url=hf.co\nusername=obama\n".encode("utf-8"))
+ stdin.flush()
+
+ # Read from stdout
+ output = stdout.read().decode("utf-8")
+ ```
+ """
+ if isinstance(command, str):
+ command = command.split()
+
+ with subprocess.Popen(
+ command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ encoding="utf-8",
+ errors="replace", # if not utf-8, replace char by �
+ cwd=folder or os.getcwd(),
+ **kwargs,
+ ) as process:
+ assert process.stdin is not None, "subprocess is opened as subprocess.PIPE"
+ assert process.stdout is not None, "subprocess is opened as subprocess.PIPE"
+ yield process.stdin, process.stdout
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_telemetry.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_telemetry.py
new file mode 100644
index 0000000000000000000000000000000000000000..5de988e2795188324f69232d1beb68191591715d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_telemetry.py
@@ -0,0 +1,118 @@
+from queue import Queue
+from threading import Lock, Thread
+from typing import Dict, Optional, Union
+from urllib.parse import quote
+
+from .. import constants, logging
+from . import build_hf_headers, get_session, hf_raise_for_status
+
+
+logger = logging.get_logger(__name__)
+
+# Telemetry is sent by a separate thread to avoid blocking the main thread.
+# A daemon thread is started once and consume tasks from the _TELEMETRY_QUEUE.
+# If the thread stops for some reason -shouldn't happen-, we restart a new one.
+_TELEMETRY_THREAD: Optional[Thread] = None
+_TELEMETRY_THREAD_LOCK = Lock() # Lock to avoid starting multiple threads in parallel
+_TELEMETRY_QUEUE: Queue = Queue()
+
+
+def send_telemetry(
+ topic: str,
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> None:
+ """
+ Sends telemetry that helps tracking usage of different HF libraries.
+
+ This usage data helps us debug issues and prioritize new features. However, we understand that not everyone wants
+ to share additional information, and we respect your privacy. You can disable telemetry collection by setting the
+ `HF_HUB_DISABLE_TELEMETRY=1` as environment variable. Telemetry is also disabled in offline mode (i.e. when setting
+ `HF_HUB_OFFLINE=1`).
+
+ Telemetry collection is run in a separate thread to minimize impact for the user.
+
+ Args:
+ topic (`str`):
+ Name of the topic that is monitored. The topic is directly used to build the URL. If you want to monitor
+ subtopics, just use "/" separation. Examples: "gradio", "transformers/examples",...
+ library_name (`str`, *optional*):
+ The name of the library that is making the HTTP request. Will be added to the user-agent header.
+ library_version (`str`, *optional*):
+ The version of the library that is making the HTTP request. Will be added to the user-agent header.
+ user_agent (`str`, `dict`, *optional*):
+ The user agent info in the form of a dictionary or a single string. It will be completed with information about the installed packages.
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import send_telemetry
+
+ # Send telemetry without library information
+ >>> send_telemetry("ping")
+
+ # Send telemetry to subtopic with library information
+ >>> send_telemetry("gradio/local_link", library_name="gradio", library_version="3.22.1")
+
+ # Send telemetry with additional data
+ >>> send_telemetry(
+ ... topic="examples",
+ ... library_name="transformers",
+ ... library_version="4.26.0",
+ ... user_agent={"pipeline": "text_classification", "framework": "flax"},
+ ... )
+ ```
+ """
+ if constants.HF_HUB_OFFLINE or constants.HF_HUB_DISABLE_TELEMETRY:
+ return
+
+ _start_telemetry_thread() # starts thread only if doesn't exist yet
+ _TELEMETRY_QUEUE.put(
+ {"topic": topic, "library_name": library_name, "library_version": library_version, "user_agent": user_agent}
+ )
+
+
+def _start_telemetry_thread():
+ """Start a daemon thread to consume tasks from the telemetry queue.
+
+ If the thread is interrupted, start a new one.
+ """
+ with _TELEMETRY_THREAD_LOCK: # avoid to start multiple threads if called concurrently
+ global _TELEMETRY_THREAD
+ if _TELEMETRY_THREAD is None or not _TELEMETRY_THREAD.is_alive():
+ _TELEMETRY_THREAD = Thread(target=_telemetry_worker, daemon=True)
+ _TELEMETRY_THREAD.start()
+
+
+def _telemetry_worker():
+ """Wait for a task and consume it."""
+ while True:
+ kwargs = _TELEMETRY_QUEUE.get()
+ _send_telemetry_in_thread(**kwargs)
+ _TELEMETRY_QUEUE.task_done()
+
+
+def _send_telemetry_in_thread(
+ topic: str,
+ *,
+ library_name: Optional[str] = None,
+ library_version: Optional[str] = None,
+ user_agent: Union[Dict, str, None] = None,
+) -> None:
+ """Contains the actual data sending data to the Hub."""
+ path = "/".join(quote(part) for part in topic.split("/") if len(part) > 0)
+ try:
+ r = get_session().head(
+ f"{constants.ENDPOINT}/api/telemetry/{path}",
+ headers=build_hf_headers(
+ token=False, # no need to send a token for telemetry
+ library_name=library_name,
+ library_version=library_version,
+ user_agent=user_agent,
+ ),
+ )
+ hf_raise_for_status(r)
+ except Exception as e:
+ # We don't want to error in case of connection errors of any kind.
+ logger.debug(f"Error while sending telemetry: {e}")
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_token.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_token.py
new file mode 100644
index 0000000000000000000000000000000000000000..3218bb45c0737f67912c9c257734c463f5871255
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_token.py
@@ -0,0 +1,130 @@
+# Copyright 2023 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains an helper to get the token from machine (env variable, secret or config file)."""
+
+import os
+import warnings
+from pathlib import Path
+from threading import Lock
+from typing import Optional
+
+from .. import constants
+from ._runtime import is_google_colab
+
+
+_IS_GOOGLE_COLAB_CHECKED = False
+_GOOGLE_COLAB_SECRET_LOCK = Lock()
+_GOOGLE_COLAB_SECRET: Optional[str] = None
+
+
+def get_token() -> Optional[str]:
+ """
+ Get token if user is logged in.
+
+ Note: in most cases, you should use [`huggingface_hub.utils.build_hf_headers`] instead. This method is only useful
+ if you want to retrieve the token for other purposes than sending an HTTP request.
+
+ Token is retrieved in priority from the `HF_TOKEN` environment variable. Otherwise, we read the token file located
+ in the Hugging Face home folder. Returns None if user is not logged in. To log in, use [`login`] or
+ `huggingface-cli login`.
+
+ Returns:
+ `str` or `None`: The token, `None` if it doesn't exist.
+ """
+ return _get_token_from_google_colab() or _get_token_from_environment() or _get_token_from_file()
+
+
+def _get_token_from_google_colab() -> Optional[str]:
+ """Get token from Google Colab secrets vault using `google.colab.userdata.get(...)`.
+
+ Token is read from the vault only once per session and then stored in a global variable to avoid re-requesting
+ access to the vault.
+ """
+ if not is_google_colab():
+ return None
+
+ # `google.colab.userdata` is not thread-safe
+ # This can lead to a deadlock if multiple threads try to access it at the same time
+ # (typically when using `snapshot_download`)
+ # => use a lock
+ # See https://github.com/huggingface/huggingface_hub/issues/1952 for more details.
+ with _GOOGLE_COLAB_SECRET_LOCK:
+ global _GOOGLE_COLAB_SECRET
+ global _IS_GOOGLE_COLAB_CHECKED
+
+ if _IS_GOOGLE_COLAB_CHECKED: # request access only once
+ return _GOOGLE_COLAB_SECRET
+
+ try:
+ from google.colab import userdata
+ from google.colab.errors import Error as ColabError
+ except ImportError:
+ return None
+
+ try:
+ token = userdata.get("HF_TOKEN")
+ _GOOGLE_COLAB_SECRET = _clean_token(token)
+ except userdata.NotebookAccessError:
+ # Means the user has a secret call `HF_TOKEN` and got a popup "please grand access to HF_TOKEN" and refused it
+ # => warn user but ignore error => do not re-request access to user
+ warnings.warn(
+ "\nAccess to the secret `HF_TOKEN` has not been granted on this notebook."
+ "\nYou will not be requested again."
+ "\nPlease restart the session if you want to be prompted again."
+ )
+ _GOOGLE_COLAB_SECRET = None
+ except userdata.SecretNotFoundError:
+ # Means the user did not define a `HF_TOKEN` secret => warn
+ warnings.warn(
+ "\nThe secret `HF_TOKEN` does not exist in your Colab secrets."
+ "\nTo authenticate with the Hugging Face Hub, create a token in your settings tab "
+ "(https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session."
+ "\nYou will be able to reuse this secret in all of your notebooks."
+ "\nPlease note that authentication is recommended but still optional to access public models or datasets."
+ )
+ _GOOGLE_COLAB_SECRET = None
+ except ColabError as e:
+ # Something happen but we don't know what => recommend to open a GitHub issue
+ warnings.warn(
+ f"\nError while fetching `HF_TOKEN` secret value from your vault: '{str(e)}'."
+ "\nYou are not authenticated with the Hugging Face Hub in this notebook."
+ "\nIf the error persists, please let us know by opening an issue on GitHub "
+ "(https://github.com/huggingface/huggingface_hub/issues/new)."
+ )
+ _GOOGLE_COLAB_SECRET = None
+
+ _IS_GOOGLE_COLAB_CHECKED = True
+ return _GOOGLE_COLAB_SECRET
+
+
+def _get_token_from_environment() -> Optional[str]:
+ # `HF_TOKEN` has priority (keep `HUGGING_FACE_HUB_TOKEN` for backward compatibility)
+ return _clean_token(os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN"))
+
+
+def _get_token_from_file() -> Optional[str]:
+ try:
+ return _clean_token(Path(constants.HF_TOKEN_PATH).read_text())
+ except FileNotFoundError:
+ return None
+
+
+def _clean_token(token: Optional[str]) -> Optional[str]:
+ """Clean token by removing trailing and leading spaces and newlines.
+
+ If token is an empty string, return None.
+ """
+ if token is None:
+ return None
+ return token.replace("\r", "").replace("\n", "").strip() or None
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_typing.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_typing.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae502b825bcb900ea076ffe1b0fe1078569821c5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_typing.py
@@ -0,0 +1,50 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Handle typing imports based on system compatibility."""
+
+from typing import Any, Callable, Literal, TypeVar
+
+
+HTTP_METHOD_T = Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]
+
+# type hint meaning "function signature not changed by decorator"
+CallableT = TypeVar("CallableT", bound=Callable)
+
+_JSON_SERIALIZABLE_TYPES = (int, float, str, bool, type(None))
+
+
+def is_jsonable(obj: Any) -> bool:
+ """Check if an object is JSON serializable.
+
+ This is a weak check, as it does not check for the actual JSON serialization, but only for the types of the object.
+ It works correctly for basic use cases but do not guarantee an exhaustive check.
+
+ Object is considered to be recursively json serializable if:
+ - it is an instance of int, float, str, bool, or NoneType
+ - it is a list or tuple and all its items are json serializable
+ - it is a dict and all its keys are strings and all its values are json serializable
+ """
+ try:
+ if isinstance(obj, _JSON_SERIALIZABLE_TYPES):
+ return True
+ if isinstance(obj, (list, tuple)):
+ return all(is_jsonable(item) for item in obj)
+ if isinstance(obj, dict):
+ return all(isinstance(key, str) and is_jsonable(value) for key, value in obj.items())
+ if hasattr(obj, "__json__"):
+ return True
+ return False
+ except RecursionError:
+ return False
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..f58d38919791127b871b5d1accb9b38b064e1075
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py
@@ -0,0 +1,231 @@
+# coding=utf-8
+# Copyright 2022-present, the HuggingFace Inc. team.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains utilities to validate argument values in `huggingface_hub`."""
+
+import inspect
+import re
+import warnings
+from functools import wraps
+from itertools import chain
+from typing import Any, Dict
+
+from ._typing import CallableT
+
+
+REPO_ID_REGEX = re.compile(
+ r"""
+ ^
+ (\b[\w\-.]+\b/)? # optional namespace (username or organization)
+ \b # starts with a word boundary
+ [\w\-.]{1,96} # repo_name: alphanumeric + . _ -
+ \b # ends with a word boundary
+ $
+ """,
+ flags=re.VERBOSE,
+)
+
+
+class HFValidationError(ValueError):
+ """Generic exception thrown by `huggingface_hub` validators.
+
+ Inherits from [`ValueError`](https://docs.python.org/3/library/exceptions.html#ValueError).
+ """
+
+
+def validate_hf_hub_args(fn: CallableT) -> CallableT:
+ """Validate values received as argument for any public method of `huggingface_hub`.
+
+ The goal of this decorator is to harmonize validation of arguments reused
+ everywhere. By default, all defined validators are tested.
+
+ Validators:
+ - [`~utils.validate_repo_id`]: `repo_id` must be `"repo_name"`
+ or `"namespace/repo_name"`. Namespace is a username or an organization.
+ - [`~utils.smoothly_deprecate_use_auth_token`]: Use `token` instead of
+ `use_auth_token` (only if `use_auth_token` is not expected by the decorated
+ function - in practice, always the case in `huggingface_hub`).
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import validate_hf_hub_args
+
+ >>> @validate_hf_hub_args
+ ... def my_cool_method(repo_id: str):
+ ... print(repo_id)
+
+ >>> my_cool_method(repo_id="valid_repo_id")
+ valid_repo_id
+
+ >>> my_cool_method("other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+
+ >>> my_cool_method(repo_id="other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+
+ >>> @validate_hf_hub_args
+ ... def my_cool_auth_method(token: str):
+ ... print(token)
+
+ >>> my_cool_auth_method(token="a token")
+ "a token"
+
+ >>> my_cool_auth_method(use_auth_token="a use_auth_token")
+ "a use_auth_token"
+
+ >>> my_cool_auth_method(token="a token", use_auth_token="a use_auth_token")
+ UserWarning: Both `token` and `use_auth_token` are passed (...)
+ "a token"
+ ```
+
+ Raises:
+ [`~utils.HFValidationError`]:
+ If an input is not valid.
+ """
+ # TODO: add an argument to opt-out validation for specific argument?
+ signature = inspect.signature(fn)
+
+ # Should the validator switch `use_auth_token` values to `token`? In practice, always
+ # True in `huggingface_hub`. Might not be the case in a downstream library.
+ check_use_auth_token = "use_auth_token" not in signature.parameters and "token" in signature.parameters
+
+ @wraps(fn)
+ def _inner_fn(*args, **kwargs):
+ has_token = False
+ for arg_name, arg_value in chain(
+ zip(signature.parameters, args), # Args values
+ kwargs.items(), # Kwargs values
+ ):
+ if arg_name in ["repo_id", "from_id", "to_id"]:
+ validate_repo_id(arg_value)
+
+ elif arg_name == "token" and arg_value is not None:
+ has_token = True
+
+ if check_use_auth_token:
+ kwargs = smoothly_deprecate_use_auth_token(fn_name=fn.__name__, has_token=has_token, kwargs=kwargs)
+
+ return fn(*args, **kwargs)
+
+ return _inner_fn # type: ignore
+
+
+def validate_repo_id(repo_id: str) -> None:
+ """Validate `repo_id` is valid.
+
+ This is not meant to replace the proper validation made on the Hub but rather to
+ avoid local inconsistencies whenever possible (example: passing `repo_type` in the
+ `repo_id` is forbidden).
+
+ Rules:
+ - Between 1 and 96 characters.
+ - Either "repo_name" or "namespace/repo_name"
+ - [a-zA-Z0-9] or "-", "_", "."
+ - "--" and ".." are forbidden
+
+ Valid: `"foo"`, `"foo/bar"`, `"123"`, `"Foo-BAR_foo.bar123"`
+
+ Not valid: `"datasets/foo/bar"`, `".repo_id"`, `"foo--bar"`, `"foo.git"`
+
+ Example:
+ ```py
+ >>> from huggingface_hub.utils import validate_repo_id
+ >>> validate_repo_id(repo_id="valid_repo_id")
+ >>> validate_repo_id(repo_id="other..repo..id")
+ huggingface_hub.utils._validators.HFValidationError: Cannot have -- or .. in repo_id: 'other..repo..id'.
+ ```
+
+ Discussed in https://github.com/huggingface/huggingface_hub/issues/1008.
+ In moon-landing (internal repository):
+ - https://github.com/huggingface/moon-landing/blob/main/server/lib/Names.ts#L27
+ - https://github.com/huggingface/moon-landing/blob/main/server/views/components/NewRepoForm/NewRepoForm.svelte#L138
+ """
+ if not isinstance(repo_id, str):
+ # Typically, a Path is not a repo_id
+ raise HFValidationError(f"Repo id must be a string, not {type(repo_id)}: '{repo_id}'.")
+
+ if repo_id.count("/") > 1:
+ raise HFValidationError(
+ "Repo id must be in the form 'repo_name' or 'namespace/repo_name':"
+ f" '{repo_id}'. Use `repo_type` argument if needed."
+ )
+
+ if not REPO_ID_REGEX.match(repo_id):
+ raise HFValidationError(
+ "Repo id must use alphanumeric chars or '-', '_', '.', '--' and '..' are"
+ " forbidden, '-' and '.' cannot start or end the name, max length is 96:"
+ f" '{repo_id}'."
+ )
+
+ if "--" in repo_id or ".." in repo_id:
+ raise HFValidationError(f"Cannot have -- or .. in repo_id: '{repo_id}'.")
+
+ if repo_id.endswith(".git"):
+ raise HFValidationError(f"Repo_id cannot end by '.git': '{repo_id}'.")
+
+
+def smoothly_deprecate_use_auth_token(fn_name: str, has_token: bool, kwargs: Dict[str, Any]) -> Dict[str, Any]:
+ """Smoothly deprecate `use_auth_token` in the `huggingface_hub` codebase.
+
+ The long-term goal is to remove any mention of `use_auth_token` in the codebase in
+ favor of a unique and less verbose `token` argument. This will be done a few steps:
+
+ 0. Step 0: methods that require a read-access to the Hub use the `use_auth_token`
+ argument (`str`, `bool` or `None`). Methods requiring write-access have a `token`
+ argument (`str`, `None`). This implicit rule exists to be able to not send the
+ token when not necessary (`use_auth_token=False`) even if logged in.
+
+ 1. Step 1: we want to harmonize everything and use `token` everywhere (supporting
+ `token=False` for read-only methods). In order not to break existing code, if
+ `use_auth_token` is passed to a function, the `use_auth_token` value is passed
+ as `token` instead, without any warning.
+ a. Corner case: if both `use_auth_token` and `token` values are passed, a warning
+ is thrown and the `use_auth_token` value is ignored.
+
+ 2. Step 2: Once it is release, we should push downstream libraries to switch from
+ `use_auth_token` to `token` as much as possible, but without throwing a warning
+ (e.g. manually create issues on the corresponding repos).
+
+ 3. Step 3: After a transitional period (6 months e.g. until April 2023?), we update
+ `huggingface_hub` to throw a warning on `use_auth_token`. Hopefully, very few
+ users will be impacted as it would have already been fixed.
+ In addition, unit tests in `huggingface_hub` must be adapted to expect warnings
+ to be thrown (but still use `use_auth_token` as before).
+
+ 4. Step 4: After a normal deprecation cycle (3 releases ?), remove this validator.
+ `use_auth_token` will definitely not be supported.
+ In addition, we update unit tests in `huggingface_hub` to use `token` everywhere.
+
+ This has been discussed in:
+ - https://github.com/huggingface/huggingface_hub/issues/1094.
+ - https://github.com/huggingface/huggingface_hub/pull/928
+ - (related) https://github.com/huggingface/huggingface_hub/pull/1064
+ """
+ new_kwargs = kwargs.copy() # do not mutate input !
+
+ use_auth_token = new_kwargs.pop("use_auth_token", None) # remove from kwargs
+ if use_auth_token is not None:
+ if has_token:
+ warnings.warn(
+ "Both `token` and `use_auth_token` are passed to"
+ f" `{fn_name}` with non-None values. `token` is now the"
+ " preferred argument to pass a User Access Token."
+ " `use_auth_token` value will be ignored."
+ )
+ else:
+ # `token` argument is not passed and a non-None value is passed in
+ # `use_auth_token` => use `use_auth_token` value as `token` kwarg.
+ new_kwargs["token"] = use_auth_token
+
+ return new_kwargs
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/endpoint_helpers.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/endpoint_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac52ce85a0f45932aeefc702eb44828ad2e17871
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/endpoint_helpers.py
@@ -0,0 +1,250 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Helpful utility functions and classes in relation to exploring API endpoints
+with the aim for a user-friendly interface.
+"""
+
+import math
+import re
+import warnings
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, List, Optional, Union
+
+from ..repocard_data import ModelCardData
+
+
+if TYPE_CHECKING:
+ from ..hf_api import ModelInfo
+
+
+def _is_emission_within_treshold(model_info: "ModelInfo", minimum_threshold: float, maximum_threshold: float) -> bool:
+ """Checks if a model's emission is within a given threshold.
+
+ Args:
+ model_info (`ModelInfo`):
+ A model info object containing the model's emission information.
+ minimum_threshold (`float`):
+ A minimum carbon threshold to filter by, such as 1.
+ maximum_threshold (`float`):
+ A maximum carbon threshold to filter by, such as 10.
+
+ Returns:
+ `bool`: Whether the model's emission is within the given threshold.
+ """
+ if minimum_threshold is None and maximum_threshold is None:
+ raise ValueError("Both `minimum_threshold` and `maximum_threshold` cannot both be `None`")
+ if minimum_threshold is None:
+ minimum_threshold = -1
+ if maximum_threshold is None:
+ maximum_threshold = math.inf
+
+ card_data = getattr(model_info, "card_data", None)
+ if card_data is None or not isinstance(card_data, (dict, ModelCardData)):
+ return False
+
+ # Get CO2 emission metadata
+ emission = card_data.get("co2_eq_emissions", None)
+ if isinstance(emission, dict):
+ emission = emission["emissions"]
+ if not emission:
+ return False
+
+ # Filter out if value is missing or out of range
+ matched = re.search(r"\d+\.\d+|\d+", str(emission))
+ if matched is None:
+ return False
+
+ emission_value = float(matched.group(0))
+ return minimum_threshold <= emission_value <= maximum_threshold
+
+
+@dataclass
+class DatasetFilter:
+ """
+ A class that converts human-readable dataset search parameters into ones
+ compatible with the REST API. For all parameters capitalization does not
+ matter.
+
+
+
+ The `DatasetFilter` class is deprecated and will be removed in huggingface_hub>=0.24. Please pass the filter parameters as keyword arguments directly to [`list_datasets`].
+
+
+
+ Args:
+ author (`str`, *optional*):
+ A string that can be used to identify datasets on
+ the Hub by the original uploader (author or organization), such as
+ `facebook` or `huggingface`.
+ benchmark (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by their official benchmark.
+ dataset_name (`str`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by its name, such as `SQAC` or `wikineural`
+ language_creators (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub with how the data was curated, such as `crowdsourced` or
+ `machine_generated`.
+ language (`str` or `List`, *optional*):
+ A string or list of strings representing a two-character language to
+ filter datasets by on the Hub.
+ multilinguality (`str` or `List`, *optional*):
+ A string or list of strings representing a filter for datasets that
+ contain multiple languages.
+ size_categories (`str` or `List`, *optional*):
+ A string or list of strings that can be used to identify datasets on
+ the Hub by the size of the dataset such as `100K>> from huggingface_hub import DatasetFilter
+
+ >>> # Using author
+ >>> new_filter = DatasetFilter(author="facebook")
+
+ >>> # Using benchmark
+ >>> new_filter = DatasetFilter(benchmark="raft")
+
+ >>> # Using dataset_name
+ >>> new_filter = DatasetFilter(dataset_name="wikineural")
+
+ >>> # Using language_creator
+ >>> new_filter = DatasetFilter(language_creator="crowdsourced")
+
+ >>> # Using language
+ >>> new_filter = DatasetFilter(language="en")
+
+ >>> # Using multilinguality
+ >>> new_filter = DatasetFilter(multilinguality="multilingual")
+
+ >>> # Using size_categories
+ >>> new_filter = DatasetFilter(size_categories="100K>> # Using task_categories
+ >>> new_filter = DatasetFilter(task_categories="audio_classification")
+
+ >>> # Using task_ids
+ >>> new_filter = DatasetFilter(task_ids="paraphrase")
+ ```
+ """
+
+ author: Optional[str] = None
+ benchmark: Optional[Union[str, List[str]]] = None
+ dataset_name: Optional[str] = None
+ language_creators: Optional[Union[str, List[str]]] = None
+ language: Optional[Union[str, List[str]]] = None
+ multilinguality: Optional[Union[str, List[str]]] = None
+ size_categories: Optional[Union[str, List[str]]] = None
+ task_categories: Optional[Union[str, List[str]]] = None
+ task_ids: Optional[Union[str, List[str]]] = None
+
+ def __post_init__(self):
+ warnings.warn(
+ "'DatasetFilter' is deprecated and will be removed in huggingface_hub>=0.24. Please pass the filter parameters as keyword arguments directly to the `list_datasets` method.",
+ category=FutureWarning,
+ )
+
+
+@dataclass
+class ModelFilter:
+ """
+ A class that converts human-readable model search parameters into ones
+ compatible with the REST API. For all parameters capitalization does not
+ matter.
+
+
+
+ The `ModelFilter` class is deprecated and will be removed in huggingface_hub>=0.24. Please pass the filter parameters as keyword arguments directly to [`list_models`].
+
+
+
+ Args:
+ author (`str`, *optional*):
+ A string that can be used to identify models on the Hub by the
+ original uploader (author or organization), such as `facebook` or
+ `huggingface`.
+ library (`str` or `List`, *optional*):
+ A string or list of strings of foundational libraries models were
+ originally trained from, such as pytorch, tensorflow, or allennlp.
+ language (`str` or `List`, *optional*):
+ A string or list of strings of languages, both by name and country
+ code, such as "en" or "English"
+ model_name (`str`, *optional*):
+ A string that contain complete or partial names for models on the
+ Hub, such as "bert" or "bert-base-cased"
+ task (`str` or `List`, *optional*):
+ A string or list of strings of tasks models were designed for, such
+ as: "fill-mask" or "automatic-speech-recognition"
+ tags (`str` or `List`, *optional*):
+ A string tag or a list of tags to filter models on the Hub by, such
+ as `text-generation` or `spacy`.
+ trained_dataset (`str` or `List`, *optional*):
+ A string tag or a list of string tags of the trained dataset for a
+ model on the Hub.
+
+ Examples:
+
+ ```python
+ >>> from huggingface_hub import ModelFilter
+
+ >>> # For the author_or_organization
+ >>> new_filter = ModelFilter(author_or_organization="facebook")
+
+ >>> # For the library
+ >>> new_filter = ModelFilter(library="pytorch")
+
+ >>> # For the language
+ >>> new_filter = ModelFilter(language="french")
+
+ >>> # For the model_name
+ >>> new_filter = ModelFilter(model_name="bert")
+
+ >>> # For the task
+ >>> new_filter = ModelFilter(task="text-classification")
+
+ >>> from huggingface_hub import HfApi
+
+ >>> api = HfApi()
+ # To list model tags
+
+ >>> new_filter = ModelFilter(tags="benchmark:raft")
+
+ >>> # Related to the dataset
+ >>> new_filter = ModelFilter(trained_dataset="common_voice")
+ ```
+ """
+
+ author: Optional[str] = None
+ library: Optional[Union[str, List[str]]] = None
+ language: Optional[Union[str, List[str]]] = None
+ model_name: Optional[str] = None
+ task: Optional[Union[str, List[str]]] = None
+ trained_dataset: Optional[Union[str, List[str]]] = None
+ tags: Optional[Union[str, List[str]]] = None
+
+ def __post_init__(self):
+ warnings.warn(
+ "'ModelFilter' is deprecated and will be removed in huggingface_hub>=0.24. Please pass the filter parameters as keyword arguments directly to the `list_models` method.",
+ FutureWarning,
+ )
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/insecure_hashlib.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/insecure_hashlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..f232ee0adcfc52dcc18b5ea4d9c913b206521f71
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/insecure_hashlib.py
@@ -0,0 +1,34 @@
+# Taken from https://github.com/mlflow/mlflow/pull/10119
+#
+# DO NOT use this function for security purposes (e.g., password hashing).
+#
+# In Python >= 3.9, insecure hashing algorithms such as MD5 fail in FIPS-compliant
+# environments unless `usedforsecurity=False` is explicitly passed.
+#
+# References:
+# - https://github.com/mlflow/mlflow/issues/9905
+# - https://github.com/mlflow/mlflow/pull/10119
+# - https://docs.python.org/3/library/hashlib.html
+# - https://github.com/huggingface/transformers/pull/27038
+#
+# Usage:
+# ```python
+# # Use
+# from huggingface_hub.utils.insecure_hashlib import sha256
+# # instead of
+# from hashlib import sha256
+#
+# # Use
+# from huggingface_hub.utils import insecure_hashlib
+# # instead of
+# import hashlib
+# ```
+import functools
+import hashlib
+import sys
+
+
+_kwargs = {"usedforsecurity": False} if sys.version_info >= (3, 9) else {}
+md5 = functools.partial(hashlib.md5, **_kwargs)
+sha1 = functools.partial(hashlib.sha1, **_kwargs)
+sha256 = functools.partial(hashlib.sha256, **_kwargs)
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/logging.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..3aafdf148135397556b4bb762862377eafdafd14
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/logging.py
@@ -0,0 +1,182 @@
+# coding=utf-8
+# Copyright 2020 Optuna, Hugging Face
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Logging utilities."""
+
+import logging
+import os
+from logging import (
+ CRITICAL, # NOQA
+ DEBUG, # NOQA
+ ERROR, # NOQA
+ FATAL, # NOQA
+ INFO, # NOQA
+ NOTSET, # NOQA
+ WARN, # NOQA
+ WARNING, # NOQA
+)
+from typing import Optional
+
+
+log_levels = {
+ "debug": logging.DEBUG,
+ "info": logging.INFO,
+ "warning": logging.WARNING,
+ "error": logging.ERROR,
+ "critical": logging.CRITICAL,
+}
+
+_default_log_level = logging.WARNING
+
+
+def _get_library_name() -> str:
+ return __name__.split(".")[0]
+
+
+def _get_library_root_logger() -> logging.Logger:
+ return logging.getLogger(_get_library_name())
+
+
+def _get_default_logging_level():
+ """
+ If `HF_HUB_VERBOSITY` env var is set to one of the valid choices return that as the new default level. If it is not
+ - fall back to `_default_log_level`
+ """
+ env_level_str = os.getenv("HF_HUB_VERBOSITY", None)
+ if env_level_str:
+ if env_level_str in log_levels:
+ return log_levels[env_level_str]
+ else:
+ logging.getLogger().warning(
+ f"Unknown option HF_HUB_VERBOSITY={env_level_str}, has to be one of: { ', '.join(log_levels.keys()) }"
+ )
+ return _default_log_level
+
+
+def _configure_library_root_logger() -> None:
+ library_root_logger = _get_library_root_logger()
+ library_root_logger.addHandler(logging.StreamHandler())
+ library_root_logger.setLevel(_get_default_logging_level())
+
+
+def _reset_library_root_logger() -> None:
+ library_root_logger = _get_library_root_logger()
+ library_root_logger.setLevel(logging.NOTSET)
+
+
+def get_logger(name: Optional[str] = None) -> logging.Logger:
+ """
+ Returns a logger with the specified name. This function is not supposed
+ to be directly accessed by library users.
+
+ Args:
+ name (`str`, *optional*):
+ The name of the logger to get, usually the filename
+
+ Example:
+
+ ```python
+ >>> from huggingface_hub import get_logger
+
+ >>> logger = get_logger(__file__)
+ >>> logger.set_verbosity_info()
+ ```
+ """
+
+ if name is None:
+ name = _get_library_name()
+
+ return logging.getLogger(name)
+
+
+def get_verbosity() -> int:
+ """Return the current level for the HuggingFace Hub's root logger.
+
+ Returns:
+ Logging level, e.g., `huggingface_hub.logging.DEBUG` and
+ `huggingface_hub.logging.INFO`.
+
+
+
+ HuggingFace Hub has following logging levels:
+
+ - `huggingface_hub.logging.CRITICAL`, `huggingface_hub.logging.FATAL`
+ - `huggingface_hub.logging.ERROR`
+ - `huggingface_hub.logging.WARNING`, `huggingface_hub.logging.WARN`
+ - `huggingface_hub.logging.INFO`
+ - `huggingface_hub.logging.DEBUG`
+
+
+ """
+ return _get_library_root_logger().getEffectiveLevel()
+
+
+def set_verbosity(verbosity: int) -> None:
+ """
+ Sets the level for the HuggingFace Hub's root logger.
+
+ Args:
+ verbosity (`int`):
+ Logging level, e.g., `huggingface_hub.logging.DEBUG` and
+ `huggingface_hub.logging.INFO`.
+ """
+ _get_library_root_logger().setLevel(verbosity)
+
+
+def set_verbosity_info():
+ """
+ Sets the verbosity to `logging.INFO`.
+ """
+ return set_verbosity(INFO)
+
+
+def set_verbosity_warning():
+ """
+ Sets the verbosity to `logging.WARNING`.
+ """
+ return set_verbosity(WARNING)
+
+
+def set_verbosity_debug():
+ """
+ Sets the verbosity to `logging.DEBUG`.
+ """
+ return set_verbosity(DEBUG)
+
+
+def set_verbosity_error():
+ """
+ Sets the verbosity to `logging.ERROR`.
+ """
+ return set_verbosity(ERROR)
+
+
+def disable_propagation() -> None:
+ """
+ Disable propagation of the library log outputs. Note that log propagation is
+ disabled by default.
+ """
+ _get_library_root_logger().propagate = False
+
+
+def enable_propagation() -> None:
+ """
+ Enable propagation of the library log outputs. Please disable the
+ HuggingFace Hub's default handler to prevent double logging if the root
+ logger has been configured.
+ """
+ _get_library_root_logger().propagate = True
+
+
+_configure_library_root_logger()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/sha.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/sha.py
new file mode 100644
index 0000000000000000000000000000000000000000..233ab074e69a47de9a443a458ce44e1429a1e22c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/sha.py
@@ -0,0 +1,29 @@
+"""Utilities to efficiently compute the SHA 256 hash of a bunch of bytes."""
+
+from typing import BinaryIO, Optional
+
+from .insecure_hashlib import sha256
+
+
+def sha_fileobj(fileobj: BinaryIO, chunk_size: Optional[int] = None) -> bytes:
+ """
+ Computes the sha256 hash of the given file object, by chunks of size `chunk_size`.
+
+ Args:
+ fileobj (file-like object):
+ The File object to compute sha256 for, typically obtained with `open(path, "rb")`
+ chunk_size (`int`, *optional*):
+ The number of bytes to read from `fileobj` at once, defaults to 1MB.
+
+ Returns:
+ `bytes`: `fileobj`'s sha256 hash as bytes
+ """
+ chunk_size = chunk_size if chunk_size is not None else 1024 * 1024
+
+ sha = sha256()
+ while True:
+ chunk = fileobj.read(chunk_size)
+ sha.update(chunk)
+ if not chunk:
+ break
+ return sha.digest()
diff --git a/.venv/lib/python3.10/site-packages/huggingface_hub/utils/tqdm.py b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/tqdm.py
new file mode 100644
index 0000000000000000000000000000000000000000..da1e421a21e65c04b7c53efd8f95d8df4f663473
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/huggingface_hub/utils/tqdm.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# coding=utf-8
+# Copyright 2021 The HuggingFace Inc. team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+"""Utility helpers to handle progress bars in `huggingface_hub`.
+
+Example:
+ 1. Use `huggingface_hub.utils.tqdm` as you would use `tqdm.tqdm` or `tqdm.auto.tqdm`.
+ 2. To disable progress bars, either use `disable_progress_bars()` helper or set the
+ environment variable `HF_HUB_DISABLE_PROGRESS_BARS` to 1.
+ 3. To re-enable progress bars, use `enable_progress_bars()`.
+ 4. To check whether progress bars are disabled, use `are_progress_bars_disabled()`.
+
+NOTE: Environment variable `HF_HUB_DISABLE_PROGRESS_BARS` has the priority.
+
+Example:
+ ```py
+ from huggingface_hub.utils import (
+ are_progress_bars_disabled,
+ disable_progress_bars,
+ enable_progress_bars,
+ tqdm,
+ )
+
+ # Disable progress bars globally
+ disable_progress_bars()
+
+ # Use as normal `tqdm`
+ for _ in tqdm(range(5)):
+ do_something()
+
+ # Still not showing progress bars, as `disable=False` is overwritten to `True`.
+ for _ in tqdm(range(5), disable=False):
+ do_something()
+
+ are_progress_bars_disabled() # True
+
+ # Re-enable progress bars globally
+ enable_progress_bars()
+
+ # Progress bar will be shown !
+ for _ in tqdm(range(5)):
+ do_something()
+ ```
+"""
+
+import io
+import warnings
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Iterator, Optional, Union
+
+from tqdm.auto import tqdm as old_tqdm
+
+from ..constants import HF_HUB_DISABLE_PROGRESS_BARS
+
+
+# `HF_HUB_DISABLE_PROGRESS_BARS` is `Optional[bool]` while `_hf_hub_progress_bars_disabled`
+# is a `bool`. If `HF_HUB_DISABLE_PROGRESS_BARS` is set to True or False, it has priority.
+# If `HF_HUB_DISABLE_PROGRESS_BARS` is None, it means the user have not set the
+# environment variable and is free to enable/disable progress bars programmatically.
+# TL;DR: env variable has priority over code.
+#
+# By default, progress bars are enabled.
+_hf_hub_progress_bars_disabled: bool = HF_HUB_DISABLE_PROGRESS_BARS or False
+
+
+def disable_progress_bars() -> None:
+ """
+ Disable globally progress bars used in `huggingface_hub` except if `HF_HUB_DISABLE_PROGRESS_BARS` environment
+ variable has been set.
+
+ Use [`~utils.enable_progress_bars`] to re-enable them.
+ """
+ if HF_HUB_DISABLE_PROGRESS_BARS is False:
+ warnings.warn(
+ "Cannot disable progress bars: environment variable `HF_HUB_DISABLE_PROGRESS_BARS=0` is set and has"
+ " priority."
+ )
+ return
+ global _hf_hub_progress_bars_disabled
+ _hf_hub_progress_bars_disabled = True
+
+
+def enable_progress_bars() -> None:
+ """
+ Enable globally progress bars used in `huggingface_hub` except if `HF_HUB_DISABLE_PROGRESS_BARS` environment
+ variable has been set.
+
+ Use [`~utils.disable_progress_bars`] to disable them.
+ """
+ if HF_HUB_DISABLE_PROGRESS_BARS is True:
+ warnings.warn(
+ "Cannot enable progress bars: environment variable `HF_HUB_DISABLE_PROGRESS_BARS=1` is set and has"
+ " priority."
+ )
+ return
+ global _hf_hub_progress_bars_disabled
+ _hf_hub_progress_bars_disabled = False
+
+
+def are_progress_bars_disabled() -> bool:
+ """Return whether progress bars are globally disabled or not.
+
+ Progress bars used in `huggingface_hub` can be enable or disabled globally using [`~utils.enable_progress_bars`]
+ and [`~utils.disable_progress_bars`] or by setting `HF_HUB_DISABLE_PROGRESS_BARS` as environment variable.
+ """
+ global _hf_hub_progress_bars_disabled
+ return _hf_hub_progress_bars_disabled
+
+
+class tqdm(old_tqdm):
+ """
+ Class to override `disable` argument in case progress bars are globally disabled.
+
+ Taken from https://github.com/tqdm/tqdm/issues/619#issuecomment-619639324.
+ """
+
+ def __init__(self, *args, **kwargs):
+ if are_progress_bars_disabled():
+ kwargs["disable"] = True
+ super().__init__(*args, **kwargs)
+
+ def __delattr__(self, attr: str) -> None:
+ """Fix for https://github.com/huggingface/huggingface_hub/issues/1603"""
+ try:
+ super().__delattr__(attr)
+ except AttributeError:
+ if attr != "_lock":
+ raise
+
+
+@contextmanager
+def tqdm_stream_file(path: Union[Path, str]) -> Iterator[io.BufferedReader]:
+ """
+ Open a file as binary and wrap the `read` method to display a progress bar when it's streamed.
+
+ First implemented in `transformers` in 2019 but removed when switched to git-lfs. Used in `huggingface_hub` to show
+ progress bar when uploading an LFS file to the Hub. See github.com/huggingface/transformers/pull/2078#discussion_r354739608
+ for implementation details.
+
+ Note: currently implementation handles only files stored on disk as it is the most common use case. Could be
+ extended to stream any `BinaryIO` object but we might have to debug some corner cases.
+
+ Example:
+ ```py
+ >>> with tqdm_stream_file("config.json") as f:
+ >>> requests.put(url, data=f)
+ config.json: 100%|█████████████████████████| 8.19k/8.19k [00:02<00:00, 3.72kB/s]
+ ```
+ """
+ if isinstance(path, str):
+ path = Path(path)
+
+ with path.open("rb") as f:
+ total_size = path.stat().st_size
+ pbar = tqdm(
+ unit="B",
+ unit_scale=True,
+ total=total_size,
+ initial=0,
+ desc=path.name,
+ )
+
+ f_read = f.read
+
+ def _inner_read(size: Optional[int] = -1) -> bytes:
+ data = f_read(size)
+ pbar.update(len(data))
+ return data
+
+ f.read = _inner_read # type: ignore
+
+ yield f
+
+ pbar.close()
diff --git a/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/LICENSE.md b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..19b6b45242c16a1025465309eec2ca5009319de3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/LICENSE.md
@@ -0,0 +1,31 @@
+BSD 3-Clause License
+
+Copyright (c) 2013-2024, Kim Davies and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/METADATA b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..b28f6ecdd5b297640bc7edac00381de90447b34b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/METADATA
@@ -0,0 +1,243 @@
+Metadata-Version: 2.1
+Name: idna
+Version: 3.7
+Summary: Internationalized Domain Names in Applications (IDNA)
+Author-email: Kim Davies
+Requires-Python: >=3.5
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Internet :: Name Service (DNS)
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
+Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst
+Project-URL: Issue tracker, https://github.com/kjd/idna/issues
+Project-URL: Source, https://github.com/kjd/idna
+
+Internationalized Domain Names in Applications (IDNA)
+=====================================================
+
+Support for the Internationalized Domain Names in
+Applications (IDNA) protocol as specified in `RFC 5891
+`_. This is the latest version of
+the protocol and is sometimes referred to as “IDNA 2008”.
+
+This library also provides support for Unicode Technical
+Standard 46, `Unicode IDNA Compatibility Processing
+`_.
+
+This acts as a suitable replacement for the “encodings.idna”
+module that comes with the Python standard library, but which
+only supports the older superseded IDNA specification (`RFC 3490
+`_).
+
+Basic functions are simply executed:
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+
+Installation
+------------
+
+This package is available for installation from PyPI:
+
+.. code-block:: bash
+
+ $ python3 -m pip install idna
+
+
+Usage
+-----
+
+For typical usage, the ``encode`` and ``decode`` functions will take a
+domain name argument and perform a conversion to A-labels or U-labels
+respectively.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+You may use the codec encoding and decoding methods using the
+``idna.codec`` module:
+
+.. code-block:: pycon
+
+ >>> import idna.codec
+ >>> print('домен.испытание'.encode('idna2008'))
+ b'xn--d1acufc.xn--80akhbyknj4f'
+ >>> print(b'xn--d1acufc.xn--80akhbyknj4f'.decode('idna2008'))
+ домен.испытание
+
+Conversions can be applied at a per-label basis using the ``ulabel`` or
+``alabel`` functions if necessary:
+
+.. code-block:: pycon
+
+ >>> idna.alabel('测试')
+ b'xn--0zwm56d'
+
+Compatibility Mapping (UTS #46)
++++++++++++++++++++++++++++++++
+
+As described in `RFC 5895 `_, the
+IDNA specification does not normalize input from different potential
+ways a user may input a domain name. This functionality, known as
+a “mapping”, is considered by the specification to be a local
+user-interface issue distinct from IDNA conversion functionality.
+
+This library provides one such mapping that was developed by the
+Unicode Consortium. Known as `Unicode IDNA Compatibility Processing
+`_, it provides for both a regular
+mapping for typical applications, as well as a transitional mapping to
+help migrate from older IDNA 2003 applications.
+
+For example, “Königsgäßchen” is not a permissible label as *LATIN
+CAPITAL LETTER K* is not allowed (nor are capital letters in general).
+UTS 46 will convert this into lower case prior to applying the IDNA
+conversion.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('Königsgäßchen')
+ ...
+ idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed
+ >>> idna.encode('Königsgäßchen', uts46=True)
+ b'xn--knigsgchen-b4a3dun'
+ >>> print(idna.decode('xn--knigsgchen-b4a3dun'))
+ königsgäßchen
+
+Transitional processing provides conversions to help transition from
+the older 2003 standard to the current standard. For example, in the
+original IDNA specification, the *LATIN SMALL LETTER SHARP S* (ß) was
+converted into two *LATIN SMALL LETTER S* (ss), whereas in the current
+IDNA specification this conversion is not performed.
+
+.. code-block:: pycon
+
+ >>> idna.encode('Königsgäßchen', uts46=True, transitional=True)
+ 'xn--knigsgsschen-lcb0w'
+
+Implementers should use transitional processing with caution, only in
+rare cases where conversion from legacy labels to current labels must be
+performed (i.e. IDNA implementations that pre-date 2008). For typical
+applications that just need to convert labels, transitional processing
+is unlikely to be beneficial and could produce unexpected incompatible
+results.
+
+``encodings.idna`` Compatibility
+++++++++++++++++++++++++++++++++
+
+Function calls from the Python built-in ``encodings.idna`` module are
+mapped to their IDNA 2008 equivalents using the ``idna.compat`` module.
+Simply substitute the ``import`` clause in your code to refer to the new
+module name.
+
+Exceptions
+----------
+
+All errors raised during the conversion following the specification
+should raise an exception derived from the ``idna.IDNAError`` base
+class.
+
+More specific exceptions that may be generated as ``idna.IDNABidiError``
+when the error reflects an illegal combination of left-to-right and
+right-to-left characters in a label; ``idna.InvalidCodepoint`` when
+a specific codepoint is an illegal character in an IDN label (i.e.
+INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is
+illegal based on its positional context (i.e. it is CONTEXTO or CONTEXTJ
+but the contextual requirements are not satisfied.)
+
+Building and Diagnostics
+------------------------
+
+The IDNA and UTS 46 functionality relies upon pre-calculated lookup
+tables for performance. These tables are derived from computing against
+eligibility criteria in the respective standards. These tables are
+computed using the command-line script ``tools/idna-data``.
+
+This tool will fetch relevant codepoint data from the Unicode repository
+and perform the required calculations to identify eligibility. There are
+three main modes:
+
+* ``idna-data make-libdata``. Generates ``idnadata.py`` and
+ ``uts46data.py``, the pre-calculated lookup tables used for IDNA and
+ UTS 46 conversions. Implementers who wish to track this library against
+ a different Unicode version may use this tool to manually generate a
+ different version of the ``idnadata.py`` and ``uts46data.py`` files.
+
+* ``idna-data make-table``. Generate a table of the IDNA disposition
+ (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix
+ B.1 of RFC 5892 and the pre-computed tables published by `IANA
+ `_.
+
+* ``idna-data U+0061``. Prints debugging output on the various
+ properties associated with an individual Unicode codepoint (in this
+ case, U+0061), that are used to assess the IDNA and UTS 46 status of a
+ codepoint. This is helpful in debugging or analysis.
+
+The tool accepts a number of arguments, described using ``idna-data
+-h``. Most notably, the ``--version`` argument allows the specification
+of the version of Unicode to be used in computing the table data. For
+example, ``idna-data --version 9.0.0 make-libdata`` will generate
+library data against Unicode 9.0.0.
+
+
+Additional Notes
+----------------
+
+* **Packages**. The latest tagged release version is published in the
+ `Python Package Index `_.
+
+* **Version support**. This library supports Python 3.5 and higher.
+ As this library serves as a low-level toolkit for a variety of
+ applications, many of which strive for broad compatibility with older
+ Python versions, there is no rush to remove older interpreter support.
+ Removing support for older versions should be well justified in that the
+ maintenance burden has become too high.
+
+* **Python 2**. Python 2 is supported by version 2.x of this library.
+ While active development of the version 2.x series has ended, notable
+ issues being corrected may be backported to 2.x. Use "idna<3" in your
+ requirements file if you need this library for a Python 2 application.
+
+* **Testing**. The library has a test suite based on each rule of the
+ IDNA specification, as well as tests that are provided as part of the
+ Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing
+ `_.
+
+* **Emoji**. It is an occasional request to support emoji domains in
+ this library. Encoding of symbols like emoji is expressly prohibited by
+ the technical standard IDNA 2008 and emoji domains are broadly phased
+ out across the domain industry due to associated security risks. For
+ now, applications that need to support these non-compliant labels
+ may wish to consider trying the encode/decode operation in this library
+ first, and then falling back to using `encodings.idna`. See `the Github
+ project `_ for more discussion.
+
diff --git a/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/RECORD b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..e93ebf5b6bca6e6e7395aef9a003b82f0fee0836
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/RECORD
@@ -0,0 +1,14 @@
+idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849
+idna/codec.py,sha256=PS6m-XmdST7Wj7J7ulRMakPDt5EBJyYrT3CPtjh-7t4,3426
+idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321
+idna/core.py,sha256=lyhpoe2vulEaB_65xhXmoKgO-xUqFDvcwxu5hpNNO4E,12663
+idna/idnadata.py,sha256=dqRwytzkjIHMBa2R1lYvHDwACenZPt8eGVu1Y8UBE-E,78320
+idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881
+idna/package_data.py,sha256=Tkt0KnIeyIlnHddOaz9WSkkislNgokJAuE-p5GorMqo,21
+idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+idna/uts46data.py,sha256=1KuksWqLuccPXm2uyRVkhfiFLNIhM_H2m4azCcnOqEU,206503
+idna-3.7.dist-info/LICENSE.md,sha256=pZ8LDvNjWHQQmkRhykT_enDVBpboFHZ7-vch1Mmw2w8,1541
+idna-3.7.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+idna-3.7.dist-info/METADATA,sha256=OixCk-dKLZkPy-MfviOmiPvwJ1O2K_8rqCrFjC_uxy4,9888
+idna-3.7.dist-info/INSTALLER,sha256=4EobgVZEtoZym__e-MIhNYRUXcWFMMbrrt6xRpKyZoQ,12
+idna-3.7.dist-info/RECORD,,
diff --git a/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/WHEEL b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..3b5e64b5e6c4a210201d1676a891fd57b15cda99
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna-3.7.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/.venv/lib/python3.10/site-packages/idna/__init__.py b/.venv/lib/python3.10/site-packages/idna/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a40eeafcc914108ca79c5d83d6e81da1b29c6e80
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/__init__.py
@@ -0,0 +1,44 @@
+from .package_data import __version__
+from .core import (
+ IDNABidiError,
+ IDNAError,
+ InvalidCodepoint,
+ InvalidCodepointContext,
+ alabel,
+ check_bidi,
+ check_hyphen_ok,
+ check_initial_combiner,
+ check_label,
+ check_nfc,
+ decode,
+ encode,
+ ulabel,
+ uts46_remap,
+ valid_contextj,
+ valid_contexto,
+ valid_label_length,
+ valid_string_length,
+)
+from .intranges import intranges_contain
+
+__all__ = [
+ "IDNABidiError",
+ "IDNAError",
+ "InvalidCodepoint",
+ "InvalidCodepointContext",
+ "alabel",
+ "check_bidi",
+ "check_hyphen_ok",
+ "check_initial_combiner",
+ "check_label",
+ "check_nfc",
+ "decode",
+ "encode",
+ "intranges_contain",
+ "ulabel",
+ "uts46_remap",
+ "valid_contextj",
+ "valid_contexto",
+ "valid_label_length",
+ "valid_string_length",
+]
diff --git a/.venv/lib/python3.10/site-packages/idna/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/idna/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4f930a5ee4829d00bbf94766e1919f7e9d16b470
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/idna/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/idna/__pycache__/core.cpython-310.pyc b/.venv/lib/python3.10/site-packages/idna/__pycache__/core.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..966117f339f84b874b0e6001d309c3b8b79139b4
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/idna/__pycache__/core.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/idna/__pycache__/idnadata.cpython-310.pyc b/.venv/lib/python3.10/site-packages/idna/__pycache__/idnadata.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6d81903f233c1ef6f46d3fb4733f3d15420d71cb
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/idna/__pycache__/idnadata.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/idna/__pycache__/intranges.cpython-310.pyc b/.venv/lib/python3.10/site-packages/idna/__pycache__/intranges.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..017629add164f33d148600d40d2e138a59a9bc75
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/idna/__pycache__/intranges.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/idna/__pycache__/package_data.cpython-310.pyc b/.venv/lib/python3.10/site-packages/idna/__pycache__/package_data.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4cc52659331a5f2b0e3c18169ffcbbbf8b5ce7be
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/idna/__pycache__/package_data.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/idna/codec.py b/.venv/lib/python3.10/site-packages/idna/codec.py
new file mode 100644
index 0000000000000000000000000000000000000000..c855a4de6d781815acf2334306f41b7a036c45d4
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/codec.py
@@ -0,0 +1,118 @@
+from .core import encode, decode, alabel, ulabel, IDNAError
+import codecs
+import re
+from typing import Any, Tuple, Optional
+
+_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]')
+
+class Codec(codecs.Codec):
+
+ def encode(self, data: str, errors: str = 'strict') -> Tuple[bytes, int]:
+ if errors != 'strict':
+ raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
+
+ if not data:
+ return b"", 0
+
+ return encode(data), len(data)
+
+ def decode(self, data: bytes, errors: str = 'strict') -> Tuple[str, int]:
+ if errors != 'strict':
+ raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
+
+ if not data:
+ return '', 0
+
+ return decode(data), len(data)
+
+class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
+ def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]:
+ if errors != 'strict':
+ raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
+
+ if not data:
+ return b'', 0
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = b''
+ if labels:
+ if not labels[-1]:
+ trailing_dot = b'.'
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = b'.'
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(alabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ # Join with U+002E
+ result_bytes = b'.'.join(result) + trailing_dot
+ size += len(trailing_dot)
+ return result_bytes, size
+
+class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
+ def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]:
+ if errors != 'strict':
+ raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
+
+ if not data:
+ return ('', 0)
+
+ if not isinstance(data, str):
+ data = str(data, 'ascii')
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = ''
+ if labels:
+ if not labels[-1]:
+ trailing_dot = '.'
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = '.'
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(ulabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ result_str = '.'.join(result) + trailing_dot
+ size += len(trailing_dot)
+ return (result_str, size)
+
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ pass
+
+
+class StreamReader(Codec, codecs.StreamReader):
+ pass
+
+
+def search_function(name: str) -> Optional[codecs.CodecInfo]:
+ if name != 'idna2008':
+ return None
+ return codecs.CodecInfo(
+ name=name,
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+codecs.register(search_function)
diff --git a/.venv/lib/python3.10/site-packages/idna/compat.py b/.venv/lib/python3.10/site-packages/idna/compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..786e6bda63699b72d588ba91dd73df017570aee5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/compat.py
@@ -0,0 +1,13 @@
+from .core import *
+from .codec import *
+from typing import Any, Union
+
+def ToASCII(label: str) -> bytes:
+ return encode(label)
+
+def ToUnicode(label: Union[bytes, bytearray]) -> str:
+ return decode(label)
+
+def nameprep(s: Any) -> None:
+ raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol')
+
diff --git a/.venv/lib/python3.10/site-packages/idna/core.py b/.venv/lib/python3.10/site-packages/idna/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..0dae61acdbccad9a08f1a82cf14839037873dc56
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/core.py
@@ -0,0 +1,395 @@
+from . import idnadata
+import bisect
+import unicodedata
+import re
+from typing import Union, Optional
+from .intranges import intranges_contain
+
+_virama_combining_class = 9
+_alabel_prefix = b'xn--'
+_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]')
+
+class IDNAError(UnicodeError):
+ """ Base exception for all IDNA-encoding related problems """
+ pass
+
+
+class IDNABidiError(IDNAError):
+ """ Exception when bidirectional requirements are not satisfied """
+ pass
+
+
+class InvalidCodepoint(IDNAError):
+ """ Exception when a disallowed or unallocated codepoint is used """
+ pass
+
+
+class InvalidCodepointContext(IDNAError):
+ """ Exception when the codepoint is not valid in the context it is used """
+ pass
+
+
+def _combining_class(cp: int) -> int:
+ v = unicodedata.combining(chr(cp))
+ if v == 0:
+ if not unicodedata.name(chr(cp)):
+ raise ValueError('Unknown character in unicodedata')
+ return v
+
+def _is_script(cp: str, script: str) -> bool:
+ return intranges_contain(ord(cp), idnadata.scripts[script])
+
+def _punycode(s: str) -> bytes:
+ return s.encode('punycode')
+
+def _unot(s: int) -> str:
+ return 'U+{:04X}'.format(s)
+
+
+def valid_label_length(label: Union[bytes, str]) -> bool:
+ if len(label) > 63:
+ return False
+ return True
+
+
+def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool:
+ if len(label) > (254 if trailing_dot else 253):
+ return False
+ return True
+
+
+def check_bidi(label: str, check_ltr: bool = False) -> bool:
+ # Bidi rules should only be applied if string contains RTL characters
+ bidi_label = False
+ for (idx, cp) in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+ if direction == '':
+ # String likely comes from a newer version of Unicode
+ raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx))
+ if direction in ['R', 'AL', 'AN']:
+ bidi_label = True
+ if not bidi_label and not check_ltr:
+ return True
+
+ # Bidi rule 1
+ direction = unicodedata.bidirectional(label[0])
+ if direction in ['R', 'AL']:
+ rtl = True
+ elif direction == 'L':
+ rtl = False
+ else:
+ raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label)))
+
+ valid_ending = False
+ number_type = None # type: Optional[str]
+ for (idx, cp) in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+
+ if rtl:
+ # Bidi rule 2
+ if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']:
+ raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx))
+ # Bidi rule 3
+ if direction in ['R', 'AL', 'EN', 'AN']:
+ valid_ending = True
+ elif direction != 'NSM':
+ valid_ending = False
+ # Bidi rule 4
+ if direction in ['AN', 'EN']:
+ if not number_type:
+ number_type = direction
+ else:
+ if number_type != direction:
+ raise IDNABidiError('Can not mix numeral types in a right-to-left label')
+ else:
+ # Bidi rule 5
+ if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']:
+ raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx))
+ # Bidi rule 6
+ if direction in ['L', 'EN']:
+ valid_ending = True
+ elif direction != 'NSM':
+ valid_ending = False
+
+ if not valid_ending:
+ raise IDNABidiError('Label ends with illegal codepoint directionality')
+
+ return True
+
+
+def check_initial_combiner(label: str) -> bool:
+ if unicodedata.category(label[0])[0] == 'M':
+ raise IDNAError('Label begins with an illegal combining character')
+ return True
+
+
+def check_hyphen_ok(label: str) -> bool:
+ if label[2:4] == '--':
+ raise IDNAError('Label has disallowed hyphens in 3rd and 4th position')
+ if label[0] == '-' or label[-1] == '-':
+ raise IDNAError('Label must not start or end with a hyphen')
+ return True
+
+
+def check_nfc(label: str) -> None:
+ if unicodedata.normalize('NFC', label) != label:
+ raise IDNAError('Label must be in Normalization Form C')
+
+
+def valid_contextj(label: str, pos: int) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x200c:
+
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+
+ ok = False
+ for i in range(pos-1, -1, -1):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord('T'):
+ continue
+ elif joining_type in [ord('L'), ord('D')]:
+ ok = True
+ break
+ else:
+ break
+
+ if not ok:
+ return False
+
+ ok = False
+ for i in range(pos+1, len(label)):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord('T'):
+ continue
+ elif joining_type in [ord('R'), ord('D')]:
+ ok = True
+ break
+ else:
+ break
+ return ok
+
+ if cp_value == 0x200d:
+
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+ return False
+
+ else:
+
+ return False
+
+
+def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x00b7:
+ if 0 < pos < len(label)-1:
+ if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c:
+ return True
+ return False
+
+ elif cp_value == 0x0375:
+ if pos < len(label)-1 and len(label) > 1:
+ return _is_script(label[pos + 1], 'Greek')
+ return False
+
+ elif cp_value == 0x05f3 or cp_value == 0x05f4:
+ if pos > 0:
+ return _is_script(label[pos - 1], 'Hebrew')
+ return False
+
+ elif cp_value == 0x30fb:
+ for cp in label:
+ if cp == '\u30fb':
+ continue
+ if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'):
+ return True
+ return False
+
+ elif 0x660 <= cp_value <= 0x669:
+ for cp in label:
+ if 0x6f0 <= ord(cp) <= 0x06f9:
+ return False
+ return True
+
+ elif 0x6f0 <= cp_value <= 0x6f9:
+ for cp in label:
+ if 0x660 <= ord(cp) <= 0x0669:
+ return False
+ return True
+
+ return False
+
+
+def check_label(label: Union[str, bytes, bytearray]) -> None:
+ if isinstance(label, (bytes, bytearray)):
+ label = label.decode('utf-8')
+ if len(label) == 0:
+ raise IDNAError('Empty Label')
+
+ check_nfc(label)
+ check_hyphen_ok(label)
+ check_initial_combiner(label)
+
+ for (pos, cp) in enumerate(label):
+ cp_value = ord(cp)
+ if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']):
+ continue
+ elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']):
+ if not valid_contextj(label, pos):
+ raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format(
+ _unot(cp_value), pos+1, repr(label)))
+ elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']):
+ if not valid_contexto(label, pos):
+ raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label)))
+ else:
+ raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label)))
+
+ check_bidi(label)
+
+
+def alabel(label: str) -> bytes:
+ try:
+ label_bytes = label.encode('ascii')
+ ulabel(label_bytes)
+ if not valid_label_length(label_bytes):
+ raise IDNAError('Label too long')
+ return label_bytes
+ except UnicodeEncodeError:
+ pass
+
+ check_label(label)
+ label_bytes = _alabel_prefix + _punycode(label)
+
+ if not valid_label_length(label_bytes):
+ raise IDNAError('Label too long')
+
+ return label_bytes
+
+
+def ulabel(label: Union[str, bytes, bytearray]) -> str:
+ if not isinstance(label, (bytes, bytearray)):
+ try:
+ label_bytes = label.encode('ascii')
+ except UnicodeEncodeError:
+ check_label(label)
+ return label
+ else:
+ label_bytes = label
+
+ label_bytes = label_bytes.lower()
+ if label_bytes.startswith(_alabel_prefix):
+ label_bytes = label_bytes[len(_alabel_prefix):]
+ if not label_bytes:
+ raise IDNAError('Malformed A-label, no Punycode eligible content found')
+ if label_bytes.decode('ascii')[-1] == '-':
+ raise IDNAError('A-label must not end with a hyphen')
+ else:
+ check_label(label_bytes)
+ return label_bytes.decode('ascii')
+
+ try:
+ label = label_bytes.decode('punycode')
+ except UnicodeError:
+ raise IDNAError('Invalid A-label')
+ check_label(label)
+ return label
+
+
+def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
+ """Re-map the characters in the string according to UTS46 processing."""
+ from .uts46data import uts46data
+ output = ''
+
+ for pos, char in enumerate(domain):
+ code_point = ord(char)
+ try:
+ uts46row = uts46data[code_point if code_point < 256 else
+ bisect.bisect_left(uts46data, (code_point, 'Z')) - 1]
+ status = uts46row[1]
+ replacement = None # type: Optional[str]
+ if len(uts46row) == 3:
+ replacement = uts46row[2]
+ if (status == 'V' or
+ (status == 'D' and not transitional) or
+ (status == '3' and not std3_rules and replacement is None)):
+ output += char
+ elif replacement is not None and (status == 'M' or
+ (status == '3' and not std3_rules) or
+ (status == 'D' and transitional)):
+ output += replacement
+ elif status != 'I':
+ raise IndexError()
+ except IndexError:
+ raise InvalidCodepoint(
+ 'Codepoint {} not allowed at position {} in {}'.format(
+ _unot(code_point), pos + 1, repr(domain)))
+
+ return unicodedata.normalize('NFC', output)
+
+
+def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes:
+ if not isinstance(s, str):
+ try:
+ s = str(s, 'ascii')
+ except UnicodeDecodeError:
+ raise IDNAError('should pass a unicode string to the function rather than a byte string.')
+ if uts46:
+ s = uts46_remap(s, std3_rules, transitional)
+ trailing_dot = False
+ result = []
+ if strict:
+ labels = s.split('.')
+ else:
+ labels = _unicode_dots_re.split(s)
+ if not labels or labels == ['']:
+ raise IDNAError('Empty domain')
+ if labels[-1] == '':
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = alabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError('Empty label')
+ if trailing_dot:
+ result.append(b'')
+ s = b'.'.join(result)
+ if not valid_string_length(s, trailing_dot):
+ raise IDNAError('Domain too long')
+ return s
+
+
+def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str:
+ try:
+ if not isinstance(s, str):
+ s = str(s, 'ascii')
+ except UnicodeDecodeError:
+ raise IDNAError('Invalid ASCII in A-label')
+ if uts46:
+ s = uts46_remap(s, std3_rules, False)
+ trailing_dot = False
+ result = []
+ if not strict:
+ labels = _unicode_dots_re.split(s)
+ else:
+ labels = s.split('.')
+ if not labels or labels == ['']:
+ raise IDNAError('Empty domain')
+ if not labels[-1]:
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = ulabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError('Empty label')
+ if trailing_dot:
+ result.append('')
+ return '.'.join(result)
diff --git a/.venv/lib/python3.10/site-packages/idna/idnadata.py b/.venv/lib/python3.10/site-packages/idna/idnadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..c61dcf977e57ec0d139e828a7c5a9cd86e55b43a
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/idnadata.py
@@ -0,0 +1,4245 @@
+# This file is automatically generated by tools/idna-data
+
+__version__ = '15.1.0'
+scripts = {
+ 'Greek': (
+ 0x37000000374,
+ 0x37500000378,
+ 0x37a0000037e,
+ 0x37f00000380,
+ 0x38400000385,
+ 0x38600000387,
+ 0x3880000038b,
+ 0x38c0000038d,
+ 0x38e000003a2,
+ 0x3a3000003e2,
+ 0x3f000000400,
+ 0x1d2600001d2b,
+ 0x1d5d00001d62,
+ 0x1d6600001d6b,
+ 0x1dbf00001dc0,
+ 0x1f0000001f16,
+ 0x1f1800001f1e,
+ 0x1f2000001f46,
+ 0x1f4800001f4e,
+ 0x1f5000001f58,
+ 0x1f5900001f5a,
+ 0x1f5b00001f5c,
+ 0x1f5d00001f5e,
+ 0x1f5f00001f7e,
+ 0x1f8000001fb5,
+ 0x1fb600001fc5,
+ 0x1fc600001fd4,
+ 0x1fd600001fdc,
+ 0x1fdd00001ff0,
+ 0x1ff200001ff5,
+ 0x1ff600001fff,
+ 0x212600002127,
+ 0xab650000ab66,
+ 0x101400001018f,
+ 0x101a0000101a1,
+ 0x1d2000001d246,
+ ),
+ 'Han': (
+ 0x2e8000002e9a,
+ 0x2e9b00002ef4,
+ 0x2f0000002fd6,
+ 0x300500003006,
+ 0x300700003008,
+ 0x30210000302a,
+ 0x30380000303c,
+ 0x340000004dc0,
+ 0x4e000000a000,
+ 0xf9000000fa6e,
+ 0xfa700000fada,
+ 0x16fe200016fe4,
+ 0x16ff000016ff2,
+ 0x200000002a6e0,
+ 0x2a7000002b73a,
+ 0x2b7400002b81e,
+ 0x2b8200002cea2,
+ 0x2ceb00002ebe1,
+ 0x2ebf00002ee5e,
+ 0x2f8000002fa1e,
+ 0x300000003134b,
+ 0x31350000323b0,
+ ),
+ 'Hebrew': (
+ 0x591000005c8,
+ 0x5d0000005eb,
+ 0x5ef000005f5,
+ 0xfb1d0000fb37,
+ 0xfb380000fb3d,
+ 0xfb3e0000fb3f,
+ 0xfb400000fb42,
+ 0xfb430000fb45,
+ 0xfb460000fb50,
+ ),
+ 'Hiragana': (
+ 0x304100003097,
+ 0x309d000030a0,
+ 0x1b0010001b120,
+ 0x1b1320001b133,
+ 0x1b1500001b153,
+ 0x1f2000001f201,
+ ),
+ 'Katakana': (
+ 0x30a1000030fb,
+ 0x30fd00003100,
+ 0x31f000003200,
+ 0x32d0000032ff,
+ 0x330000003358,
+ 0xff660000ff70,
+ 0xff710000ff9e,
+ 0x1aff00001aff4,
+ 0x1aff50001affc,
+ 0x1affd0001afff,
+ 0x1b0000001b001,
+ 0x1b1200001b123,
+ 0x1b1550001b156,
+ 0x1b1640001b168,
+ ),
+}
+joining_types = {
+ 0xad: 84,
+ 0x300: 84,
+ 0x301: 84,
+ 0x302: 84,
+ 0x303: 84,
+ 0x304: 84,
+ 0x305: 84,
+ 0x306: 84,
+ 0x307: 84,
+ 0x308: 84,
+ 0x309: 84,
+ 0x30a: 84,
+ 0x30b: 84,
+ 0x30c: 84,
+ 0x30d: 84,
+ 0x30e: 84,
+ 0x30f: 84,
+ 0x310: 84,
+ 0x311: 84,
+ 0x312: 84,
+ 0x313: 84,
+ 0x314: 84,
+ 0x315: 84,
+ 0x316: 84,
+ 0x317: 84,
+ 0x318: 84,
+ 0x319: 84,
+ 0x31a: 84,
+ 0x31b: 84,
+ 0x31c: 84,
+ 0x31d: 84,
+ 0x31e: 84,
+ 0x31f: 84,
+ 0x320: 84,
+ 0x321: 84,
+ 0x322: 84,
+ 0x323: 84,
+ 0x324: 84,
+ 0x325: 84,
+ 0x326: 84,
+ 0x327: 84,
+ 0x328: 84,
+ 0x329: 84,
+ 0x32a: 84,
+ 0x32b: 84,
+ 0x32c: 84,
+ 0x32d: 84,
+ 0x32e: 84,
+ 0x32f: 84,
+ 0x330: 84,
+ 0x331: 84,
+ 0x332: 84,
+ 0x333: 84,
+ 0x334: 84,
+ 0x335: 84,
+ 0x336: 84,
+ 0x337: 84,
+ 0x338: 84,
+ 0x339: 84,
+ 0x33a: 84,
+ 0x33b: 84,
+ 0x33c: 84,
+ 0x33d: 84,
+ 0x33e: 84,
+ 0x33f: 84,
+ 0x340: 84,
+ 0x341: 84,
+ 0x342: 84,
+ 0x343: 84,
+ 0x344: 84,
+ 0x345: 84,
+ 0x346: 84,
+ 0x347: 84,
+ 0x348: 84,
+ 0x349: 84,
+ 0x34a: 84,
+ 0x34b: 84,
+ 0x34c: 84,
+ 0x34d: 84,
+ 0x34e: 84,
+ 0x34f: 84,
+ 0x350: 84,
+ 0x351: 84,
+ 0x352: 84,
+ 0x353: 84,
+ 0x354: 84,
+ 0x355: 84,
+ 0x356: 84,
+ 0x357: 84,
+ 0x358: 84,
+ 0x359: 84,
+ 0x35a: 84,
+ 0x35b: 84,
+ 0x35c: 84,
+ 0x35d: 84,
+ 0x35e: 84,
+ 0x35f: 84,
+ 0x360: 84,
+ 0x361: 84,
+ 0x362: 84,
+ 0x363: 84,
+ 0x364: 84,
+ 0x365: 84,
+ 0x366: 84,
+ 0x367: 84,
+ 0x368: 84,
+ 0x369: 84,
+ 0x36a: 84,
+ 0x36b: 84,
+ 0x36c: 84,
+ 0x36d: 84,
+ 0x36e: 84,
+ 0x36f: 84,
+ 0x483: 84,
+ 0x484: 84,
+ 0x485: 84,
+ 0x486: 84,
+ 0x487: 84,
+ 0x488: 84,
+ 0x489: 84,
+ 0x591: 84,
+ 0x592: 84,
+ 0x593: 84,
+ 0x594: 84,
+ 0x595: 84,
+ 0x596: 84,
+ 0x597: 84,
+ 0x598: 84,
+ 0x599: 84,
+ 0x59a: 84,
+ 0x59b: 84,
+ 0x59c: 84,
+ 0x59d: 84,
+ 0x59e: 84,
+ 0x59f: 84,
+ 0x5a0: 84,
+ 0x5a1: 84,
+ 0x5a2: 84,
+ 0x5a3: 84,
+ 0x5a4: 84,
+ 0x5a5: 84,
+ 0x5a6: 84,
+ 0x5a7: 84,
+ 0x5a8: 84,
+ 0x5a9: 84,
+ 0x5aa: 84,
+ 0x5ab: 84,
+ 0x5ac: 84,
+ 0x5ad: 84,
+ 0x5ae: 84,
+ 0x5af: 84,
+ 0x5b0: 84,
+ 0x5b1: 84,
+ 0x5b2: 84,
+ 0x5b3: 84,
+ 0x5b4: 84,
+ 0x5b5: 84,
+ 0x5b6: 84,
+ 0x5b7: 84,
+ 0x5b8: 84,
+ 0x5b9: 84,
+ 0x5ba: 84,
+ 0x5bb: 84,
+ 0x5bc: 84,
+ 0x5bd: 84,
+ 0x5bf: 84,
+ 0x5c1: 84,
+ 0x5c2: 84,
+ 0x5c4: 84,
+ 0x5c5: 84,
+ 0x5c7: 84,
+ 0x610: 84,
+ 0x611: 84,
+ 0x612: 84,
+ 0x613: 84,
+ 0x614: 84,
+ 0x615: 84,
+ 0x616: 84,
+ 0x617: 84,
+ 0x618: 84,
+ 0x619: 84,
+ 0x61a: 84,
+ 0x61c: 84,
+ 0x620: 68,
+ 0x622: 82,
+ 0x623: 82,
+ 0x624: 82,
+ 0x625: 82,
+ 0x626: 68,
+ 0x627: 82,
+ 0x628: 68,
+ 0x629: 82,
+ 0x62a: 68,
+ 0x62b: 68,
+ 0x62c: 68,
+ 0x62d: 68,
+ 0x62e: 68,
+ 0x62f: 82,
+ 0x630: 82,
+ 0x631: 82,
+ 0x632: 82,
+ 0x633: 68,
+ 0x634: 68,
+ 0x635: 68,
+ 0x636: 68,
+ 0x637: 68,
+ 0x638: 68,
+ 0x639: 68,
+ 0x63a: 68,
+ 0x63b: 68,
+ 0x63c: 68,
+ 0x63d: 68,
+ 0x63e: 68,
+ 0x63f: 68,
+ 0x640: 67,
+ 0x641: 68,
+ 0x642: 68,
+ 0x643: 68,
+ 0x644: 68,
+ 0x645: 68,
+ 0x646: 68,
+ 0x647: 68,
+ 0x648: 82,
+ 0x649: 68,
+ 0x64a: 68,
+ 0x64b: 84,
+ 0x64c: 84,
+ 0x64d: 84,
+ 0x64e: 84,
+ 0x64f: 84,
+ 0x650: 84,
+ 0x651: 84,
+ 0x652: 84,
+ 0x653: 84,
+ 0x654: 84,
+ 0x655: 84,
+ 0x656: 84,
+ 0x657: 84,
+ 0x658: 84,
+ 0x659: 84,
+ 0x65a: 84,
+ 0x65b: 84,
+ 0x65c: 84,
+ 0x65d: 84,
+ 0x65e: 84,
+ 0x65f: 84,
+ 0x66e: 68,
+ 0x66f: 68,
+ 0x670: 84,
+ 0x671: 82,
+ 0x672: 82,
+ 0x673: 82,
+ 0x675: 82,
+ 0x676: 82,
+ 0x677: 82,
+ 0x678: 68,
+ 0x679: 68,
+ 0x67a: 68,
+ 0x67b: 68,
+ 0x67c: 68,
+ 0x67d: 68,
+ 0x67e: 68,
+ 0x67f: 68,
+ 0x680: 68,
+ 0x681: 68,
+ 0x682: 68,
+ 0x683: 68,
+ 0x684: 68,
+ 0x685: 68,
+ 0x686: 68,
+ 0x687: 68,
+ 0x688: 82,
+ 0x689: 82,
+ 0x68a: 82,
+ 0x68b: 82,
+ 0x68c: 82,
+ 0x68d: 82,
+ 0x68e: 82,
+ 0x68f: 82,
+ 0x690: 82,
+ 0x691: 82,
+ 0x692: 82,
+ 0x693: 82,
+ 0x694: 82,
+ 0x695: 82,
+ 0x696: 82,
+ 0x697: 82,
+ 0x698: 82,
+ 0x699: 82,
+ 0x69a: 68,
+ 0x69b: 68,
+ 0x69c: 68,
+ 0x69d: 68,
+ 0x69e: 68,
+ 0x69f: 68,
+ 0x6a0: 68,
+ 0x6a1: 68,
+ 0x6a2: 68,
+ 0x6a3: 68,
+ 0x6a4: 68,
+ 0x6a5: 68,
+ 0x6a6: 68,
+ 0x6a7: 68,
+ 0x6a8: 68,
+ 0x6a9: 68,
+ 0x6aa: 68,
+ 0x6ab: 68,
+ 0x6ac: 68,
+ 0x6ad: 68,
+ 0x6ae: 68,
+ 0x6af: 68,
+ 0x6b0: 68,
+ 0x6b1: 68,
+ 0x6b2: 68,
+ 0x6b3: 68,
+ 0x6b4: 68,
+ 0x6b5: 68,
+ 0x6b6: 68,
+ 0x6b7: 68,
+ 0x6b8: 68,
+ 0x6b9: 68,
+ 0x6ba: 68,
+ 0x6bb: 68,
+ 0x6bc: 68,
+ 0x6bd: 68,
+ 0x6be: 68,
+ 0x6bf: 68,
+ 0x6c0: 82,
+ 0x6c1: 68,
+ 0x6c2: 68,
+ 0x6c3: 82,
+ 0x6c4: 82,
+ 0x6c5: 82,
+ 0x6c6: 82,
+ 0x6c7: 82,
+ 0x6c8: 82,
+ 0x6c9: 82,
+ 0x6ca: 82,
+ 0x6cb: 82,
+ 0x6cc: 68,
+ 0x6cd: 82,
+ 0x6ce: 68,
+ 0x6cf: 82,
+ 0x6d0: 68,
+ 0x6d1: 68,
+ 0x6d2: 82,
+ 0x6d3: 82,
+ 0x6d5: 82,
+ 0x6d6: 84,
+ 0x6d7: 84,
+ 0x6d8: 84,
+ 0x6d9: 84,
+ 0x6da: 84,
+ 0x6db: 84,
+ 0x6dc: 84,
+ 0x6df: 84,
+ 0x6e0: 84,
+ 0x6e1: 84,
+ 0x6e2: 84,
+ 0x6e3: 84,
+ 0x6e4: 84,
+ 0x6e7: 84,
+ 0x6e8: 84,
+ 0x6ea: 84,
+ 0x6eb: 84,
+ 0x6ec: 84,
+ 0x6ed: 84,
+ 0x6ee: 82,
+ 0x6ef: 82,
+ 0x6fa: 68,
+ 0x6fb: 68,
+ 0x6fc: 68,
+ 0x6ff: 68,
+ 0x70f: 84,
+ 0x710: 82,
+ 0x711: 84,
+ 0x712: 68,
+ 0x713: 68,
+ 0x714: 68,
+ 0x715: 82,
+ 0x716: 82,
+ 0x717: 82,
+ 0x718: 82,
+ 0x719: 82,
+ 0x71a: 68,
+ 0x71b: 68,
+ 0x71c: 68,
+ 0x71d: 68,
+ 0x71e: 82,
+ 0x71f: 68,
+ 0x720: 68,
+ 0x721: 68,
+ 0x722: 68,
+ 0x723: 68,
+ 0x724: 68,
+ 0x725: 68,
+ 0x726: 68,
+ 0x727: 68,
+ 0x728: 82,
+ 0x729: 68,
+ 0x72a: 82,
+ 0x72b: 68,
+ 0x72c: 82,
+ 0x72d: 68,
+ 0x72e: 68,
+ 0x72f: 82,
+ 0x730: 84,
+ 0x731: 84,
+ 0x732: 84,
+ 0x733: 84,
+ 0x734: 84,
+ 0x735: 84,
+ 0x736: 84,
+ 0x737: 84,
+ 0x738: 84,
+ 0x739: 84,
+ 0x73a: 84,
+ 0x73b: 84,
+ 0x73c: 84,
+ 0x73d: 84,
+ 0x73e: 84,
+ 0x73f: 84,
+ 0x740: 84,
+ 0x741: 84,
+ 0x742: 84,
+ 0x743: 84,
+ 0x744: 84,
+ 0x745: 84,
+ 0x746: 84,
+ 0x747: 84,
+ 0x748: 84,
+ 0x749: 84,
+ 0x74a: 84,
+ 0x74d: 82,
+ 0x74e: 68,
+ 0x74f: 68,
+ 0x750: 68,
+ 0x751: 68,
+ 0x752: 68,
+ 0x753: 68,
+ 0x754: 68,
+ 0x755: 68,
+ 0x756: 68,
+ 0x757: 68,
+ 0x758: 68,
+ 0x759: 82,
+ 0x75a: 82,
+ 0x75b: 82,
+ 0x75c: 68,
+ 0x75d: 68,
+ 0x75e: 68,
+ 0x75f: 68,
+ 0x760: 68,
+ 0x761: 68,
+ 0x762: 68,
+ 0x763: 68,
+ 0x764: 68,
+ 0x765: 68,
+ 0x766: 68,
+ 0x767: 68,
+ 0x768: 68,
+ 0x769: 68,
+ 0x76a: 68,
+ 0x76b: 82,
+ 0x76c: 82,
+ 0x76d: 68,
+ 0x76e: 68,
+ 0x76f: 68,
+ 0x770: 68,
+ 0x771: 82,
+ 0x772: 68,
+ 0x773: 82,
+ 0x774: 82,
+ 0x775: 68,
+ 0x776: 68,
+ 0x777: 68,
+ 0x778: 82,
+ 0x779: 82,
+ 0x77a: 68,
+ 0x77b: 68,
+ 0x77c: 68,
+ 0x77d: 68,
+ 0x77e: 68,
+ 0x77f: 68,
+ 0x7a6: 84,
+ 0x7a7: 84,
+ 0x7a8: 84,
+ 0x7a9: 84,
+ 0x7aa: 84,
+ 0x7ab: 84,
+ 0x7ac: 84,
+ 0x7ad: 84,
+ 0x7ae: 84,
+ 0x7af: 84,
+ 0x7b0: 84,
+ 0x7ca: 68,
+ 0x7cb: 68,
+ 0x7cc: 68,
+ 0x7cd: 68,
+ 0x7ce: 68,
+ 0x7cf: 68,
+ 0x7d0: 68,
+ 0x7d1: 68,
+ 0x7d2: 68,
+ 0x7d3: 68,
+ 0x7d4: 68,
+ 0x7d5: 68,
+ 0x7d6: 68,
+ 0x7d7: 68,
+ 0x7d8: 68,
+ 0x7d9: 68,
+ 0x7da: 68,
+ 0x7db: 68,
+ 0x7dc: 68,
+ 0x7dd: 68,
+ 0x7de: 68,
+ 0x7df: 68,
+ 0x7e0: 68,
+ 0x7e1: 68,
+ 0x7e2: 68,
+ 0x7e3: 68,
+ 0x7e4: 68,
+ 0x7e5: 68,
+ 0x7e6: 68,
+ 0x7e7: 68,
+ 0x7e8: 68,
+ 0x7e9: 68,
+ 0x7ea: 68,
+ 0x7eb: 84,
+ 0x7ec: 84,
+ 0x7ed: 84,
+ 0x7ee: 84,
+ 0x7ef: 84,
+ 0x7f0: 84,
+ 0x7f1: 84,
+ 0x7f2: 84,
+ 0x7f3: 84,
+ 0x7fa: 67,
+ 0x7fd: 84,
+ 0x816: 84,
+ 0x817: 84,
+ 0x818: 84,
+ 0x819: 84,
+ 0x81b: 84,
+ 0x81c: 84,
+ 0x81d: 84,
+ 0x81e: 84,
+ 0x81f: 84,
+ 0x820: 84,
+ 0x821: 84,
+ 0x822: 84,
+ 0x823: 84,
+ 0x825: 84,
+ 0x826: 84,
+ 0x827: 84,
+ 0x829: 84,
+ 0x82a: 84,
+ 0x82b: 84,
+ 0x82c: 84,
+ 0x82d: 84,
+ 0x840: 82,
+ 0x841: 68,
+ 0x842: 68,
+ 0x843: 68,
+ 0x844: 68,
+ 0x845: 68,
+ 0x846: 82,
+ 0x847: 82,
+ 0x848: 68,
+ 0x849: 82,
+ 0x84a: 68,
+ 0x84b: 68,
+ 0x84c: 68,
+ 0x84d: 68,
+ 0x84e: 68,
+ 0x84f: 68,
+ 0x850: 68,
+ 0x851: 68,
+ 0x852: 68,
+ 0x853: 68,
+ 0x854: 82,
+ 0x855: 68,
+ 0x856: 82,
+ 0x857: 82,
+ 0x858: 82,
+ 0x859: 84,
+ 0x85a: 84,
+ 0x85b: 84,
+ 0x860: 68,
+ 0x862: 68,
+ 0x863: 68,
+ 0x864: 68,
+ 0x865: 68,
+ 0x867: 82,
+ 0x868: 68,
+ 0x869: 82,
+ 0x86a: 82,
+ 0x870: 82,
+ 0x871: 82,
+ 0x872: 82,
+ 0x873: 82,
+ 0x874: 82,
+ 0x875: 82,
+ 0x876: 82,
+ 0x877: 82,
+ 0x878: 82,
+ 0x879: 82,
+ 0x87a: 82,
+ 0x87b: 82,
+ 0x87c: 82,
+ 0x87d: 82,
+ 0x87e: 82,
+ 0x87f: 82,
+ 0x880: 82,
+ 0x881: 82,
+ 0x882: 82,
+ 0x883: 67,
+ 0x884: 67,
+ 0x885: 67,
+ 0x886: 68,
+ 0x889: 68,
+ 0x88a: 68,
+ 0x88b: 68,
+ 0x88c: 68,
+ 0x88d: 68,
+ 0x88e: 82,
+ 0x898: 84,
+ 0x899: 84,
+ 0x89a: 84,
+ 0x89b: 84,
+ 0x89c: 84,
+ 0x89d: 84,
+ 0x89e: 84,
+ 0x89f: 84,
+ 0x8a0: 68,
+ 0x8a1: 68,
+ 0x8a2: 68,
+ 0x8a3: 68,
+ 0x8a4: 68,
+ 0x8a5: 68,
+ 0x8a6: 68,
+ 0x8a7: 68,
+ 0x8a8: 68,
+ 0x8a9: 68,
+ 0x8aa: 82,
+ 0x8ab: 82,
+ 0x8ac: 82,
+ 0x8ae: 82,
+ 0x8af: 68,
+ 0x8b0: 68,
+ 0x8b1: 82,
+ 0x8b2: 82,
+ 0x8b3: 68,
+ 0x8b4: 68,
+ 0x8b5: 68,
+ 0x8b6: 68,
+ 0x8b7: 68,
+ 0x8b8: 68,
+ 0x8b9: 82,
+ 0x8ba: 68,
+ 0x8bb: 68,
+ 0x8bc: 68,
+ 0x8bd: 68,
+ 0x8be: 68,
+ 0x8bf: 68,
+ 0x8c0: 68,
+ 0x8c1: 68,
+ 0x8c2: 68,
+ 0x8c3: 68,
+ 0x8c4: 68,
+ 0x8c5: 68,
+ 0x8c6: 68,
+ 0x8c7: 68,
+ 0x8c8: 68,
+ 0x8ca: 84,
+ 0x8cb: 84,
+ 0x8cc: 84,
+ 0x8cd: 84,
+ 0x8ce: 84,
+ 0x8cf: 84,
+ 0x8d0: 84,
+ 0x8d1: 84,
+ 0x8d2: 84,
+ 0x8d3: 84,
+ 0x8d4: 84,
+ 0x8d5: 84,
+ 0x8d6: 84,
+ 0x8d7: 84,
+ 0x8d8: 84,
+ 0x8d9: 84,
+ 0x8da: 84,
+ 0x8db: 84,
+ 0x8dc: 84,
+ 0x8dd: 84,
+ 0x8de: 84,
+ 0x8df: 84,
+ 0x8e0: 84,
+ 0x8e1: 84,
+ 0x8e3: 84,
+ 0x8e4: 84,
+ 0x8e5: 84,
+ 0x8e6: 84,
+ 0x8e7: 84,
+ 0x8e8: 84,
+ 0x8e9: 84,
+ 0x8ea: 84,
+ 0x8eb: 84,
+ 0x8ec: 84,
+ 0x8ed: 84,
+ 0x8ee: 84,
+ 0x8ef: 84,
+ 0x8f0: 84,
+ 0x8f1: 84,
+ 0x8f2: 84,
+ 0x8f3: 84,
+ 0x8f4: 84,
+ 0x8f5: 84,
+ 0x8f6: 84,
+ 0x8f7: 84,
+ 0x8f8: 84,
+ 0x8f9: 84,
+ 0x8fa: 84,
+ 0x8fb: 84,
+ 0x8fc: 84,
+ 0x8fd: 84,
+ 0x8fe: 84,
+ 0x8ff: 84,
+ 0x900: 84,
+ 0x901: 84,
+ 0x902: 84,
+ 0x93a: 84,
+ 0x93c: 84,
+ 0x941: 84,
+ 0x942: 84,
+ 0x943: 84,
+ 0x944: 84,
+ 0x945: 84,
+ 0x946: 84,
+ 0x947: 84,
+ 0x948: 84,
+ 0x94d: 84,
+ 0x951: 84,
+ 0x952: 84,
+ 0x953: 84,
+ 0x954: 84,
+ 0x955: 84,
+ 0x956: 84,
+ 0x957: 84,
+ 0x962: 84,
+ 0x963: 84,
+ 0x981: 84,
+ 0x9bc: 84,
+ 0x9c1: 84,
+ 0x9c2: 84,
+ 0x9c3: 84,
+ 0x9c4: 84,
+ 0x9cd: 84,
+ 0x9e2: 84,
+ 0x9e3: 84,
+ 0x9fe: 84,
+ 0xa01: 84,
+ 0xa02: 84,
+ 0xa3c: 84,
+ 0xa41: 84,
+ 0xa42: 84,
+ 0xa47: 84,
+ 0xa48: 84,
+ 0xa4b: 84,
+ 0xa4c: 84,
+ 0xa4d: 84,
+ 0xa51: 84,
+ 0xa70: 84,
+ 0xa71: 84,
+ 0xa75: 84,
+ 0xa81: 84,
+ 0xa82: 84,
+ 0xabc: 84,
+ 0xac1: 84,
+ 0xac2: 84,
+ 0xac3: 84,
+ 0xac4: 84,
+ 0xac5: 84,
+ 0xac7: 84,
+ 0xac8: 84,
+ 0xacd: 84,
+ 0xae2: 84,
+ 0xae3: 84,
+ 0xafa: 84,
+ 0xafb: 84,
+ 0xafc: 84,
+ 0xafd: 84,
+ 0xafe: 84,
+ 0xaff: 84,
+ 0xb01: 84,
+ 0xb3c: 84,
+ 0xb3f: 84,
+ 0xb41: 84,
+ 0xb42: 84,
+ 0xb43: 84,
+ 0xb44: 84,
+ 0xb4d: 84,
+ 0xb55: 84,
+ 0xb56: 84,
+ 0xb62: 84,
+ 0xb63: 84,
+ 0xb82: 84,
+ 0xbc0: 84,
+ 0xbcd: 84,
+ 0xc00: 84,
+ 0xc04: 84,
+ 0xc3c: 84,
+ 0xc3e: 84,
+ 0xc3f: 84,
+ 0xc40: 84,
+ 0xc46: 84,
+ 0xc47: 84,
+ 0xc48: 84,
+ 0xc4a: 84,
+ 0xc4b: 84,
+ 0xc4c: 84,
+ 0xc4d: 84,
+ 0xc55: 84,
+ 0xc56: 84,
+ 0xc62: 84,
+ 0xc63: 84,
+ 0xc81: 84,
+ 0xcbc: 84,
+ 0xcbf: 84,
+ 0xcc6: 84,
+ 0xccc: 84,
+ 0xccd: 84,
+ 0xce2: 84,
+ 0xce3: 84,
+ 0xd00: 84,
+ 0xd01: 84,
+ 0xd3b: 84,
+ 0xd3c: 84,
+ 0xd41: 84,
+ 0xd42: 84,
+ 0xd43: 84,
+ 0xd44: 84,
+ 0xd4d: 84,
+ 0xd62: 84,
+ 0xd63: 84,
+ 0xd81: 84,
+ 0xdca: 84,
+ 0xdd2: 84,
+ 0xdd3: 84,
+ 0xdd4: 84,
+ 0xdd6: 84,
+ 0xe31: 84,
+ 0xe34: 84,
+ 0xe35: 84,
+ 0xe36: 84,
+ 0xe37: 84,
+ 0xe38: 84,
+ 0xe39: 84,
+ 0xe3a: 84,
+ 0xe47: 84,
+ 0xe48: 84,
+ 0xe49: 84,
+ 0xe4a: 84,
+ 0xe4b: 84,
+ 0xe4c: 84,
+ 0xe4d: 84,
+ 0xe4e: 84,
+ 0xeb1: 84,
+ 0xeb4: 84,
+ 0xeb5: 84,
+ 0xeb6: 84,
+ 0xeb7: 84,
+ 0xeb8: 84,
+ 0xeb9: 84,
+ 0xeba: 84,
+ 0xebb: 84,
+ 0xebc: 84,
+ 0xec8: 84,
+ 0xec9: 84,
+ 0xeca: 84,
+ 0xecb: 84,
+ 0xecc: 84,
+ 0xecd: 84,
+ 0xece: 84,
+ 0xf18: 84,
+ 0xf19: 84,
+ 0xf35: 84,
+ 0xf37: 84,
+ 0xf39: 84,
+ 0xf71: 84,
+ 0xf72: 84,
+ 0xf73: 84,
+ 0xf74: 84,
+ 0xf75: 84,
+ 0xf76: 84,
+ 0xf77: 84,
+ 0xf78: 84,
+ 0xf79: 84,
+ 0xf7a: 84,
+ 0xf7b: 84,
+ 0xf7c: 84,
+ 0xf7d: 84,
+ 0xf7e: 84,
+ 0xf80: 84,
+ 0xf81: 84,
+ 0xf82: 84,
+ 0xf83: 84,
+ 0xf84: 84,
+ 0xf86: 84,
+ 0xf87: 84,
+ 0xf8d: 84,
+ 0xf8e: 84,
+ 0xf8f: 84,
+ 0xf90: 84,
+ 0xf91: 84,
+ 0xf92: 84,
+ 0xf93: 84,
+ 0xf94: 84,
+ 0xf95: 84,
+ 0xf96: 84,
+ 0xf97: 84,
+ 0xf99: 84,
+ 0xf9a: 84,
+ 0xf9b: 84,
+ 0xf9c: 84,
+ 0xf9d: 84,
+ 0xf9e: 84,
+ 0xf9f: 84,
+ 0xfa0: 84,
+ 0xfa1: 84,
+ 0xfa2: 84,
+ 0xfa3: 84,
+ 0xfa4: 84,
+ 0xfa5: 84,
+ 0xfa6: 84,
+ 0xfa7: 84,
+ 0xfa8: 84,
+ 0xfa9: 84,
+ 0xfaa: 84,
+ 0xfab: 84,
+ 0xfac: 84,
+ 0xfad: 84,
+ 0xfae: 84,
+ 0xfaf: 84,
+ 0xfb0: 84,
+ 0xfb1: 84,
+ 0xfb2: 84,
+ 0xfb3: 84,
+ 0xfb4: 84,
+ 0xfb5: 84,
+ 0xfb6: 84,
+ 0xfb7: 84,
+ 0xfb8: 84,
+ 0xfb9: 84,
+ 0xfba: 84,
+ 0xfbb: 84,
+ 0xfbc: 84,
+ 0xfc6: 84,
+ 0x102d: 84,
+ 0x102e: 84,
+ 0x102f: 84,
+ 0x1030: 84,
+ 0x1032: 84,
+ 0x1033: 84,
+ 0x1034: 84,
+ 0x1035: 84,
+ 0x1036: 84,
+ 0x1037: 84,
+ 0x1039: 84,
+ 0x103a: 84,
+ 0x103d: 84,
+ 0x103e: 84,
+ 0x1058: 84,
+ 0x1059: 84,
+ 0x105e: 84,
+ 0x105f: 84,
+ 0x1060: 84,
+ 0x1071: 84,
+ 0x1072: 84,
+ 0x1073: 84,
+ 0x1074: 84,
+ 0x1082: 84,
+ 0x1085: 84,
+ 0x1086: 84,
+ 0x108d: 84,
+ 0x109d: 84,
+ 0x135d: 84,
+ 0x135e: 84,
+ 0x135f: 84,
+ 0x1712: 84,
+ 0x1713: 84,
+ 0x1714: 84,
+ 0x1732: 84,
+ 0x1733: 84,
+ 0x1752: 84,
+ 0x1753: 84,
+ 0x1772: 84,
+ 0x1773: 84,
+ 0x17b4: 84,
+ 0x17b5: 84,
+ 0x17b7: 84,
+ 0x17b8: 84,
+ 0x17b9: 84,
+ 0x17ba: 84,
+ 0x17bb: 84,
+ 0x17bc: 84,
+ 0x17bd: 84,
+ 0x17c6: 84,
+ 0x17c9: 84,
+ 0x17ca: 84,
+ 0x17cb: 84,
+ 0x17cc: 84,
+ 0x17cd: 84,
+ 0x17ce: 84,
+ 0x17cf: 84,
+ 0x17d0: 84,
+ 0x17d1: 84,
+ 0x17d2: 84,
+ 0x17d3: 84,
+ 0x17dd: 84,
+ 0x1807: 68,
+ 0x180a: 67,
+ 0x180b: 84,
+ 0x180c: 84,
+ 0x180d: 84,
+ 0x180f: 84,
+ 0x1820: 68,
+ 0x1821: 68,
+ 0x1822: 68,
+ 0x1823: 68,
+ 0x1824: 68,
+ 0x1825: 68,
+ 0x1826: 68,
+ 0x1827: 68,
+ 0x1828: 68,
+ 0x1829: 68,
+ 0x182a: 68,
+ 0x182b: 68,
+ 0x182c: 68,
+ 0x182d: 68,
+ 0x182e: 68,
+ 0x182f: 68,
+ 0x1830: 68,
+ 0x1831: 68,
+ 0x1832: 68,
+ 0x1833: 68,
+ 0x1834: 68,
+ 0x1835: 68,
+ 0x1836: 68,
+ 0x1837: 68,
+ 0x1838: 68,
+ 0x1839: 68,
+ 0x183a: 68,
+ 0x183b: 68,
+ 0x183c: 68,
+ 0x183d: 68,
+ 0x183e: 68,
+ 0x183f: 68,
+ 0x1840: 68,
+ 0x1841: 68,
+ 0x1842: 68,
+ 0x1843: 68,
+ 0x1844: 68,
+ 0x1845: 68,
+ 0x1846: 68,
+ 0x1847: 68,
+ 0x1848: 68,
+ 0x1849: 68,
+ 0x184a: 68,
+ 0x184b: 68,
+ 0x184c: 68,
+ 0x184d: 68,
+ 0x184e: 68,
+ 0x184f: 68,
+ 0x1850: 68,
+ 0x1851: 68,
+ 0x1852: 68,
+ 0x1853: 68,
+ 0x1854: 68,
+ 0x1855: 68,
+ 0x1856: 68,
+ 0x1857: 68,
+ 0x1858: 68,
+ 0x1859: 68,
+ 0x185a: 68,
+ 0x185b: 68,
+ 0x185c: 68,
+ 0x185d: 68,
+ 0x185e: 68,
+ 0x185f: 68,
+ 0x1860: 68,
+ 0x1861: 68,
+ 0x1862: 68,
+ 0x1863: 68,
+ 0x1864: 68,
+ 0x1865: 68,
+ 0x1866: 68,
+ 0x1867: 68,
+ 0x1868: 68,
+ 0x1869: 68,
+ 0x186a: 68,
+ 0x186b: 68,
+ 0x186c: 68,
+ 0x186d: 68,
+ 0x186e: 68,
+ 0x186f: 68,
+ 0x1870: 68,
+ 0x1871: 68,
+ 0x1872: 68,
+ 0x1873: 68,
+ 0x1874: 68,
+ 0x1875: 68,
+ 0x1876: 68,
+ 0x1877: 68,
+ 0x1878: 68,
+ 0x1885: 84,
+ 0x1886: 84,
+ 0x1887: 68,
+ 0x1888: 68,
+ 0x1889: 68,
+ 0x188a: 68,
+ 0x188b: 68,
+ 0x188c: 68,
+ 0x188d: 68,
+ 0x188e: 68,
+ 0x188f: 68,
+ 0x1890: 68,
+ 0x1891: 68,
+ 0x1892: 68,
+ 0x1893: 68,
+ 0x1894: 68,
+ 0x1895: 68,
+ 0x1896: 68,
+ 0x1897: 68,
+ 0x1898: 68,
+ 0x1899: 68,
+ 0x189a: 68,
+ 0x189b: 68,
+ 0x189c: 68,
+ 0x189d: 68,
+ 0x189e: 68,
+ 0x189f: 68,
+ 0x18a0: 68,
+ 0x18a1: 68,
+ 0x18a2: 68,
+ 0x18a3: 68,
+ 0x18a4: 68,
+ 0x18a5: 68,
+ 0x18a6: 68,
+ 0x18a7: 68,
+ 0x18a8: 68,
+ 0x18a9: 84,
+ 0x18aa: 68,
+ 0x1920: 84,
+ 0x1921: 84,
+ 0x1922: 84,
+ 0x1927: 84,
+ 0x1928: 84,
+ 0x1932: 84,
+ 0x1939: 84,
+ 0x193a: 84,
+ 0x193b: 84,
+ 0x1a17: 84,
+ 0x1a18: 84,
+ 0x1a1b: 84,
+ 0x1a56: 84,
+ 0x1a58: 84,
+ 0x1a59: 84,
+ 0x1a5a: 84,
+ 0x1a5b: 84,
+ 0x1a5c: 84,
+ 0x1a5d: 84,
+ 0x1a5e: 84,
+ 0x1a60: 84,
+ 0x1a62: 84,
+ 0x1a65: 84,
+ 0x1a66: 84,
+ 0x1a67: 84,
+ 0x1a68: 84,
+ 0x1a69: 84,
+ 0x1a6a: 84,
+ 0x1a6b: 84,
+ 0x1a6c: 84,
+ 0x1a73: 84,
+ 0x1a74: 84,
+ 0x1a75: 84,
+ 0x1a76: 84,
+ 0x1a77: 84,
+ 0x1a78: 84,
+ 0x1a79: 84,
+ 0x1a7a: 84,
+ 0x1a7b: 84,
+ 0x1a7c: 84,
+ 0x1a7f: 84,
+ 0x1ab0: 84,
+ 0x1ab1: 84,
+ 0x1ab2: 84,
+ 0x1ab3: 84,
+ 0x1ab4: 84,
+ 0x1ab5: 84,
+ 0x1ab6: 84,
+ 0x1ab7: 84,
+ 0x1ab8: 84,
+ 0x1ab9: 84,
+ 0x1aba: 84,
+ 0x1abb: 84,
+ 0x1abc: 84,
+ 0x1abd: 84,
+ 0x1abe: 84,
+ 0x1abf: 84,
+ 0x1ac0: 84,
+ 0x1ac1: 84,
+ 0x1ac2: 84,
+ 0x1ac3: 84,
+ 0x1ac4: 84,
+ 0x1ac5: 84,
+ 0x1ac6: 84,
+ 0x1ac7: 84,
+ 0x1ac8: 84,
+ 0x1ac9: 84,
+ 0x1aca: 84,
+ 0x1acb: 84,
+ 0x1acc: 84,
+ 0x1acd: 84,
+ 0x1ace: 84,
+ 0x1b00: 84,
+ 0x1b01: 84,
+ 0x1b02: 84,
+ 0x1b03: 84,
+ 0x1b34: 84,
+ 0x1b36: 84,
+ 0x1b37: 84,
+ 0x1b38: 84,
+ 0x1b39: 84,
+ 0x1b3a: 84,
+ 0x1b3c: 84,
+ 0x1b42: 84,
+ 0x1b6b: 84,
+ 0x1b6c: 84,
+ 0x1b6d: 84,
+ 0x1b6e: 84,
+ 0x1b6f: 84,
+ 0x1b70: 84,
+ 0x1b71: 84,
+ 0x1b72: 84,
+ 0x1b73: 84,
+ 0x1b80: 84,
+ 0x1b81: 84,
+ 0x1ba2: 84,
+ 0x1ba3: 84,
+ 0x1ba4: 84,
+ 0x1ba5: 84,
+ 0x1ba8: 84,
+ 0x1ba9: 84,
+ 0x1bab: 84,
+ 0x1bac: 84,
+ 0x1bad: 84,
+ 0x1be6: 84,
+ 0x1be8: 84,
+ 0x1be9: 84,
+ 0x1bed: 84,
+ 0x1bef: 84,
+ 0x1bf0: 84,
+ 0x1bf1: 84,
+ 0x1c2c: 84,
+ 0x1c2d: 84,
+ 0x1c2e: 84,
+ 0x1c2f: 84,
+ 0x1c30: 84,
+ 0x1c31: 84,
+ 0x1c32: 84,
+ 0x1c33: 84,
+ 0x1c36: 84,
+ 0x1c37: 84,
+ 0x1cd0: 84,
+ 0x1cd1: 84,
+ 0x1cd2: 84,
+ 0x1cd4: 84,
+ 0x1cd5: 84,
+ 0x1cd6: 84,
+ 0x1cd7: 84,
+ 0x1cd8: 84,
+ 0x1cd9: 84,
+ 0x1cda: 84,
+ 0x1cdb: 84,
+ 0x1cdc: 84,
+ 0x1cdd: 84,
+ 0x1cde: 84,
+ 0x1cdf: 84,
+ 0x1ce0: 84,
+ 0x1ce2: 84,
+ 0x1ce3: 84,
+ 0x1ce4: 84,
+ 0x1ce5: 84,
+ 0x1ce6: 84,
+ 0x1ce7: 84,
+ 0x1ce8: 84,
+ 0x1ced: 84,
+ 0x1cf4: 84,
+ 0x1cf8: 84,
+ 0x1cf9: 84,
+ 0x1dc0: 84,
+ 0x1dc1: 84,
+ 0x1dc2: 84,
+ 0x1dc3: 84,
+ 0x1dc4: 84,
+ 0x1dc5: 84,
+ 0x1dc6: 84,
+ 0x1dc7: 84,
+ 0x1dc8: 84,
+ 0x1dc9: 84,
+ 0x1dca: 84,
+ 0x1dcb: 84,
+ 0x1dcc: 84,
+ 0x1dcd: 84,
+ 0x1dce: 84,
+ 0x1dcf: 84,
+ 0x1dd0: 84,
+ 0x1dd1: 84,
+ 0x1dd2: 84,
+ 0x1dd3: 84,
+ 0x1dd4: 84,
+ 0x1dd5: 84,
+ 0x1dd6: 84,
+ 0x1dd7: 84,
+ 0x1dd8: 84,
+ 0x1dd9: 84,
+ 0x1dda: 84,
+ 0x1ddb: 84,
+ 0x1ddc: 84,
+ 0x1ddd: 84,
+ 0x1dde: 84,
+ 0x1ddf: 84,
+ 0x1de0: 84,
+ 0x1de1: 84,
+ 0x1de2: 84,
+ 0x1de3: 84,
+ 0x1de4: 84,
+ 0x1de5: 84,
+ 0x1de6: 84,
+ 0x1de7: 84,
+ 0x1de8: 84,
+ 0x1de9: 84,
+ 0x1dea: 84,
+ 0x1deb: 84,
+ 0x1dec: 84,
+ 0x1ded: 84,
+ 0x1dee: 84,
+ 0x1def: 84,
+ 0x1df0: 84,
+ 0x1df1: 84,
+ 0x1df2: 84,
+ 0x1df3: 84,
+ 0x1df4: 84,
+ 0x1df5: 84,
+ 0x1df6: 84,
+ 0x1df7: 84,
+ 0x1df8: 84,
+ 0x1df9: 84,
+ 0x1dfa: 84,
+ 0x1dfb: 84,
+ 0x1dfc: 84,
+ 0x1dfd: 84,
+ 0x1dfe: 84,
+ 0x1dff: 84,
+ 0x200b: 84,
+ 0x200d: 67,
+ 0x200e: 84,
+ 0x200f: 84,
+ 0x202a: 84,
+ 0x202b: 84,
+ 0x202c: 84,
+ 0x202d: 84,
+ 0x202e: 84,
+ 0x2060: 84,
+ 0x2061: 84,
+ 0x2062: 84,
+ 0x2063: 84,
+ 0x2064: 84,
+ 0x206a: 84,
+ 0x206b: 84,
+ 0x206c: 84,
+ 0x206d: 84,
+ 0x206e: 84,
+ 0x206f: 84,
+ 0x20d0: 84,
+ 0x20d1: 84,
+ 0x20d2: 84,
+ 0x20d3: 84,
+ 0x20d4: 84,
+ 0x20d5: 84,
+ 0x20d6: 84,
+ 0x20d7: 84,
+ 0x20d8: 84,
+ 0x20d9: 84,
+ 0x20da: 84,
+ 0x20db: 84,
+ 0x20dc: 84,
+ 0x20dd: 84,
+ 0x20de: 84,
+ 0x20df: 84,
+ 0x20e0: 84,
+ 0x20e1: 84,
+ 0x20e2: 84,
+ 0x20e3: 84,
+ 0x20e4: 84,
+ 0x20e5: 84,
+ 0x20e6: 84,
+ 0x20e7: 84,
+ 0x20e8: 84,
+ 0x20e9: 84,
+ 0x20ea: 84,
+ 0x20eb: 84,
+ 0x20ec: 84,
+ 0x20ed: 84,
+ 0x20ee: 84,
+ 0x20ef: 84,
+ 0x20f0: 84,
+ 0x2cef: 84,
+ 0x2cf0: 84,
+ 0x2cf1: 84,
+ 0x2d7f: 84,
+ 0x2de0: 84,
+ 0x2de1: 84,
+ 0x2de2: 84,
+ 0x2de3: 84,
+ 0x2de4: 84,
+ 0x2de5: 84,
+ 0x2de6: 84,
+ 0x2de7: 84,
+ 0x2de8: 84,
+ 0x2de9: 84,
+ 0x2dea: 84,
+ 0x2deb: 84,
+ 0x2dec: 84,
+ 0x2ded: 84,
+ 0x2dee: 84,
+ 0x2def: 84,
+ 0x2df0: 84,
+ 0x2df1: 84,
+ 0x2df2: 84,
+ 0x2df3: 84,
+ 0x2df4: 84,
+ 0x2df5: 84,
+ 0x2df6: 84,
+ 0x2df7: 84,
+ 0x2df8: 84,
+ 0x2df9: 84,
+ 0x2dfa: 84,
+ 0x2dfb: 84,
+ 0x2dfc: 84,
+ 0x2dfd: 84,
+ 0x2dfe: 84,
+ 0x2dff: 84,
+ 0x302a: 84,
+ 0x302b: 84,
+ 0x302c: 84,
+ 0x302d: 84,
+ 0x3099: 84,
+ 0x309a: 84,
+ 0xa66f: 84,
+ 0xa670: 84,
+ 0xa671: 84,
+ 0xa672: 84,
+ 0xa674: 84,
+ 0xa675: 84,
+ 0xa676: 84,
+ 0xa677: 84,
+ 0xa678: 84,
+ 0xa679: 84,
+ 0xa67a: 84,
+ 0xa67b: 84,
+ 0xa67c: 84,
+ 0xa67d: 84,
+ 0xa69e: 84,
+ 0xa69f: 84,
+ 0xa6f0: 84,
+ 0xa6f1: 84,
+ 0xa802: 84,
+ 0xa806: 84,
+ 0xa80b: 84,
+ 0xa825: 84,
+ 0xa826: 84,
+ 0xa82c: 84,
+ 0xa840: 68,
+ 0xa841: 68,
+ 0xa842: 68,
+ 0xa843: 68,
+ 0xa844: 68,
+ 0xa845: 68,
+ 0xa846: 68,
+ 0xa847: 68,
+ 0xa848: 68,
+ 0xa849: 68,
+ 0xa84a: 68,
+ 0xa84b: 68,
+ 0xa84c: 68,
+ 0xa84d: 68,
+ 0xa84e: 68,
+ 0xa84f: 68,
+ 0xa850: 68,
+ 0xa851: 68,
+ 0xa852: 68,
+ 0xa853: 68,
+ 0xa854: 68,
+ 0xa855: 68,
+ 0xa856: 68,
+ 0xa857: 68,
+ 0xa858: 68,
+ 0xa859: 68,
+ 0xa85a: 68,
+ 0xa85b: 68,
+ 0xa85c: 68,
+ 0xa85d: 68,
+ 0xa85e: 68,
+ 0xa85f: 68,
+ 0xa860: 68,
+ 0xa861: 68,
+ 0xa862: 68,
+ 0xa863: 68,
+ 0xa864: 68,
+ 0xa865: 68,
+ 0xa866: 68,
+ 0xa867: 68,
+ 0xa868: 68,
+ 0xa869: 68,
+ 0xa86a: 68,
+ 0xa86b: 68,
+ 0xa86c: 68,
+ 0xa86d: 68,
+ 0xa86e: 68,
+ 0xa86f: 68,
+ 0xa870: 68,
+ 0xa871: 68,
+ 0xa872: 76,
+ 0xa8c4: 84,
+ 0xa8c5: 84,
+ 0xa8e0: 84,
+ 0xa8e1: 84,
+ 0xa8e2: 84,
+ 0xa8e3: 84,
+ 0xa8e4: 84,
+ 0xa8e5: 84,
+ 0xa8e6: 84,
+ 0xa8e7: 84,
+ 0xa8e8: 84,
+ 0xa8e9: 84,
+ 0xa8ea: 84,
+ 0xa8eb: 84,
+ 0xa8ec: 84,
+ 0xa8ed: 84,
+ 0xa8ee: 84,
+ 0xa8ef: 84,
+ 0xa8f0: 84,
+ 0xa8f1: 84,
+ 0xa8ff: 84,
+ 0xa926: 84,
+ 0xa927: 84,
+ 0xa928: 84,
+ 0xa929: 84,
+ 0xa92a: 84,
+ 0xa92b: 84,
+ 0xa92c: 84,
+ 0xa92d: 84,
+ 0xa947: 84,
+ 0xa948: 84,
+ 0xa949: 84,
+ 0xa94a: 84,
+ 0xa94b: 84,
+ 0xa94c: 84,
+ 0xa94d: 84,
+ 0xa94e: 84,
+ 0xa94f: 84,
+ 0xa950: 84,
+ 0xa951: 84,
+ 0xa980: 84,
+ 0xa981: 84,
+ 0xa982: 84,
+ 0xa9b3: 84,
+ 0xa9b6: 84,
+ 0xa9b7: 84,
+ 0xa9b8: 84,
+ 0xa9b9: 84,
+ 0xa9bc: 84,
+ 0xa9bd: 84,
+ 0xa9e5: 84,
+ 0xaa29: 84,
+ 0xaa2a: 84,
+ 0xaa2b: 84,
+ 0xaa2c: 84,
+ 0xaa2d: 84,
+ 0xaa2e: 84,
+ 0xaa31: 84,
+ 0xaa32: 84,
+ 0xaa35: 84,
+ 0xaa36: 84,
+ 0xaa43: 84,
+ 0xaa4c: 84,
+ 0xaa7c: 84,
+ 0xaab0: 84,
+ 0xaab2: 84,
+ 0xaab3: 84,
+ 0xaab4: 84,
+ 0xaab7: 84,
+ 0xaab8: 84,
+ 0xaabe: 84,
+ 0xaabf: 84,
+ 0xaac1: 84,
+ 0xaaec: 84,
+ 0xaaed: 84,
+ 0xaaf6: 84,
+ 0xabe5: 84,
+ 0xabe8: 84,
+ 0xabed: 84,
+ 0xfb1e: 84,
+ 0xfe00: 84,
+ 0xfe01: 84,
+ 0xfe02: 84,
+ 0xfe03: 84,
+ 0xfe04: 84,
+ 0xfe05: 84,
+ 0xfe06: 84,
+ 0xfe07: 84,
+ 0xfe08: 84,
+ 0xfe09: 84,
+ 0xfe0a: 84,
+ 0xfe0b: 84,
+ 0xfe0c: 84,
+ 0xfe0d: 84,
+ 0xfe0e: 84,
+ 0xfe0f: 84,
+ 0xfe20: 84,
+ 0xfe21: 84,
+ 0xfe22: 84,
+ 0xfe23: 84,
+ 0xfe24: 84,
+ 0xfe25: 84,
+ 0xfe26: 84,
+ 0xfe27: 84,
+ 0xfe28: 84,
+ 0xfe29: 84,
+ 0xfe2a: 84,
+ 0xfe2b: 84,
+ 0xfe2c: 84,
+ 0xfe2d: 84,
+ 0xfe2e: 84,
+ 0xfe2f: 84,
+ 0xfeff: 84,
+ 0xfff9: 84,
+ 0xfffa: 84,
+ 0xfffb: 84,
+ 0x101fd: 84,
+ 0x102e0: 84,
+ 0x10376: 84,
+ 0x10377: 84,
+ 0x10378: 84,
+ 0x10379: 84,
+ 0x1037a: 84,
+ 0x10a01: 84,
+ 0x10a02: 84,
+ 0x10a03: 84,
+ 0x10a05: 84,
+ 0x10a06: 84,
+ 0x10a0c: 84,
+ 0x10a0d: 84,
+ 0x10a0e: 84,
+ 0x10a0f: 84,
+ 0x10a38: 84,
+ 0x10a39: 84,
+ 0x10a3a: 84,
+ 0x10a3f: 84,
+ 0x10ac0: 68,
+ 0x10ac1: 68,
+ 0x10ac2: 68,
+ 0x10ac3: 68,
+ 0x10ac4: 68,
+ 0x10ac5: 82,
+ 0x10ac7: 82,
+ 0x10ac9: 82,
+ 0x10aca: 82,
+ 0x10acd: 76,
+ 0x10ace: 82,
+ 0x10acf: 82,
+ 0x10ad0: 82,
+ 0x10ad1: 82,
+ 0x10ad2: 82,
+ 0x10ad3: 68,
+ 0x10ad4: 68,
+ 0x10ad5: 68,
+ 0x10ad6: 68,
+ 0x10ad7: 76,
+ 0x10ad8: 68,
+ 0x10ad9: 68,
+ 0x10ada: 68,
+ 0x10adb: 68,
+ 0x10adc: 68,
+ 0x10add: 82,
+ 0x10ade: 68,
+ 0x10adf: 68,
+ 0x10ae0: 68,
+ 0x10ae1: 82,
+ 0x10ae4: 82,
+ 0x10ae5: 84,
+ 0x10ae6: 84,
+ 0x10aeb: 68,
+ 0x10aec: 68,
+ 0x10aed: 68,
+ 0x10aee: 68,
+ 0x10aef: 82,
+ 0x10b80: 68,
+ 0x10b81: 82,
+ 0x10b82: 68,
+ 0x10b83: 82,
+ 0x10b84: 82,
+ 0x10b85: 82,
+ 0x10b86: 68,
+ 0x10b87: 68,
+ 0x10b88: 68,
+ 0x10b89: 82,
+ 0x10b8a: 68,
+ 0x10b8b: 68,
+ 0x10b8c: 82,
+ 0x10b8d: 68,
+ 0x10b8e: 82,
+ 0x10b8f: 82,
+ 0x10b90: 68,
+ 0x10b91: 82,
+ 0x10ba9: 82,
+ 0x10baa: 82,
+ 0x10bab: 82,
+ 0x10bac: 82,
+ 0x10bad: 68,
+ 0x10bae: 68,
+ 0x10d00: 76,
+ 0x10d01: 68,
+ 0x10d02: 68,
+ 0x10d03: 68,
+ 0x10d04: 68,
+ 0x10d05: 68,
+ 0x10d06: 68,
+ 0x10d07: 68,
+ 0x10d08: 68,
+ 0x10d09: 68,
+ 0x10d0a: 68,
+ 0x10d0b: 68,
+ 0x10d0c: 68,
+ 0x10d0d: 68,
+ 0x10d0e: 68,
+ 0x10d0f: 68,
+ 0x10d10: 68,
+ 0x10d11: 68,
+ 0x10d12: 68,
+ 0x10d13: 68,
+ 0x10d14: 68,
+ 0x10d15: 68,
+ 0x10d16: 68,
+ 0x10d17: 68,
+ 0x10d18: 68,
+ 0x10d19: 68,
+ 0x10d1a: 68,
+ 0x10d1b: 68,
+ 0x10d1c: 68,
+ 0x10d1d: 68,
+ 0x10d1e: 68,
+ 0x10d1f: 68,
+ 0x10d20: 68,
+ 0x10d21: 68,
+ 0x10d22: 82,
+ 0x10d23: 68,
+ 0x10d24: 84,
+ 0x10d25: 84,
+ 0x10d26: 84,
+ 0x10d27: 84,
+ 0x10eab: 84,
+ 0x10eac: 84,
+ 0x10efd: 84,
+ 0x10efe: 84,
+ 0x10eff: 84,
+ 0x10f30: 68,
+ 0x10f31: 68,
+ 0x10f32: 68,
+ 0x10f33: 82,
+ 0x10f34: 68,
+ 0x10f35: 68,
+ 0x10f36: 68,
+ 0x10f37: 68,
+ 0x10f38: 68,
+ 0x10f39: 68,
+ 0x10f3a: 68,
+ 0x10f3b: 68,
+ 0x10f3c: 68,
+ 0x10f3d: 68,
+ 0x10f3e: 68,
+ 0x10f3f: 68,
+ 0x10f40: 68,
+ 0x10f41: 68,
+ 0x10f42: 68,
+ 0x10f43: 68,
+ 0x10f44: 68,
+ 0x10f46: 84,
+ 0x10f47: 84,
+ 0x10f48: 84,
+ 0x10f49: 84,
+ 0x10f4a: 84,
+ 0x10f4b: 84,
+ 0x10f4c: 84,
+ 0x10f4d: 84,
+ 0x10f4e: 84,
+ 0x10f4f: 84,
+ 0x10f50: 84,
+ 0x10f51: 68,
+ 0x10f52: 68,
+ 0x10f53: 68,
+ 0x10f54: 82,
+ 0x10f70: 68,
+ 0x10f71: 68,
+ 0x10f72: 68,
+ 0x10f73: 68,
+ 0x10f74: 82,
+ 0x10f75: 82,
+ 0x10f76: 68,
+ 0x10f77: 68,
+ 0x10f78: 68,
+ 0x10f79: 68,
+ 0x10f7a: 68,
+ 0x10f7b: 68,
+ 0x10f7c: 68,
+ 0x10f7d: 68,
+ 0x10f7e: 68,
+ 0x10f7f: 68,
+ 0x10f80: 68,
+ 0x10f81: 68,
+ 0x10f82: 84,
+ 0x10f83: 84,
+ 0x10f84: 84,
+ 0x10f85: 84,
+ 0x10fb0: 68,
+ 0x10fb2: 68,
+ 0x10fb3: 68,
+ 0x10fb4: 82,
+ 0x10fb5: 82,
+ 0x10fb6: 82,
+ 0x10fb8: 68,
+ 0x10fb9: 82,
+ 0x10fba: 82,
+ 0x10fbb: 68,
+ 0x10fbc: 68,
+ 0x10fbd: 82,
+ 0x10fbe: 68,
+ 0x10fbf: 68,
+ 0x10fc1: 68,
+ 0x10fc2: 82,
+ 0x10fc3: 82,
+ 0x10fc4: 68,
+ 0x10fc9: 82,
+ 0x10fca: 68,
+ 0x10fcb: 76,
+ 0x11001: 84,
+ 0x11038: 84,
+ 0x11039: 84,
+ 0x1103a: 84,
+ 0x1103b: 84,
+ 0x1103c: 84,
+ 0x1103d: 84,
+ 0x1103e: 84,
+ 0x1103f: 84,
+ 0x11040: 84,
+ 0x11041: 84,
+ 0x11042: 84,
+ 0x11043: 84,
+ 0x11044: 84,
+ 0x11045: 84,
+ 0x11046: 84,
+ 0x11070: 84,
+ 0x11073: 84,
+ 0x11074: 84,
+ 0x1107f: 84,
+ 0x11080: 84,
+ 0x11081: 84,
+ 0x110b3: 84,
+ 0x110b4: 84,
+ 0x110b5: 84,
+ 0x110b6: 84,
+ 0x110b9: 84,
+ 0x110ba: 84,
+ 0x110c2: 84,
+ 0x11100: 84,
+ 0x11101: 84,
+ 0x11102: 84,
+ 0x11127: 84,
+ 0x11128: 84,
+ 0x11129: 84,
+ 0x1112a: 84,
+ 0x1112b: 84,
+ 0x1112d: 84,
+ 0x1112e: 84,
+ 0x1112f: 84,
+ 0x11130: 84,
+ 0x11131: 84,
+ 0x11132: 84,
+ 0x11133: 84,
+ 0x11134: 84,
+ 0x11173: 84,
+ 0x11180: 84,
+ 0x11181: 84,
+ 0x111b6: 84,
+ 0x111b7: 84,
+ 0x111b8: 84,
+ 0x111b9: 84,
+ 0x111ba: 84,
+ 0x111bb: 84,
+ 0x111bc: 84,
+ 0x111bd: 84,
+ 0x111be: 84,
+ 0x111c9: 84,
+ 0x111ca: 84,
+ 0x111cb: 84,
+ 0x111cc: 84,
+ 0x111cf: 84,
+ 0x1122f: 84,
+ 0x11230: 84,
+ 0x11231: 84,
+ 0x11234: 84,
+ 0x11236: 84,
+ 0x11237: 84,
+ 0x1123e: 84,
+ 0x11241: 84,
+ 0x112df: 84,
+ 0x112e3: 84,
+ 0x112e4: 84,
+ 0x112e5: 84,
+ 0x112e6: 84,
+ 0x112e7: 84,
+ 0x112e8: 84,
+ 0x112e9: 84,
+ 0x112ea: 84,
+ 0x11300: 84,
+ 0x11301: 84,
+ 0x1133b: 84,
+ 0x1133c: 84,
+ 0x11340: 84,
+ 0x11366: 84,
+ 0x11367: 84,
+ 0x11368: 84,
+ 0x11369: 84,
+ 0x1136a: 84,
+ 0x1136b: 84,
+ 0x1136c: 84,
+ 0x11370: 84,
+ 0x11371: 84,
+ 0x11372: 84,
+ 0x11373: 84,
+ 0x11374: 84,
+ 0x11438: 84,
+ 0x11439: 84,
+ 0x1143a: 84,
+ 0x1143b: 84,
+ 0x1143c: 84,
+ 0x1143d: 84,
+ 0x1143e: 84,
+ 0x1143f: 84,
+ 0x11442: 84,
+ 0x11443: 84,
+ 0x11444: 84,
+ 0x11446: 84,
+ 0x1145e: 84,
+ 0x114b3: 84,
+ 0x114b4: 84,
+ 0x114b5: 84,
+ 0x114b6: 84,
+ 0x114b7: 84,
+ 0x114b8: 84,
+ 0x114ba: 84,
+ 0x114bf: 84,
+ 0x114c0: 84,
+ 0x114c2: 84,
+ 0x114c3: 84,
+ 0x115b2: 84,
+ 0x115b3: 84,
+ 0x115b4: 84,
+ 0x115b5: 84,
+ 0x115bc: 84,
+ 0x115bd: 84,
+ 0x115bf: 84,
+ 0x115c0: 84,
+ 0x115dc: 84,
+ 0x115dd: 84,
+ 0x11633: 84,
+ 0x11634: 84,
+ 0x11635: 84,
+ 0x11636: 84,
+ 0x11637: 84,
+ 0x11638: 84,
+ 0x11639: 84,
+ 0x1163a: 84,
+ 0x1163d: 84,
+ 0x1163f: 84,
+ 0x11640: 84,
+ 0x116ab: 84,
+ 0x116ad: 84,
+ 0x116b0: 84,
+ 0x116b1: 84,
+ 0x116b2: 84,
+ 0x116b3: 84,
+ 0x116b4: 84,
+ 0x116b5: 84,
+ 0x116b7: 84,
+ 0x1171d: 84,
+ 0x1171e: 84,
+ 0x1171f: 84,
+ 0x11722: 84,
+ 0x11723: 84,
+ 0x11724: 84,
+ 0x11725: 84,
+ 0x11727: 84,
+ 0x11728: 84,
+ 0x11729: 84,
+ 0x1172a: 84,
+ 0x1172b: 84,
+ 0x1182f: 84,
+ 0x11830: 84,
+ 0x11831: 84,
+ 0x11832: 84,
+ 0x11833: 84,
+ 0x11834: 84,
+ 0x11835: 84,
+ 0x11836: 84,
+ 0x11837: 84,
+ 0x11839: 84,
+ 0x1183a: 84,
+ 0x1193b: 84,
+ 0x1193c: 84,
+ 0x1193e: 84,
+ 0x11943: 84,
+ 0x119d4: 84,
+ 0x119d5: 84,
+ 0x119d6: 84,
+ 0x119d7: 84,
+ 0x119da: 84,
+ 0x119db: 84,
+ 0x119e0: 84,
+ 0x11a01: 84,
+ 0x11a02: 84,
+ 0x11a03: 84,
+ 0x11a04: 84,
+ 0x11a05: 84,
+ 0x11a06: 84,
+ 0x11a07: 84,
+ 0x11a08: 84,
+ 0x11a09: 84,
+ 0x11a0a: 84,
+ 0x11a33: 84,
+ 0x11a34: 84,
+ 0x11a35: 84,
+ 0x11a36: 84,
+ 0x11a37: 84,
+ 0x11a38: 84,
+ 0x11a3b: 84,
+ 0x11a3c: 84,
+ 0x11a3d: 84,
+ 0x11a3e: 84,
+ 0x11a47: 84,
+ 0x11a51: 84,
+ 0x11a52: 84,
+ 0x11a53: 84,
+ 0x11a54: 84,
+ 0x11a55: 84,
+ 0x11a56: 84,
+ 0x11a59: 84,
+ 0x11a5a: 84,
+ 0x11a5b: 84,
+ 0x11a8a: 84,
+ 0x11a8b: 84,
+ 0x11a8c: 84,
+ 0x11a8d: 84,
+ 0x11a8e: 84,
+ 0x11a8f: 84,
+ 0x11a90: 84,
+ 0x11a91: 84,
+ 0x11a92: 84,
+ 0x11a93: 84,
+ 0x11a94: 84,
+ 0x11a95: 84,
+ 0x11a96: 84,
+ 0x11a98: 84,
+ 0x11a99: 84,
+ 0x11c30: 84,
+ 0x11c31: 84,
+ 0x11c32: 84,
+ 0x11c33: 84,
+ 0x11c34: 84,
+ 0x11c35: 84,
+ 0x11c36: 84,
+ 0x11c38: 84,
+ 0x11c39: 84,
+ 0x11c3a: 84,
+ 0x11c3b: 84,
+ 0x11c3c: 84,
+ 0x11c3d: 84,
+ 0x11c3f: 84,
+ 0x11c92: 84,
+ 0x11c93: 84,
+ 0x11c94: 84,
+ 0x11c95: 84,
+ 0x11c96: 84,
+ 0x11c97: 84,
+ 0x11c98: 84,
+ 0x11c99: 84,
+ 0x11c9a: 84,
+ 0x11c9b: 84,
+ 0x11c9c: 84,
+ 0x11c9d: 84,
+ 0x11c9e: 84,
+ 0x11c9f: 84,
+ 0x11ca0: 84,
+ 0x11ca1: 84,
+ 0x11ca2: 84,
+ 0x11ca3: 84,
+ 0x11ca4: 84,
+ 0x11ca5: 84,
+ 0x11ca6: 84,
+ 0x11ca7: 84,
+ 0x11caa: 84,
+ 0x11cab: 84,
+ 0x11cac: 84,
+ 0x11cad: 84,
+ 0x11cae: 84,
+ 0x11caf: 84,
+ 0x11cb0: 84,
+ 0x11cb2: 84,
+ 0x11cb3: 84,
+ 0x11cb5: 84,
+ 0x11cb6: 84,
+ 0x11d31: 84,
+ 0x11d32: 84,
+ 0x11d33: 84,
+ 0x11d34: 84,
+ 0x11d35: 84,
+ 0x11d36: 84,
+ 0x11d3a: 84,
+ 0x11d3c: 84,
+ 0x11d3d: 84,
+ 0x11d3f: 84,
+ 0x11d40: 84,
+ 0x11d41: 84,
+ 0x11d42: 84,
+ 0x11d43: 84,
+ 0x11d44: 84,
+ 0x11d45: 84,
+ 0x11d47: 84,
+ 0x11d90: 84,
+ 0x11d91: 84,
+ 0x11d95: 84,
+ 0x11d97: 84,
+ 0x11ef3: 84,
+ 0x11ef4: 84,
+ 0x11f00: 84,
+ 0x11f01: 84,
+ 0x11f36: 84,
+ 0x11f37: 84,
+ 0x11f38: 84,
+ 0x11f39: 84,
+ 0x11f3a: 84,
+ 0x11f40: 84,
+ 0x11f42: 84,
+ 0x13430: 84,
+ 0x13431: 84,
+ 0x13432: 84,
+ 0x13433: 84,
+ 0x13434: 84,
+ 0x13435: 84,
+ 0x13436: 84,
+ 0x13437: 84,
+ 0x13438: 84,
+ 0x13439: 84,
+ 0x1343a: 84,
+ 0x1343b: 84,
+ 0x1343c: 84,
+ 0x1343d: 84,
+ 0x1343e: 84,
+ 0x1343f: 84,
+ 0x13440: 84,
+ 0x13447: 84,
+ 0x13448: 84,
+ 0x13449: 84,
+ 0x1344a: 84,
+ 0x1344b: 84,
+ 0x1344c: 84,
+ 0x1344d: 84,
+ 0x1344e: 84,
+ 0x1344f: 84,
+ 0x13450: 84,
+ 0x13451: 84,
+ 0x13452: 84,
+ 0x13453: 84,
+ 0x13454: 84,
+ 0x13455: 84,
+ 0x16af0: 84,
+ 0x16af1: 84,
+ 0x16af2: 84,
+ 0x16af3: 84,
+ 0x16af4: 84,
+ 0x16b30: 84,
+ 0x16b31: 84,
+ 0x16b32: 84,
+ 0x16b33: 84,
+ 0x16b34: 84,
+ 0x16b35: 84,
+ 0x16b36: 84,
+ 0x16f4f: 84,
+ 0x16f8f: 84,
+ 0x16f90: 84,
+ 0x16f91: 84,
+ 0x16f92: 84,
+ 0x16fe4: 84,
+ 0x1bc9d: 84,
+ 0x1bc9e: 84,
+ 0x1bca0: 84,
+ 0x1bca1: 84,
+ 0x1bca2: 84,
+ 0x1bca3: 84,
+ 0x1cf00: 84,
+ 0x1cf01: 84,
+ 0x1cf02: 84,
+ 0x1cf03: 84,
+ 0x1cf04: 84,
+ 0x1cf05: 84,
+ 0x1cf06: 84,
+ 0x1cf07: 84,
+ 0x1cf08: 84,
+ 0x1cf09: 84,
+ 0x1cf0a: 84,
+ 0x1cf0b: 84,
+ 0x1cf0c: 84,
+ 0x1cf0d: 84,
+ 0x1cf0e: 84,
+ 0x1cf0f: 84,
+ 0x1cf10: 84,
+ 0x1cf11: 84,
+ 0x1cf12: 84,
+ 0x1cf13: 84,
+ 0x1cf14: 84,
+ 0x1cf15: 84,
+ 0x1cf16: 84,
+ 0x1cf17: 84,
+ 0x1cf18: 84,
+ 0x1cf19: 84,
+ 0x1cf1a: 84,
+ 0x1cf1b: 84,
+ 0x1cf1c: 84,
+ 0x1cf1d: 84,
+ 0x1cf1e: 84,
+ 0x1cf1f: 84,
+ 0x1cf20: 84,
+ 0x1cf21: 84,
+ 0x1cf22: 84,
+ 0x1cf23: 84,
+ 0x1cf24: 84,
+ 0x1cf25: 84,
+ 0x1cf26: 84,
+ 0x1cf27: 84,
+ 0x1cf28: 84,
+ 0x1cf29: 84,
+ 0x1cf2a: 84,
+ 0x1cf2b: 84,
+ 0x1cf2c: 84,
+ 0x1cf2d: 84,
+ 0x1cf30: 84,
+ 0x1cf31: 84,
+ 0x1cf32: 84,
+ 0x1cf33: 84,
+ 0x1cf34: 84,
+ 0x1cf35: 84,
+ 0x1cf36: 84,
+ 0x1cf37: 84,
+ 0x1cf38: 84,
+ 0x1cf39: 84,
+ 0x1cf3a: 84,
+ 0x1cf3b: 84,
+ 0x1cf3c: 84,
+ 0x1cf3d: 84,
+ 0x1cf3e: 84,
+ 0x1cf3f: 84,
+ 0x1cf40: 84,
+ 0x1cf41: 84,
+ 0x1cf42: 84,
+ 0x1cf43: 84,
+ 0x1cf44: 84,
+ 0x1cf45: 84,
+ 0x1cf46: 84,
+ 0x1d167: 84,
+ 0x1d168: 84,
+ 0x1d169: 84,
+ 0x1d173: 84,
+ 0x1d174: 84,
+ 0x1d175: 84,
+ 0x1d176: 84,
+ 0x1d177: 84,
+ 0x1d178: 84,
+ 0x1d179: 84,
+ 0x1d17a: 84,
+ 0x1d17b: 84,
+ 0x1d17c: 84,
+ 0x1d17d: 84,
+ 0x1d17e: 84,
+ 0x1d17f: 84,
+ 0x1d180: 84,
+ 0x1d181: 84,
+ 0x1d182: 84,
+ 0x1d185: 84,
+ 0x1d186: 84,
+ 0x1d187: 84,
+ 0x1d188: 84,
+ 0x1d189: 84,
+ 0x1d18a: 84,
+ 0x1d18b: 84,
+ 0x1d1aa: 84,
+ 0x1d1ab: 84,
+ 0x1d1ac: 84,
+ 0x1d1ad: 84,
+ 0x1d242: 84,
+ 0x1d243: 84,
+ 0x1d244: 84,
+ 0x1da00: 84,
+ 0x1da01: 84,
+ 0x1da02: 84,
+ 0x1da03: 84,
+ 0x1da04: 84,
+ 0x1da05: 84,
+ 0x1da06: 84,
+ 0x1da07: 84,
+ 0x1da08: 84,
+ 0x1da09: 84,
+ 0x1da0a: 84,
+ 0x1da0b: 84,
+ 0x1da0c: 84,
+ 0x1da0d: 84,
+ 0x1da0e: 84,
+ 0x1da0f: 84,
+ 0x1da10: 84,
+ 0x1da11: 84,
+ 0x1da12: 84,
+ 0x1da13: 84,
+ 0x1da14: 84,
+ 0x1da15: 84,
+ 0x1da16: 84,
+ 0x1da17: 84,
+ 0x1da18: 84,
+ 0x1da19: 84,
+ 0x1da1a: 84,
+ 0x1da1b: 84,
+ 0x1da1c: 84,
+ 0x1da1d: 84,
+ 0x1da1e: 84,
+ 0x1da1f: 84,
+ 0x1da20: 84,
+ 0x1da21: 84,
+ 0x1da22: 84,
+ 0x1da23: 84,
+ 0x1da24: 84,
+ 0x1da25: 84,
+ 0x1da26: 84,
+ 0x1da27: 84,
+ 0x1da28: 84,
+ 0x1da29: 84,
+ 0x1da2a: 84,
+ 0x1da2b: 84,
+ 0x1da2c: 84,
+ 0x1da2d: 84,
+ 0x1da2e: 84,
+ 0x1da2f: 84,
+ 0x1da30: 84,
+ 0x1da31: 84,
+ 0x1da32: 84,
+ 0x1da33: 84,
+ 0x1da34: 84,
+ 0x1da35: 84,
+ 0x1da36: 84,
+ 0x1da3b: 84,
+ 0x1da3c: 84,
+ 0x1da3d: 84,
+ 0x1da3e: 84,
+ 0x1da3f: 84,
+ 0x1da40: 84,
+ 0x1da41: 84,
+ 0x1da42: 84,
+ 0x1da43: 84,
+ 0x1da44: 84,
+ 0x1da45: 84,
+ 0x1da46: 84,
+ 0x1da47: 84,
+ 0x1da48: 84,
+ 0x1da49: 84,
+ 0x1da4a: 84,
+ 0x1da4b: 84,
+ 0x1da4c: 84,
+ 0x1da4d: 84,
+ 0x1da4e: 84,
+ 0x1da4f: 84,
+ 0x1da50: 84,
+ 0x1da51: 84,
+ 0x1da52: 84,
+ 0x1da53: 84,
+ 0x1da54: 84,
+ 0x1da55: 84,
+ 0x1da56: 84,
+ 0x1da57: 84,
+ 0x1da58: 84,
+ 0x1da59: 84,
+ 0x1da5a: 84,
+ 0x1da5b: 84,
+ 0x1da5c: 84,
+ 0x1da5d: 84,
+ 0x1da5e: 84,
+ 0x1da5f: 84,
+ 0x1da60: 84,
+ 0x1da61: 84,
+ 0x1da62: 84,
+ 0x1da63: 84,
+ 0x1da64: 84,
+ 0x1da65: 84,
+ 0x1da66: 84,
+ 0x1da67: 84,
+ 0x1da68: 84,
+ 0x1da69: 84,
+ 0x1da6a: 84,
+ 0x1da6b: 84,
+ 0x1da6c: 84,
+ 0x1da75: 84,
+ 0x1da84: 84,
+ 0x1da9b: 84,
+ 0x1da9c: 84,
+ 0x1da9d: 84,
+ 0x1da9e: 84,
+ 0x1da9f: 84,
+ 0x1daa1: 84,
+ 0x1daa2: 84,
+ 0x1daa3: 84,
+ 0x1daa4: 84,
+ 0x1daa5: 84,
+ 0x1daa6: 84,
+ 0x1daa7: 84,
+ 0x1daa8: 84,
+ 0x1daa9: 84,
+ 0x1daaa: 84,
+ 0x1daab: 84,
+ 0x1daac: 84,
+ 0x1daad: 84,
+ 0x1daae: 84,
+ 0x1daaf: 84,
+ 0x1e000: 84,
+ 0x1e001: 84,
+ 0x1e002: 84,
+ 0x1e003: 84,
+ 0x1e004: 84,
+ 0x1e005: 84,
+ 0x1e006: 84,
+ 0x1e008: 84,
+ 0x1e009: 84,
+ 0x1e00a: 84,
+ 0x1e00b: 84,
+ 0x1e00c: 84,
+ 0x1e00d: 84,
+ 0x1e00e: 84,
+ 0x1e00f: 84,
+ 0x1e010: 84,
+ 0x1e011: 84,
+ 0x1e012: 84,
+ 0x1e013: 84,
+ 0x1e014: 84,
+ 0x1e015: 84,
+ 0x1e016: 84,
+ 0x1e017: 84,
+ 0x1e018: 84,
+ 0x1e01b: 84,
+ 0x1e01c: 84,
+ 0x1e01d: 84,
+ 0x1e01e: 84,
+ 0x1e01f: 84,
+ 0x1e020: 84,
+ 0x1e021: 84,
+ 0x1e023: 84,
+ 0x1e024: 84,
+ 0x1e026: 84,
+ 0x1e027: 84,
+ 0x1e028: 84,
+ 0x1e029: 84,
+ 0x1e02a: 84,
+ 0x1e08f: 84,
+ 0x1e130: 84,
+ 0x1e131: 84,
+ 0x1e132: 84,
+ 0x1e133: 84,
+ 0x1e134: 84,
+ 0x1e135: 84,
+ 0x1e136: 84,
+ 0x1e2ae: 84,
+ 0x1e2ec: 84,
+ 0x1e2ed: 84,
+ 0x1e2ee: 84,
+ 0x1e2ef: 84,
+ 0x1e4ec: 84,
+ 0x1e4ed: 84,
+ 0x1e4ee: 84,
+ 0x1e4ef: 84,
+ 0x1e8d0: 84,
+ 0x1e8d1: 84,
+ 0x1e8d2: 84,
+ 0x1e8d3: 84,
+ 0x1e8d4: 84,
+ 0x1e8d5: 84,
+ 0x1e8d6: 84,
+ 0x1e900: 68,
+ 0x1e901: 68,
+ 0x1e902: 68,
+ 0x1e903: 68,
+ 0x1e904: 68,
+ 0x1e905: 68,
+ 0x1e906: 68,
+ 0x1e907: 68,
+ 0x1e908: 68,
+ 0x1e909: 68,
+ 0x1e90a: 68,
+ 0x1e90b: 68,
+ 0x1e90c: 68,
+ 0x1e90d: 68,
+ 0x1e90e: 68,
+ 0x1e90f: 68,
+ 0x1e910: 68,
+ 0x1e911: 68,
+ 0x1e912: 68,
+ 0x1e913: 68,
+ 0x1e914: 68,
+ 0x1e915: 68,
+ 0x1e916: 68,
+ 0x1e917: 68,
+ 0x1e918: 68,
+ 0x1e919: 68,
+ 0x1e91a: 68,
+ 0x1e91b: 68,
+ 0x1e91c: 68,
+ 0x1e91d: 68,
+ 0x1e91e: 68,
+ 0x1e91f: 68,
+ 0x1e920: 68,
+ 0x1e921: 68,
+ 0x1e922: 68,
+ 0x1e923: 68,
+ 0x1e924: 68,
+ 0x1e925: 68,
+ 0x1e926: 68,
+ 0x1e927: 68,
+ 0x1e928: 68,
+ 0x1e929: 68,
+ 0x1e92a: 68,
+ 0x1e92b: 68,
+ 0x1e92c: 68,
+ 0x1e92d: 68,
+ 0x1e92e: 68,
+ 0x1e92f: 68,
+ 0x1e930: 68,
+ 0x1e931: 68,
+ 0x1e932: 68,
+ 0x1e933: 68,
+ 0x1e934: 68,
+ 0x1e935: 68,
+ 0x1e936: 68,
+ 0x1e937: 68,
+ 0x1e938: 68,
+ 0x1e939: 68,
+ 0x1e93a: 68,
+ 0x1e93b: 68,
+ 0x1e93c: 68,
+ 0x1e93d: 68,
+ 0x1e93e: 68,
+ 0x1e93f: 68,
+ 0x1e940: 68,
+ 0x1e941: 68,
+ 0x1e942: 68,
+ 0x1e943: 68,
+ 0x1e944: 84,
+ 0x1e945: 84,
+ 0x1e946: 84,
+ 0x1e947: 84,
+ 0x1e948: 84,
+ 0x1e949: 84,
+ 0x1e94a: 84,
+ 0x1e94b: 84,
+ 0xe0001: 84,
+ 0xe0020: 84,
+ 0xe0021: 84,
+ 0xe0022: 84,
+ 0xe0023: 84,
+ 0xe0024: 84,
+ 0xe0025: 84,
+ 0xe0026: 84,
+ 0xe0027: 84,
+ 0xe0028: 84,
+ 0xe0029: 84,
+ 0xe002a: 84,
+ 0xe002b: 84,
+ 0xe002c: 84,
+ 0xe002d: 84,
+ 0xe002e: 84,
+ 0xe002f: 84,
+ 0xe0030: 84,
+ 0xe0031: 84,
+ 0xe0032: 84,
+ 0xe0033: 84,
+ 0xe0034: 84,
+ 0xe0035: 84,
+ 0xe0036: 84,
+ 0xe0037: 84,
+ 0xe0038: 84,
+ 0xe0039: 84,
+ 0xe003a: 84,
+ 0xe003b: 84,
+ 0xe003c: 84,
+ 0xe003d: 84,
+ 0xe003e: 84,
+ 0xe003f: 84,
+ 0xe0040: 84,
+ 0xe0041: 84,
+ 0xe0042: 84,
+ 0xe0043: 84,
+ 0xe0044: 84,
+ 0xe0045: 84,
+ 0xe0046: 84,
+ 0xe0047: 84,
+ 0xe0048: 84,
+ 0xe0049: 84,
+ 0xe004a: 84,
+ 0xe004b: 84,
+ 0xe004c: 84,
+ 0xe004d: 84,
+ 0xe004e: 84,
+ 0xe004f: 84,
+ 0xe0050: 84,
+ 0xe0051: 84,
+ 0xe0052: 84,
+ 0xe0053: 84,
+ 0xe0054: 84,
+ 0xe0055: 84,
+ 0xe0056: 84,
+ 0xe0057: 84,
+ 0xe0058: 84,
+ 0xe0059: 84,
+ 0xe005a: 84,
+ 0xe005b: 84,
+ 0xe005c: 84,
+ 0xe005d: 84,
+ 0xe005e: 84,
+ 0xe005f: 84,
+ 0xe0060: 84,
+ 0xe0061: 84,
+ 0xe0062: 84,
+ 0xe0063: 84,
+ 0xe0064: 84,
+ 0xe0065: 84,
+ 0xe0066: 84,
+ 0xe0067: 84,
+ 0xe0068: 84,
+ 0xe0069: 84,
+ 0xe006a: 84,
+ 0xe006b: 84,
+ 0xe006c: 84,
+ 0xe006d: 84,
+ 0xe006e: 84,
+ 0xe006f: 84,
+ 0xe0070: 84,
+ 0xe0071: 84,
+ 0xe0072: 84,
+ 0xe0073: 84,
+ 0xe0074: 84,
+ 0xe0075: 84,
+ 0xe0076: 84,
+ 0xe0077: 84,
+ 0xe0078: 84,
+ 0xe0079: 84,
+ 0xe007a: 84,
+ 0xe007b: 84,
+ 0xe007c: 84,
+ 0xe007d: 84,
+ 0xe007e: 84,
+ 0xe007f: 84,
+ 0xe0100: 84,
+ 0xe0101: 84,
+ 0xe0102: 84,
+ 0xe0103: 84,
+ 0xe0104: 84,
+ 0xe0105: 84,
+ 0xe0106: 84,
+ 0xe0107: 84,
+ 0xe0108: 84,
+ 0xe0109: 84,
+ 0xe010a: 84,
+ 0xe010b: 84,
+ 0xe010c: 84,
+ 0xe010d: 84,
+ 0xe010e: 84,
+ 0xe010f: 84,
+ 0xe0110: 84,
+ 0xe0111: 84,
+ 0xe0112: 84,
+ 0xe0113: 84,
+ 0xe0114: 84,
+ 0xe0115: 84,
+ 0xe0116: 84,
+ 0xe0117: 84,
+ 0xe0118: 84,
+ 0xe0119: 84,
+ 0xe011a: 84,
+ 0xe011b: 84,
+ 0xe011c: 84,
+ 0xe011d: 84,
+ 0xe011e: 84,
+ 0xe011f: 84,
+ 0xe0120: 84,
+ 0xe0121: 84,
+ 0xe0122: 84,
+ 0xe0123: 84,
+ 0xe0124: 84,
+ 0xe0125: 84,
+ 0xe0126: 84,
+ 0xe0127: 84,
+ 0xe0128: 84,
+ 0xe0129: 84,
+ 0xe012a: 84,
+ 0xe012b: 84,
+ 0xe012c: 84,
+ 0xe012d: 84,
+ 0xe012e: 84,
+ 0xe012f: 84,
+ 0xe0130: 84,
+ 0xe0131: 84,
+ 0xe0132: 84,
+ 0xe0133: 84,
+ 0xe0134: 84,
+ 0xe0135: 84,
+ 0xe0136: 84,
+ 0xe0137: 84,
+ 0xe0138: 84,
+ 0xe0139: 84,
+ 0xe013a: 84,
+ 0xe013b: 84,
+ 0xe013c: 84,
+ 0xe013d: 84,
+ 0xe013e: 84,
+ 0xe013f: 84,
+ 0xe0140: 84,
+ 0xe0141: 84,
+ 0xe0142: 84,
+ 0xe0143: 84,
+ 0xe0144: 84,
+ 0xe0145: 84,
+ 0xe0146: 84,
+ 0xe0147: 84,
+ 0xe0148: 84,
+ 0xe0149: 84,
+ 0xe014a: 84,
+ 0xe014b: 84,
+ 0xe014c: 84,
+ 0xe014d: 84,
+ 0xe014e: 84,
+ 0xe014f: 84,
+ 0xe0150: 84,
+ 0xe0151: 84,
+ 0xe0152: 84,
+ 0xe0153: 84,
+ 0xe0154: 84,
+ 0xe0155: 84,
+ 0xe0156: 84,
+ 0xe0157: 84,
+ 0xe0158: 84,
+ 0xe0159: 84,
+ 0xe015a: 84,
+ 0xe015b: 84,
+ 0xe015c: 84,
+ 0xe015d: 84,
+ 0xe015e: 84,
+ 0xe015f: 84,
+ 0xe0160: 84,
+ 0xe0161: 84,
+ 0xe0162: 84,
+ 0xe0163: 84,
+ 0xe0164: 84,
+ 0xe0165: 84,
+ 0xe0166: 84,
+ 0xe0167: 84,
+ 0xe0168: 84,
+ 0xe0169: 84,
+ 0xe016a: 84,
+ 0xe016b: 84,
+ 0xe016c: 84,
+ 0xe016d: 84,
+ 0xe016e: 84,
+ 0xe016f: 84,
+ 0xe0170: 84,
+ 0xe0171: 84,
+ 0xe0172: 84,
+ 0xe0173: 84,
+ 0xe0174: 84,
+ 0xe0175: 84,
+ 0xe0176: 84,
+ 0xe0177: 84,
+ 0xe0178: 84,
+ 0xe0179: 84,
+ 0xe017a: 84,
+ 0xe017b: 84,
+ 0xe017c: 84,
+ 0xe017d: 84,
+ 0xe017e: 84,
+ 0xe017f: 84,
+ 0xe0180: 84,
+ 0xe0181: 84,
+ 0xe0182: 84,
+ 0xe0183: 84,
+ 0xe0184: 84,
+ 0xe0185: 84,
+ 0xe0186: 84,
+ 0xe0187: 84,
+ 0xe0188: 84,
+ 0xe0189: 84,
+ 0xe018a: 84,
+ 0xe018b: 84,
+ 0xe018c: 84,
+ 0xe018d: 84,
+ 0xe018e: 84,
+ 0xe018f: 84,
+ 0xe0190: 84,
+ 0xe0191: 84,
+ 0xe0192: 84,
+ 0xe0193: 84,
+ 0xe0194: 84,
+ 0xe0195: 84,
+ 0xe0196: 84,
+ 0xe0197: 84,
+ 0xe0198: 84,
+ 0xe0199: 84,
+ 0xe019a: 84,
+ 0xe019b: 84,
+ 0xe019c: 84,
+ 0xe019d: 84,
+ 0xe019e: 84,
+ 0xe019f: 84,
+ 0xe01a0: 84,
+ 0xe01a1: 84,
+ 0xe01a2: 84,
+ 0xe01a3: 84,
+ 0xe01a4: 84,
+ 0xe01a5: 84,
+ 0xe01a6: 84,
+ 0xe01a7: 84,
+ 0xe01a8: 84,
+ 0xe01a9: 84,
+ 0xe01aa: 84,
+ 0xe01ab: 84,
+ 0xe01ac: 84,
+ 0xe01ad: 84,
+ 0xe01ae: 84,
+ 0xe01af: 84,
+ 0xe01b0: 84,
+ 0xe01b1: 84,
+ 0xe01b2: 84,
+ 0xe01b3: 84,
+ 0xe01b4: 84,
+ 0xe01b5: 84,
+ 0xe01b6: 84,
+ 0xe01b7: 84,
+ 0xe01b8: 84,
+ 0xe01b9: 84,
+ 0xe01ba: 84,
+ 0xe01bb: 84,
+ 0xe01bc: 84,
+ 0xe01bd: 84,
+ 0xe01be: 84,
+ 0xe01bf: 84,
+ 0xe01c0: 84,
+ 0xe01c1: 84,
+ 0xe01c2: 84,
+ 0xe01c3: 84,
+ 0xe01c4: 84,
+ 0xe01c5: 84,
+ 0xe01c6: 84,
+ 0xe01c7: 84,
+ 0xe01c8: 84,
+ 0xe01c9: 84,
+ 0xe01ca: 84,
+ 0xe01cb: 84,
+ 0xe01cc: 84,
+ 0xe01cd: 84,
+ 0xe01ce: 84,
+ 0xe01cf: 84,
+ 0xe01d0: 84,
+ 0xe01d1: 84,
+ 0xe01d2: 84,
+ 0xe01d3: 84,
+ 0xe01d4: 84,
+ 0xe01d5: 84,
+ 0xe01d6: 84,
+ 0xe01d7: 84,
+ 0xe01d8: 84,
+ 0xe01d9: 84,
+ 0xe01da: 84,
+ 0xe01db: 84,
+ 0xe01dc: 84,
+ 0xe01dd: 84,
+ 0xe01de: 84,
+ 0xe01df: 84,
+ 0xe01e0: 84,
+ 0xe01e1: 84,
+ 0xe01e2: 84,
+ 0xe01e3: 84,
+ 0xe01e4: 84,
+ 0xe01e5: 84,
+ 0xe01e6: 84,
+ 0xe01e7: 84,
+ 0xe01e8: 84,
+ 0xe01e9: 84,
+ 0xe01ea: 84,
+ 0xe01eb: 84,
+ 0xe01ec: 84,
+ 0xe01ed: 84,
+ 0xe01ee: 84,
+ 0xe01ef: 84,
+}
+codepoint_classes = {
+ 'PVALID': (
+ 0x2d0000002e,
+ 0x300000003a,
+ 0x610000007b,
+ 0xdf000000f7,
+ 0xf800000100,
+ 0x10100000102,
+ 0x10300000104,
+ 0x10500000106,
+ 0x10700000108,
+ 0x1090000010a,
+ 0x10b0000010c,
+ 0x10d0000010e,
+ 0x10f00000110,
+ 0x11100000112,
+ 0x11300000114,
+ 0x11500000116,
+ 0x11700000118,
+ 0x1190000011a,
+ 0x11b0000011c,
+ 0x11d0000011e,
+ 0x11f00000120,
+ 0x12100000122,
+ 0x12300000124,
+ 0x12500000126,
+ 0x12700000128,
+ 0x1290000012a,
+ 0x12b0000012c,
+ 0x12d0000012e,
+ 0x12f00000130,
+ 0x13100000132,
+ 0x13500000136,
+ 0x13700000139,
+ 0x13a0000013b,
+ 0x13c0000013d,
+ 0x13e0000013f,
+ 0x14200000143,
+ 0x14400000145,
+ 0x14600000147,
+ 0x14800000149,
+ 0x14b0000014c,
+ 0x14d0000014e,
+ 0x14f00000150,
+ 0x15100000152,
+ 0x15300000154,
+ 0x15500000156,
+ 0x15700000158,
+ 0x1590000015a,
+ 0x15b0000015c,
+ 0x15d0000015e,
+ 0x15f00000160,
+ 0x16100000162,
+ 0x16300000164,
+ 0x16500000166,
+ 0x16700000168,
+ 0x1690000016a,
+ 0x16b0000016c,
+ 0x16d0000016e,
+ 0x16f00000170,
+ 0x17100000172,
+ 0x17300000174,
+ 0x17500000176,
+ 0x17700000178,
+ 0x17a0000017b,
+ 0x17c0000017d,
+ 0x17e0000017f,
+ 0x18000000181,
+ 0x18300000184,
+ 0x18500000186,
+ 0x18800000189,
+ 0x18c0000018e,
+ 0x19200000193,
+ 0x19500000196,
+ 0x1990000019c,
+ 0x19e0000019f,
+ 0x1a1000001a2,
+ 0x1a3000001a4,
+ 0x1a5000001a6,
+ 0x1a8000001a9,
+ 0x1aa000001ac,
+ 0x1ad000001ae,
+ 0x1b0000001b1,
+ 0x1b4000001b5,
+ 0x1b6000001b7,
+ 0x1b9000001bc,
+ 0x1bd000001c4,
+ 0x1ce000001cf,
+ 0x1d0000001d1,
+ 0x1d2000001d3,
+ 0x1d4000001d5,
+ 0x1d6000001d7,
+ 0x1d8000001d9,
+ 0x1da000001db,
+ 0x1dc000001de,
+ 0x1df000001e0,
+ 0x1e1000001e2,
+ 0x1e3000001e4,
+ 0x1e5000001e6,
+ 0x1e7000001e8,
+ 0x1e9000001ea,
+ 0x1eb000001ec,
+ 0x1ed000001ee,
+ 0x1ef000001f1,
+ 0x1f5000001f6,
+ 0x1f9000001fa,
+ 0x1fb000001fc,
+ 0x1fd000001fe,
+ 0x1ff00000200,
+ 0x20100000202,
+ 0x20300000204,
+ 0x20500000206,
+ 0x20700000208,
+ 0x2090000020a,
+ 0x20b0000020c,
+ 0x20d0000020e,
+ 0x20f00000210,
+ 0x21100000212,
+ 0x21300000214,
+ 0x21500000216,
+ 0x21700000218,
+ 0x2190000021a,
+ 0x21b0000021c,
+ 0x21d0000021e,
+ 0x21f00000220,
+ 0x22100000222,
+ 0x22300000224,
+ 0x22500000226,
+ 0x22700000228,
+ 0x2290000022a,
+ 0x22b0000022c,
+ 0x22d0000022e,
+ 0x22f00000230,
+ 0x23100000232,
+ 0x2330000023a,
+ 0x23c0000023d,
+ 0x23f00000241,
+ 0x24200000243,
+ 0x24700000248,
+ 0x2490000024a,
+ 0x24b0000024c,
+ 0x24d0000024e,
+ 0x24f000002b0,
+ 0x2b9000002c2,
+ 0x2c6000002d2,
+ 0x2ec000002ed,
+ 0x2ee000002ef,
+ 0x30000000340,
+ 0x34200000343,
+ 0x3460000034f,
+ 0x35000000370,
+ 0x37100000372,
+ 0x37300000374,
+ 0x37700000378,
+ 0x37b0000037e,
+ 0x39000000391,
+ 0x3ac000003cf,
+ 0x3d7000003d8,
+ 0x3d9000003da,
+ 0x3db000003dc,
+ 0x3dd000003de,
+ 0x3df000003e0,
+ 0x3e1000003e2,
+ 0x3e3000003e4,
+ 0x3e5000003e6,
+ 0x3e7000003e8,
+ 0x3e9000003ea,
+ 0x3eb000003ec,
+ 0x3ed000003ee,
+ 0x3ef000003f0,
+ 0x3f3000003f4,
+ 0x3f8000003f9,
+ 0x3fb000003fd,
+ 0x43000000460,
+ 0x46100000462,
+ 0x46300000464,
+ 0x46500000466,
+ 0x46700000468,
+ 0x4690000046a,
+ 0x46b0000046c,
+ 0x46d0000046e,
+ 0x46f00000470,
+ 0x47100000472,
+ 0x47300000474,
+ 0x47500000476,
+ 0x47700000478,
+ 0x4790000047a,
+ 0x47b0000047c,
+ 0x47d0000047e,
+ 0x47f00000480,
+ 0x48100000482,
+ 0x48300000488,
+ 0x48b0000048c,
+ 0x48d0000048e,
+ 0x48f00000490,
+ 0x49100000492,
+ 0x49300000494,
+ 0x49500000496,
+ 0x49700000498,
+ 0x4990000049a,
+ 0x49b0000049c,
+ 0x49d0000049e,
+ 0x49f000004a0,
+ 0x4a1000004a2,
+ 0x4a3000004a4,
+ 0x4a5000004a6,
+ 0x4a7000004a8,
+ 0x4a9000004aa,
+ 0x4ab000004ac,
+ 0x4ad000004ae,
+ 0x4af000004b0,
+ 0x4b1000004b2,
+ 0x4b3000004b4,
+ 0x4b5000004b6,
+ 0x4b7000004b8,
+ 0x4b9000004ba,
+ 0x4bb000004bc,
+ 0x4bd000004be,
+ 0x4bf000004c0,
+ 0x4c2000004c3,
+ 0x4c4000004c5,
+ 0x4c6000004c7,
+ 0x4c8000004c9,
+ 0x4ca000004cb,
+ 0x4cc000004cd,
+ 0x4ce000004d0,
+ 0x4d1000004d2,
+ 0x4d3000004d4,
+ 0x4d5000004d6,
+ 0x4d7000004d8,
+ 0x4d9000004da,
+ 0x4db000004dc,
+ 0x4dd000004de,
+ 0x4df000004e0,
+ 0x4e1000004e2,
+ 0x4e3000004e4,
+ 0x4e5000004e6,
+ 0x4e7000004e8,
+ 0x4e9000004ea,
+ 0x4eb000004ec,
+ 0x4ed000004ee,
+ 0x4ef000004f0,
+ 0x4f1000004f2,
+ 0x4f3000004f4,
+ 0x4f5000004f6,
+ 0x4f7000004f8,
+ 0x4f9000004fa,
+ 0x4fb000004fc,
+ 0x4fd000004fe,
+ 0x4ff00000500,
+ 0x50100000502,
+ 0x50300000504,
+ 0x50500000506,
+ 0x50700000508,
+ 0x5090000050a,
+ 0x50b0000050c,
+ 0x50d0000050e,
+ 0x50f00000510,
+ 0x51100000512,
+ 0x51300000514,
+ 0x51500000516,
+ 0x51700000518,
+ 0x5190000051a,
+ 0x51b0000051c,
+ 0x51d0000051e,
+ 0x51f00000520,
+ 0x52100000522,
+ 0x52300000524,
+ 0x52500000526,
+ 0x52700000528,
+ 0x5290000052a,
+ 0x52b0000052c,
+ 0x52d0000052e,
+ 0x52f00000530,
+ 0x5590000055a,
+ 0x56000000587,
+ 0x58800000589,
+ 0x591000005be,
+ 0x5bf000005c0,
+ 0x5c1000005c3,
+ 0x5c4000005c6,
+ 0x5c7000005c8,
+ 0x5d0000005eb,
+ 0x5ef000005f3,
+ 0x6100000061b,
+ 0x62000000640,
+ 0x64100000660,
+ 0x66e00000675,
+ 0x679000006d4,
+ 0x6d5000006dd,
+ 0x6df000006e9,
+ 0x6ea000006f0,
+ 0x6fa00000700,
+ 0x7100000074b,
+ 0x74d000007b2,
+ 0x7c0000007f6,
+ 0x7fd000007fe,
+ 0x8000000082e,
+ 0x8400000085c,
+ 0x8600000086b,
+ 0x87000000888,
+ 0x8890000088f,
+ 0x898000008e2,
+ 0x8e300000958,
+ 0x96000000964,
+ 0x96600000970,
+ 0x97100000984,
+ 0x9850000098d,
+ 0x98f00000991,
+ 0x993000009a9,
+ 0x9aa000009b1,
+ 0x9b2000009b3,
+ 0x9b6000009ba,
+ 0x9bc000009c5,
+ 0x9c7000009c9,
+ 0x9cb000009cf,
+ 0x9d7000009d8,
+ 0x9e0000009e4,
+ 0x9e6000009f2,
+ 0x9fc000009fd,
+ 0x9fe000009ff,
+ 0xa0100000a04,
+ 0xa0500000a0b,
+ 0xa0f00000a11,
+ 0xa1300000a29,
+ 0xa2a00000a31,
+ 0xa3200000a33,
+ 0xa3500000a36,
+ 0xa3800000a3a,
+ 0xa3c00000a3d,
+ 0xa3e00000a43,
+ 0xa4700000a49,
+ 0xa4b00000a4e,
+ 0xa5100000a52,
+ 0xa5c00000a5d,
+ 0xa6600000a76,
+ 0xa8100000a84,
+ 0xa8500000a8e,
+ 0xa8f00000a92,
+ 0xa9300000aa9,
+ 0xaaa00000ab1,
+ 0xab200000ab4,
+ 0xab500000aba,
+ 0xabc00000ac6,
+ 0xac700000aca,
+ 0xacb00000ace,
+ 0xad000000ad1,
+ 0xae000000ae4,
+ 0xae600000af0,
+ 0xaf900000b00,
+ 0xb0100000b04,
+ 0xb0500000b0d,
+ 0xb0f00000b11,
+ 0xb1300000b29,
+ 0xb2a00000b31,
+ 0xb3200000b34,
+ 0xb3500000b3a,
+ 0xb3c00000b45,
+ 0xb4700000b49,
+ 0xb4b00000b4e,
+ 0xb5500000b58,
+ 0xb5f00000b64,
+ 0xb6600000b70,
+ 0xb7100000b72,
+ 0xb8200000b84,
+ 0xb8500000b8b,
+ 0xb8e00000b91,
+ 0xb9200000b96,
+ 0xb9900000b9b,
+ 0xb9c00000b9d,
+ 0xb9e00000ba0,
+ 0xba300000ba5,
+ 0xba800000bab,
+ 0xbae00000bba,
+ 0xbbe00000bc3,
+ 0xbc600000bc9,
+ 0xbca00000bce,
+ 0xbd000000bd1,
+ 0xbd700000bd8,
+ 0xbe600000bf0,
+ 0xc0000000c0d,
+ 0xc0e00000c11,
+ 0xc1200000c29,
+ 0xc2a00000c3a,
+ 0xc3c00000c45,
+ 0xc4600000c49,
+ 0xc4a00000c4e,
+ 0xc5500000c57,
+ 0xc5800000c5b,
+ 0xc5d00000c5e,
+ 0xc6000000c64,
+ 0xc6600000c70,
+ 0xc8000000c84,
+ 0xc8500000c8d,
+ 0xc8e00000c91,
+ 0xc9200000ca9,
+ 0xcaa00000cb4,
+ 0xcb500000cba,
+ 0xcbc00000cc5,
+ 0xcc600000cc9,
+ 0xcca00000cce,
+ 0xcd500000cd7,
+ 0xcdd00000cdf,
+ 0xce000000ce4,
+ 0xce600000cf0,
+ 0xcf100000cf4,
+ 0xd0000000d0d,
+ 0xd0e00000d11,
+ 0xd1200000d45,
+ 0xd4600000d49,
+ 0xd4a00000d4f,
+ 0xd5400000d58,
+ 0xd5f00000d64,
+ 0xd6600000d70,
+ 0xd7a00000d80,
+ 0xd8100000d84,
+ 0xd8500000d97,
+ 0xd9a00000db2,
+ 0xdb300000dbc,
+ 0xdbd00000dbe,
+ 0xdc000000dc7,
+ 0xdca00000dcb,
+ 0xdcf00000dd5,
+ 0xdd600000dd7,
+ 0xdd800000de0,
+ 0xde600000df0,
+ 0xdf200000df4,
+ 0xe0100000e33,
+ 0xe3400000e3b,
+ 0xe4000000e4f,
+ 0xe5000000e5a,
+ 0xe8100000e83,
+ 0xe8400000e85,
+ 0xe8600000e8b,
+ 0xe8c00000ea4,
+ 0xea500000ea6,
+ 0xea700000eb3,
+ 0xeb400000ebe,
+ 0xec000000ec5,
+ 0xec600000ec7,
+ 0xec800000ecf,
+ 0xed000000eda,
+ 0xede00000ee0,
+ 0xf0000000f01,
+ 0xf0b00000f0c,
+ 0xf1800000f1a,
+ 0xf2000000f2a,
+ 0xf3500000f36,
+ 0xf3700000f38,
+ 0xf3900000f3a,
+ 0xf3e00000f43,
+ 0xf4400000f48,
+ 0xf4900000f4d,
+ 0xf4e00000f52,
+ 0xf5300000f57,
+ 0xf5800000f5c,
+ 0xf5d00000f69,
+ 0xf6a00000f6d,
+ 0xf7100000f73,
+ 0xf7400000f75,
+ 0xf7a00000f81,
+ 0xf8200000f85,
+ 0xf8600000f93,
+ 0xf9400000f98,
+ 0xf9900000f9d,
+ 0xf9e00000fa2,
+ 0xfa300000fa7,
+ 0xfa800000fac,
+ 0xfad00000fb9,
+ 0xfba00000fbd,
+ 0xfc600000fc7,
+ 0x10000000104a,
+ 0x10500000109e,
+ 0x10d0000010fb,
+ 0x10fd00001100,
+ 0x120000001249,
+ 0x124a0000124e,
+ 0x125000001257,
+ 0x125800001259,
+ 0x125a0000125e,
+ 0x126000001289,
+ 0x128a0000128e,
+ 0x1290000012b1,
+ 0x12b2000012b6,
+ 0x12b8000012bf,
+ 0x12c0000012c1,
+ 0x12c2000012c6,
+ 0x12c8000012d7,
+ 0x12d800001311,
+ 0x131200001316,
+ 0x13180000135b,
+ 0x135d00001360,
+ 0x138000001390,
+ 0x13a0000013f6,
+ 0x14010000166d,
+ 0x166f00001680,
+ 0x16810000169b,
+ 0x16a0000016eb,
+ 0x16f1000016f9,
+ 0x170000001716,
+ 0x171f00001735,
+ 0x174000001754,
+ 0x17600000176d,
+ 0x176e00001771,
+ 0x177200001774,
+ 0x1780000017b4,
+ 0x17b6000017d4,
+ 0x17d7000017d8,
+ 0x17dc000017de,
+ 0x17e0000017ea,
+ 0x18100000181a,
+ 0x182000001879,
+ 0x1880000018ab,
+ 0x18b0000018f6,
+ 0x19000000191f,
+ 0x19200000192c,
+ 0x19300000193c,
+ 0x19460000196e,
+ 0x197000001975,
+ 0x1980000019ac,
+ 0x19b0000019ca,
+ 0x19d0000019da,
+ 0x1a0000001a1c,
+ 0x1a2000001a5f,
+ 0x1a6000001a7d,
+ 0x1a7f00001a8a,
+ 0x1a9000001a9a,
+ 0x1aa700001aa8,
+ 0x1ab000001abe,
+ 0x1abf00001acf,
+ 0x1b0000001b4d,
+ 0x1b5000001b5a,
+ 0x1b6b00001b74,
+ 0x1b8000001bf4,
+ 0x1c0000001c38,
+ 0x1c4000001c4a,
+ 0x1c4d00001c7e,
+ 0x1cd000001cd3,
+ 0x1cd400001cfb,
+ 0x1d0000001d2c,
+ 0x1d2f00001d30,
+ 0x1d3b00001d3c,
+ 0x1d4e00001d4f,
+ 0x1d6b00001d78,
+ 0x1d7900001d9b,
+ 0x1dc000001e00,
+ 0x1e0100001e02,
+ 0x1e0300001e04,
+ 0x1e0500001e06,
+ 0x1e0700001e08,
+ 0x1e0900001e0a,
+ 0x1e0b00001e0c,
+ 0x1e0d00001e0e,
+ 0x1e0f00001e10,
+ 0x1e1100001e12,
+ 0x1e1300001e14,
+ 0x1e1500001e16,
+ 0x1e1700001e18,
+ 0x1e1900001e1a,
+ 0x1e1b00001e1c,
+ 0x1e1d00001e1e,
+ 0x1e1f00001e20,
+ 0x1e2100001e22,
+ 0x1e2300001e24,
+ 0x1e2500001e26,
+ 0x1e2700001e28,
+ 0x1e2900001e2a,
+ 0x1e2b00001e2c,
+ 0x1e2d00001e2e,
+ 0x1e2f00001e30,
+ 0x1e3100001e32,
+ 0x1e3300001e34,
+ 0x1e3500001e36,
+ 0x1e3700001e38,
+ 0x1e3900001e3a,
+ 0x1e3b00001e3c,
+ 0x1e3d00001e3e,
+ 0x1e3f00001e40,
+ 0x1e4100001e42,
+ 0x1e4300001e44,
+ 0x1e4500001e46,
+ 0x1e4700001e48,
+ 0x1e4900001e4a,
+ 0x1e4b00001e4c,
+ 0x1e4d00001e4e,
+ 0x1e4f00001e50,
+ 0x1e5100001e52,
+ 0x1e5300001e54,
+ 0x1e5500001e56,
+ 0x1e5700001e58,
+ 0x1e5900001e5a,
+ 0x1e5b00001e5c,
+ 0x1e5d00001e5e,
+ 0x1e5f00001e60,
+ 0x1e6100001e62,
+ 0x1e6300001e64,
+ 0x1e6500001e66,
+ 0x1e6700001e68,
+ 0x1e6900001e6a,
+ 0x1e6b00001e6c,
+ 0x1e6d00001e6e,
+ 0x1e6f00001e70,
+ 0x1e7100001e72,
+ 0x1e7300001e74,
+ 0x1e7500001e76,
+ 0x1e7700001e78,
+ 0x1e7900001e7a,
+ 0x1e7b00001e7c,
+ 0x1e7d00001e7e,
+ 0x1e7f00001e80,
+ 0x1e8100001e82,
+ 0x1e8300001e84,
+ 0x1e8500001e86,
+ 0x1e8700001e88,
+ 0x1e8900001e8a,
+ 0x1e8b00001e8c,
+ 0x1e8d00001e8e,
+ 0x1e8f00001e90,
+ 0x1e9100001e92,
+ 0x1e9300001e94,
+ 0x1e9500001e9a,
+ 0x1e9c00001e9e,
+ 0x1e9f00001ea0,
+ 0x1ea100001ea2,
+ 0x1ea300001ea4,
+ 0x1ea500001ea6,
+ 0x1ea700001ea8,
+ 0x1ea900001eaa,
+ 0x1eab00001eac,
+ 0x1ead00001eae,
+ 0x1eaf00001eb0,
+ 0x1eb100001eb2,
+ 0x1eb300001eb4,
+ 0x1eb500001eb6,
+ 0x1eb700001eb8,
+ 0x1eb900001eba,
+ 0x1ebb00001ebc,
+ 0x1ebd00001ebe,
+ 0x1ebf00001ec0,
+ 0x1ec100001ec2,
+ 0x1ec300001ec4,
+ 0x1ec500001ec6,
+ 0x1ec700001ec8,
+ 0x1ec900001eca,
+ 0x1ecb00001ecc,
+ 0x1ecd00001ece,
+ 0x1ecf00001ed0,
+ 0x1ed100001ed2,
+ 0x1ed300001ed4,
+ 0x1ed500001ed6,
+ 0x1ed700001ed8,
+ 0x1ed900001eda,
+ 0x1edb00001edc,
+ 0x1edd00001ede,
+ 0x1edf00001ee0,
+ 0x1ee100001ee2,
+ 0x1ee300001ee4,
+ 0x1ee500001ee6,
+ 0x1ee700001ee8,
+ 0x1ee900001eea,
+ 0x1eeb00001eec,
+ 0x1eed00001eee,
+ 0x1eef00001ef0,
+ 0x1ef100001ef2,
+ 0x1ef300001ef4,
+ 0x1ef500001ef6,
+ 0x1ef700001ef8,
+ 0x1ef900001efa,
+ 0x1efb00001efc,
+ 0x1efd00001efe,
+ 0x1eff00001f08,
+ 0x1f1000001f16,
+ 0x1f2000001f28,
+ 0x1f3000001f38,
+ 0x1f4000001f46,
+ 0x1f5000001f58,
+ 0x1f6000001f68,
+ 0x1f7000001f71,
+ 0x1f7200001f73,
+ 0x1f7400001f75,
+ 0x1f7600001f77,
+ 0x1f7800001f79,
+ 0x1f7a00001f7b,
+ 0x1f7c00001f7d,
+ 0x1fb000001fb2,
+ 0x1fb600001fb7,
+ 0x1fc600001fc7,
+ 0x1fd000001fd3,
+ 0x1fd600001fd8,
+ 0x1fe000001fe3,
+ 0x1fe400001fe8,
+ 0x1ff600001ff7,
+ 0x214e0000214f,
+ 0x218400002185,
+ 0x2c3000002c60,
+ 0x2c6100002c62,
+ 0x2c6500002c67,
+ 0x2c6800002c69,
+ 0x2c6a00002c6b,
+ 0x2c6c00002c6d,
+ 0x2c7100002c72,
+ 0x2c7300002c75,
+ 0x2c7600002c7c,
+ 0x2c8100002c82,
+ 0x2c8300002c84,
+ 0x2c8500002c86,
+ 0x2c8700002c88,
+ 0x2c8900002c8a,
+ 0x2c8b00002c8c,
+ 0x2c8d00002c8e,
+ 0x2c8f00002c90,
+ 0x2c9100002c92,
+ 0x2c9300002c94,
+ 0x2c9500002c96,
+ 0x2c9700002c98,
+ 0x2c9900002c9a,
+ 0x2c9b00002c9c,
+ 0x2c9d00002c9e,
+ 0x2c9f00002ca0,
+ 0x2ca100002ca2,
+ 0x2ca300002ca4,
+ 0x2ca500002ca6,
+ 0x2ca700002ca8,
+ 0x2ca900002caa,
+ 0x2cab00002cac,
+ 0x2cad00002cae,
+ 0x2caf00002cb0,
+ 0x2cb100002cb2,
+ 0x2cb300002cb4,
+ 0x2cb500002cb6,
+ 0x2cb700002cb8,
+ 0x2cb900002cba,
+ 0x2cbb00002cbc,
+ 0x2cbd00002cbe,
+ 0x2cbf00002cc0,
+ 0x2cc100002cc2,
+ 0x2cc300002cc4,
+ 0x2cc500002cc6,
+ 0x2cc700002cc8,
+ 0x2cc900002cca,
+ 0x2ccb00002ccc,
+ 0x2ccd00002cce,
+ 0x2ccf00002cd0,
+ 0x2cd100002cd2,
+ 0x2cd300002cd4,
+ 0x2cd500002cd6,
+ 0x2cd700002cd8,
+ 0x2cd900002cda,
+ 0x2cdb00002cdc,
+ 0x2cdd00002cde,
+ 0x2cdf00002ce0,
+ 0x2ce100002ce2,
+ 0x2ce300002ce5,
+ 0x2cec00002ced,
+ 0x2cee00002cf2,
+ 0x2cf300002cf4,
+ 0x2d0000002d26,
+ 0x2d2700002d28,
+ 0x2d2d00002d2e,
+ 0x2d3000002d68,
+ 0x2d7f00002d97,
+ 0x2da000002da7,
+ 0x2da800002daf,
+ 0x2db000002db7,
+ 0x2db800002dbf,
+ 0x2dc000002dc7,
+ 0x2dc800002dcf,
+ 0x2dd000002dd7,
+ 0x2dd800002ddf,
+ 0x2de000002e00,
+ 0x2e2f00002e30,
+ 0x300500003008,
+ 0x302a0000302e,
+ 0x303c0000303d,
+ 0x304100003097,
+ 0x30990000309b,
+ 0x309d0000309f,
+ 0x30a1000030fb,
+ 0x30fc000030ff,
+ 0x310500003130,
+ 0x31a0000031c0,
+ 0x31f000003200,
+ 0x340000004dc0,
+ 0x4e000000a48d,
+ 0xa4d00000a4fe,
+ 0xa5000000a60d,
+ 0xa6100000a62c,
+ 0xa6410000a642,
+ 0xa6430000a644,
+ 0xa6450000a646,
+ 0xa6470000a648,
+ 0xa6490000a64a,
+ 0xa64b0000a64c,
+ 0xa64d0000a64e,
+ 0xa64f0000a650,
+ 0xa6510000a652,
+ 0xa6530000a654,
+ 0xa6550000a656,
+ 0xa6570000a658,
+ 0xa6590000a65a,
+ 0xa65b0000a65c,
+ 0xa65d0000a65e,
+ 0xa65f0000a660,
+ 0xa6610000a662,
+ 0xa6630000a664,
+ 0xa6650000a666,
+ 0xa6670000a668,
+ 0xa6690000a66a,
+ 0xa66b0000a66c,
+ 0xa66d0000a670,
+ 0xa6740000a67e,
+ 0xa67f0000a680,
+ 0xa6810000a682,
+ 0xa6830000a684,
+ 0xa6850000a686,
+ 0xa6870000a688,
+ 0xa6890000a68a,
+ 0xa68b0000a68c,
+ 0xa68d0000a68e,
+ 0xa68f0000a690,
+ 0xa6910000a692,
+ 0xa6930000a694,
+ 0xa6950000a696,
+ 0xa6970000a698,
+ 0xa6990000a69a,
+ 0xa69b0000a69c,
+ 0xa69e0000a6e6,
+ 0xa6f00000a6f2,
+ 0xa7170000a720,
+ 0xa7230000a724,
+ 0xa7250000a726,
+ 0xa7270000a728,
+ 0xa7290000a72a,
+ 0xa72b0000a72c,
+ 0xa72d0000a72e,
+ 0xa72f0000a732,
+ 0xa7330000a734,
+ 0xa7350000a736,
+ 0xa7370000a738,
+ 0xa7390000a73a,
+ 0xa73b0000a73c,
+ 0xa73d0000a73e,
+ 0xa73f0000a740,
+ 0xa7410000a742,
+ 0xa7430000a744,
+ 0xa7450000a746,
+ 0xa7470000a748,
+ 0xa7490000a74a,
+ 0xa74b0000a74c,
+ 0xa74d0000a74e,
+ 0xa74f0000a750,
+ 0xa7510000a752,
+ 0xa7530000a754,
+ 0xa7550000a756,
+ 0xa7570000a758,
+ 0xa7590000a75a,
+ 0xa75b0000a75c,
+ 0xa75d0000a75e,
+ 0xa75f0000a760,
+ 0xa7610000a762,
+ 0xa7630000a764,
+ 0xa7650000a766,
+ 0xa7670000a768,
+ 0xa7690000a76a,
+ 0xa76b0000a76c,
+ 0xa76d0000a76e,
+ 0xa76f0000a770,
+ 0xa7710000a779,
+ 0xa77a0000a77b,
+ 0xa77c0000a77d,
+ 0xa77f0000a780,
+ 0xa7810000a782,
+ 0xa7830000a784,
+ 0xa7850000a786,
+ 0xa7870000a789,
+ 0xa78c0000a78d,
+ 0xa78e0000a790,
+ 0xa7910000a792,
+ 0xa7930000a796,
+ 0xa7970000a798,
+ 0xa7990000a79a,
+ 0xa79b0000a79c,
+ 0xa79d0000a79e,
+ 0xa79f0000a7a0,
+ 0xa7a10000a7a2,
+ 0xa7a30000a7a4,
+ 0xa7a50000a7a6,
+ 0xa7a70000a7a8,
+ 0xa7a90000a7aa,
+ 0xa7af0000a7b0,
+ 0xa7b50000a7b6,
+ 0xa7b70000a7b8,
+ 0xa7b90000a7ba,
+ 0xa7bb0000a7bc,
+ 0xa7bd0000a7be,
+ 0xa7bf0000a7c0,
+ 0xa7c10000a7c2,
+ 0xa7c30000a7c4,
+ 0xa7c80000a7c9,
+ 0xa7ca0000a7cb,
+ 0xa7d10000a7d2,
+ 0xa7d30000a7d4,
+ 0xa7d50000a7d6,
+ 0xa7d70000a7d8,
+ 0xa7d90000a7da,
+ 0xa7f60000a7f8,
+ 0xa7fa0000a828,
+ 0xa82c0000a82d,
+ 0xa8400000a874,
+ 0xa8800000a8c6,
+ 0xa8d00000a8da,
+ 0xa8e00000a8f8,
+ 0xa8fb0000a8fc,
+ 0xa8fd0000a92e,
+ 0xa9300000a954,
+ 0xa9800000a9c1,
+ 0xa9cf0000a9da,
+ 0xa9e00000a9ff,
+ 0xaa000000aa37,
+ 0xaa400000aa4e,
+ 0xaa500000aa5a,
+ 0xaa600000aa77,
+ 0xaa7a0000aac3,
+ 0xaadb0000aade,
+ 0xaae00000aaf0,
+ 0xaaf20000aaf7,
+ 0xab010000ab07,
+ 0xab090000ab0f,
+ 0xab110000ab17,
+ 0xab200000ab27,
+ 0xab280000ab2f,
+ 0xab300000ab5b,
+ 0xab600000ab69,
+ 0xabc00000abeb,
+ 0xabec0000abee,
+ 0xabf00000abfa,
+ 0xac000000d7a4,
+ 0xfa0e0000fa10,
+ 0xfa110000fa12,
+ 0xfa130000fa15,
+ 0xfa1f0000fa20,
+ 0xfa210000fa22,
+ 0xfa230000fa25,
+ 0xfa270000fa2a,
+ 0xfb1e0000fb1f,
+ 0xfe200000fe30,
+ 0xfe730000fe74,
+ 0x100000001000c,
+ 0x1000d00010027,
+ 0x100280001003b,
+ 0x1003c0001003e,
+ 0x1003f0001004e,
+ 0x100500001005e,
+ 0x10080000100fb,
+ 0x101fd000101fe,
+ 0x102800001029d,
+ 0x102a0000102d1,
+ 0x102e0000102e1,
+ 0x1030000010320,
+ 0x1032d00010341,
+ 0x103420001034a,
+ 0x103500001037b,
+ 0x103800001039e,
+ 0x103a0000103c4,
+ 0x103c8000103d0,
+ 0x104280001049e,
+ 0x104a0000104aa,
+ 0x104d8000104fc,
+ 0x1050000010528,
+ 0x1053000010564,
+ 0x10597000105a2,
+ 0x105a3000105b2,
+ 0x105b3000105ba,
+ 0x105bb000105bd,
+ 0x1060000010737,
+ 0x1074000010756,
+ 0x1076000010768,
+ 0x1078000010781,
+ 0x1080000010806,
+ 0x1080800010809,
+ 0x1080a00010836,
+ 0x1083700010839,
+ 0x1083c0001083d,
+ 0x1083f00010856,
+ 0x1086000010877,
+ 0x108800001089f,
+ 0x108e0000108f3,
+ 0x108f4000108f6,
+ 0x1090000010916,
+ 0x109200001093a,
+ 0x10980000109b8,
+ 0x109be000109c0,
+ 0x10a0000010a04,
+ 0x10a0500010a07,
+ 0x10a0c00010a14,
+ 0x10a1500010a18,
+ 0x10a1900010a36,
+ 0x10a3800010a3b,
+ 0x10a3f00010a40,
+ 0x10a6000010a7d,
+ 0x10a8000010a9d,
+ 0x10ac000010ac8,
+ 0x10ac900010ae7,
+ 0x10b0000010b36,
+ 0x10b4000010b56,
+ 0x10b6000010b73,
+ 0x10b8000010b92,
+ 0x10c0000010c49,
+ 0x10cc000010cf3,
+ 0x10d0000010d28,
+ 0x10d3000010d3a,
+ 0x10e8000010eaa,
+ 0x10eab00010ead,
+ 0x10eb000010eb2,
+ 0x10efd00010f1d,
+ 0x10f2700010f28,
+ 0x10f3000010f51,
+ 0x10f7000010f86,
+ 0x10fb000010fc5,
+ 0x10fe000010ff7,
+ 0x1100000011047,
+ 0x1106600011076,
+ 0x1107f000110bb,
+ 0x110c2000110c3,
+ 0x110d0000110e9,
+ 0x110f0000110fa,
+ 0x1110000011135,
+ 0x1113600011140,
+ 0x1114400011148,
+ 0x1115000011174,
+ 0x1117600011177,
+ 0x11180000111c5,
+ 0x111c9000111cd,
+ 0x111ce000111db,
+ 0x111dc000111dd,
+ 0x1120000011212,
+ 0x1121300011238,
+ 0x1123e00011242,
+ 0x1128000011287,
+ 0x1128800011289,
+ 0x1128a0001128e,
+ 0x1128f0001129e,
+ 0x1129f000112a9,
+ 0x112b0000112eb,
+ 0x112f0000112fa,
+ 0x1130000011304,
+ 0x113050001130d,
+ 0x1130f00011311,
+ 0x1131300011329,
+ 0x1132a00011331,
+ 0x1133200011334,
+ 0x113350001133a,
+ 0x1133b00011345,
+ 0x1134700011349,
+ 0x1134b0001134e,
+ 0x1135000011351,
+ 0x1135700011358,
+ 0x1135d00011364,
+ 0x113660001136d,
+ 0x1137000011375,
+ 0x114000001144b,
+ 0x114500001145a,
+ 0x1145e00011462,
+ 0x11480000114c6,
+ 0x114c7000114c8,
+ 0x114d0000114da,
+ 0x11580000115b6,
+ 0x115b8000115c1,
+ 0x115d8000115de,
+ 0x1160000011641,
+ 0x1164400011645,
+ 0x116500001165a,
+ 0x11680000116b9,
+ 0x116c0000116ca,
+ 0x117000001171b,
+ 0x1171d0001172c,
+ 0x117300001173a,
+ 0x1174000011747,
+ 0x118000001183b,
+ 0x118c0000118ea,
+ 0x118ff00011907,
+ 0x119090001190a,
+ 0x1190c00011914,
+ 0x1191500011917,
+ 0x1191800011936,
+ 0x1193700011939,
+ 0x1193b00011944,
+ 0x119500001195a,
+ 0x119a0000119a8,
+ 0x119aa000119d8,
+ 0x119da000119e2,
+ 0x119e3000119e5,
+ 0x11a0000011a3f,
+ 0x11a4700011a48,
+ 0x11a5000011a9a,
+ 0x11a9d00011a9e,
+ 0x11ab000011af9,
+ 0x11c0000011c09,
+ 0x11c0a00011c37,
+ 0x11c3800011c41,
+ 0x11c5000011c5a,
+ 0x11c7200011c90,
+ 0x11c9200011ca8,
+ 0x11ca900011cb7,
+ 0x11d0000011d07,
+ 0x11d0800011d0a,
+ 0x11d0b00011d37,
+ 0x11d3a00011d3b,
+ 0x11d3c00011d3e,
+ 0x11d3f00011d48,
+ 0x11d5000011d5a,
+ 0x11d6000011d66,
+ 0x11d6700011d69,
+ 0x11d6a00011d8f,
+ 0x11d9000011d92,
+ 0x11d9300011d99,
+ 0x11da000011daa,
+ 0x11ee000011ef7,
+ 0x11f0000011f11,
+ 0x11f1200011f3b,
+ 0x11f3e00011f43,
+ 0x11f5000011f5a,
+ 0x11fb000011fb1,
+ 0x120000001239a,
+ 0x1248000012544,
+ 0x12f9000012ff1,
+ 0x1300000013430,
+ 0x1344000013456,
+ 0x1440000014647,
+ 0x1680000016a39,
+ 0x16a4000016a5f,
+ 0x16a6000016a6a,
+ 0x16a7000016abf,
+ 0x16ac000016aca,
+ 0x16ad000016aee,
+ 0x16af000016af5,
+ 0x16b0000016b37,
+ 0x16b4000016b44,
+ 0x16b5000016b5a,
+ 0x16b6300016b78,
+ 0x16b7d00016b90,
+ 0x16e6000016e80,
+ 0x16f0000016f4b,
+ 0x16f4f00016f88,
+ 0x16f8f00016fa0,
+ 0x16fe000016fe2,
+ 0x16fe300016fe5,
+ 0x16ff000016ff2,
+ 0x17000000187f8,
+ 0x1880000018cd6,
+ 0x18d0000018d09,
+ 0x1aff00001aff4,
+ 0x1aff50001affc,
+ 0x1affd0001afff,
+ 0x1b0000001b123,
+ 0x1b1320001b133,
+ 0x1b1500001b153,
+ 0x1b1550001b156,
+ 0x1b1640001b168,
+ 0x1b1700001b2fc,
+ 0x1bc000001bc6b,
+ 0x1bc700001bc7d,
+ 0x1bc800001bc89,
+ 0x1bc900001bc9a,
+ 0x1bc9d0001bc9f,
+ 0x1cf000001cf2e,
+ 0x1cf300001cf47,
+ 0x1da000001da37,
+ 0x1da3b0001da6d,
+ 0x1da750001da76,
+ 0x1da840001da85,
+ 0x1da9b0001daa0,
+ 0x1daa10001dab0,
+ 0x1df000001df1f,
+ 0x1df250001df2b,
+ 0x1e0000001e007,
+ 0x1e0080001e019,
+ 0x1e01b0001e022,
+ 0x1e0230001e025,
+ 0x1e0260001e02b,
+ 0x1e08f0001e090,
+ 0x1e1000001e12d,
+ 0x1e1300001e13e,
+ 0x1e1400001e14a,
+ 0x1e14e0001e14f,
+ 0x1e2900001e2af,
+ 0x1e2c00001e2fa,
+ 0x1e4d00001e4fa,
+ 0x1e7e00001e7e7,
+ 0x1e7e80001e7ec,
+ 0x1e7ed0001e7ef,
+ 0x1e7f00001e7ff,
+ 0x1e8000001e8c5,
+ 0x1e8d00001e8d7,
+ 0x1e9220001e94c,
+ 0x1e9500001e95a,
+ 0x200000002a6e0,
+ 0x2a7000002b73a,
+ 0x2b7400002b81e,
+ 0x2b8200002cea2,
+ 0x2ceb00002ebe1,
+ 0x2ebf00002ee5e,
+ 0x300000003134b,
+ 0x31350000323b0,
+ ),
+ 'CONTEXTJ': (
+ 0x200c0000200e,
+ ),
+ 'CONTEXTO': (
+ 0xb7000000b8,
+ 0x37500000376,
+ 0x5f3000005f5,
+ 0x6600000066a,
+ 0x6f0000006fa,
+ 0x30fb000030fc,
+ ),
+}
diff --git a/.venv/lib/python3.10/site-packages/idna/intranges.py b/.venv/lib/python3.10/site-packages/idna/intranges.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a43b0475347cb50d0d65ada1000a82eeca9e882
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/intranges.py
@@ -0,0 +1,54 @@
+"""
+Given a list of integers, made up of (hopefully) a small number of long runs
+of consecutive integers, compute a representation of the form
+((start1, end1), (start2, end2) ...). Then answer the question "was x present
+in the original list?" in time O(log(# runs)).
+"""
+
+import bisect
+from typing import List, Tuple
+
+def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
+ """Represent a list of integers as a sequence of ranges:
+ ((start_0, end_0), (start_1, end_1), ...), such that the original
+ integers are exactly those x such that start_i <= x < end_i for some i.
+
+ Ranges are encoded as single integers (start << 32 | end), not as tuples.
+ """
+
+ sorted_list = sorted(list_)
+ ranges = []
+ last_write = -1
+ for i in range(len(sorted_list)):
+ if i+1 < len(sorted_list):
+ if sorted_list[i] == sorted_list[i+1]-1:
+ continue
+ current_range = sorted_list[last_write+1:i+1]
+ ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
+ last_write = i
+
+ return tuple(ranges)
+
+def _encode_range(start: int, end: int) -> int:
+ return (start << 32) | end
+
+def _decode_range(r: int) -> Tuple[int, int]:
+ return (r >> 32), (r & ((1 << 32) - 1))
+
+
+def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool:
+ """Determine if `int_` falls into one of the ranges in `ranges`."""
+ tuple_ = _encode_range(int_, 0)
+ pos = bisect.bisect_left(ranges, tuple_)
+ # we could be immediately ahead of a tuple (start, end)
+ # with start < int_ <= end
+ if pos > 0:
+ left, right = _decode_range(ranges[pos-1])
+ if left <= int_ < right:
+ return True
+ # or we could be immediately behind a tuple (int_, end)
+ if pos < len(ranges):
+ left, _ = _decode_range(ranges[pos])
+ if left == int_:
+ return True
+ return False
diff --git a/.venv/lib/python3.10/site-packages/idna/package_data.py b/.venv/lib/python3.10/site-packages/idna/package_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed8111336331554058dfb0a8d5dcbc0defc62dfe
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/package_data.py
@@ -0,0 +1,2 @@
+__version__ = '3.7'
+
diff --git a/.venv/lib/python3.10/site-packages/idna/py.typed b/.venv/lib/python3.10/site-packages/idna/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.venv/lib/python3.10/site-packages/idna/uts46data.py b/.venv/lib/python3.10/site-packages/idna/uts46data.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a1eddbfd7543b5e2d866c320c3aebfc2bd5ba0e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/idna/uts46data.py
@@ -0,0 +1,8598 @@
+# This file is automatically generated by tools/idna-data
+# vim: set fileencoding=utf-8 :
+
+from typing import List, Tuple, Union
+
+
+"""IDNA Mapping Table from UTS46."""
+
+
+__version__ = '15.1.0'
+def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x0, '3'),
+ (0x1, '3'),
+ (0x2, '3'),
+ (0x3, '3'),
+ (0x4, '3'),
+ (0x5, '3'),
+ (0x6, '3'),
+ (0x7, '3'),
+ (0x8, '3'),
+ (0x9, '3'),
+ (0xA, '3'),
+ (0xB, '3'),
+ (0xC, '3'),
+ (0xD, '3'),
+ (0xE, '3'),
+ (0xF, '3'),
+ (0x10, '3'),
+ (0x11, '3'),
+ (0x12, '3'),
+ (0x13, '3'),
+ (0x14, '3'),
+ (0x15, '3'),
+ (0x16, '3'),
+ (0x17, '3'),
+ (0x18, '3'),
+ (0x19, '3'),
+ (0x1A, '3'),
+ (0x1B, '3'),
+ (0x1C, '3'),
+ (0x1D, '3'),
+ (0x1E, '3'),
+ (0x1F, '3'),
+ (0x20, '3'),
+ (0x21, '3'),
+ (0x22, '3'),
+ (0x23, '3'),
+ (0x24, '3'),
+ (0x25, '3'),
+ (0x26, '3'),
+ (0x27, '3'),
+ (0x28, '3'),
+ (0x29, '3'),
+ (0x2A, '3'),
+ (0x2B, '3'),
+ (0x2C, '3'),
+ (0x2D, 'V'),
+ (0x2E, 'V'),
+ (0x2F, '3'),
+ (0x30, 'V'),
+ (0x31, 'V'),
+ (0x32, 'V'),
+ (0x33, 'V'),
+ (0x34, 'V'),
+ (0x35, 'V'),
+ (0x36, 'V'),
+ (0x37, 'V'),
+ (0x38, 'V'),
+ (0x39, 'V'),
+ (0x3A, '3'),
+ (0x3B, '3'),
+ (0x3C, '3'),
+ (0x3D, '3'),
+ (0x3E, '3'),
+ (0x3F, '3'),
+ (0x40, '3'),
+ (0x41, 'M', 'a'),
+ (0x42, 'M', 'b'),
+ (0x43, 'M', 'c'),
+ (0x44, 'M', 'd'),
+ (0x45, 'M', 'e'),
+ (0x46, 'M', 'f'),
+ (0x47, 'M', 'g'),
+ (0x48, 'M', 'h'),
+ (0x49, 'M', 'i'),
+ (0x4A, 'M', 'j'),
+ (0x4B, 'M', 'k'),
+ (0x4C, 'M', 'l'),
+ (0x4D, 'M', 'm'),
+ (0x4E, 'M', 'n'),
+ (0x4F, 'M', 'o'),
+ (0x50, 'M', 'p'),
+ (0x51, 'M', 'q'),
+ (0x52, 'M', 'r'),
+ (0x53, 'M', 's'),
+ (0x54, 'M', 't'),
+ (0x55, 'M', 'u'),
+ (0x56, 'M', 'v'),
+ (0x57, 'M', 'w'),
+ (0x58, 'M', 'x'),
+ (0x59, 'M', 'y'),
+ (0x5A, 'M', 'z'),
+ (0x5B, '3'),
+ (0x5C, '3'),
+ (0x5D, '3'),
+ (0x5E, '3'),
+ (0x5F, '3'),
+ (0x60, '3'),
+ (0x61, 'V'),
+ (0x62, 'V'),
+ (0x63, 'V'),
+ ]
+
+def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x64, 'V'),
+ (0x65, 'V'),
+ (0x66, 'V'),
+ (0x67, 'V'),
+ (0x68, 'V'),
+ (0x69, 'V'),
+ (0x6A, 'V'),
+ (0x6B, 'V'),
+ (0x6C, 'V'),
+ (0x6D, 'V'),
+ (0x6E, 'V'),
+ (0x6F, 'V'),
+ (0x70, 'V'),
+ (0x71, 'V'),
+ (0x72, 'V'),
+ (0x73, 'V'),
+ (0x74, 'V'),
+ (0x75, 'V'),
+ (0x76, 'V'),
+ (0x77, 'V'),
+ (0x78, 'V'),
+ (0x79, 'V'),
+ (0x7A, 'V'),
+ (0x7B, '3'),
+ (0x7C, '3'),
+ (0x7D, '3'),
+ (0x7E, '3'),
+ (0x7F, '3'),
+ (0x80, 'X'),
+ (0x81, 'X'),
+ (0x82, 'X'),
+ (0x83, 'X'),
+ (0x84, 'X'),
+ (0x85, 'X'),
+ (0x86, 'X'),
+ (0x87, 'X'),
+ (0x88, 'X'),
+ (0x89, 'X'),
+ (0x8A, 'X'),
+ (0x8B, 'X'),
+ (0x8C, 'X'),
+ (0x8D, 'X'),
+ (0x8E, 'X'),
+ (0x8F, 'X'),
+ (0x90, 'X'),
+ (0x91, 'X'),
+ (0x92, 'X'),
+ (0x93, 'X'),
+ (0x94, 'X'),
+ (0x95, 'X'),
+ (0x96, 'X'),
+ (0x97, 'X'),
+ (0x98, 'X'),
+ (0x99, 'X'),
+ (0x9A, 'X'),
+ (0x9B, 'X'),
+ (0x9C, 'X'),
+ (0x9D, 'X'),
+ (0x9E, 'X'),
+ (0x9F, 'X'),
+ (0xA0, '3', ' '),
+ (0xA1, 'V'),
+ (0xA2, 'V'),
+ (0xA3, 'V'),
+ (0xA4, 'V'),
+ (0xA5, 'V'),
+ (0xA6, 'V'),
+ (0xA7, 'V'),
+ (0xA8, '3', ' ̈'),
+ (0xA9, 'V'),
+ (0xAA, 'M', 'a'),
+ (0xAB, 'V'),
+ (0xAC, 'V'),
+ (0xAD, 'I'),
+ (0xAE, 'V'),
+ (0xAF, '3', ' ̄'),
+ (0xB0, 'V'),
+ (0xB1, 'V'),
+ (0xB2, 'M', '2'),
+ (0xB3, 'M', '3'),
+ (0xB4, '3', ' ́'),
+ (0xB5, 'M', 'μ'),
+ (0xB6, 'V'),
+ (0xB7, 'V'),
+ (0xB8, '3', ' ̧'),
+ (0xB9, 'M', '1'),
+ (0xBA, 'M', 'o'),
+ (0xBB, 'V'),
+ (0xBC, 'M', '1⁄4'),
+ (0xBD, 'M', '1⁄2'),
+ (0xBE, 'M', '3⁄4'),
+ (0xBF, 'V'),
+ (0xC0, 'M', 'à'),
+ (0xC1, 'M', 'á'),
+ (0xC2, 'M', 'â'),
+ (0xC3, 'M', 'ã'),
+ (0xC4, 'M', 'ä'),
+ (0xC5, 'M', 'å'),
+ (0xC6, 'M', 'æ'),
+ (0xC7, 'M', 'ç'),
+ ]
+
+def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC8, 'M', 'è'),
+ (0xC9, 'M', 'é'),
+ (0xCA, 'M', 'ê'),
+ (0xCB, 'M', 'ë'),
+ (0xCC, 'M', 'ì'),
+ (0xCD, 'M', 'í'),
+ (0xCE, 'M', 'î'),
+ (0xCF, 'M', 'ï'),
+ (0xD0, 'M', 'ð'),
+ (0xD1, 'M', 'ñ'),
+ (0xD2, 'M', 'ò'),
+ (0xD3, 'M', 'ó'),
+ (0xD4, 'M', 'ô'),
+ (0xD5, 'M', 'õ'),
+ (0xD6, 'M', 'ö'),
+ (0xD7, 'V'),
+ (0xD8, 'M', 'ø'),
+ (0xD9, 'M', 'ù'),
+ (0xDA, 'M', 'ú'),
+ (0xDB, 'M', 'û'),
+ (0xDC, 'M', 'ü'),
+ (0xDD, 'M', 'ý'),
+ (0xDE, 'M', 'þ'),
+ (0xDF, 'D', 'ss'),
+ (0xE0, 'V'),
+ (0xE1, 'V'),
+ (0xE2, 'V'),
+ (0xE3, 'V'),
+ (0xE4, 'V'),
+ (0xE5, 'V'),
+ (0xE6, 'V'),
+ (0xE7, 'V'),
+ (0xE8, 'V'),
+ (0xE9, 'V'),
+ (0xEA, 'V'),
+ (0xEB, 'V'),
+ (0xEC, 'V'),
+ (0xED, 'V'),
+ (0xEE, 'V'),
+ (0xEF, 'V'),
+ (0xF0, 'V'),
+ (0xF1, 'V'),
+ (0xF2, 'V'),
+ (0xF3, 'V'),
+ (0xF4, 'V'),
+ (0xF5, 'V'),
+ (0xF6, 'V'),
+ (0xF7, 'V'),
+ (0xF8, 'V'),
+ (0xF9, 'V'),
+ (0xFA, 'V'),
+ (0xFB, 'V'),
+ (0xFC, 'V'),
+ (0xFD, 'V'),
+ (0xFE, 'V'),
+ (0xFF, 'V'),
+ (0x100, 'M', 'ā'),
+ (0x101, 'V'),
+ (0x102, 'M', 'ă'),
+ (0x103, 'V'),
+ (0x104, 'M', 'ą'),
+ (0x105, 'V'),
+ (0x106, 'M', 'ć'),
+ (0x107, 'V'),
+ (0x108, 'M', 'ĉ'),
+ (0x109, 'V'),
+ (0x10A, 'M', 'ċ'),
+ (0x10B, 'V'),
+ (0x10C, 'M', 'č'),
+ (0x10D, 'V'),
+ (0x10E, 'M', 'ď'),
+ (0x10F, 'V'),
+ (0x110, 'M', 'đ'),
+ (0x111, 'V'),
+ (0x112, 'M', 'ē'),
+ (0x113, 'V'),
+ (0x114, 'M', 'ĕ'),
+ (0x115, 'V'),
+ (0x116, 'M', 'ė'),
+ (0x117, 'V'),
+ (0x118, 'M', 'ę'),
+ (0x119, 'V'),
+ (0x11A, 'M', 'ě'),
+ (0x11B, 'V'),
+ (0x11C, 'M', 'ĝ'),
+ (0x11D, 'V'),
+ (0x11E, 'M', 'ğ'),
+ (0x11F, 'V'),
+ (0x120, 'M', 'ġ'),
+ (0x121, 'V'),
+ (0x122, 'M', 'ģ'),
+ (0x123, 'V'),
+ (0x124, 'M', 'ĥ'),
+ (0x125, 'V'),
+ (0x126, 'M', 'ħ'),
+ (0x127, 'V'),
+ (0x128, 'M', 'ĩ'),
+ (0x129, 'V'),
+ (0x12A, 'M', 'ī'),
+ (0x12B, 'V'),
+ ]
+
+def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x12C, 'M', 'ĭ'),
+ (0x12D, 'V'),
+ (0x12E, 'M', 'į'),
+ (0x12F, 'V'),
+ (0x130, 'M', 'i̇'),
+ (0x131, 'V'),
+ (0x132, 'M', 'ij'),
+ (0x134, 'M', 'ĵ'),
+ (0x135, 'V'),
+ (0x136, 'M', 'ķ'),
+ (0x137, 'V'),
+ (0x139, 'M', 'ĺ'),
+ (0x13A, 'V'),
+ (0x13B, 'M', 'ļ'),
+ (0x13C, 'V'),
+ (0x13D, 'M', 'ľ'),
+ (0x13E, 'V'),
+ (0x13F, 'M', 'l·'),
+ (0x141, 'M', 'ł'),
+ (0x142, 'V'),
+ (0x143, 'M', 'ń'),
+ (0x144, 'V'),
+ (0x145, 'M', 'ņ'),
+ (0x146, 'V'),
+ (0x147, 'M', 'ň'),
+ (0x148, 'V'),
+ (0x149, 'M', 'ʼn'),
+ (0x14A, 'M', 'ŋ'),
+ (0x14B, 'V'),
+ (0x14C, 'M', 'ō'),
+ (0x14D, 'V'),
+ (0x14E, 'M', 'ŏ'),
+ (0x14F, 'V'),
+ (0x150, 'M', 'ő'),
+ (0x151, 'V'),
+ (0x152, 'M', 'œ'),
+ (0x153, 'V'),
+ (0x154, 'M', 'ŕ'),
+ (0x155, 'V'),
+ (0x156, 'M', 'ŗ'),
+ (0x157, 'V'),
+ (0x158, 'M', 'ř'),
+ (0x159, 'V'),
+ (0x15A, 'M', 'ś'),
+ (0x15B, 'V'),
+ (0x15C, 'M', 'ŝ'),
+ (0x15D, 'V'),
+ (0x15E, 'M', 'ş'),
+ (0x15F, 'V'),
+ (0x160, 'M', 'š'),
+ (0x161, 'V'),
+ (0x162, 'M', 'ţ'),
+ (0x163, 'V'),
+ (0x164, 'M', 'ť'),
+ (0x165, 'V'),
+ (0x166, 'M', 'ŧ'),
+ (0x167, 'V'),
+ (0x168, 'M', 'ũ'),
+ (0x169, 'V'),
+ (0x16A, 'M', 'ū'),
+ (0x16B, 'V'),
+ (0x16C, 'M', 'ŭ'),
+ (0x16D, 'V'),
+ (0x16E, 'M', 'ů'),
+ (0x16F, 'V'),
+ (0x170, 'M', 'ű'),
+ (0x171, 'V'),
+ (0x172, 'M', 'ų'),
+ (0x173, 'V'),
+ (0x174, 'M', 'ŵ'),
+ (0x175, 'V'),
+ (0x176, 'M', 'ŷ'),
+ (0x177, 'V'),
+ (0x178, 'M', 'ÿ'),
+ (0x179, 'M', 'ź'),
+ (0x17A, 'V'),
+ (0x17B, 'M', 'ż'),
+ (0x17C, 'V'),
+ (0x17D, 'M', 'ž'),
+ (0x17E, 'V'),
+ (0x17F, 'M', 's'),
+ (0x180, 'V'),
+ (0x181, 'M', 'ɓ'),
+ (0x182, 'M', 'ƃ'),
+ (0x183, 'V'),
+ (0x184, 'M', 'ƅ'),
+ (0x185, 'V'),
+ (0x186, 'M', 'ɔ'),
+ (0x187, 'M', 'ƈ'),
+ (0x188, 'V'),
+ (0x189, 'M', 'ɖ'),
+ (0x18A, 'M', 'ɗ'),
+ (0x18B, 'M', 'ƌ'),
+ (0x18C, 'V'),
+ (0x18E, 'M', 'ǝ'),
+ (0x18F, 'M', 'ə'),
+ (0x190, 'M', 'ɛ'),
+ (0x191, 'M', 'ƒ'),
+ (0x192, 'V'),
+ (0x193, 'M', 'ɠ'),
+ ]
+
+def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x194, 'M', 'ɣ'),
+ (0x195, 'V'),
+ (0x196, 'M', 'ɩ'),
+ (0x197, 'M', 'ɨ'),
+ (0x198, 'M', 'ƙ'),
+ (0x199, 'V'),
+ (0x19C, 'M', 'ɯ'),
+ (0x19D, 'M', 'ɲ'),
+ (0x19E, 'V'),
+ (0x19F, 'M', 'ɵ'),
+ (0x1A0, 'M', 'ơ'),
+ (0x1A1, 'V'),
+ (0x1A2, 'M', 'ƣ'),
+ (0x1A3, 'V'),
+ (0x1A4, 'M', 'ƥ'),
+ (0x1A5, 'V'),
+ (0x1A6, 'M', 'ʀ'),
+ (0x1A7, 'M', 'ƨ'),
+ (0x1A8, 'V'),
+ (0x1A9, 'M', 'ʃ'),
+ (0x1AA, 'V'),
+ (0x1AC, 'M', 'ƭ'),
+ (0x1AD, 'V'),
+ (0x1AE, 'M', 'ʈ'),
+ (0x1AF, 'M', 'ư'),
+ (0x1B0, 'V'),
+ (0x1B1, 'M', 'ʊ'),
+ (0x1B2, 'M', 'ʋ'),
+ (0x1B3, 'M', 'ƴ'),
+ (0x1B4, 'V'),
+ (0x1B5, 'M', 'ƶ'),
+ (0x1B6, 'V'),
+ (0x1B7, 'M', 'ʒ'),
+ (0x1B8, 'M', 'ƹ'),
+ (0x1B9, 'V'),
+ (0x1BC, 'M', 'ƽ'),
+ (0x1BD, 'V'),
+ (0x1C4, 'M', 'dž'),
+ (0x1C7, 'M', 'lj'),
+ (0x1CA, 'M', 'nj'),
+ (0x1CD, 'M', 'ǎ'),
+ (0x1CE, 'V'),
+ (0x1CF, 'M', 'ǐ'),
+ (0x1D0, 'V'),
+ (0x1D1, 'M', 'ǒ'),
+ (0x1D2, 'V'),
+ (0x1D3, 'M', 'ǔ'),
+ (0x1D4, 'V'),
+ (0x1D5, 'M', 'ǖ'),
+ (0x1D6, 'V'),
+ (0x1D7, 'M', 'ǘ'),
+ (0x1D8, 'V'),
+ (0x1D9, 'M', 'ǚ'),
+ (0x1DA, 'V'),
+ (0x1DB, 'M', 'ǜ'),
+ (0x1DC, 'V'),
+ (0x1DE, 'M', 'ǟ'),
+ (0x1DF, 'V'),
+ (0x1E0, 'M', 'ǡ'),
+ (0x1E1, 'V'),
+ (0x1E2, 'M', 'ǣ'),
+ (0x1E3, 'V'),
+ (0x1E4, 'M', 'ǥ'),
+ (0x1E5, 'V'),
+ (0x1E6, 'M', 'ǧ'),
+ (0x1E7, 'V'),
+ (0x1E8, 'M', 'ǩ'),
+ (0x1E9, 'V'),
+ (0x1EA, 'M', 'ǫ'),
+ (0x1EB, 'V'),
+ (0x1EC, 'M', 'ǭ'),
+ (0x1ED, 'V'),
+ (0x1EE, 'M', 'ǯ'),
+ (0x1EF, 'V'),
+ (0x1F1, 'M', 'dz'),
+ (0x1F4, 'M', 'ǵ'),
+ (0x1F5, 'V'),
+ (0x1F6, 'M', 'ƕ'),
+ (0x1F7, 'M', 'ƿ'),
+ (0x1F8, 'M', 'ǹ'),
+ (0x1F9, 'V'),
+ (0x1FA, 'M', 'ǻ'),
+ (0x1FB, 'V'),
+ (0x1FC, 'M', 'ǽ'),
+ (0x1FD, 'V'),
+ (0x1FE, 'M', 'ǿ'),
+ (0x1FF, 'V'),
+ (0x200, 'M', 'ȁ'),
+ (0x201, 'V'),
+ (0x202, 'M', 'ȃ'),
+ (0x203, 'V'),
+ (0x204, 'M', 'ȅ'),
+ (0x205, 'V'),
+ (0x206, 'M', 'ȇ'),
+ (0x207, 'V'),
+ (0x208, 'M', 'ȉ'),
+ (0x209, 'V'),
+ (0x20A, 'M', 'ȋ'),
+ (0x20B, 'V'),
+ (0x20C, 'M', 'ȍ'),
+ ]
+
+def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x20D, 'V'),
+ (0x20E, 'M', 'ȏ'),
+ (0x20F, 'V'),
+ (0x210, 'M', 'ȑ'),
+ (0x211, 'V'),
+ (0x212, 'M', 'ȓ'),
+ (0x213, 'V'),
+ (0x214, 'M', 'ȕ'),
+ (0x215, 'V'),
+ (0x216, 'M', 'ȗ'),
+ (0x217, 'V'),
+ (0x218, 'M', 'ș'),
+ (0x219, 'V'),
+ (0x21A, 'M', 'ț'),
+ (0x21B, 'V'),
+ (0x21C, 'M', 'ȝ'),
+ (0x21D, 'V'),
+ (0x21E, 'M', 'ȟ'),
+ (0x21F, 'V'),
+ (0x220, 'M', 'ƞ'),
+ (0x221, 'V'),
+ (0x222, 'M', 'ȣ'),
+ (0x223, 'V'),
+ (0x224, 'M', 'ȥ'),
+ (0x225, 'V'),
+ (0x226, 'M', 'ȧ'),
+ (0x227, 'V'),
+ (0x228, 'M', 'ȩ'),
+ (0x229, 'V'),
+ (0x22A, 'M', 'ȫ'),
+ (0x22B, 'V'),
+ (0x22C, 'M', 'ȭ'),
+ (0x22D, 'V'),
+ (0x22E, 'M', 'ȯ'),
+ (0x22F, 'V'),
+ (0x230, 'M', 'ȱ'),
+ (0x231, 'V'),
+ (0x232, 'M', 'ȳ'),
+ (0x233, 'V'),
+ (0x23A, 'M', 'ⱥ'),
+ (0x23B, 'M', 'ȼ'),
+ (0x23C, 'V'),
+ (0x23D, 'M', 'ƚ'),
+ (0x23E, 'M', 'ⱦ'),
+ (0x23F, 'V'),
+ (0x241, 'M', 'ɂ'),
+ (0x242, 'V'),
+ (0x243, 'M', 'ƀ'),
+ (0x244, 'M', 'ʉ'),
+ (0x245, 'M', 'ʌ'),
+ (0x246, 'M', 'ɇ'),
+ (0x247, 'V'),
+ (0x248, 'M', 'ɉ'),
+ (0x249, 'V'),
+ (0x24A, 'M', 'ɋ'),
+ (0x24B, 'V'),
+ (0x24C, 'M', 'ɍ'),
+ (0x24D, 'V'),
+ (0x24E, 'M', 'ɏ'),
+ (0x24F, 'V'),
+ (0x2B0, 'M', 'h'),
+ (0x2B1, 'M', 'ɦ'),
+ (0x2B2, 'M', 'j'),
+ (0x2B3, 'M', 'r'),
+ (0x2B4, 'M', 'ɹ'),
+ (0x2B5, 'M', 'ɻ'),
+ (0x2B6, 'M', 'ʁ'),
+ (0x2B7, 'M', 'w'),
+ (0x2B8, 'M', 'y'),
+ (0x2B9, 'V'),
+ (0x2D8, '3', ' ̆'),
+ (0x2D9, '3', ' ̇'),
+ (0x2DA, '3', ' ̊'),
+ (0x2DB, '3', ' ̨'),
+ (0x2DC, '3', ' ̃'),
+ (0x2DD, '3', ' ̋'),
+ (0x2DE, 'V'),
+ (0x2E0, 'M', 'ɣ'),
+ (0x2E1, 'M', 'l'),
+ (0x2E2, 'M', 's'),
+ (0x2E3, 'M', 'x'),
+ (0x2E4, 'M', 'ʕ'),
+ (0x2E5, 'V'),
+ (0x340, 'M', '̀'),
+ (0x341, 'M', '́'),
+ (0x342, 'V'),
+ (0x343, 'M', '̓'),
+ (0x344, 'M', '̈́'),
+ (0x345, 'M', 'ι'),
+ (0x346, 'V'),
+ (0x34F, 'I'),
+ (0x350, 'V'),
+ (0x370, 'M', 'ͱ'),
+ (0x371, 'V'),
+ (0x372, 'M', 'ͳ'),
+ (0x373, 'V'),
+ (0x374, 'M', 'ʹ'),
+ (0x375, 'V'),
+ (0x376, 'M', 'ͷ'),
+ (0x377, 'V'),
+ ]
+
+def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x378, 'X'),
+ (0x37A, '3', ' ι'),
+ (0x37B, 'V'),
+ (0x37E, '3', ';'),
+ (0x37F, 'M', 'ϳ'),
+ (0x380, 'X'),
+ (0x384, '3', ' ́'),
+ (0x385, '3', ' ̈́'),
+ (0x386, 'M', 'ά'),
+ (0x387, 'M', '·'),
+ (0x388, 'M', 'έ'),
+ (0x389, 'M', 'ή'),
+ (0x38A, 'M', 'ί'),
+ (0x38B, 'X'),
+ (0x38C, 'M', 'ό'),
+ (0x38D, 'X'),
+ (0x38E, 'M', 'ύ'),
+ (0x38F, 'M', 'ώ'),
+ (0x390, 'V'),
+ (0x391, 'M', 'α'),
+ (0x392, 'M', 'β'),
+ (0x393, 'M', 'γ'),
+ (0x394, 'M', 'δ'),
+ (0x395, 'M', 'ε'),
+ (0x396, 'M', 'ζ'),
+ (0x397, 'M', 'η'),
+ (0x398, 'M', 'θ'),
+ (0x399, 'M', 'ι'),
+ (0x39A, 'M', 'κ'),
+ (0x39B, 'M', 'λ'),
+ (0x39C, 'M', 'μ'),
+ (0x39D, 'M', 'ν'),
+ (0x39E, 'M', 'ξ'),
+ (0x39F, 'M', 'ο'),
+ (0x3A0, 'M', 'π'),
+ (0x3A1, 'M', 'ρ'),
+ (0x3A2, 'X'),
+ (0x3A3, 'M', 'σ'),
+ (0x3A4, 'M', 'τ'),
+ (0x3A5, 'M', 'υ'),
+ (0x3A6, 'M', 'φ'),
+ (0x3A7, 'M', 'χ'),
+ (0x3A8, 'M', 'ψ'),
+ (0x3A9, 'M', 'ω'),
+ (0x3AA, 'M', 'ϊ'),
+ (0x3AB, 'M', 'ϋ'),
+ (0x3AC, 'V'),
+ (0x3C2, 'D', 'σ'),
+ (0x3C3, 'V'),
+ (0x3CF, 'M', 'ϗ'),
+ (0x3D0, 'M', 'β'),
+ (0x3D1, 'M', 'θ'),
+ (0x3D2, 'M', 'υ'),
+ (0x3D3, 'M', 'ύ'),
+ (0x3D4, 'M', 'ϋ'),
+ (0x3D5, 'M', 'φ'),
+ (0x3D6, 'M', 'π'),
+ (0x3D7, 'V'),
+ (0x3D8, 'M', 'ϙ'),
+ (0x3D9, 'V'),
+ (0x3DA, 'M', 'ϛ'),
+ (0x3DB, 'V'),
+ (0x3DC, 'M', 'ϝ'),
+ (0x3DD, 'V'),
+ (0x3DE, 'M', 'ϟ'),
+ (0x3DF, 'V'),
+ (0x3E0, 'M', 'ϡ'),
+ (0x3E1, 'V'),
+ (0x3E2, 'M', 'ϣ'),
+ (0x3E3, 'V'),
+ (0x3E4, 'M', 'ϥ'),
+ (0x3E5, 'V'),
+ (0x3E6, 'M', 'ϧ'),
+ (0x3E7, 'V'),
+ (0x3E8, 'M', 'ϩ'),
+ (0x3E9, 'V'),
+ (0x3EA, 'M', 'ϫ'),
+ (0x3EB, 'V'),
+ (0x3EC, 'M', 'ϭ'),
+ (0x3ED, 'V'),
+ (0x3EE, 'M', 'ϯ'),
+ (0x3EF, 'V'),
+ (0x3F0, 'M', 'κ'),
+ (0x3F1, 'M', 'ρ'),
+ (0x3F2, 'M', 'σ'),
+ (0x3F3, 'V'),
+ (0x3F4, 'M', 'θ'),
+ (0x3F5, 'M', 'ε'),
+ (0x3F6, 'V'),
+ (0x3F7, 'M', 'ϸ'),
+ (0x3F8, 'V'),
+ (0x3F9, 'M', 'σ'),
+ (0x3FA, 'M', 'ϻ'),
+ (0x3FB, 'V'),
+ (0x3FD, 'M', 'ͻ'),
+ (0x3FE, 'M', 'ͼ'),
+ (0x3FF, 'M', 'ͽ'),
+ (0x400, 'M', 'ѐ'),
+ (0x401, 'M', 'ё'),
+ (0x402, 'M', 'ђ'),
+ ]
+
+def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x403, 'M', 'ѓ'),
+ (0x404, 'M', 'є'),
+ (0x405, 'M', 'ѕ'),
+ (0x406, 'M', 'і'),
+ (0x407, 'M', 'ї'),
+ (0x408, 'M', 'ј'),
+ (0x409, 'M', 'љ'),
+ (0x40A, 'M', 'њ'),
+ (0x40B, 'M', 'ћ'),
+ (0x40C, 'M', 'ќ'),
+ (0x40D, 'M', 'ѝ'),
+ (0x40E, 'M', 'ў'),
+ (0x40F, 'M', 'џ'),
+ (0x410, 'M', 'а'),
+ (0x411, 'M', 'б'),
+ (0x412, 'M', 'в'),
+ (0x413, 'M', 'г'),
+ (0x414, 'M', 'д'),
+ (0x415, 'M', 'е'),
+ (0x416, 'M', 'ж'),
+ (0x417, 'M', 'з'),
+ (0x418, 'M', 'и'),
+ (0x419, 'M', 'й'),
+ (0x41A, 'M', 'к'),
+ (0x41B, 'M', 'л'),
+ (0x41C, 'M', 'м'),
+ (0x41D, 'M', 'н'),
+ (0x41E, 'M', 'о'),
+ (0x41F, 'M', 'п'),
+ (0x420, 'M', 'р'),
+ (0x421, 'M', 'с'),
+ (0x422, 'M', 'т'),
+ (0x423, 'M', 'у'),
+ (0x424, 'M', 'ф'),
+ (0x425, 'M', 'х'),
+ (0x426, 'M', 'ц'),
+ (0x427, 'M', 'ч'),
+ (0x428, 'M', 'ш'),
+ (0x429, 'M', 'щ'),
+ (0x42A, 'M', 'ъ'),
+ (0x42B, 'M', 'ы'),
+ (0x42C, 'M', 'ь'),
+ (0x42D, 'M', 'э'),
+ (0x42E, 'M', 'ю'),
+ (0x42F, 'M', 'я'),
+ (0x430, 'V'),
+ (0x460, 'M', 'ѡ'),
+ (0x461, 'V'),
+ (0x462, 'M', 'ѣ'),
+ (0x463, 'V'),
+ (0x464, 'M', 'ѥ'),
+ (0x465, 'V'),
+ (0x466, 'M', 'ѧ'),
+ (0x467, 'V'),
+ (0x468, 'M', 'ѩ'),
+ (0x469, 'V'),
+ (0x46A, 'M', 'ѫ'),
+ (0x46B, 'V'),
+ (0x46C, 'M', 'ѭ'),
+ (0x46D, 'V'),
+ (0x46E, 'M', 'ѯ'),
+ (0x46F, 'V'),
+ (0x470, 'M', 'ѱ'),
+ (0x471, 'V'),
+ (0x472, 'M', 'ѳ'),
+ (0x473, 'V'),
+ (0x474, 'M', 'ѵ'),
+ (0x475, 'V'),
+ (0x476, 'M', 'ѷ'),
+ (0x477, 'V'),
+ (0x478, 'M', 'ѹ'),
+ (0x479, 'V'),
+ (0x47A, 'M', 'ѻ'),
+ (0x47B, 'V'),
+ (0x47C, 'M', 'ѽ'),
+ (0x47D, 'V'),
+ (0x47E, 'M', 'ѿ'),
+ (0x47F, 'V'),
+ (0x480, 'M', 'ҁ'),
+ (0x481, 'V'),
+ (0x48A, 'M', 'ҋ'),
+ (0x48B, 'V'),
+ (0x48C, 'M', 'ҍ'),
+ (0x48D, 'V'),
+ (0x48E, 'M', 'ҏ'),
+ (0x48F, 'V'),
+ (0x490, 'M', 'ґ'),
+ (0x491, 'V'),
+ (0x492, 'M', 'ғ'),
+ (0x493, 'V'),
+ (0x494, 'M', 'ҕ'),
+ (0x495, 'V'),
+ (0x496, 'M', 'җ'),
+ (0x497, 'V'),
+ (0x498, 'M', 'ҙ'),
+ (0x499, 'V'),
+ (0x49A, 'M', 'қ'),
+ (0x49B, 'V'),
+ (0x49C, 'M', 'ҝ'),
+ (0x49D, 'V'),
+ ]
+
+def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x49E, 'M', 'ҟ'),
+ (0x49F, 'V'),
+ (0x4A0, 'M', 'ҡ'),
+ (0x4A1, 'V'),
+ (0x4A2, 'M', 'ң'),
+ (0x4A3, 'V'),
+ (0x4A4, 'M', 'ҥ'),
+ (0x4A5, 'V'),
+ (0x4A6, 'M', 'ҧ'),
+ (0x4A7, 'V'),
+ (0x4A8, 'M', 'ҩ'),
+ (0x4A9, 'V'),
+ (0x4AA, 'M', 'ҫ'),
+ (0x4AB, 'V'),
+ (0x4AC, 'M', 'ҭ'),
+ (0x4AD, 'V'),
+ (0x4AE, 'M', 'ү'),
+ (0x4AF, 'V'),
+ (0x4B0, 'M', 'ұ'),
+ (0x4B1, 'V'),
+ (0x4B2, 'M', 'ҳ'),
+ (0x4B3, 'V'),
+ (0x4B4, 'M', 'ҵ'),
+ (0x4B5, 'V'),
+ (0x4B6, 'M', 'ҷ'),
+ (0x4B7, 'V'),
+ (0x4B8, 'M', 'ҹ'),
+ (0x4B9, 'V'),
+ (0x4BA, 'M', 'һ'),
+ (0x4BB, 'V'),
+ (0x4BC, 'M', 'ҽ'),
+ (0x4BD, 'V'),
+ (0x4BE, 'M', 'ҿ'),
+ (0x4BF, 'V'),
+ (0x4C0, 'X'),
+ (0x4C1, 'M', 'ӂ'),
+ (0x4C2, 'V'),
+ (0x4C3, 'M', 'ӄ'),
+ (0x4C4, 'V'),
+ (0x4C5, 'M', 'ӆ'),
+ (0x4C6, 'V'),
+ (0x4C7, 'M', 'ӈ'),
+ (0x4C8, 'V'),
+ (0x4C9, 'M', 'ӊ'),
+ (0x4CA, 'V'),
+ (0x4CB, 'M', 'ӌ'),
+ (0x4CC, 'V'),
+ (0x4CD, 'M', 'ӎ'),
+ (0x4CE, 'V'),
+ (0x4D0, 'M', 'ӑ'),
+ (0x4D1, 'V'),
+ (0x4D2, 'M', 'ӓ'),
+ (0x4D3, 'V'),
+ (0x4D4, 'M', 'ӕ'),
+ (0x4D5, 'V'),
+ (0x4D6, 'M', 'ӗ'),
+ (0x4D7, 'V'),
+ (0x4D8, 'M', 'ә'),
+ (0x4D9, 'V'),
+ (0x4DA, 'M', 'ӛ'),
+ (0x4DB, 'V'),
+ (0x4DC, 'M', 'ӝ'),
+ (0x4DD, 'V'),
+ (0x4DE, 'M', 'ӟ'),
+ (0x4DF, 'V'),
+ (0x4E0, 'M', 'ӡ'),
+ (0x4E1, 'V'),
+ (0x4E2, 'M', 'ӣ'),
+ (0x4E3, 'V'),
+ (0x4E4, 'M', 'ӥ'),
+ (0x4E5, 'V'),
+ (0x4E6, 'M', 'ӧ'),
+ (0x4E7, 'V'),
+ (0x4E8, 'M', 'ө'),
+ (0x4E9, 'V'),
+ (0x4EA, 'M', 'ӫ'),
+ (0x4EB, 'V'),
+ (0x4EC, 'M', 'ӭ'),
+ (0x4ED, 'V'),
+ (0x4EE, 'M', 'ӯ'),
+ (0x4EF, 'V'),
+ (0x4F0, 'M', 'ӱ'),
+ (0x4F1, 'V'),
+ (0x4F2, 'M', 'ӳ'),
+ (0x4F3, 'V'),
+ (0x4F4, 'M', 'ӵ'),
+ (0x4F5, 'V'),
+ (0x4F6, 'M', 'ӷ'),
+ (0x4F7, 'V'),
+ (0x4F8, 'M', 'ӹ'),
+ (0x4F9, 'V'),
+ (0x4FA, 'M', 'ӻ'),
+ (0x4FB, 'V'),
+ (0x4FC, 'M', 'ӽ'),
+ (0x4FD, 'V'),
+ (0x4FE, 'M', 'ӿ'),
+ (0x4FF, 'V'),
+ (0x500, 'M', 'ԁ'),
+ (0x501, 'V'),
+ (0x502, 'M', 'ԃ'),
+ ]
+
+def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x503, 'V'),
+ (0x504, 'M', 'ԅ'),
+ (0x505, 'V'),
+ (0x506, 'M', 'ԇ'),
+ (0x507, 'V'),
+ (0x508, 'M', 'ԉ'),
+ (0x509, 'V'),
+ (0x50A, 'M', 'ԋ'),
+ (0x50B, 'V'),
+ (0x50C, 'M', 'ԍ'),
+ (0x50D, 'V'),
+ (0x50E, 'M', 'ԏ'),
+ (0x50F, 'V'),
+ (0x510, 'M', 'ԑ'),
+ (0x511, 'V'),
+ (0x512, 'M', 'ԓ'),
+ (0x513, 'V'),
+ (0x514, 'M', 'ԕ'),
+ (0x515, 'V'),
+ (0x516, 'M', 'ԗ'),
+ (0x517, 'V'),
+ (0x518, 'M', 'ԙ'),
+ (0x519, 'V'),
+ (0x51A, 'M', 'ԛ'),
+ (0x51B, 'V'),
+ (0x51C, 'M', 'ԝ'),
+ (0x51D, 'V'),
+ (0x51E, 'M', 'ԟ'),
+ (0x51F, 'V'),
+ (0x520, 'M', 'ԡ'),
+ (0x521, 'V'),
+ (0x522, 'M', 'ԣ'),
+ (0x523, 'V'),
+ (0x524, 'M', 'ԥ'),
+ (0x525, 'V'),
+ (0x526, 'M', 'ԧ'),
+ (0x527, 'V'),
+ (0x528, 'M', 'ԩ'),
+ (0x529, 'V'),
+ (0x52A, 'M', 'ԫ'),
+ (0x52B, 'V'),
+ (0x52C, 'M', 'ԭ'),
+ (0x52D, 'V'),
+ (0x52E, 'M', 'ԯ'),
+ (0x52F, 'V'),
+ (0x530, 'X'),
+ (0x531, 'M', 'ա'),
+ (0x532, 'M', 'բ'),
+ (0x533, 'M', 'գ'),
+ (0x534, 'M', 'դ'),
+ (0x535, 'M', 'ե'),
+ (0x536, 'M', 'զ'),
+ (0x537, 'M', 'է'),
+ (0x538, 'M', 'ը'),
+ (0x539, 'M', 'թ'),
+ (0x53A, 'M', 'ժ'),
+ (0x53B, 'M', 'ի'),
+ (0x53C, 'M', 'լ'),
+ (0x53D, 'M', 'խ'),
+ (0x53E, 'M', 'ծ'),
+ (0x53F, 'M', 'կ'),
+ (0x540, 'M', 'հ'),
+ (0x541, 'M', 'ձ'),
+ (0x542, 'M', 'ղ'),
+ (0x543, 'M', 'ճ'),
+ (0x544, 'M', 'մ'),
+ (0x545, 'M', 'յ'),
+ (0x546, 'M', 'ն'),
+ (0x547, 'M', 'շ'),
+ (0x548, 'M', 'ո'),
+ (0x549, 'M', 'չ'),
+ (0x54A, 'M', 'պ'),
+ (0x54B, 'M', 'ջ'),
+ (0x54C, 'M', 'ռ'),
+ (0x54D, 'M', 'ս'),
+ (0x54E, 'M', 'վ'),
+ (0x54F, 'M', 'տ'),
+ (0x550, 'M', 'ր'),
+ (0x551, 'M', 'ց'),
+ (0x552, 'M', 'ւ'),
+ (0x553, 'M', 'փ'),
+ (0x554, 'M', 'ք'),
+ (0x555, 'M', 'օ'),
+ (0x556, 'M', 'ֆ'),
+ (0x557, 'X'),
+ (0x559, 'V'),
+ (0x587, 'M', 'եւ'),
+ (0x588, 'V'),
+ (0x58B, 'X'),
+ (0x58D, 'V'),
+ (0x590, 'X'),
+ (0x591, 'V'),
+ (0x5C8, 'X'),
+ (0x5D0, 'V'),
+ (0x5EB, 'X'),
+ (0x5EF, 'V'),
+ (0x5F5, 'X'),
+ (0x606, 'V'),
+ (0x61C, 'X'),
+ (0x61D, 'V'),
+ ]
+
+def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x675, 'M', 'اٴ'),
+ (0x676, 'M', 'وٴ'),
+ (0x677, 'M', 'ۇٴ'),
+ (0x678, 'M', 'يٴ'),
+ (0x679, 'V'),
+ (0x6DD, 'X'),
+ (0x6DE, 'V'),
+ (0x70E, 'X'),
+ (0x710, 'V'),
+ (0x74B, 'X'),
+ (0x74D, 'V'),
+ (0x7B2, 'X'),
+ (0x7C0, 'V'),
+ (0x7FB, 'X'),
+ (0x7FD, 'V'),
+ (0x82E, 'X'),
+ (0x830, 'V'),
+ (0x83F, 'X'),
+ (0x840, 'V'),
+ (0x85C, 'X'),
+ (0x85E, 'V'),
+ (0x85F, 'X'),
+ (0x860, 'V'),
+ (0x86B, 'X'),
+ (0x870, 'V'),
+ (0x88F, 'X'),
+ (0x898, 'V'),
+ (0x8E2, 'X'),
+ (0x8E3, 'V'),
+ (0x958, 'M', 'क़'),
+ (0x959, 'M', 'ख़'),
+ (0x95A, 'M', 'ग़'),
+ (0x95B, 'M', 'ज़'),
+ (0x95C, 'M', 'ड़'),
+ (0x95D, 'M', 'ढ़'),
+ (0x95E, 'M', 'फ़'),
+ (0x95F, 'M', 'य़'),
+ (0x960, 'V'),
+ (0x984, 'X'),
+ (0x985, 'V'),
+ (0x98D, 'X'),
+ (0x98F, 'V'),
+ (0x991, 'X'),
+ (0x993, 'V'),
+ (0x9A9, 'X'),
+ (0x9AA, 'V'),
+ (0x9B1, 'X'),
+ (0x9B2, 'V'),
+ (0x9B3, 'X'),
+ (0x9B6, 'V'),
+ (0x9BA, 'X'),
+ (0x9BC, 'V'),
+ (0x9C5, 'X'),
+ (0x9C7, 'V'),
+ (0x9C9, 'X'),
+ (0x9CB, 'V'),
+ (0x9CF, 'X'),
+ (0x9D7, 'V'),
+ (0x9D8, 'X'),
+ (0x9DC, 'M', 'ড়'),
+ (0x9DD, 'M', 'ঢ়'),
+ (0x9DE, 'X'),
+ (0x9DF, 'M', 'য়'),
+ (0x9E0, 'V'),
+ (0x9E4, 'X'),
+ (0x9E6, 'V'),
+ (0x9FF, 'X'),
+ (0xA01, 'V'),
+ (0xA04, 'X'),
+ (0xA05, 'V'),
+ (0xA0B, 'X'),
+ (0xA0F, 'V'),
+ (0xA11, 'X'),
+ (0xA13, 'V'),
+ (0xA29, 'X'),
+ (0xA2A, 'V'),
+ (0xA31, 'X'),
+ (0xA32, 'V'),
+ (0xA33, 'M', 'ਲ਼'),
+ (0xA34, 'X'),
+ (0xA35, 'V'),
+ (0xA36, 'M', 'ਸ਼'),
+ (0xA37, 'X'),
+ (0xA38, 'V'),
+ (0xA3A, 'X'),
+ (0xA3C, 'V'),
+ (0xA3D, 'X'),
+ (0xA3E, 'V'),
+ (0xA43, 'X'),
+ (0xA47, 'V'),
+ (0xA49, 'X'),
+ (0xA4B, 'V'),
+ (0xA4E, 'X'),
+ (0xA51, 'V'),
+ (0xA52, 'X'),
+ (0xA59, 'M', 'ਖ਼'),
+ (0xA5A, 'M', 'ਗ਼'),
+ (0xA5B, 'M', 'ਜ਼'),
+ (0xA5C, 'V'),
+ (0xA5D, 'X'),
+ ]
+
+def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA5E, 'M', 'ਫ਼'),
+ (0xA5F, 'X'),
+ (0xA66, 'V'),
+ (0xA77, 'X'),
+ (0xA81, 'V'),
+ (0xA84, 'X'),
+ (0xA85, 'V'),
+ (0xA8E, 'X'),
+ (0xA8F, 'V'),
+ (0xA92, 'X'),
+ (0xA93, 'V'),
+ (0xAA9, 'X'),
+ (0xAAA, 'V'),
+ (0xAB1, 'X'),
+ (0xAB2, 'V'),
+ (0xAB4, 'X'),
+ (0xAB5, 'V'),
+ (0xABA, 'X'),
+ (0xABC, 'V'),
+ (0xAC6, 'X'),
+ (0xAC7, 'V'),
+ (0xACA, 'X'),
+ (0xACB, 'V'),
+ (0xACE, 'X'),
+ (0xAD0, 'V'),
+ (0xAD1, 'X'),
+ (0xAE0, 'V'),
+ (0xAE4, 'X'),
+ (0xAE6, 'V'),
+ (0xAF2, 'X'),
+ (0xAF9, 'V'),
+ (0xB00, 'X'),
+ (0xB01, 'V'),
+ (0xB04, 'X'),
+ (0xB05, 'V'),
+ (0xB0D, 'X'),
+ (0xB0F, 'V'),
+ (0xB11, 'X'),
+ (0xB13, 'V'),
+ (0xB29, 'X'),
+ (0xB2A, 'V'),
+ (0xB31, 'X'),
+ (0xB32, 'V'),
+ (0xB34, 'X'),
+ (0xB35, 'V'),
+ (0xB3A, 'X'),
+ (0xB3C, 'V'),
+ (0xB45, 'X'),
+ (0xB47, 'V'),
+ (0xB49, 'X'),
+ (0xB4B, 'V'),
+ (0xB4E, 'X'),
+ (0xB55, 'V'),
+ (0xB58, 'X'),
+ (0xB5C, 'M', 'ଡ଼'),
+ (0xB5D, 'M', 'ଢ଼'),
+ (0xB5E, 'X'),
+ (0xB5F, 'V'),
+ (0xB64, 'X'),
+ (0xB66, 'V'),
+ (0xB78, 'X'),
+ (0xB82, 'V'),
+ (0xB84, 'X'),
+ (0xB85, 'V'),
+ (0xB8B, 'X'),
+ (0xB8E, 'V'),
+ (0xB91, 'X'),
+ (0xB92, 'V'),
+ (0xB96, 'X'),
+ (0xB99, 'V'),
+ (0xB9B, 'X'),
+ (0xB9C, 'V'),
+ (0xB9D, 'X'),
+ (0xB9E, 'V'),
+ (0xBA0, 'X'),
+ (0xBA3, 'V'),
+ (0xBA5, 'X'),
+ (0xBA8, 'V'),
+ (0xBAB, 'X'),
+ (0xBAE, 'V'),
+ (0xBBA, 'X'),
+ (0xBBE, 'V'),
+ (0xBC3, 'X'),
+ (0xBC6, 'V'),
+ (0xBC9, 'X'),
+ (0xBCA, 'V'),
+ (0xBCE, 'X'),
+ (0xBD0, 'V'),
+ (0xBD1, 'X'),
+ (0xBD7, 'V'),
+ (0xBD8, 'X'),
+ (0xBE6, 'V'),
+ (0xBFB, 'X'),
+ (0xC00, 'V'),
+ (0xC0D, 'X'),
+ (0xC0E, 'V'),
+ (0xC11, 'X'),
+ (0xC12, 'V'),
+ (0xC29, 'X'),
+ (0xC2A, 'V'),
+ ]
+
+def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC3A, 'X'),
+ (0xC3C, 'V'),
+ (0xC45, 'X'),
+ (0xC46, 'V'),
+ (0xC49, 'X'),
+ (0xC4A, 'V'),
+ (0xC4E, 'X'),
+ (0xC55, 'V'),
+ (0xC57, 'X'),
+ (0xC58, 'V'),
+ (0xC5B, 'X'),
+ (0xC5D, 'V'),
+ (0xC5E, 'X'),
+ (0xC60, 'V'),
+ (0xC64, 'X'),
+ (0xC66, 'V'),
+ (0xC70, 'X'),
+ (0xC77, 'V'),
+ (0xC8D, 'X'),
+ (0xC8E, 'V'),
+ (0xC91, 'X'),
+ (0xC92, 'V'),
+ (0xCA9, 'X'),
+ (0xCAA, 'V'),
+ (0xCB4, 'X'),
+ (0xCB5, 'V'),
+ (0xCBA, 'X'),
+ (0xCBC, 'V'),
+ (0xCC5, 'X'),
+ (0xCC6, 'V'),
+ (0xCC9, 'X'),
+ (0xCCA, 'V'),
+ (0xCCE, 'X'),
+ (0xCD5, 'V'),
+ (0xCD7, 'X'),
+ (0xCDD, 'V'),
+ (0xCDF, 'X'),
+ (0xCE0, 'V'),
+ (0xCE4, 'X'),
+ (0xCE6, 'V'),
+ (0xCF0, 'X'),
+ (0xCF1, 'V'),
+ (0xCF4, 'X'),
+ (0xD00, 'V'),
+ (0xD0D, 'X'),
+ (0xD0E, 'V'),
+ (0xD11, 'X'),
+ (0xD12, 'V'),
+ (0xD45, 'X'),
+ (0xD46, 'V'),
+ (0xD49, 'X'),
+ (0xD4A, 'V'),
+ (0xD50, 'X'),
+ (0xD54, 'V'),
+ (0xD64, 'X'),
+ (0xD66, 'V'),
+ (0xD80, 'X'),
+ (0xD81, 'V'),
+ (0xD84, 'X'),
+ (0xD85, 'V'),
+ (0xD97, 'X'),
+ (0xD9A, 'V'),
+ (0xDB2, 'X'),
+ (0xDB3, 'V'),
+ (0xDBC, 'X'),
+ (0xDBD, 'V'),
+ (0xDBE, 'X'),
+ (0xDC0, 'V'),
+ (0xDC7, 'X'),
+ (0xDCA, 'V'),
+ (0xDCB, 'X'),
+ (0xDCF, 'V'),
+ (0xDD5, 'X'),
+ (0xDD6, 'V'),
+ (0xDD7, 'X'),
+ (0xDD8, 'V'),
+ (0xDE0, 'X'),
+ (0xDE6, 'V'),
+ (0xDF0, 'X'),
+ (0xDF2, 'V'),
+ (0xDF5, 'X'),
+ (0xE01, 'V'),
+ (0xE33, 'M', 'ํา'),
+ (0xE34, 'V'),
+ (0xE3B, 'X'),
+ (0xE3F, 'V'),
+ (0xE5C, 'X'),
+ (0xE81, 'V'),
+ (0xE83, 'X'),
+ (0xE84, 'V'),
+ (0xE85, 'X'),
+ (0xE86, 'V'),
+ (0xE8B, 'X'),
+ (0xE8C, 'V'),
+ (0xEA4, 'X'),
+ (0xEA5, 'V'),
+ (0xEA6, 'X'),
+ (0xEA7, 'V'),
+ (0xEB3, 'M', 'ໍາ'),
+ (0xEB4, 'V'),
+ ]
+
+def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xEBE, 'X'),
+ (0xEC0, 'V'),
+ (0xEC5, 'X'),
+ (0xEC6, 'V'),
+ (0xEC7, 'X'),
+ (0xEC8, 'V'),
+ (0xECF, 'X'),
+ (0xED0, 'V'),
+ (0xEDA, 'X'),
+ (0xEDC, 'M', 'ຫນ'),
+ (0xEDD, 'M', 'ຫມ'),
+ (0xEDE, 'V'),
+ (0xEE0, 'X'),
+ (0xF00, 'V'),
+ (0xF0C, 'M', '་'),
+ (0xF0D, 'V'),
+ (0xF43, 'M', 'གྷ'),
+ (0xF44, 'V'),
+ (0xF48, 'X'),
+ (0xF49, 'V'),
+ (0xF4D, 'M', 'ཌྷ'),
+ (0xF4E, 'V'),
+ (0xF52, 'M', 'དྷ'),
+ (0xF53, 'V'),
+ (0xF57, 'M', 'བྷ'),
+ (0xF58, 'V'),
+ (0xF5C, 'M', 'ཛྷ'),
+ (0xF5D, 'V'),
+ (0xF69, 'M', 'ཀྵ'),
+ (0xF6A, 'V'),
+ (0xF6D, 'X'),
+ (0xF71, 'V'),
+ (0xF73, 'M', 'ཱི'),
+ (0xF74, 'V'),
+ (0xF75, 'M', 'ཱུ'),
+ (0xF76, 'M', 'ྲྀ'),
+ (0xF77, 'M', 'ྲཱྀ'),
+ (0xF78, 'M', 'ླྀ'),
+ (0xF79, 'M', 'ླཱྀ'),
+ (0xF7A, 'V'),
+ (0xF81, 'M', 'ཱྀ'),
+ (0xF82, 'V'),
+ (0xF93, 'M', 'ྒྷ'),
+ (0xF94, 'V'),
+ (0xF98, 'X'),
+ (0xF99, 'V'),
+ (0xF9D, 'M', 'ྜྷ'),
+ (0xF9E, 'V'),
+ (0xFA2, 'M', 'ྡྷ'),
+ (0xFA3, 'V'),
+ (0xFA7, 'M', 'ྦྷ'),
+ (0xFA8, 'V'),
+ (0xFAC, 'M', 'ྫྷ'),
+ (0xFAD, 'V'),
+ (0xFB9, 'M', 'ྐྵ'),
+ (0xFBA, 'V'),
+ (0xFBD, 'X'),
+ (0xFBE, 'V'),
+ (0xFCD, 'X'),
+ (0xFCE, 'V'),
+ (0xFDB, 'X'),
+ (0x1000, 'V'),
+ (0x10A0, 'X'),
+ (0x10C7, 'M', 'ⴧ'),
+ (0x10C8, 'X'),
+ (0x10CD, 'M', 'ⴭ'),
+ (0x10CE, 'X'),
+ (0x10D0, 'V'),
+ (0x10FC, 'M', 'ნ'),
+ (0x10FD, 'V'),
+ (0x115F, 'X'),
+ (0x1161, 'V'),
+ (0x1249, 'X'),
+ (0x124A, 'V'),
+ (0x124E, 'X'),
+ (0x1250, 'V'),
+ (0x1257, 'X'),
+ (0x1258, 'V'),
+ (0x1259, 'X'),
+ (0x125A, 'V'),
+ (0x125E, 'X'),
+ (0x1260, 'V'),
+ (0x1289, 'X'),
+ (0x128A, 'V'),
+ (0x128E, 'X'),
+ (0x1290, 'V'),
+ (0x12B1, 'X'),
+ (0x12B2, 'V'),
+ (0x12B6, 'X'),
+ (0x12B8, 'V'),
+ (0x12BF, 'X'),
+ (0x12C0, 'V'),
+ (0x12C1, 'X'),
+ (0x12C2, 'V'),
+ (0x12C6, 'X'),
+ (0x12C8, 'V'),
+ (0x12D7, 'X'),
+ (0x12D8, 'V'),
+ (0x1311, 'X'),
+ (0x1312, 'V'),
+ ]
+
+def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1316, 'X'),
+ (0x1318, 'V'),
+ (0x135B, 'X'),
+ (0x135D, 'V'),
+ (0x137D, 'X'),
+ (0x1380, 'V'),
+ (0x139A, 'X'),
+ (0x13A0, 'V'),
+ (0x13F6, 'X'),
+ (0x13F8, 'M', 'Ᏸ'),
+ (0x13F9, 'M', 'Ᏹ'),
+ (0x13FA, 'M', 'Ᏺ'),
+ (0x13FB, 'M', 'Ᏻ'),
+ (0x13FC, 'M', 'Ᏼ'),
+ (0x13FD, 'M', 'Ᏽ'),
+ (0x13FE, 'X'),
+ (0x1400, 'V'),
+ (0x1680, 'X'),
+ (0x1681, 'V'),
+ (0x169D, 'X'),
+ (0x16A0, 'V'),
+ (0x16F9, 'X'),
+ (0x1700, 'V'),
+ (0x1716, 'X'),
+ (0x171F, 'V'),
+ (0x1737, 'X'),
+ (0x1740, 'V'),
+ (0x1754, 'X'),
+ (0x1760, 'V'),
+ (0x176D, 'X'),
+ (0x176E, 'V'),
+ (0x1771, 'X'),
+ (0x1772, 'V'),
+ (0x1774, 'X'),
+ (0x1780, 'V'),
+ (0x17B4, 'X'),
+ (0x17B6, 'V'),
+ (0x17DE, 'X'),
+ (0x17E0, 'V'),
+ (0x17EA, 'X'),
+ (0x17F0, 'V'),
+ (0x17FA, 'X'),
+ (0x1800, 'V'),
+ (0x1806, 'X'),
+ (0x1807, 'V'),
+ (0x180B, 'I'),
+ (0x180E, 'X'),
+ (0x180F, 'I'),
+ (0x1810, 'V'),
+ (0x181A, 'X'),
+ (0x1820, 'V'),
+ (0x1879, 'X'),
+ (0x1880, 'V'),
+ (0x18AB, 'X'),
+ (0x18B0, 'V'),
+ (0x18F6, 'X'),
+ (0x1900, 'V'),
+ (0x191F, 'X'),
+ (0x1920, 'V'),
+ (0x192C, 'X'),
+ (0x1930, 'V'),
+ (0x193C, 'X'),
+ (0x1940, 'V'),
+ (0x1941, 'X'),
+ (0x1944, 'V'),
+ (0x196E, 'X'),
+ (0x1970, 'V'),
+ (0x1975, 'X'),
+ (0x1980, 'V'),
+ (0x19AC, 'X'),
+ (0x19B0, 'V'),
+ (0x19CA, 'X'),
+ (0x19D0, 'V'),
+ (0x19DB, 'X'),
+ (0x19DE, 'V'),
+ (0x1A1C, 'X'),
+ (0x1A1E, 'V'),
+ (0x1A5F, 'X'),
+ (0x1A60, 'V'),
+ (0x1A7D, 'X'),
+ (0x1A7F, 'V'),
+ (0x1A8A, 'X'),
+ (0x1A90, 'V'),
+ (0x1A9A, 'X'),
+ (0x1AA0, 'V'),
+ (0x1AAE, 'X'),
+ (0x1AB0, 'V'),
+ (0x1ACF, 'X'),
+ (0x1B00, 'V'),
+ (0x1B4D, 'X'),
+ (0x1B50, 'V'),
+ (0x1B7F, 'X'),
+ (0x1B80, 'V'),
+ (0x1BF4, 'X'),
+ (0x1BFC, 'V'),
+ (0x1C38, 'X'),
+ (0x1C3B, 'V'),
+ (0x1C4A, 'X'),
+ (0x1C4D, 'V'),
+ (0x1C80, 'M', 'в'),
+ ]
+
+def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1C81, 'M', 'д'),
+ (0x1C82, 'M', 'о'),
+ (0x1C83, 'M', 'с'),
+ (0x1C84, 'M', 'т'),
+ (0x1C86, 'M', 'ъ'),
+ (0x1C87, 'M', 'ѣ'),
+ (0x1C88, 'M', 'ꙋ'),
+ (0x1C89, 'X'),
+ (0x1C90, 'M', 'ა'),
+ (0x1C91, 'M', 'ბ'),
+ (0x1C92, 'M', 'გ'),
+ (0x1C93, 'M', 'დ'),
+ (0x1C94, 'M', 'ე'),
+ (0x1C95, 'M', 'ვ'),
+ (0x1C96, 'M', 'ზ'),
+ (0x1C97, 'M', 'თ'),
+ (0x1C98, 'M', 'ი'),
+ (0x1C99, 'M', 'კ'),
+ (0x1C9A, 'M', 'ლ'),
+ (0x1C9B, 'M', 'მ'),
+ (0x1C9C, 'M', 'ნ'),
+ (0x1C9D, 'M', 'ო'),
+ (0x1C9E, 'M', 'პ'),
+ (0x1C9F, 'M', 'ჟ'),
+ (0x1CA0, 'M', 'რ'),
+ (0x1CA1, 'M', 'ს'),
+ (0x1CA2, 'M', 'ტ'),
+ (0x1CA3, 'M', 'უ'),
+ (0x1CA4, 'M', 'ფ'),
+ (0x1CA5, 'M', 'ქ'),
+ (0x1CA6, 'M', 'ღ'),
+ (0x1CA7, 'M', 'ყ'),
+ (0x1CA8, 'M', 'შ'),
+ (0x1CA9, 'M', 'ჩ'),
+ (0x1CAA, 'M', 'ც'),
+ (0x1CAB, 'M', 'ძ'),
+ (0x1CAC, 'M', 'წ'),
+ (0x1CAD, 'M', 'ჭ'),
+ (0x1CAE, 'M', 'ხ'),
+ (0x1CAF, 'M', 'ჯ'),
+ (0x1CB0, 'M', 'ჰ'),
+ (0x1CB1, 'M', 'ჱ'),
+ (0x1CB2, 'M', 'ჲ'),
+ (0x1CB3, 'M', 'ჳ'),
+ (0x1CB4, 'M', 'ჴ'),
+ (0x1CB5, 'M', 'ჵ'),
+ (0x1CB6, 'M', 'ჶ'),
+ (0x1CB7, 'M', 'ჷ'),
+ (0x1CB8, 'M', 'ჸ'),
+ (0x1CB9, 'M', 'ჹ'),
+ (0x1CBA, 'M', 'ჺ'),
+ (0x1CBB, 'X'),
+ (0x1CBD, 'M', 'ჽ'),
+ (0x1CBE, 'M', 'ჾ'),
+ (0x1CBF, 'M', 'ჿ'),
+ (0x1CC0, 'V'),
+ (0x1CC8, 'X'),
+ (0x1CD0, 'V'),
+ (0x1CFB, 'X'),
+ (0x1D00, 'V'),
+ (0x1D2C, 'M', 'a'),
+ (0x1D2D, 'M', 'æ'),
+ (0x1D2E, 'M', 'b'),
+ (0x1D2F, 'V'),
+ (0x1D30, 'M', 'd'),
+ (0x1D31, 'M', 'e'),
+ (0x1D32, 'M', 'ǝ'),
+ (0x1D33, 'M', 'g'),
+ (0x1D34, 'M', 'h'),
+ (0x1D35, 'M', 'i'),
+ (0x1D36, 'M', 'j'),
+ (0x1D37, 'M', 'k'),
+ (0x1D38, 'M', 'l'),
+ (0x1D39, 'M', 'm'),
+ (0x1D3A, 'M', 'n'),
+ (0x1D3B, 'V'),
+ (0x1D3C, 'M', 'o'),
+ (0x1D3D, 'M', 'ȣ'),
+ (0x1D3E, 'M', 'p'),
+ (0x1D3F, 'M', 'r'),
+ (0x1D40, 'M', 't'),
+ (0x1D41, 'M', 'u'),
+ (0x1D42, 'M', 'w'),
+ (0x1D43, 'M', 'a'),
+ (0x1D44, 'M', 'ɐ'),
+ (0x1D45, 'M', 'ɑ'),
+ (0x1D46, 'M', 'ᴂ'),
+ (0x1D47, 'M', 'b'),
+ (0x1D48, 'M', 'd'),
+ (0x1D49, 'M', 'e'),
+ (0x1D4A, 'M', 'ə'),
+ (0x1D4B, 'M', 'ɛ'),
+ (0x1D4C, 'M', 'ɜ'),
+ (0x1D4D, 'M', 'g'),
+ (0x1D4E, 'V'),
+ (0x1D4F, 'M', 'k'),
+ (0x1D50, 'M', 'm'),
+ (0x1D51, 'M', 'ŋ'),
+ (0x1D52, 'M', 'o'),
+ (0x1D53, 'M', 'ɔ'),
+ ]
+
+def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D54, 'M', 'ᴖ'),
+ (0x1D55, 'M', 'ᴗ'),
+ (0x1D56, 'M', 'p'),
+ (0x1D57, 'M', 't'),
+ (0x1D58, 'M', 'u'),
+ (0x1D59, 'M', 'ᴝ'),
+ (0x1D5A, 'M', 'ɯ'),
+ (0x1D5B, 'M', 'v'),
+ (0x1D5C, 'M', 'ᴥ'),
+ (0x1D5D, 'M', 'β'),
+ (0x1D5E, 'M', 'γ'),
+ (0x1D5F, 'M', 'δ'),
+ (0x1D60, 'M', 'φ'),
+ (0x1D61, 'M', 'χ'),
+ (0x1D62, 'M', 'i'),
+ (0x1D63, 'M', 'r'),
+ (0x1D64, 'M', 'u'),
+ (0x1D65, 'M', 'v'),
+ (0x1D66, 'M', 'β'),
+ (0x1D67, 'M', 'γ'),
+ (0x1D68, 'M', 'ρ'),
+ (0x1D69, 'M', 'φ'),
+ (0x1D6A, 'M', 'χ'),
+ (0x1D6B, 'V'),
+ (0x1D78, 'M', 'н'),
+ (0x1D79, 'V'),
+ (0x1D9B, 'M', 'ɒ'),
+ (0x1D9C, 'M', 'c'),
+ (0x1D9D, 'M', 'ɕ'),
+ (0x1D9E, 'M', 'ð'),
+ (0x1D9F, 'M', 'ɜ'),
+ (0x1DA0, 'M', 'f'),
+ (0x1DA1, 'M', 'ɟ'),
+ (0x1DA2, 'M', 'ɡ'),
+ (0x1DA3, 'M', 'ɥ'),
+ (0x1DA4, 'M', 'ɨ'),
+ (0x1DA5, 'M', 'ɩ'),
+ (0x1DA6, 'M', 'ɪ'),
+ (0x1DA7, 'M', 'ᵻ'),
+ (0x1DA8, 'M', 'ʝ'),
+ (0x1DA9, 'M', 'ɭ'),
+ (0x1DAA, 'M', 'ᶅ'),
+ (0x1DAB, 'M', 'ʟ'),
+ (0x1DAC, 'M', 'ɱ'),
+ (0x1DAD, 'M', 'ɰ'),
+ (0x1DAE, 'M', 'ɲ'),
+ (0x1DAF, 'M', 'ɳ'),
+ (0x1DB0, 'M', 'ɴ'),
+ (0x1DB1, 'M', 'ɵ'),
+ (0x1DB2, 'M', 'ɸ'),
+ (0x1DB3, 'M', 'ʂ'),
+ (0x1DB4, 'M', 'ʃ'),
+ (0x1DB5, 'M', 'ƫ'),
+ (0x1DB6, 'M', 'ʉ'),
+ (0x1DB7, 'M', 'ʊ'),
+ (0x1DB8, 'M', 'ᴜ'),
+ (0x1DB9, 'M', 'ʋ'),
+ (0x1DBA, 'M', 'ʌ'),
+ (0x1DBB, 'M', 'z'),
+ (0x1DBC, 'M', 'ʐ'),
+ (0x1DBD, 'M', 'ʑ'),
+ (0x1DBE, 'M', 'ʒ'),
+ (0x1DBF, 'M', 'θ'),
+ (0x1DC0, 'V'),
+ (0x1E00, 'M', 'ḁ'),
+ (0x1E01, 'V'),
+ (0x1E02, 'M', 'ḃ'),
+ (0x1E03, 'V'),
+ (0x1E04, 'M', 'ḅ'),
+ (0x1E05, 'V'),
+ (0x1E06, 'M', 'ḇ'),
+ (0x1E07, 'V'),
+ (0x1E08, 'M', 'ḉ'),
+ (0x1E09, 'V'),
+ (0x1E0A, 'M', 'ḋ'),
+ (0x1E0B, 'V'),
+ (0x1E0C, 'M', 'ḍ'),
+ (0x1E0D, 'V'),
+ (0x1E0E, 'M', 'ḏ'),
+ (0x1E0F, 'V'),
+ (0x1E10, 'M', 'ḑ'),
+ (0x1E11, 'V'),
+ (0x1E12, 'M', 'ḓ'),
+ (0x1E13, 'V'),
+ (0x1E14, 'M', 'ḕ'),
+ (0x1E15, 'V'),
+ (0x1E16, 'M', 'ḗ'),
+ (0x1E17, 'V'),
+ (0x1E18, 'M', 'ḙ'),
+ (0x1E19, 'V'),
+ (0x1E1A, 'M', 'ḛ'),
+ (0x1E1B, 'V'),
+ (0x1E1C, 'M', 'ḝ'),
+ (0x1E1D, 'V'),
+ (0x1E1E, 'M', 'ḟ'),
+ (0x1E1F, 'V'),
+ (0x1E20, 'M', 'ḡ'),
+ (0x1E21, 'V'),
+ (0x1E22, 'M', 'ḣ'),
+ (0x1E23, 'V'),
+ ]
+
+def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E24, 'M', 'ḥ'),
+ (0x1E25, 'V'),
+ (0x1E26, 'M', 'ḧ'),
+ (0x1E27, 'V'),
+ (0x1E28, 'M', 'ḩ'),
+ (0x1E29, 'V'),
+ (0x1E2A, 'M', 'ḫ'),
+ (0x1E2B, 'V'),
+ (0x1E2C, 'M', 'ḭ'),
+ (0x1E2D, 'V'),
+ (0x1E2E, 'M', 'ḯ'),
+ (0x1E2F, 'V'),
+ (0x1E30, 'M', 'ḱ'),
+ (0x1E31, 'V'),
+ (0x1E32, 'M', 'ḳ'),
+ (0x1E33, 'V'),
+ (0x1E34, 'M', 'ḵ'),
+ (0x1E35, 'V'),
+ (0x1E36, 'M', 'ḷ'),
+ (0x1E37, 'V'),
+ (0x1E38, 'M', 'ḹ'),
+ (0x1E39, 'V'),
+ (0x1E3A, 'M', 'ḻ'),
+ (0x1E3B, 'V'),
+ (0x1E3C, 'M', 'ḽ'),
+ (0x1E3D, 'V'),
+ (0x1E3E, 'M', 'ḿ'),
+ (0x1E3F, 'V'),
+ (0x1E40, 'M', 'ṁ'),
+ (0x1E41, 'V'),
+ (0x1E42, 'M', 'ṃ'),
+ (0x1E43, 'V'),
+ (0x1E44, 'M', 'ṅ'),
+ (0x1E45, 'V'),
+ (0x1E46, 'M', 'ṇ'),
+ (0x1E47, 'V'),
+ (0x1E48, 'M', 'ṉ'),
+ (0x1E49, 'V'),
+ (0x1E4A, 'M', 'ṋ'),
+ (0x1E4B, 'V'),
+ (0x1E4C, 'M', 'ṍ'),
+ (0x1E4D, 'V'),
+ (0x1E4E, 'M', 'ṏ'),
+ (0x1E4F, 'V'),
+ (0x1E50, 'M', 'ṑ'),
+ (0x1E51, 'V'),
+ (0x1E52, 'M', 'ṓ'),
+ (0x1E53, 'V'),
+ (0x1E54, 'M', 'ṕ'),
+ (0x1E55, 'V'),
+ (0x1E56, 'M', 'ṗ'),
+ (0x1E57, 'V'),
+ (0x1E58, 'M', 'ṙ'),
+ (0x1E59, 'V'),
+ (0x1E5A, 'M', 'ṛ'),
+ (0x1E5B, 'V'),
+ (0x1E5C, 'M', 'ṝ'),
+ (0x1E5D, 'V'),
+ (0x1E5E, 'M', 'ṟ'),
+ (0x1E5F, 'V'),
+ (0x1E60, 'M', 'ṡ'),
+ (0x1E61, 'V'),
+ (0x1E62, 'M', 'ṣ'),
+ (0x1E63, 'V'),
+ (0x1E64, 'M', 'ṥ'),
+ (0x1E65, 'V'),
+ (0x1E66, 'M', 'ṧ'),
+ (0x1E67, 'V'),
+ (0x1E68, 'M', 'ṩ'),
+ (0x1E69, 'V'),
+ (0x1E6A, 'M', 'ṫ'),
+ (0x1E6B, 'V'),
+ (0x1E6C, 'M', 'ṭ'),
+ (0x1E6D, 'V'),
+ (0x1E6E, 'M', 'ṯ'),
+ (0x1E6F, 'V'),
+ (0x1E70, 'M', 'ṱ'),
+ (0x1E71, 'V'),
+ (0x1E72, 'M', 'ṳ'),
+ (0x1E73, 'V'),
+ (0x1E74, 'M', 'ṵ'),
+ (0x1E75, 'V'),
+ (0x1E76, 'M', 'ṷ'),
+ (0x1E77, 'V'),
+ (0x1E78, 'M', 'ṹ'),
+ (0x1E79, 'V'),
+ (0x1E7A, 'M', 'ṻ'),
+ (0x1E7B, 'V'),
+ (0x1E7C, 'M', 'ṽ'),
+ (0x1E7D, 'V'),
+ (0x1E7E, 'M', 'ṿ'),
+ (0x1E7F, 'V'),
+ (0x1E80, 'M', 'ẁ'),
+ (0x1E81, 'V'),
+ (0x1E82, 'M', 'ẃ'),
+ (0x1E83, 'V'),
+ (0x1E84, 'M', 'ẅ'),
+ (0x1E85, 'V'),
+ (0x1E86, 'M', 'ẇ'),
+ (0x1E87, 'V'),
+ ]
+
+def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E88, 'M', 'ẉ'),
+ (0x1E89, 'V'),
+ (0x1E8A, 'M', 'ẋ'),
+ (0x1E8B, 'V'),
+ (0x1E8C, 'M', 'ẍ'),
+ (0x1E8D, 'V'),
+ (0x1E8E, 'M', 'ẏ'),
+ (0x1E8F, 'V'),
+ (0x1E90, 'M', 'ẑ'),
+ (0x1E91, 'V'),
+ (0x1E92, 'M', 'ẓ'),
+ (0x1E93, 'V'),
+ (0x1E94, 'M', 'ẕ'),
+ (0x1E95, 'V'),
+ (0x1E9A, 'M', 'aʾ'),
+ (0x1E9B, 'M', 'ṡ'),
+ (0x1E9C, 'V'),
+ (0x1E9E, 'M', 'ß'),
+ (0x1E9F, 'V'),
+ (0x1EA0, 'M', 'ạ'),
+ (0x1EA1, 'V'),
+ (0x1EA2, 'M', 'ả'),
+ (0x1EA3, 'V'),
+ (0x1EA4, 'M', 'ấ'),
+ (0x1EA5, 'V'),
+ (0x1EA6, 'M', 'ầ'),
+ (0x1EA7, 'V'),
+ (0x1EA8, 'M', 'ẩ'),
+ (0x1EA9, 'V'),
+ (0x1EAA, 'M', 'ẫ'),
+ (0x1EAB, 'V'),
+ (0x1EAC, 'M', 'ậ'),
+ (0x1EAD, 'V'),
+ (0x1EAE, 'M', 'ắ'),
+ (0x1EAF, 'V'),
+ (0x1EB0, 'M', 'ằ'),
+ (0x1EB1, 'V'),
+ (0x1EB2, 'M', 'ẳ'),
+ (0x1EB3, 'V'),
+ (0x1EB4, 'M', 'ẵ'),
+ (0x1EB5, 'V'),
+ (0x1EB6, 'M', 'ặ'),
+ (0x1EB7, 'V'),
+ (0x1EB8, 'M', 'ẹ'),
+ (0x1EB9, 'V'),
+ (0x1EBA, 'M', 'ẻ'),
+ (0x1EBB, 'V'),
+ (0x1EBC, 'M', 'ẽ'),
+ (0x1EBD, 'V'),
+ (0x1EBE, 'M', 'ế'),
+ (0x1EBF, 'V'),
+ (0x1EC0, 'M', 'ề'),
+ (0x1EC1, 'V'),
+ (0x1EC2, 'M', 'ể'),
+ (0x1EC3, 'V'),
+ (0x1EC4, 'M', 'ễ'),
+ (0x1EC5, 'V'),
+ (0x1EC6, 'M', 'ệ'),
+ (0x1EC7, 'V'),
+ (0x1EC8, 'M', 'ỉ'),
+ (0x1EC9, 'V'),
+ (0x1ECA, 'M', 'ị'),
+ (0x1ECB, 'V'),
+ (0x1ECC, 'M', 'ọ'),
+ (0x1ECD, 'V'),
+ (0x1ECE, 'M', 'ỏ'),
+ (0x1ECF, 'V'),
+ (0x1ED0, 'M', 'ố'),
+ (0x1ED1, 'V'),
+ (0x1ED2, 'M', 'ồ'),
+ (0x1ED3, 'V'),
+ (0x1ED4, 'M', 'ổ'),
+ (0x1ED5, 'V'),
+ (0x1ED6, 'M', 'ỗ'),
+ (0x1ED7, 'V'),
+ (0x1ED8, 'M', 'ộ'),
+ (0x1ED9, 'V'),
+ (0x1EDA, 'M', 'ớ'),
+ (0x1EDB, 'V'),
+ (0x1EDC, 'M', 'ờ'),
+ (0x1EDD, 'V'),
+ (0x1EDE, 'M', 'ở'),
+ (0x1EDF, 'V'),
+ (0x1EE0, 'M', 'ỡ'),
+ (0x1EE1, 'V'),
+ (0x1EE2, 'M', 'ợ'),
+ (0x1EE3, 'V'),
+ (0x1EE4, 'M', 'ụ'),
+ (0x1EE5, 'V'),
+ (0x1EE6, 'M', 'ủ'),
+ (0x1EE7, 'V'),
+ (0x1EE8, 'M', 'ứ'),
+ (0x1EE9, 'V'),
+ (0x1EEA, 'M', 'ừ'),
+ (0x1EEB, 'V'),
+ (0x1EEC, 'M', 'ử'),
+ (0x1EED, 'V'),
+ (0x1EEE, 'M', 'ữ'),
+ (0x1EEF, 'V'),
+ (0x1EF0, 'M', 'ự'),
+ ]
+
+def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EF1, 'V'),
+ (0x1EF2, 'M', 'ỳ'),
+ (0x1EF3, 'V'),
+ (0x1EF4, 'M', 'ỵ'),
+ (0x1EF5, 'V'),
+ (0x1EF6, 'M', 'ỷ'),
+ (0x1EF7, 'V'),
+ (0x1EF8, 'M', 'ỹ'),
+ (0x1EF9, 'V'),
+ (0x1EFA, 'M', 'ỻ'),
+ (0x1EFB, 'V'),
+ (0x1EFC, 'M', 'ỽ'),
+ (0x1EFD, 'V'),
+ (0x1EFE, 'M', 'ỿ'),
+ (0x1EFF, 'V'),
+ (0x1F08, 'M', 'ἀ'),
+ (0x1F09, 'M', 'ἁ'),
+ (0x1F0A, 'M', 'ἂ'),
+ (0x1F0B, 'M', 'ἃ'),
+ (0x1F0C, 'M', 'ἄ'),
+ (0x1F0D, 'M', 'ἅ'),
+ (0x1F0E, 'M', 'ἆ'),
+ (0x1F0F, 'M', 'ἇ'),
+ (0x1F10, 'V'),
+ (0x1F16, 'X'),
+ (0x1F18, 'M', 'ἐ'),
+ (0x1F19, 'M', 'ἑ'),
+ (0x1F1A, 'M', 'ἒ'),
+ (0x1F1B, 'M', 'ἓ'),
+ (0x1F1C, 'M', 'ἔ'),
+ (0x1F1D, 'M', 'ἕ'),
+ (0x1F1E, 'X'),
+ (0x1F20, 'V'),
+ (0x1F28, 'M', 'ἠ'),
+ (0x1F29, 'M', 'ἡ'),
+ (0x1F2A, 'M', 'ἢ'),
+ (0x1F2B, 'M', 'ἣ'),
+ (0x1F2C, 'M', 'ἤ'),
+ (0x1F2D, 'M', 'ἥ'),
+ (0x1F2E, 'M', 'ἦ'),
+ (0x1F2F, 'M', 'ἧ'),
+ (0x1F30, 'V'),
+ (0x1F38, 'M', 'ἰ'),
+ (0x1F39, 'M', 'ἱ'),
+ (0x1F3A, 'M', 'ἲ'),
+ (0x1F3B, 'M', 'ἳ'),
+ (0x1F3C, 'M', 'ἴ'),
+ (0x1F3D, 'M', 'ἵ'),
+ (0x1F3E, 'M', 'ἶ'),
+ (0x1F3F, 'M', 'ἷ'),
+ (0x1F40, 'V'),
+ (0x1F46, 'X'),
+ (0x1F48, 'M', 'ὀ'),
+ (0x1F49, 'M', 'ὁ'),
+ (0x1F4A, 'M', 'ὂ'),
+ (0x1F4B, 'M', 'ὃ'),
+ (0x1F4C, 'M', 'ὄ'),
+ (0x1F4D, 'M', 'ὅ'),
+ (0x1F4E, 'X'),
+ (0x1F50, 'V'),
+ (0x1F58, 'X'),
+ (0x1F59, 'M', 'ὑ'),
+ (0x1F5A, 'X'),
+ (0x1F5B, 'M', 'ὓ'),
+ (0x1F5C, 'X'),
+ (0x1F5D, 'M', 'ὕ'),
+ (0x1F5E, 'X'),
+ (0x1F5F, 'M', 'ὗ'),
+ (0x1F60, 'V'),
+ (0x1F68, 'M', 'ὠ'),
+ (0x1F69, 'M', 'ὡ'),
+ (0x1F6A, 'M', 'ὢ'),
+ (0x1F6B, 'M', 'ὣ'),
+ (0x1F6C, 'M', 'ὤ'),
+ (0x1F6D, 'M', 'ὥ'),
+ (0x1F6E, 'M', 'ὦ'),
+ (0x1F6F, 'M', 'ὧ'),
+ (0x1F70, 'V'),
+ (0x1F71, 'M', 'ά'),
+ (0x1F72, 'V'),
+ (0x1F73, 'M', 'έ'),
+ (0x1F74, 'V'),
+ (0x1F75, 'M', 'ή'),
+ (0x1F76, 'V'),
+ (0x1F77, 'M', 'ί'),
+ (0x1F78, 'V'),
+ (0x1F79, 'M', 'ό'),
+ (0x1F7A, 'V'),
+ (0x1F7B, 'M', 'ύ'),
+ (0x1F7C, 'V'),
+ (0x1F7D, 'M', 'ώ'),
+ (0x1F7E, 'X'),
+ (0x1F80, 'M', 'ἀι'),
+ (0x1F81, 'M', 'ἁι'),
+ (0x1F82, 'M', 'ἂι'),
+ (0x1F83, 'M', 'ἃι'),
+ (0x1F84, 'M', 'ἄι'),
+ (0x1F85, 'M', 'ἅι'),
+ (0x1F86, 'M', 'ἆι'),
+ (0x1F87, 'M', 'ἇι'),
+ ]
+
+def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F88, 'M', 'ἀι'),
+ (0x1F89, 'M', 'ἁι'),
+ (0x1F8A, 'M', 'ἂι'),
+ (0x1F8B, 'M', 'ἃι'),
+ (0x1F8C, 'M', 'ἄι'),
+ (0x1F8D, 'M', 'ἅι'),
+ (0x1F8E, 'M', 'ἆι'),
+ (0x1F8F, 'M', 'ἇι'),
+ (0x1F90, 'M', 'ἠι'),
+ (0x1F91, 'M', 'ἡι'),
+ (0x1F92, 'M', 'ἢι'),
+ (0x1F93, 'M', 'ἣι'),
+ (0x1F94, 'M', 'ἤι'),
+ (0x1F95, 'M', 'ἥι'),
+ (0x1F96, 'M', 'ἦι'),
+ (0x1F97, 'M', 'ἧι'),
+ (0x1F98, 'M', 'ἠι'),
+ (0x1F99, 'M', 'ἡι'),
+ (0x1F9A, 'M', 'ἢι'),
+ (0x1F9B, 'M', 'ἣι'),
+ (0x1F9C, 'M', 'ἤι'),
+ (0x1F9D, 'M', 'ἥι'),
+ (0x1F9E, 'M', 'ἦι'),
+ (0x1F9F, 'M', 'ἧι'),
+ (0x1FA0, 'M', 'ὠι'),
+ (0x1FA1, 'M', 'ὡι'),
+ (0x1FA2, 'M', 'ὢι'),
+ (0x1FA3, 'M', 'ὣι'),
+ (0x1FA4, 'M', 'ὤι'),
+ (0x1FA5, 'M', 'ὥι'),
+ (0x1FA6, 'M', 'ὦι'),
+ (0x1FA7, 'M', 'ὧι'),
+ (0x1FA8, 'M', 'ὠι'),
+ (0x1FA9, 'M', 'ὡι'),
+ (0x1FAA, 'M', 'ὢι'),
+ (0x1FAB, 'M', 'ὣι'),
+ (0x1FAC, 'M', 'ὤι'),
+ (0x1FAD, 'M', 'ὥι'),
+ (0x1FAE, 'M', 'ὦι'),
+ (0x1FAF, 'M', 'ὧι'),
+ (0x1FB0, 'V'),
+ (0x1FB2, 'M', 'ὰι'),
+ (0x1FB3, 'M', 'αι'),
+ (0x1FB4, 'M', 'άι'),
+ (0x1FB5, 'X'),
+ (0x1FB6, 'V'),
+ (0x1FB7, 'M', 'ᾶι'),
+ (0x1FB8, 'M', 'ᾰ'),
+ (0x1FB9, 'M', 'ᾱ'),
+ (0x1FBA, 'M', 'ὰ'),
+ (0x1FBB, 'M', 'ά'),
+ (0x1FBC, 'M', 'αι'),
+ (0x1FBD, '3', ' ̓'),
+ (0x1FBE, 'M', 'ι'),
+ (0x1FBF, '3', ' ̓'),
+ (0x1FC0, '3', ' ͂'),
+ (0x1FC1, '3', ' ̈͂'),
+ (0x1FC2, 'M', 'ὴι'),
+ (0x1FC3, 'M', 'ηι'),
+ (0x1FC4, 'M', 'ήι'),
+ (0x1FC5, 'X'),
+ (0x1FC6, 'V'),
+ (0x1FC7, 'M', 'ῆι'),
+ (0x1FC8, 'M', 'ὲ'),
+ (0x1FC9, 'M', 'έ'),
+ (0x1FCA, 'M', 'ὴ'),
+ (0x1FCB, 'M', 'ή'),
+ (0x1FCC, 'M', 'ηι'),
+ (0x1FCD, '3', ' ̓̀'),
+ (0x1FCE, '3', ' ̓́'),
+ (0x1FCF, '3', ' ̓͂'),
+ (0x1FD0, 'V'),
+ (0x1FD3, 'M', 'ΐ'),
+ (0x1FD4, 'X'),
+ (0x1FD6, 'V'),
+ (0x1FD8, 'M', 'ῐ'),
+ (0x1FD9, 'M', 'ῑ'),
+ (0x1FDA, 'M', 'ὶ'),
+ (0x1FDB, 'M', 'ί'),
+ (0x1FDC, 'X'),
+ (0x1FDD, '3', ' ̔̀'),
+ (0x1FDE, '3', ' ̔́'),
+ (0x1FDF, '3', ' ̔͂'),
+ (0x1FE0, 'V'),
+ (0x1FE3, 'M', 'ΰ'),
+ (0x1FE4, 'V'),
+ (0x1FE8, 'M', 'ῠ'),
+ (0x1FE9, 'M', 'ῡ'),
+ (0x1FEA, 'M', 'ὺ'),
+ (0x1FEB, 'M', 'ύ'),
+ (0x1FEC, 'M', 'ῥ'),
+ (0x1FED, '3', ' ̈̀'),
+ (0x1FEE, '3', ' ̈́'),
+ (0x1FEF, '3', '`'),
+ (0x1FF0, 'X'),
+ (0x1FF2, 'M', 'ὼι'),
+ (0x1FF3, 'M', 'ωι'),
+ (0x1FF4, 'M', 'ώι'),
+ (0x1FF5, 'X'),
+ (0x1FF6, 'V'),
+ ]
+
+def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1FF7, 'M', 'ῶι'),
+ (0x1FF8, 'M', 'ὸ'),
+ (0x1FF9, 'M', 'ό'),
+ (0x1FFA, 'M', 'ὼ'),
+ (0x1FFB, 'M', 'ώ'),
+ (0x1FFC, 'M', 'ωι'),
+ (0x1FFD, '3', ' ́'),
+ (0x1FFE, '3', ' ̔'),
+ (0x1FFF, 'X'),
+ (0x2000, '3', ' '),
+ (0x200B, 'I'),
+ (0x200C, 'D', ''),
+ (0x200E, 'X'),
+ (0x2010, 'V'),
+ (0x2011, 'M', '‐'),
+ (0x2012, 'V'),
+ (0x2017, '3', ' ̳'),
+ (0x2018, 'V'),
+ (0x2024, 'X'),
+ (0x2027, 'V'),
+ (0x2028, 'X'),
+ (0x202F, '3', ' '),
+ (0x2030, 'V'),
+ (0x2033, 'M', '′′'),
+ (0x2034, 'M', '′′′'),
+ (0x2035, 'V'),
+ (0x2036, 'M', '‵‵'),
+ (0x2037, 'M', '‵‵‵'),
+ (0x2038, 'V'),
+ (0x203C, '3', '!!'),
+ (0x203D, 'V'),
+ (0x203E, '3', ' ̅'),
+ (0x203F, 'V'),
+ (0x2047, '3', '??'),
+ (0x2048, '3', '?!'),
+ (0x2049, '3', '!?'),
+ (0x204A, 'V'),
+ (0x2057, 'M', '′′′′'),
+ (0x2058, 'V'),
+ (0x205F, '3', ' '),
+ (0x2060, 'I'),
+ (0x2061, 'X'),
+ (0x2064, 'I'),
+ (0x2065, 'X'),
+ (0x2070, 'M', '0'),
+ (0x2071, 'M', 'i'),
+ (0x2072, 'X'),
+ (0x2074, 'M', '4'),
+ (0x2075, 'M', '5'),
+ (0x2076, 'M', '6'),
+ (0x2077, 'M', '7'),
+ (0x2078, 'M', '8'),
+ (0x2079, 'M', '9'),
+ (0x207A, '3', '+'),
+ (0x207B, 'M', '−'),
+ (0x207C, '3', '='),
+ (0x207D, '3', '('),
+ (0x207E, '3', ')'),
+ (0x207F, 'M', 'n'),
+ (0x2080, 'M', '0'),
+ (0x2081, 'M', '1'),
+ (0x2082, 'M', '2'),
+ (0x2083, 'M', '3'),
+ (0x2084, 'M', '4'),
+ (0x2085, 'M', '5'),
+ (0x2086, 'M', '6'),
+ (0x2087, 'M', '7'),
+ (0x2088, 'M', '8'),
+ (0x2089, 'M', '9'),
+ (0x208A, '3', '+'),
+ (0x208B, 'M', '−'),
+ (0x208C, '3', '='),
+ (0x208D, '3', '('),
+ (0x208E, '3', ')'),
+ (0x208F, 'X'),
+ (0x2090, 'M', 'a'),
+ (0x2091, 'M', 'e'),
+ (0x2092, 'M', 'o'),
+ (0x2093, 'M', 'x'),
+ (0x2094, 'M', 'ə'),
+ (0x2095, 'M', 'h'),
+ (0x2096, 'M', 'k'),
+ (0x2097, 'M', 'l'),
+ (0x2098, 'M', 'm'),
+ (0x2099, 'M', 'n'),
+ (0x209A, 'M', 'p'),
+ (0x209B, 'M', 's'),
+ (0x209C, 'M', 't'),
+ (0x209D, 'X'),
+ (0x20A0, 'V'),
+ (0x20A8, 'M', 'rs'),
+ (0x20A9, 'V'),
+ (0x20C1, 'X'),
+ (0x20D0, 'V'),
+ (0x20F1, 'X'),
+ (0x2100, '3', 'a/c'),
+ (0x2101, '3', 'a/s'),
+ (0x2102, 'M', 'c'),
+ (0x2103, 'M', '°c'),
+ (0x2104, 'V'),
+ ]
+
+def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2105, '3', 'c/o'),
+ (0x2106, '3', 'c/u'),
+ (0x2107, 'M', 'ɛ'),
+ (0x2108, 'V'),
+ (0x2109, 'M', '°f'),
+ (0x210A, 'M', 'g'),
+ (0x210B, 'M', 'h'),
+ (0x210F, 'M', 'ħ'),
+ (0x2110, 'M', 'i'),
+ (0x2112, 'M', 'l'),
+ (0x2114, 'V'),
+ (0x2115, 'M', 'n'),
+ (0x2116, 'M', 'no'),
+ (0x2117, 'V'),
+ (0x2119, 'M', 'p'),
+ (0x211A, 'M', 'q'),
+ (0x211B, 'M', 'r'),
+ (0x211E, 'V'),
+ (0x2120, 'M', 'sm'),
+ (0x2121, 'M', 'tel'),
+ (0x2122, 'M', 'tm'),
+ (0x2123, 'V'),
+ (0x2124, 'M', 'z'),
+ (0x2125, 'V'),
+ (0x2126, 'M', 'ω'),
+ (0x2127, 'V'),
+ (0x2128, 'M', 'z'),
+ (0x2129, 'V'),
+ (0x212A, 'M', 'k'),
+ (0x212B, 'M', 'å'),
+ (0x212C, 'M', 'b'),
+ (0x212D, 'M', 'c'),
+ (0x212E, 'V'),
+ (0x212F, 'M', 'e'),
+ (0x2131, 'M', 'f'),
+ (0x2132, 'X'),
+ (0x2133, 'M', 'm'),
+ (0x2134, 'M', 'o'),
+ (0x2135, 'M', 'א'),
+ (0x2136, 'M', 'ב'),
+ (0x2137, 'M', 'ג'),
+ (0x2138, 'M', 'ד'),
+ (0x2139, 'M', 'i'),
+ (0x213A, 'V'),
+ (0x213B, 'M', 'fax'),
+ (0x213C, 'M', 'π'),
+ (0x213D, 'M', 'γ'),
+ (0x213F, 'M', 'π'),
+ (0x2140, 'M', '∑'),
+ (0x2141, 'V'),
+ (0x2145, 'M', 'd'),
+ (0x2147, 'M', 'e'),
+ (0x2148, 'M', 'i'),
+ (0x2149, 'M', 'j'),
+ (0x214A, 'V'),
+ (0x2150, 'M', '1⁄7'),
+ (0x2151, 'M', '1⁄9'),
+ (0x2152, 'M', '1⁄10'),
+ (0x2153, 'M', '1⁄3'),
+ (0x2154, 'M', '2⁄3'),
+ (0x2155, 'M', '1⁄5'),
+ (0x2156, 'M', '2⁄5'),
+ (0x2157, 'M', '3⁄5'),
+ (0x2158, 'M', '4⁄5'),
+ (0x2159, 'M', '1⁄6'),
+ (0x215A, 'M', '5⁄6'),
+ (0x215B, 'M', '1⁄8'),
+ (0x215C, 'M', '3⁄8'),
+ (0x215D, 'M', '5⁄8'),
+ (0x215E, 'M', '7⁄8'),
+ (0x215F, 'M', '1⁄'),
+ (0x2160, 'M', 'i'),
+ (0x2161, 'M', 'ii'),
+ (0x2162, 'M', 'iii'),
+ (0x2163, 'M', 'iv'),
+ (0x2164, 'M', 'v'),
+ (0x2165, 'M', 'vi'),
+ (0x2166, 'M', 'vii'),
+ (0x2167, 'M', 'viii'),
+ (0x2168, 'M', 'ix'),
+ (0x2169, 'M', 'x'),
+ (0x216A, 'M', 'xi'),
+ (0x216B, 'M', 'xii'),
+ (0x216C, 'M', 'l'),
+ (0x216D, 'M', 'c'),
+ (0x216E, 'M', 'd'),
+ (0x216F, 'M', 'm'),
+ (0x2170, 'M', 'i'),
+ (0x2171, 'M', 'ii'),
+ (0x2172, 'M', 'iii'),
+ (0x2173, 'M', 'iv'),
+ (0x2174, 'M', 'v'),
+ (0x2175, 'M', 'vi'),
+ (0x2176, 'M', 'vii'),
+ (0x2177, 'M', 'viii'),
+ (0x2178, 'M', 'ix'),
+ (0x2179, 'M', 'x'),
+ (0x217A, 'M', 'xi'),
+ (0x217B, 'M', 'xii'),
+ (0x217C, 'M', 'l'),
+ ]
+
+def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x217D, 'M', 'c'),
+ (0x217E, 'M', 'd'),
+ (0x217F, 'M', 'm'),
+ (0x2180, 'V'),
+ (0x2183, 'X'),
+ (0x2184, 'V'),
+ (0x2189, 'M', '0⁄3'),
+ (0x218A, 'V'),
+ (0x218C, 'X'),
+ (0x2190, 'V'),
+ (0x222C, 'M', '∫∫'),
+ (0x222D, 'M', '∫∫∫'),
+ (0x222E, 'V'),
+ (0x222F, 'M', '∮∮'),
+ (0x2230, 'M', '∮∮∮'),
+ (0x2231, 'V'),
+ (0x2329, 'M', '〈'),
+ (0x232A, 'M', '〉'),
+ (0x232B, 'V'),
+ (0x2427, 'X'),
+ (0x2440, 'V'),
+ (0x244B, 'X'),
+ (0x2460, 'M', '1'),
+ (0x2461, 'M', '2'),
+ (0x2462, 'M', '3'),
+ (0x2463, 'M', '4'),
+ (0x2464, 'M', '5'),
+ (0x2465, 'M', '6'),
+ (0x2466, 'M', '7'),
+ (0x2467, 'M', '8'),
+ (0x2468, 'M', '9'),
+ (0x2469, 'M', '10'),
+ (0x246A, 'M', '11'),
+ (0x246B, 'M', '12'),
+ (0x246C, 'M', '13'),
+ (0x246D, 'M', '14'),
+ (0x246E, 'M', '15'),
+ (0x246F, 'M', '16'),
+ (0x2470, 'M', '17'),
+ (0x2471, 'M', '18'),
+ (0x2472, 'M', '19'),
+ (0x2473, 'M', '20'),
+ (0x2474, '3', '(1)'),
+ (0x2475, '3', '(2)'),
+ (0x2476, '3', '(3)'),
+ (0x2477, '3', '(4)'),
+ (0x2478, '3', '(5)'),
+ (0x2479, '3', '(6)'),
+ (0x247A, '3', '(7)'),
+ (0x247B, '3', '(8)'),
+ (0x247C, '3', '(9)'),
+ (0x247D, '3', '(10)'),
+ (0x247E, '3', '(11)'),
+ (0x247F, '3', '(12)'),
+ (0x2480, '3', '(13)'),
+ (0x2481, '3', '(14)'),
+ (0x2482, '3', '(15)'),
+ (0x2483, '3', '(16)'),
+ (0x2484, '3', '(17)'),
+ (0x2485, '3', '(18)'),
+ (0x2486, '3', '(19)'),
+ (0x2487, '3', '(20)'),
+ (0x2488, 'X'),
+ (0x249C, '3', '(a)'),
+ (0x249D, '3', '(b)'),
+ (0x249E, '3', '(c)'),
+ (0x249F, '3', '(d)'),
+ (0x24A0, '3', '(e)'),
+ (0x24A1, '3', '(f)'),
+ (0x24A2, '3', '(g)'),
+ (0x24A3, '3', '(h)'),
+ (0x24A4, '3', '(i)'),
+ (0x24A5, '3', '(j)'),
+ (0x24A6, '3', '(k)'),
+ (0x24A7, '3', '(l)'),
+ (0x24A8, '3', '(m)'),
+ (0x24A9, '3', '(n)'),
+ (0x24AA, '3', '(o)'),
+ (0x24AB, '3', '(p)'),
+ (0x24AC, '3', '(q)'),
+ (0x24AD, '3', '(r)'),
+ (0x24AE, '3', '(s)'),
+ (0x24AF, '3', '(t)'),
+ (0x24B0, '3', '(u)'),
+ (0x24B1, '3', '(v)'),
+ (0x24B2, '3', '(w)'),
+ (0x24B3, '3', '(x)'),
+ (0x24B4, '3', '(y)'),
+ (0x24B5, '3', '(z)'),
+ (0x24B6, 'M', 'a'),
+ (0x24B7, 'M', 'b'),
+ (0x24B8, 'M', 'c'),
+ (0x24B9, 'M', 'd'),
+ (0x24BA, 'M', 'e'),
+ (0x24BB, 'M', 'f'),
+ (0x24BC, 'M', 'g'),
+ (0x24BD, 'M', 'h'),
+ (0x24BE, 'M', 'i'),
+ (0x24BF, 'M', 'j'),
+ (0x24C0, 'M', 'k'),
+ ]
+
+def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x24C1, 'M', 'l'),
+ (0x24C2, 'M', 'm'),
+ (0x24C3, 'M', 'n'),
+ (0x24C4, 'M', 'o'),
+ (0x24C5, 'M', 'p'),
+ (0x24C6, 'M', 'q'),
+ (0x24C7, 'M', 'r'),
+ (0x24C8, 'M', 's'),
+ (0x24C9, 'M', 't'),
+ (0x24CA, 'M', 'u'),
+ (0x24CB, 'M', 'v'),
+ (0x24CC, 'M', 'w'),
+ (0x24CD, 'M', 'x'),
+ (0x24CE, 'M', 'y'),
+ (0x24CF, 'M', 'z'),
+ (0x24D0, 'M', 'a'),
+ (0x24D1, 'M', 'b'),
+ (0x24D2, 'M', 'c'),
+ (0x24D3, 'M', 'd'),
+ (0x24D4, 'M', 'e'),
+ (0x24D5, 'M', 'f'),
+ (0x24D6, 'M', 'g'),
+ (0x24D7, 'M', 'h'),
+ (0x24D8, 'M', 'i'),
+ (0x24D9, 'M', 'j'),
+ (0x24DA, 'M', 'k'),
+ (0x24DB, 'M', 'l'),
+ (0x24DC, 'M', 'm'),
+ (0x24DD, 'M', 'n'),
+ (0x24DE, 'M', 'o'),
+ (0x24DF, 'M', 'p'),
+ (0x24E0, 'M', 'q'),
+ (0x24E1, 'M', 'r'),
+ (0x24E2, 'M', 's'),
+ (0x24E3, 'M', 't'),
+ (0x24E4, 'M', 'u'),
+ (0x24E5, 'M', 'v'),
+ (0x24E6, 'M', 'w'),
+ (0x24E7, 'M', 'x'),
+ (0x24E8, 'M', 'y'),
+ (0x24E9, 'M', 'z'),
+ (0x24EA, 'M', '0'),
+ (0x24EB, 'V'),
+ (0x2A0C, 'M', '∫∫∫∫'),
+ (0x2A0D, 'V'),
+ (0x2A74, '3', '::='),
+ (0x2A75, '3', '=='),
+ (0x2A76, '3', '==='),
+ (0x2A77, 'V'),
+ (0x2ADC, 'M', '⫝̸'),
+ (0x2ADD, 'V'),
+ (0x2B74, 'X'),
+ (0x2B76, 'V'),
+ (0x2B96, 'X'),
+ (0x2B97, 'V'),
+ (0x2C00, 'M', 'ⰰ'),
+ (0x2C01, 'M', 'ⰱ'),
+ (0x2C02, 'M', 'ⰲ'),
+ (0x2C03, 'M', 'ⰳ'),
+ (0x2C04, 'M', 'ⰴ'),
+ (0x2C05, 'M', 'ⰵ'),
+ (0x2C06, 'M', 'ⰶ'),
+ (0x2C07, 'M', 'ⰷ'),
+ (0x2C08, 'M', 'ⰸ'),
+ (0x2C09, 'M', 'ⰹ'),
+ (0x2C0A, 'M', 'ⰺ'),
+ (0x2C0B, 'M', 'ⰻ'),
+ (0x2C0C, 'M', 'ⰼ'),
+ (0x2C0D, 'M', 'ⰽ'),
+ (0x2C0E, 'M', 'ⰾ'),
+ (0x2C0F, 'M', 'ⰿ'),
+ (0x2C10, 'M', 'ⱀ'),
+ (0x2C11, 'M', 'ⱁ'),
+ (0x2C12, 'M', 'ⱂ'),
+ (0x2C13, 'M', 'ⱃ'),
+ (0x2C14, 'M', 'ⱄ'),
+ (0x2C15, 'M', 'ⱅ'),
+ (0x2C16, 'M', 'ⱆ'),
+ (0x2C17, 'M', 'ⱇ'),
+ (0x2C18, 'M', 'ⱈ'),
+ (0x2C19, 'M', 'ⱉ'),
+ (0x2C1A, 'M', 'ⱊ'),
+ (0x2C1B, 'M', 'ⱋ'),
+ (0x2C1C, 'M', 'ⱌ'),
+ (0x2C1D, 'M', 'ⱍ'),
+ (0x2C1E, 'M', 'ⱎ'),
+ (0x2C1F, 'M', 'ⱏ'),
+ (0x2C20, 'M', 'ⱐ'),
+ (0x2C21, 'M', 'ⱑ'),
+ (0x2C22, 'M', 'ⱒ'),
+ (0x2C23, 'M', 'ⱓ'),
+ (0x2C24, 'M', 'ⱔ'),
+ (0x2C25, 'M', 'ⱕ'),
+ (0x2C26, 'M', 'ⱖ'),
+ (0x2C27, 'M', 'ⱗ'),
+ (0x2C28, 'M', 'ⱘ'),
+ (0x2C29, 'M', 'ⱙ'),
+ (0x2C2A, 'M', 'ⱚ'),
+ (0x2C2B, 'M', 'ⱛ'),
+ (0x2C2C, 'M', 'ⱜ'),
+ ]
+
+def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2C2D, 'M', 'ⱝ'),
+ (0x2C2E, 'M', 'ⱞ'),
+ (0x2C2F, 'M', 'ⱟ'),
+ (0x2C30, 'V'),
+ (0x2C60, 'M', 'ⱡ'),
+ (0x2C61, 'V'),
+ (0x2C62, 'M', 'ɫ'),
+ (0x2C63, 'M', 'ᵽ'),
+ (0x2C64, 'M', 'ɽ'),
+ (0x2C65, 'V'),
+ (0x2C67, 'M', 'ⱨ'),
+ (0x2C68, 'V'),
+ (0x2C69, 'M', 'ⱪ'),
+ (0x2C6A, 'V'),
+ (0x2C6B, 'M', 'ⱬ'),
+ (0x2C6C, 'V'),
+ (0x2C6D, 'M', 'ɑ'),
+ (0x2C6E, 'M', 'ɱ'),
+ (0x2C6F, 'M', 'ɐ'),
+ (0x2C70, 'M', 'ɒ'),
+ (0x2C71, 'V'),
+ (0x2C72, 'M', 'ⱳ'),
+ (0x2C73, 'V'),
+ (0x2C75, 'M', 'ⱶ'),
+ (0x2C76, 'V'),
+ (0x2C7C, 'M', 'j'),
+ (0x2C7D, 'M', 'v'),
+ (0x2C7E, 'M', 'ȿ'),
+ (0x2C7F, 'M', 'ɀ'),
+ (0x2C80, 'M', 'ⲁ'),
+ (0x2C81, 'V'),
+ (0x2C82, 'M', 'ⲃ'),
+ (0x2C83, 'V'),
+ (0x2C84, 'M', 'ⲅ'),
+ (0x2C85, 'V'),
+ (0x2C86, 'M', 'ⲇ'),
+ (0x2C87, 'V'),
+ (0x2C88, 'M', 'ⲉ'),
+ (0x2C89, 'V'),
+ (0x2C8A, 'M', 'ⲋ'),
+ (0x2C8B, 'V'),
+ (0x2C8C, 'M', 'ⲍ'),
+ (0x2C8D, 'V'),
+ (0x2C8E, 'M', 'ⲏ'),
+ (0x2C8F, 'V'),
+ (0x2C90, 'M', 'ⲑ'),
+ (0x2C91, 'V'),
+ (0x2C92, 'M', 'ⲓ'),
+ (0x2C93, 'V'),
+ (0x2C94, 'M', 'ⲕ'),
+ (0x2C95, 'V'),
+ (0x2C96, 'M', 'ⲗ'),
+ (0x2C97, 'V'),
+ (0x2C98, 'M', 'ⲙ'),
+ (0x2C99, 'V'),
+ (0x2C9A, 'M', 'ⲛ'),
+ (0x2C9B, 'V'),
+ (0x2C9C, 'M', 'ⲝ'),
+ (0x2C9D, 'V'),
+ (0x2C9E, 'M', 'ⲟ'),
+ (0x2C9F, 'V'),
+ (0x2CA0, 'M', 'ⲡ'),
+ (0x2CA1, 'V'),
+ (0x2CA2, 'M', 'ⲣ'),
+ (0x2CA3, 'V'),
+ (0x2CA4, 'M', 'ⲥ'),
+ (0x2CA5, 'V'),
+ (0x2CA6, 'M', 'ⲧ'),
+ (0x2CA7, 'V'),
+ (0x2CA8, 'M', 'ⲩ'),
+ (0x2CA9, 'V'),
+ (0x2CAA, 'M', 'ⲫ'),
+ (0x2CAB, 'V'),
+ (0x2CAC, 'M', 'ⲭ'),
+ (0x2CAD, 'V'),
+ (0x2CAE, 'M', 'ⲯ'),
+ (0x2CAF, 'V'),
+ (0x2CB0, 'M', 'ⲱ'),
+ (0x2CB1, 'V'),
+ (0x2CB2, 'M', 'ⲳ'),
+ (0x2CB3, 'V'),
+ (0x2CB4, 'M', 'ⲵ'),
+ (0x2CB5, 'V'),
+ (0x2CB6, 'M', 'ⲷ'),
+ (0x2CB7, 'V'),
+ (0x2CB8, 'M', 'ⲹ'),
+ (0x2CB9, 'V'),
+ (0x2CBA, 'M', 'ⲻ'),
+ (0x2CBB, 'V'),
+ (0x2CBC, 'M', 'ⲽ'),
+ (0x2CBD, 'V'),
+ (0x2CBE, 'M', 'ⲿ'),
+ (0x2CBF, 'V'),
+ (0x2CC0, 'M', 'ⳁ'),
+ (0x2CC1, 'V'),
+ (0x2CC2, 'M', 'ⳃ'),
+ (0x2CC3, 'V'),
+ (0x2CC4, 'M', 'ⳅ'),
+ (0x2CC5, 'V'),
+ (0x2CC6, 'M', 'ⳇ'),
+ ]
+
+def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2CC7, 'V'),
+ (0x2CC8, 'M', 'ⳉ'),
+ (0x2CC9, 'V'),
+ (0x2CCA, 'M', 'ⳋ'),
+ (0x2CCB, 'V'),
+ (0x2CCC, 'M', 'ⳍ'),
+ (0x2CCD, 'V'),
+ (0x2CCE, 'M', 'ⳏ'),
+ (0x2CCF, 'V'),
+ (0x2CD0, 'M', 'ⳑ'),
+ (0x2CD1, 'V'),
+ (0x2CD2, 'M', 'ⳓ'),
+ (0x2CD3, 'V'),
+ (0x2CD4, 'M', 'ⳕ'),
+ (0x2CD5, 'V'),
+ (0x2CD6, 'M', 'ⳗ'),
+ (0x2CD7, 'V'),
+ (0x2CD8, 'M', 'ⳙ'),
+ (0x2CD9, 'V'),
+ (0x2CDA, 'M', 'ⳛ'),
+ (0x2CDB, 'V'),
+ (0x2CDC, 'M', 'ⳝ'),
+ (0x2CDD, 'V'),
+ (0x2CDE, 'M', 'ⳟ'),
+ (0x2CDF, 'V'),
+ (0x2CE0, 'M', 'ⳡ'),
+ (0x2CE1, 'V'),
+ (0x2CE2, 'M', 'ⳣ'),
+ (0x2CE3, 'V'),
+ (0x2CEB, 'M', 'ⳬ'),
+ (0x2CEC, 'V'),
+ (0x2CED, 'M', 'ⳮ'),
+ (0x2CEE, 'V'),
+ (0x2CF2, 'M', 'ⳳ'),
+ (0x2CF3, 'V'),
+ (0x2CF4, 'X'),
+ (0x2CF9, 'V'),
+ (0x2D26, 'X'),
+ (0x2D27, 'V'),
+ (0x2D28, 'X'),
+ (0x2D2D, 'V'),
+ (0x2D2E, 'X'),
+ (0x2D30, 'V'),
+ (0x2D68, 'X'),
+ (0x2D6F, 'M', 'ⵡ'),
+ (0x2D70, 'V'),
+ (0x2D71, 'X'),
+ (0x2D7F, 'V'),
+ (0x2D97, 'X'),
+ (0x2DA0, 'V'),
+ (0x2DA7, 'X'),
+ (0x2DA8, 'V'),
+ (0x2DAF, 'X'),
+ (0x2DB0, 'V'),
+ (0x2DB7, 'X'),
+ (0x2DB8, 'V'),
+ (0x2DBF, 'X'),
+ (0x2DC0, 'V'),
+ (0x2DC7, 'X'),
+ (0x2DC8, 'V'),
+ (0x2DCF, 'X'),
+ (0x2DD0, 'V'),
+ (0x2DD7, 'X'),
+ (0x2DD8, 'V'),
+ (0x2DDF, 'X'),
+ (0x2DE0, 'V'),
+ (0x2E5E, 'X'),
+ (0x2E80, 'V'),
+ (0x2E9A, 'X'),
+ (0x2E9B, 'V'),
+ (0x2E9F, 'M', '母'),
+ (0x2EA0, 'V'),
+ (0x2EF3, 'M', '龟'),
+ (0x2EF4, 'X'),
+ (0x2F00, 'M', '一'),
+ (0x2F01, 'M', '丨'),
+ (0x2F02, 'M', '丶'),
+ (0x2F03, 'M', '丿'),
+ (0x2F04, 'M', '乙'),
+ (0x2F05, 'M', '亅'),
+ (0x2F06, 'M', '二'),
+ (0x2F07, 'M', '亠'),
+ (0x2F08, 'M', '人'),
+ (0x2F09, 'M', '儿'),
+ (0x2F0A, 'M', '入'),
+ (0x2F0B, 'M', '八'),
+ (0x2F0C, 'M', '冂'),
+ (0x2F0D, 'M', '冖'),
+ (0x2F0E, 'M', '冫'),
+ (0x2F0F, 'M', '几'),
+ (0x2F10, 'M', '凵'),
+ (0x2F11, 'M', '刀'),
+ (0x2F12, 'M', '力'),
+ (0x2F13, 'M', '勹'),
+ (0x2F14, 'M', '匕'),
+ (0x2F15, 'M', '匚'),
+ (0x2F16, 'M', '匸'),
+ (0x2F17, 'M', '十'),
+ (0x2F18, 'M', '卜'),
+ (0x2F19, 'M', '卩'),
+ ]
+
+def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F1A, 'M', '厂'),
+ (0x2F1B, 'M', '厶'),
+ (0x2F1C, 'M', '又'),
+ (0x2F1D, 'M', '口'),
+ (0x2F1E, 'M', '囗'),
+ (0x2F1F, 'M', '土'),
+ (0x2F20, 'M', '士'),
+ (0x2F21, 'M', '夂'),
+ (0x2F22, 'M', '夊'),
+ (0x2F23, 'M', '夕'),
+ (0x2F24, 'M', '大'),
+ (0x2F25, 'M', '女'),
+ (0x2F26, 'M', '子'),
+ (0x2F27, 'M', '宀'),
+ (0x2F28, 'M', '寸'),
+ (0x2F29, 'M', '小'),
+ (0x2F2A, 'M', '尢'),
+ (0x2F2B, 'M', '尸'),
+ (0x2F2C, 'M', '屮'),
+ (0x2F2D, 'M', '山'),
+ (0x2F2E, 'M', '巛'),
+ (0x2F2F, 'M', '工'),
+ (0x2F30, 'M', '己'),
+ (0x2F31, 'M', '巾'),
+ (0x2F32, 'M', '干'),
+ (0x2F33, 'M', '幺'),
+ (0x2F34, 'M', '广'),
+ (0x2F35, 'M', '廴'),
+ (0x2F36, 'M', '廾'),
+ (0x2F37, 'M', '弋'),
+ (0x2F38, 'M', '弓'),
+ (0x2F39, 'M', '彐'),
+ (0x2F3A, 'M', '彡'),
+ (0x2F3B, 'M', '彳'),
+ (0x2F3C, 'M', '心'),
+ (0x2F3D, 'M', '戈'),
+ (0x2F3E, 'M', '戶'),
+ (0x2F3F, 'M', '手'),
+ (0x2F40, 'M', '支'),
+ (0x2F41, 'M', '攴'),
+ (0x2F42, 'M', '文'),
+ (0x2F43, 'M', '斗'),
+ (0x2F44, 'M', '斤'),
+ (0x2F45, 'M', '方'),
+ (0x2F46, 'M', '无'),
+ (0x2F47, 'M', '日'),
+ (0x2F48, 'M', '曰'),
+ (0x2F49, 'M', '月'),
+ (0x2F4A, 'M', '木'),
+ (0x2F4B, 'M', '欠'),
+ (0x2F4C, 'M', '止'),
+ (0x2F4D, 'M', '歹'),
+ (0x2F4E, 'M', '殳'),
+ (0x2F4F, 'M', '毋'),
+ (0x2F50, 'M', '比'),
+ (0x2F51, 'M', '毛'),
+ (0x2F52, 'M', '氏'),
+ (0x2F53, 'M', '气'),
+ (0x2F54, 'M', '水'),
+ (0x2F55, 'M', '火'),
+ (0x2F56, 'M', '爪'),
+ (0x2F57, 'M', '父'),
+ (0x2F58, 'M', '爻'),
+ (0x2F59, 'M', '爿'),
+ (0x2F5A, 'M', '片'),
+ (0x2F5B, 'M', '牙'),
+ (0x2F5C, 'M', '牛'),
+ (0x2F5D, 'M', '犬'),
+ (0x2F5E, 'M', '玄'),
+ (0x2F5F, 'M', '玉'),
+ (0x2F60, 'M', '瓜'),
+ (0x2F61, 'M', '瓦'),
+ (0x2F62, 'M', '甘'),
+ (0x2F63, 'M', '生'),
+ (0x2F64, 'M', '用'),
+ (0x2F65, 'M', '田'),
+ (0x2F66, 'M', '疋'),
+ (0x2F67, 'M', '疒'),
+ (0x2F68, 'M', '癶'),
+ (0x2F69, 'M', '白'),
+ (0x2F6A, 'M', '皮'),
+ (0x2F6B, 'M', '皿'),
+ (0x2F6C, 'M', '目'),
+ (0x2F6D, 'M', '矛'),
+ (0x2F6E, 'M', '矢'),
+ (0x2F6F, 'M', '石'),
+ (0x2F70, 'M', '示'),
+ (0x2F71, 'M', '禸'),
+ (0x2F72, 'M', '禾'),
+ (0x2F73, 'M', '穴'),
+ (0x2F74, 'M', '立'),
+ (0x2F75, 'M', '竹'),
+ (0x2F76, 'M', '米'),
+ (0x2F77, 'M', '糸'),
+ (0x2F78, 'M', '缶'),
+ (0x2F79, 'M', '网'),
+ (0x2F7A, 'M', '羊'),
+ (0x2F7B, 'M', '羽'),
+ (0x2F7C, 'M', '老'),
+ (0x2F7D, 'M', '而'),
+ ]
+
+def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F7E, 'M', '耒'),
+ (0x2F7F, 'M', '耳'),
+ (0x2F80, 'M', '聿'),
+ (0x2F81, 'M', '肉'),
+ (0x2F82, 'M', '臣'),
+ (0x2F83, 'M', '自'),
+ (0x2F84, 'M', '至'),
+ (0x2F85, 'M', '臼'),
+ (0x2F86, 'M', '舌'),
+ (0x2F87, 'M', '舛'),
+ (0x2F88, 'M', '舟'),
+ (0x2F89, 'M', '艮'),
+ (0x2F8A, 'M', '色'),
+ (0x2F8B, 'M', '艸'),
+ (0x2F8C, 'M', '虍'),
+ (0x2F8D, 'M', '虫'),
+ (0x2F8E, 'M', '血'),
+ (0x2F8F, 'M', '行'),
+ (0x2F90, 'M', '衣'),
+ (0x2F91, 'M', '襾'),
+ (0x2F92, 'M', '見'),
+ (0x2F93, 'M', '角'),
+ (0x2F94, 'M', '言'),
+ (0x2F95, 'M', '谷'),
+ (0x2F96, 'M', '豆'),
+ (0x2F97, 'M', '豕'),
+ (0x2F98, 'M', '豸'),
+ (0x2F99, 'M', '貝'),
+ (0x2F9A, 'M', '赤'),
+ (0x2F9B, 'M', '走'),
+ (0x2F9C, 'M', '足'),
+ (0x2F9D, 'M', '身'),
+ (0x2F9E, 'M', '車'),
+ (0x2F9F, 'M', '辛'),
+ (0x2FA0, 'M', '辰'),
+ (0x2FA1, 'M', '辵'),
+ (0x2FA2, 'M', '邑'),
+ (0x2FA3, 'M', '酉'),
+ (0x2FA4, 'M', '釆'),
+ (0x2FA5, 'M', '里'),
+ (0x2FA6, 'M', '金'),
+ (0x2FA7, 'M', '長'),
+ (0x2FA8, 'M', '門'),
+ (0x2FA9, 'M', '阜'),
+ (0x2FAA, 'M', '隶'),
+ (0x2FAB, 'M', '隹'),
+ (0x2FAC, 'M', '雨'),
+ (0x2FAD, 'M', '靑'),
+ (0x2FAE, 'M', '非'),
+ (0x2FAF, 'M', '面'),
+ (0x2FB0, 'M', '革'),
+ (0x2FB1, 'M', '韋'),
+ (0x2FB2, 'M', '韭'),
+ (0x2FB3, 'M', '音'),
+ (0x2FB4, 'M', '頁'),
+ (0x2FB5, 'M', '風'),
+ (0x2FB6, 'M', '飛'),
+ (0x2FB7, 'M', '食'),
+ (0x2FB8, 'M', '首'),
+ (0x2FB9, 'M', '香'),
+ (0x2FBA, 'M', '馬'),
+ (0x2FBB, 'M', '骨'),
+ (0x2FBC, 'M', '高'),
+ (0x2FBD, 'M', '髟'),
+ (0x2FBE, 'M', '鬥'),
+ (0x2FBF, 'M', '鬯'),
+ (0x2FC0, 'M', '鬲'),
+ (0x2FC1, 'M', '鬼'),
+ (0x2FC2, 'M', '魚'),
+ (0x2FC3, 'M', '鳥'),
+ (0x2FC4, 'M', '鹵'),
+ (0x2FC5, 'M', '鹿'),
+ (0x2FC6, 'M', '麥'),
+ (0x2FC7, 'M', '麻'),
+ (0x2FC8, 'M', '黃'),
+ (0x2FC9, 'M', '黍'),
+ (0x2FCA, 'M', '黑'),
+ (0x2FCB, 'M', '黹'),
+ (0x2FCC, 'M', '黽'),
+ (0x2FCD, 'M', '鼎'),
+ (0x2FCE, 'M', '鼓'),
+ (0x2FCF, 'M', '鼠'),
+ (0x2FD0, 'M', '鼻'),
+ (0x2FD1, 'M', '齊'),
+ (0x2FD2, 'M', '齒'),
+ (0x2FD3, 'M', '龍'),
+ (0x2FD4, 'M', '龜'),
+ (0x2FD5, 'M', '龠'),
+ (0x2FD6, 'X'),
+ (0x3000, '3', ' '),
+ (0x3001, 'V'),
+ (0x3002, 'M', '.'),
+ (0x3003, 'V'),
+ (0x3036, 'M', '〒'),
+ (0x3037, 'V'),
+ (0x3038, 'M', '十'),
+ (0x3039, 'M', '卄'),
+ (0x303A, 'M', '卅'),
+ (0x303B, 'V'),
+ (0x3040, 'X'),
+ ]
+
+def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3041, 'V'),
+ (0x3097, 'X'),
+ (0x3099, 'V'),
+ (0x309B, '3', ' ゙'),
+ (0x309C, '3', ' ゚'),
+ (0x309D, 'V'),
+ (0x309F, 'M', 'より'),
+ (0x30A0, 'V'),
+ (0x30FF, 'M', 'コト'),
+ (0x3100, 'X'),
+ (0x3105, 'V'),
+ (0x3130, 'X'),
+ (0x3131, 'M', 'ᄀ'),
+ (0x3132, 'M', 'ᄁ'),
+ (0x3133, 'M', 'ᆪ'),
+ (0x3134, 'M', 'ᄂ'),
+ (0x3135, 'M', 'ᆬ'),
+ (0x3136, 'M', 'ᆭ'),
+ (0x3137, 'M', 'ᄃ'),
+ (0x3138, 'M', 'ᄄ'),
+ (0x3139, 'M', 'ᄅ'),
+ (0x313A, 'M', 'ᆰ'),
+ (0x313B, 'M', 'ᆱ'),
+ (0x313C, 'M', 'ᆲ'),
+ (0x313D, 'M', 'ᆳ'),
+ (0x313E, 'M', 'ᆴ'),
+ (0x313F, 'M', 'ᆵ'),
+ (0x3140, 'M', 'ᄚ'),
+ (0x3141, 'M', 'ᄆ'),
+ (0x3142, 'M', 'ᄇ'),
+ (0x3143, 'M', 'ᄈ'),
+ (0x3144, 'M', 'ᄡ'),
+ (0x3145, 'M', 'ᄉ'),
+ (0x3146, 'M', 'ᄊ'),
+ (0x3147, 'M', 'ᄋ'),
+ (0x3148, 'M', 'ᄌ'),
+ (0x3149, 'M', 'ᄍ'),
+ (0x314A, 'M', 'ᄎ'),
+ (0x314B, 'M', 'ᄏ'),
+ (0x314C, 'M', 'ᄐ'),
+ (0x314D, 'M', 'ᄑ'),
+ (0x314E, 'M', 'ᄒ'),
+ (0x314F, 'M', 'ᅡ'),
+ (0x3150, 'M', 'ᅢ'),
+ (0x3151, 'M', 'ᅣ'),
+ (0x3152, 'M', 'ᅤ'),
+ (0x3153, 'M', 'ᅥ'),
+ (0x3154, 'M', 'ᅦ'),
+ (0x3155, 'M', 'ᅧ'),
+ (0x3156, 'M', 'ᅨ'),
+ (0x3157, 'M', 'ᅩ'),
+ (0x3158, 'M', 'ᅪ'),
+ (0x3159, 'M', 'ᅫ'),
+ (0x315A, 'M', 'ᅬ'),
+ (0x315B, 'M', 'ᅭ'),
+ (0x315C, 'M', 'ᅮ'),
+ (0x315D, 'M', 'ᅯ'),
+ (0x315E, 'M', 'ᅰ'),
+ (0x315F, 'M', 'ᅱ'),
+ (0x3160, 'M', 'ᅲ'),
+ (0x3161, 'M', 'ᅳ'),
+ (0x3162, 'M', 'ᅴ'),
+ (0x3163, 'M', 'ᅵ'),
+ (0x3164, 'X'),
+ (0x3165, 'M', 'ᄔ'),
+ (0x3166, 'M', 'ᄕ'),
+ (0x3167, 'M', 'ᇇ'),
+ (0x3168, 'M', 'ᇈ'),
+ (0x3169, 'M', 'ᇌ'),
+ (0x316A, 'M', 'ᇎ'),
+ (0x316B, 'M', 'ᇓ'),
+ (0x316C, 'M', 'ᇗ'),
+ (0x316D, 'M', 'ᇙ'),
+ (0x316E, 'M', 'ᄜ'),
+ (0x316F, 'M', 'ᇝ'),
+ (0x3170, 'M', 'ᇟ'),
+ (0x3171, 'M', 'ᄝ'),
+ (0x3172, 'M', 'ᄞ'),
+ (0x3173, 'M', 'ᄠ'),
+ (0x3174, 'M', 'ᄢ'),
+ (0x3175, 'M', 'ᄣ'),
+ (0x3176, 'M', 'ᄧ'),
+ (0x3177, 'M', 'ᄩ'),
+ (0x3178, 'M', 'ᄫ'),
+ (0x3179, 'M', 'ᄬ'),
+ (0x317A, 'M', 'ᄭ'),
+ (0x317B, 'M', 'ᄮ'),
+ (0x317C, 'M', 'ᄯ'),
+ (0x317D, 'M', 'ᄲ'),
+ (0x317E, 'M', 'ᄶ'),
+ (0x317F, 'M', 'ᅀ'),
+ (0x3180, 'M', 'ᅇ'),
+ (0x3181, 'M', 'ᅌ'),
+ (0x3182, 'M', 'ᇱ'),
+ (0x3183, 'M', 'ᇲ'),
+ (0x3184, 'M', 'ᅗ'),
+ (0x3185, 'M', 'ᅘ'),
+ (0x3186, 'M', 'ᅙ'),
+ (0x3187, 'M', 'ᆄ'),
+ (0x3188, 'M', 'ᆅ'),
+ ]
+
+def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3189, 'M', 'ᆈ'),
+ (0x318A, 'M', 'ᆑ'),
+ (0x318B, 'M', 'ᆒ'),
+ (0x318C, 'M', 'ᆔ'),
+ (0x318D, 'M', 'ᆞ'),
+ (0x318E, 'M', 'ᆡ'),
+ (0x318F, 'X'),
+ (0x3190, 'V'),
+ (0x3192, 'M', '一'),
+ (0x3193, 'M', '二'),
+ (0x3194, 'M', '三'),
+ (0x3195, 'M', '四'),
+ (0x3196, 'M', '上'),
+ (0x3197, 'M', '中'),
+ (0x3198, 'M', '下'),
+ (0x3199, 'M', '甲'),
+ (0x319A, 'M', '乙'),
+ (0x319B, 'M', '丙'),
+ (0x319C, 'M', '丁'),
+ (0x319D, 'M', '天'),
+ (0x319E, 'M', '地'),
+ (0x319F, 'M', '人'),
+ (0x31A0, 'V'),
+ (0x31E4, 'X'),
+ (0x31F0, 'V'),
+ (0x3200, '3', '(ᄀ)'),
+ (0x3201, '3', '(ᄂ)'),
+ (0x3202, '3', '(ᄃ)'),
+ (0x3203, '3', '(ᄅ)'),
+ (0x3204, '3', '(ᄆ)'),
+ (0x3205, '3', '(ᄇ)'),
+ (0x3206, '3', '(ᄉ)'),
+ (0x3207, '3', '(ᄋ)'),
+ (0x3208, '3', '(ᄌ)'),
+ (0x3209, '3', '(ᄎ)'),
+ (0x320A, '3', '(ᄏ)'),
+ (0x320B, '3', '(ᄐ)'),
+ (0x320C, '3', '(ᄑ)'),
+ (0x320D, '3', '(ᄒ)'),
+ (0x320E, '3', '(가)'),
+ (0x320F, '3', '(나)'),
+ (0x3210, '3', '(다)'),
+ (0x3211, '3', '(라)'),
+ (0x3212, '3', '(마)'),
+ (0x3213, '3', '(바)'),
+ (0x3214, '3', '(사)'),
+ (0x3215, '3', '(아)'),
+ (0x3216, '3', '(자)'),
+ (0x3217, '3', '(차)'),
+ (0x3218, '3', '(카)'),
+ (0x3219, '3', '(타)'),
+ (0x321A, '3', '(파)'),
+ (0x321B, '3', '(하)'),
+ (0x321C, '3', '(주)'),
+ (0x321D, '3', '(오전)'),
+ (0x321E, '3', '(오후)'),
+ (0x321F, 'X'),
+ (0x3220, '3', '(一)'),
+ (0x3221, '3', '(二)'),
+ (0x3222, '3', '(三)'),
+ (0x3223, '3', '(四)'),
+ (0x3224, '3', '(五)'),
+ (0x3225, '3', '(六)'),
+ (0x3226, '3', '(七)'),
+ (0x3227, '3', '(八)'),
+ (0x3228, '3', '(九)'),
+ (0x3229, '3', '(十)'),
+ (0x322A, '3', '(月)'),
+ (0x322B, '3', '(火)'),
+ (0x322C, '3', '(水)'),
+ (0x322D, '3', '(木)'),
+ (0x322E, '3', '(金)'),
+ (0x322F, '3', '(土)'),
+ (0x3230, '3', '(日)'),
+ (0x3231, '3', '(株)'),
+ (0x3232, '3', '(有)'),
+ (0x3233, '3', '(社)'),
+ (0x3234, '3', '(名)'),
+ (0x3235, '3', '(特)'),
+ (0x3236, '3', '(財)'),
+ (0x3237, '3', '(祝)'),
+ (0x3238, '3', '(労)'),
+ (0x3239, '3', '(代)'),
+ (0x323A, '3', '(呼)'),
+ (0x323B, '3', '(学)'),
+ (0x323C, '3', '(監)'),
+ (0x323D, '3', '(企)'),
+ (0x323E, '3', '(資)'),
+ (0x323F, '3', '(協)'),
+ (0x3240, '3', '(祭)'),
+ (0x3241, '3', '(休)'),
+ (0x3242, '3', '(自)'),
+ (0x3243, '3', '(至)'),
+ (0x3244, 'M', '問'),
+ (0x3245, 'M', '幼'),
+ (0x3246, 'M', '文'),
+ (0x3247, 'M', '箏'),
+ (0x3248, 'V'),
+ (0x3250, 'M', 'pte'),
+ (0x3251, 'M', '21'),
+ ]
+
+def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3252, 'M', '22'),
+ (0x3253, 'M', '23'),
+ (0x3254, 'M', '24'),
+ (0x3255, 'M', '25'),
+ (0x3256, 'M', '26'),
+ (0x3257, 'M', '27'),
+ (0x3258, 'M', '28'),
+ (0x3259, 'M', '29'),
+ (0x325A, 'M', '30'),
+ (0x325B, 'M', '31'),
+ (0x325C, 'M', '32'),
+ (0x325D, 'M', '33'),
+ (0x325E, 'M', '34'),
+ (0x325F, 'M', '35'),
+ (0x3260, 'M', 'ᄀ'),
+ (0x3261, 'M', 'ᄂ'),
+ (0x3262, 'M', 'ᄃ'),
+ (0x3263, 'M', 'ᄅ'),
+ (0x3264, 'M', 'ᄆ'),
+ (0x3265, 'M', 'ᄇ'),
+ (0x3266, 'M', 'ᄉ'),
+ (0x3267, 'M', 'ᄋ'),
+ (0x3268, 'M', 'ᄌ'),
+ (0x3269, 'M', 'ᄎ'),
+ (0x326A, 'M', 'ᄏ'),
+ (0x326B, 'M', 'ᄐ'),
+ (0x326C, 'M', 'ᄑ'),
+ (0x326D, 'M', 'ᄒ'),
+ (0x326E, 'M', '가'),
+ (0x326F, 'M', '나'),
+ (0x3270, 'M', '다'),
+ (0x3271, 'M', '라'),
+ (0x3272, 'M', '마'),
+ (0x3273, 'M', '바'),
+ (0x3274, 'M', '사'),
+ (0x3275, 'M', '아'),
+ (0x3276, 'M', '자'),
+ (0x3277, 'M', '차'),
+ (0x3278, 'M', '카'),
+ (0x3279, 'M', '타'),
+ (0x327A, 'M', '파'),
+ (0x327B, 'M', '하'),
+ (0x327C, 'M', '참고'),
+ (0x327D, 'M', '주의'),
+ (0x327E, 'M', '우'),
+ (0x327F, 'V'),
+ (0x3280, 'M', '一'),
+ (0x3281, 'M', '二'),
+ (0x3282, 'M', '三'),
+ (0x3283, 'M', '四'),
+ (0x3284, 'M', '五'),
+ (0x3285, 'M', '六'),
+ (0x3286, 'M', '七'),
+ (0x3287, 'M', '八'),
+ (0x3288, 'M', '九'),
+ (0x3289, 'M', '十'),
+ (0x328A, 'M', '月'),
+ (0x328B, 'M', '火'),
+ (0x328C, 'M', '水'),
+ (0x328D, 'M', '木'),
+ (0x328E, 'M', '金'),
+ (0x328F, 'M', '土'),
+ (0x3290, 'M', '日'),
+ (0x3291, 'M', '株'),
+ (0x3292, 'M', '有'),
+ (0x3293, 'M', '社'),
+ (0x3294, 'M', '名'),
+ (0x3295, 'M', '特'),
+ (0x3296, 'M', '財'),
+ (0x3297, 'M', '祝'),
+ (0x3298, 'M', '労'),
+ (0x3299, 'M', '秘'),
+ (0x329A, 'M', '男'),
+ (0x329B, 'M', '女'),
+ (0x329C, 'M', '適'),
+ (0x329D, 'M', '優'),
+ (0x329E, 'M', '印'),
+ (0x329F, 'M', '注'),
+ (0x32A0, 'M', '項'),
+ (0x32A1, 'M', '休'),
+ (0x32A2, 'M', '写'),
+ (0x32A3, 'M', '正'),
+ (0x32A4, 'M', '上'),
+ (0x32A5, 'M', '中'),
+ (0x32A6, 'M', '下'),
+ (0x32A7, 'M', '左'),
+ (0x32A8, 'M', '右'),
+ (0x32A9, 'M', '医'),
+ (0x32AA, 'M', '宗'),
+ (0x32AB, 'M', '学'),
+ (0x32AC, 'M', '監'),
+ (0x32AD, 'M', '企'),
+ (0x32AE, 'M', '資'),
+ (0x32AF, 'M', '協'),
+ (0x32B0, 'M', '夜'),
+ (0x32B1, 'M', '36'),
+ (0x32B2, 'M', '37'),
+ (0x32B3, 'M', '38'),
+ (0x32B4, 'M', '39'),
+ (0x32B5, 'M', '40'),
+ ]
+
+def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x32B6, 'M', '41'),
+ (0x32B7, 'M', '42'),
+ (0x32B8, 'M', '43'),
+ (0x32B9, 'M', '44'),
+ (0x32BA, 'M', '45'),
+ (0x32BB, 'M', '46'),
+ (0x32BC, 'M', '47'),
+ (0x32BD, 'M', '48'),
+ (0x32BE, 'M', '49'),
+ (0x32BF, 'M', '50'),
+ (0x32C0, 'M', '1月'),
+ (0x32C1, 'M', '2月'),
+ (0x32C2, 'M', '3月'),
+ (0x32C3, 'M', '4月'),
+ (0x32C4, 'M', '5月'),
+ (0x32C5, 'M', '6月'),
+ (0x32C6, 'M', '7月'),
+ (0x32C7, 'M', '8月'),
+ (0x32C8, 'M', '9月'),
+ (0x32C9, 'M', '10月'),
+ (0x32CA, 'M', '11月'),
+ (0x32CB, 'M', '12月'),
+ (0x32CC, 'M', 'hg'),
+ (0x32CD, 'M', 'erg'),
+ (0x32CE, 'M', 'ev'),
+ (0x32CF, 'M', 'ltd'),
+ (0x32D0, 'M', 'ア'),
+ (0x32D1, 'M', 'イ'),
+ (0x32D2, 'M', 'ウ'),
+ (0x32D3, 'M', 'エ'),
+ (0x32D4, 'M', 'オ'),
+ (0x32D5, 'M', 'カ'),
+ (0x32D6, 'M', 'キ'),
+ (0x32D7, 'M', 'ク'),
+ (0x32D8, 'M', 'ケ'),
+ (0x32D9, 'M', 'コ'),
+ (0x32DA, 'M', 'サ'),
+ (0x32DB, 'M', 'シ'),
+ (0x32DC, 'M', 'ス'),
+ (0x32DD, 'M', 'セ'),
+ (0x32DE, 'M', 'ソ'),
+ (0x32DF, 'M', 'タ'),
+ (0x32E0, 'M', 'チ'),
+ (0x32E1, 'M', 'ツ'),
+ (0x32E2, 'M', 'テ'),
+ (0x32E3, 'M', 'ト'),
+ (0x32E4, 'M', 'ナ'),
+ (0x32E5, 'M', 'ニ'),
+ (0x32E6, 'M', 'ヌ'),
+ (0x32E7, 'M', 'ネ'),
+ (0x32E8, 'M', 'ノ'),
+ (0x32E9, 'M', 'ハ'),
+ (0x32EA, 'M', 'ヒ'),
+ (0x32EB, 'M', 'フ'),
+ (0x32EC, 'M', 'ヘ'),
+ (0x32ED, 'M', 'ホ'),
+ (0x32EE, 'M', 'マ'),
+ (0x32EF, 'M', 'ミ'),
+ (0x32F0, 'M', 'ム'),
+ (0x32F1, 'M', 'メ'),
+ (0x32F2, 'M', 'モ'),
+ (0x32F3, 'M', 'ヤ'),
+ (0x32F4, 'M', 'ユ'),
+ (0x32F5, 'M', 'ヨ'),
+ (0x32F6, 'M', 'ラ'),
+ (0x32F7, 'M', 'リ'),
+ (0x32F8, 'M', 'ル'),
+ (0x32F9, 'M', 'レ'),
+ (0x32FA, 'M', 'ロ'),
+ (0x32FB, 'M', 'ワ'),
+ (0x32FC, 'M', 'ヰ'),
+ (0x32FD, 'M', 'ヱ'),
+ (0x32FE, 'M', 'ヲ'),
+ (0x32FF, 'M', '令和'),
+ (0x3300, 'M', 'アパート'),
+ (0x3301, 'M', 'アルファ'),
+ (0x3302, 'M', 'アンペア'),
+ (0x3303, 'M', 'アール'),
+ (0x3304, 'M', 'イニング'),
+ (0x3305, 'M', 'インチ'),
+ (0x3306, 'M', 'ウォン'),
+ (0x3307, 'M', 'エスクード'),
+ (0x3308, 'M', 'エーカー'),
+ (0x3309, 'M', 'オンス'),
+ (0x330A, 'M', 'オーム'),
+ (0x330B, 'M', 'カイリ'),
+ (0x330C, 'M', 'カラット'),
+ (0x330D, 'M', 'カロリー'),
+ (0x330E, 'M', 'ガロン'),
+ (0x330F, 'M', 'ガンマ'),
+ (0x3310, 'M', 'ギガ'),
+ (0x3311, 'M', 'ギニー'),
+ (0x3312, 'M', 'キュリー'),
+ (0x3313, 'M', 'ギルダー'),
+ (0x3314, 'M', 'キロ'),
+ (0x3315, 'M', 'キログラム'),
+ (0x3316, 'M', 'キロメートル'),
+ (0x3317, 'M', 'キロワット'),
+ (0x3318, 'M', 'グラム'),
+ (0x3319, 'M', 'グラムトン'),
+ ]
+
+def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x331A, 'M', 'クルゼイロ'),
+ (0x331B, 'M', 'クローネ'),
+ (0x331C, 'M', 'ケース'),
+ (0x331D, 'M', 'コルナ'),
+ (0x331E, 'M', 'コーポ'),
+ (0x331F, 'M', 'サイクル'),
+ (0x3320, 'M', 'サンチーム'),
+ (0x3321, 'M', 'シリング'),
+ (0x3322, 'M', 'センチ'),
+ (0x3323, 'M', 'セント'),
+ (0x3324, 'M', 'ダース'),
+ (0x3325, 'M', 'デシ'),
+ (0x3326, 'M', 'ドル'),
+ (0x3327, 'M', 'トン'),
+ (0x3328, 'M', 'ナノ'),
+ (0x3329, 'M', 'ノット'),
+ (0x332A, 'M', 'ハイツ'),
+ (0x332B, 'M', 'パーセント'),
+ (0x332C, 'M', 'パーツ'),
+ (0x332D, 'M', 'バーレル'),
+ (0x332E, 'M', 'ピアストル'),
+ (0x332F, 'M', 'ピクル'),
+ (0x3330, 'M', 'ピコ'),
+ (0x3331, 'M', 'ビル'),
+ (0x3332, 'M', 'ファラッド'),
+ (0x3333, 'M', 'フィート'),
+ (0x3334, 'M', 'ブッシェル'),
+ (0x3335, 'M', 'フラン'),
+ (0x3336, 'M', 'ヘクタール'),
+ (0x3337, 'M', 'ペソ'),
+ (0x3338, 'M', 'ペニヒ'),
+ (0x3339, 'M', 'ヘルツ'),
+ (0x333A, 'M', 'ペンス'),
+ (0x333B, 'M', 'ページ'),
+ (0x333C, 'M', 'ベータ'),
+ (0x333D, 'M', 'ポイント'),
+ (0x333E, 'M', 'ボルト'),
+ (0x333F, 'M', 'ホン'),
+ (0x3340, 'M', 'ポンド'),
+ (0x3341, 'M', 'ホール'),
+ (0x3342, 'M', 'ホーン'),
+ (0x3343, 'M', 'マイクロ'),
+ (0x3344, 'M', 'マイル'),
+ (0x3345, 'M', 'マッハ'),
+ (0x3346, 'M', 'マルク'),
+ (0x3347, 'M', 'マンション'),
+ (0x3348, 'M', 'ミクロン'),
+ (0x3349, 'M', 'ミリ'),
+ (0x334A, 'M', 'ミリバール'),
+ (0x334B, 'M', 'メガ'),
+ (0x334C, 'M', 'メガトン'),
+ (0x334D, 'M', 'メートル'),
+ (0x334E, 'M', 'ヤード'),
+ (0x334F, 'M', 'ヤール'),
+ (0x3350, 'M', 'ユアン'),
+ (0x3351, 'M', 'リットル'),
+ (0x3352, 'M', 'リラ'),
+ (0x3353, 'M', 'ルピー'),
+ (0x3354, 'M', 'ルーブル'),
+ (0x3355, 'M', 'レム'),
+ (0x3356, 'M', 'レントゲン'),
+ (0x3357, 'M', 'ワット'),
+ (0x3358, 'M', '0点'),
+ (0x3359, 'M', '1点'),
+ (0x335A, 'M', '2点'),
+ (0x335B, 'M', '3点'),
+ (0x335C, 'M', '4点'),
+ (0x335D, 'M', '5点'),
+ (0x335E, 'M', '6点'),
+ (0x335F, 'M', '7点'),
+ (0x3360, 'M', '8点'),
+ (0x3361, 'M', '9点'),
+ (0x3362, 'M', '10点'),
+ (0x3363, 'M', '11点'),
+ (0x3364, 'M', '12点'),
+ (0x3365, 'M', '13点'),
+ (0x3366, 'M', '14点'),
+ (0x3367, 'M', '15点'),
+ (0x3368, 'M', '16点'),
+ (0x3369, 'M', '17点'),
+ (0x336A, 'M', '18点'),
+ (0x336B, 'M', '19点'),
+ (0x336C, 'M', '20点'),
+ (0x336D, 'M', '21点'),
+ (0x336E, 'M', '22点'),
+ (0x336F, 'M', '23点'),
+ (0x3370, 'M', '24点'),
+ (0x3371, 'M', 'hpa'),
+ (0x3372, 'M', 'da'),
+ (0x3373, 'M', 'au'),
+ (0x3374, 'M', 'bar'),
+ (0x3375, 'M', 'ov'),
+ (0x3376, 'M', 'pc'),
+ (0x3377, 'M', 'dm'),
+ (0x3378, 'M', 'dm2'),
+ (0x3379, 'M', 'dm3'),
+ (0x337A, 'M', 'iu'),
+ (0x337B, 'M', '平成'),
+ (0x337C, 'M', '昭和'),
+ (0x337D, 'M', '大正'),
+ ]
+
+def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x337E, 'M', '明治'),
+ (0x337F, 'M', '株式会社'),
+ (0x3380, 'M', 'pa'),
+ (0x3381, 'M', 'na'),
+ (0x3382, 'M', 'μa'),
+ (0x3383, 'M', 'ma'),
+ (0x3384, 'M', 'ka'),
+ (0x3385, 'M', 'kb'),
+ (0x3386, 'M', 'mb'),
+ (0x3387, 'M', 'gb'),
+ (0x3388, 'M', 'cal'),
+ (0x3389, 'M', 'kcal'),
+ (0x338A, 'M', 'pf'),
+ (0x338B, 'M', 'nf'),
+ (0x338C, 'M', 'μf'),
+ (0x338D, 'M', 'μg'),
+ (0x338E, 'M', 'mg'),
+ (0x338F, 'M', 'kg'),
+ (0x3390, 'M', 'hz'),
+ (0x3391, 'M', 'khz'),
+ (0x3392, 'M', 'mhz'),
+ (0x3393, 'M', 'ghz'),
+ (0x3394, 'M', 'thz'),
+ (0x3395, 'M', 'μl'),
+ (0x3396, 'M', 'ml'),
+ (0x3397, 'M', 'dl'),
+ (0x3398, 'M', 'kl'),
+ (0x3399, 'M', 'fm'),
+ (0x339A, 'M', 'nm'),
+ (0x339B, 'M', 'μm'),
+ (0x339C, 'M', 'mm'),
+ (0x339D, 'M', 'cm'),
+ (0x339E, 'M', 'km'),
+ (0x339F, 'M', 'mm2'),
+ (0x33A0, 'M', 'cm2'),
+ (0x33A1, 'M', 'm2'),
+ (0x33A2, 'M', 'km2'),
+ (0x33A3, 'M', 'mm3'),
+ (0x33A4, 'M', 'cm3'),
+ (0x33A5, 'M', 'm3'),
+ (0x33A6, 'M', 'km3'),
+ (0x33A7, 'M', 'm∕s'),
+ (0x33A8, 'M', 'm∕s2'),
+ (0x33A9, 'M', 'pa'),
+ (0x33AA, 'M', 'kpa'),
+ (0x33AB, 'M', 'mpa'),
+ (0x33AC, 'M', 'gpa'),
+ (0x33AD, 'M', 'rad'),
+ (0x33AE, 'M', 'rad∕s'),
+ (0x33AF, 'M', 'rad∕s2'),
+ (0x33B0, 'M', 'ps'),
+ (0x33B1, 'M', 'ns'),
+ (0x33B2, 'M', 'μs'),
+ (0x33B3, 'M', 'ms'),
+ (0x33B4, 'M', 'pv'),
+ (0x33B5, 'M', 'nv'),
+ (0x33B6, 'M', 'μv'),
+ (0x33B7, 'M', 'mv'),
+ (0x33B8, 'M', 'kv'),
+ (0x33B9, 'M', 'mv'),
+ (0x33BA, 'M', 'pw'),
+ (0x33BB, 'M', 'nw'),
+ (0x33BC, 'M', 'μw'),
+ (0x33BD, 'M', 'mw'),
+ (0x33BE, 'M', 'kw'),
+ (0x33BF, 'M', 'mw'),
+ (0x33C0, 'M', 'kω'),
+ (0x33C1, 'M', 'mω'),
+ (0x33C2, 'X'),
+ (0x33C3, 'M', 'bq'),
+ (0x33C4, 'M', 'cc'),
+ (0x33C5, 'M', 'cd'),
+ (0x33C6, 'M', 'c∕kg'),
+ (0x33C7, 'X'),
+ (0x33C8, 'M', 'db'),
+ (0x33C9, 'M', 'gy'),
+ (0x33CA, 'M', 'ha'),
+ (0x33CB, 'M', 'hp'),
+ (0x33CC, 'M', 'in'),
+ (0x33CD, 'M', 'kk'),
+ (0x33CE, 'M', 'km'),
+ (0x33CF, 'M', 'kt'),
+ (0x33D0, 'M', 'lm'),
+ (0x33D1, 'M', 'ln'),
+ (0x33D2, 'M', 'log'),
+ (0x33D3, 'M', 'lx'),
+ (0x33D4, 'M', 'mb'),
+ (0x33D5, 'M', 'mil'),
+ (0x33D6, 'M', 'mol'),
+ (0x33D7, 'M', 'ph'),
+ (0x33D8, 'X'),
+ (0x33D9, 'M', 'ppm'),
+ (0x33DA, 'M', 'pr'),
+ (0x33DB, 'M', 'sr'),
+ (0x33DC, 'M', 'sv'),
+ (0x33DD, 'M', 'wb'),
+ (0x33DE, 'M', 'v∕m'),
+ (0x33DF, 'M', 'a∕m'),
+ (0x33E0, 'M', '1日'),
+ (0x33E1, 'M', '2日'),
+ ]
+
+def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x33E2, 'M', '3日'),
+ (0x33E3, 'M', '4日'),
+ (0x33E4, 'M', '5日'),
+ (0x33E5, 'M', '6日'),
+ (0x33E6, 'M', '7日'),
+ (0x33E7, 'M', '8日'),
+ (0x33E8, 'M', '9日'),
+ (0x33E9, 'M', '10日'),
+ (0x33EA, 'M', '11日'),
+ (0x33EB, 'M', '12日'),
+ (0x33EC, 'M', '13日'),
+ (0x33ED, 'M', '14日'),
+ (0x33EE, 'M', '15日'),
+ (0x33EF, 'M', '16日'),
+ (0x33F0, 'M', '17日'),
+ (0x33F1, 'M', '18日'),
+ (0x33F2, 'M', '19日'),
+ (0x33F3, 'M', '20日'),
+ (0x33F4, 'M', '21日'),
+ (0x33F5, 'M', '22日'),
+ (0x33F6, 'M', '23日'),
+ (0x33F7, 'M', '24日'),
+ (0x33F8, 'M', '25日'),
+ (0x33F9, 'M', '26日'),
+ (0x33FA, 'M', '27日'),
+ (0x33FB, 'M', '28日'),
+ (0x33FC, 'M', '29日'),
+ (0x33FD, 'M', '30日'),
+ (0x33FE, 'M', '31日'),
+ (0x33FF, 'M', 'gal'),
+ (0x3400, 'V'),
+ (0xA48D, 'X'),
+ (0xA490, 'V'),
+ (0xA4C7, 'X'),
+ (0xA4D0, 'V'),
+ (0xA62C, 'X'),
+ (0xA640, 'M', 'ꙁ'),
+ (0xA641, 'V'),
+ (0xA642, 'M', 'ꙃ'),
+ (0xA643, 'V'),
+ (0xA644, 'M', 'ꙅ'),
+ (0xA645, 'V'),
+ (0xA646, 'M', 'ꙇ'),
+ (0xA647, 'V'),
+ (0xA648, 'M', 'ꙉ'),
+ (0xA649, 'V'),
+ (0xA64A, 'M', 'ꙋ'),
+ (0xA64B, 'V'),
+ (0xA64C, 'M', 'ꙍ'),
+ (0xA64D, 'V'),
+ (0xA64E, 'M', 'ꙏ'),
+ (0xA64F, 'V'),
+ (0xA650, 'M', 'ꙑ'),
+ (0xA651, 'V'),
+ (0xA652, 'M', 'ꙓ'),
+ (0xA653, 'V'),
+ (0xA654, 'M', 'ꙕ'),
+ (0xA655, 'V'),
+ (0xA656, 'M', 'ꙗ'),
+ (0xA657, 'V'),
+ (0xA658, 'M', 'ꙙ'),
+ (0xA659, 'V'),
+ (0xA65A, 'M', 'ꙛ'),
+ (0xA65B, 'V'),
+ (0xA65C, 'M', 'ꙝ'),
+ (0xA65D, 'V'),
+ (0xA65E, 'M', 'ꙟ'),
+ (0xA65F, 'V'),
+ (0xA660, 'M', 'ꙡ'),
+ (0xA661, 'V'),
+ (0xA662, 'M', 'ꙣ'),
+ (0xA663, 'V'),
+ (0xA664, 'M', 'ꙥ'),
+ (0xA665, 'V'),
+ (0xA666, 'M', 'ꙧ'),
+ (0xA667, 'V'),
+ (0xA668, 'M', 'ꙩ'),
+ (0xA669, 'V'),
+ (0xA66A, 'M', 'ꙫ'),
+ (0xA66B, 'V'),
+ (0xA66C, 'M', 'ꙭ'),
+ (0xA66D, 'V'),
+ (0xA680, 'M', 'ꚁ'),
+ (0xA681, 'V'),
+ (0xA682, 'M', 'ꚃ'),
+ (0xA683, 'V'),
+ (0xA684, 'M', 'ꚅ'),
+ (0xA685, 'V'),
+ (0xA686, 'M', 'ꚇ'),
+ (0xA687, 'V'),
+ (0xA688, 'M', 'ꚉ'),
+ (0xA689, 'V'),
+ (0xA68A, 'M', 'ꚋ'),
+ (0xA68B, 'V'),
+ (0xA68C, 'M', 'ꚍ'),
+ (0xA68D, 'V'),
+ (0xA68E, 'M', 'ꚏ'),
+ (0xA68F, 'V'),
+ (0xA690, 'M', 'ꚑ'),
+ (0xA691, 'V'),
+ ]
+
+def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA692, 'M', 'ꚓ'),
+ (0xA693, 'V'),
+ (0xA694, 'M', 'ꚕ'),
+ (0xA695, 'V'),
+ (0xA696, 'M', 'ꚗ'),
+ (0xA697, 'V'),
+ (0xA698, 'M', 'ꚙ'),
+ (0xA699, 'V'),
+ (0xA69A, 'M', 'ꚛ'),
+ (0xA69B, 'V'),
+ (0xA69C, 'M', 'ъ'),
+ (0xA69D, 'M', 'ь'),
+ (0xA69E, 'V'),
+ (0xA6F8, 'X'),
+ (0xA700, 'V'),
+ (0xA722, 'M', 'ꜣ'),
+ (0xA723, 'V'),
+ (0xA724, 'M', 'ꜥ'),
+ (0xA725, 'V'),
+ (0xA726, 'M', 'ꜧ'),
+ (0xA727, 'V'),
+ (0xA728, 'M', 'ꜩ'),
+ (0xA729, 'V'),
+ (0xA72A, 'M', 'ꜫ'),
+ (0xA72B, 'V'),
+ (0xA72C, 'M', 'ꜭ'),
+ (0xA72D, 'V'),
+ (0xA72E, 'M', 'ꜯ'),
+ (0xA72F, 'V'),
+ (0xA732, 'M', 'ꜳ'),
+ (0xA733, 'V'),
+ (0xA734, 'M', 'ꜵ'),
+ (0xA735, 'V'),
+ (0xA736, 'M', 'ꜷ'),
+ (0xA737, 'V'),
+ (0xA738, 'M', 'ꜹ'),
+ (0xA739, 'V'),
+ (0xA73A, 'M', 'ꜻ'),
+ (0xA73B, 'V'),
+ (0xA73C, 'M', 'ꜽ'),
+ (0xA73D, 'V'),
+ (0xA73E, 'M', 'ꜿ'),
+ (0xA73F, 'V'),
+ (0xA740, 'M', 'ꝁ'),
+ (0xA741, 'V'),
+ (0xA742, 'M', 'ꝃ'),
+ (0xA743, 'V'),
+ (0xA744, 'M', 'ꝅ'),
+ (0xA745, 'V'),
+ (0xA746, 'M', 'ꝇ'),
+ (0xA747, 'V'),
+ (0xA748, 'M', 'ꝉ'),
+ (0xA749, 'V'),
+ (0xA74A, 'M', 'ꝋ'),
+ (0xA74B, 'V'),
+ (0xA74C, 'M', 'ꝍ'),
+ (0xA74D, 'V'),
+ (0xA74E, 'M', 'ꝏ'),
+ (0xA74F, 'V'),
+ (0xA750, 'M', 'ꝑ'),
+ (0xA751, 'V'),
+ (0xA752, 'M', 'ꝓ'),
+ (0xA753, 'V'),
+ (0xA754, 'M', 'ꝕ'),
+ (0xA755, 'V'),
+ (0xA756, 'M', 'ꝗ'),
+ (0xA757, 'V'),
+ (0xA758, 'M', 'ꝙ'),
+ (0xA759, 'V'),
+ (0xA75A, 'M', 'ꝛ'),
+ (0xA75B, 'V'),
+ (0xA75C, 'M', 'ꝝ'),
+ (0xA75D, 'V'),
+ (0xA75E, 'M', 'ꝟ'),
+ (0xA75F, 'V'),
+ (0xA760, 'M', 'ꝡ'),
+ (0xA761, 'V'),
+ (0xA762, 'M', 'ꝣ'),
+ (0xA763, 'V'),
+ (0xA764, 'M', 'ꝥ'),
+ (0xA765, 'V'),
+ (0xA766, 'M', 'ꝧ'),
+ (0xA767, 'V'),
+ (0xA768, 'M', 'ꝩ'),
+ (0xA769, 'V'),
+ (0xA76A, 'M', 'ꝫ'),
+ (0xA76B, 'V'),
+ (0xA76C, 'M', 'ꝭ'),
+ (0xA76D, 'V'),
+ (0xA76E, 'M', 'ꝯ'),
+ (0xA76F, 'V'),
+ (0xA770, 'M', 'ꝯ'),
+ (0xA771, 'V'),
+ (0xA779, 'M', 'ꝺ'),
+ (0xA77A, 'V'),
+ (0xA77B, 'M', 'ꝼ'),
+ (0xA77C, 'V'),
+ (0xA77D, 'M', 'ᵹ'),
+ (0xA77E, 'M', 'ꝿ'),
+ (0xA77F, 'V'),
+ ]
+
+def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA780, 'M', 'ꞁ'),
+ (0xA781, 'V'),
+ (0xA782, 'M', 'ꞃ'),
+ (0xA783, 'V'),
+ (0xA784, 'M', 'ꞅ'),
+ (0xA785, 'V'),
+ (0xA786, 'M', 'ꞇ'),
+ (0xA787, 'V'),
+ (0xA78B, 'M', 'ꞌ'),
+ (0xA78C, 'V'),
+ (0xA78D, 'M', 'ɥ'),
+ (0xA78E, 'V'),
+ (0xA790, 'M', 'ꞑ'),
+ (0xA791, 'V'),
+ (0xA792, 'M', 'ꞓ'),
+ (0xA793, 'V'),
+ (0xA796, 'M', 'ꞗ'),
+ (0xA797, 'V'),
+ (0xA798, 'M', 'ꞙ'),
+ (0xA799, 'V'),
+ (0xA79A, 'M', 'ꞛ'),
+ (0xA79B, 'V'),
+ (0xA79C, 'M', 'ꞝ'),
+ (0xA79D, 'V'),
+ (0xA79E, 'M', 'ꞟ'),
+ (0xA79F, 'V'),
+ (0xA7A0, 'M', 'ꞡ'),
+ (0xA7A1, 'V'),
+ (0xA7A2, 'M', 'ꞣ'),
+ (0xA7A3, 'V'),
+ (0xA7A4, 'M', 'ꞥ'),
+ (0xA7A5, 'V'),
+ (0xA7A6, 'M', 'ꞧ'),
+ (0xA7A7, 'V'),
+ (0xA7A8, 'M', 'ꞩ'),
+ (0xA7A9, 'V'),
+ (0xA7AA, 'M', 'ɦ'),
+ (0xA7AB, 'M', 'ɜ'),
+ (0xA7AC, 'M', 'ɡ'),
+ (0xA7AD, 'M', 'ɬ'),
+ (0xA7AE, 'M', 'ɪ'),
+ (0xA7AF, 'V'),
+ (0xA7B0, 'M', 'ʞ'),
+ (0xA7B1, 'M', 'ʇ'),
+ (0xA7B2, 'M', 'ʝ'),
+ (0xA7B3, 'M', 'ꭓ'),
+ (0xA7B4, 'M', 'ꞵ'),
+ (0xA7B5, 'V'),
+ (0xA7B6, 'M', 'ꞷ'),
+ (0xA7B7, 'V'),
+ (0xA7B8, 'M', 'ꞹ'),
+ (0xA7B9, 'V'),
+ (0xA7BA, 'M', 'ꞻ'),
+ (0xA7BB, 'V'),
+ (0xA7BC, 'M', 'ꞽ'),
+ (0xA7BD, 'V'),
+ (0xA7BE, 'M', 'ꞿ'),
+ (0xA7BF, 'V'),
+ (0xA7C0, 'M', 'ꟁ'),
+ (0xA7C1, 'V'),
+ (0xA7C2, 'M', 'ꟃ'),
+ (0xA7C3, 'V'),
+ (0xA7C4, 'M', 'ꞔ'),
+ (0xA7C5, 'M', 'ʂ'),
+ (0xA7C6, 'M', 'ᶎ'),
+ (0xA7C7, 'M', 'ꟈ'),
+ (0xA7C8, 'V'),
+ (0xA7C9, 'M', 'ꟊ'),
+ (0xA7CA, 'V'),
+ (0xA7CB, 'X'),
+ (0xA7D0, 'M', 'ꟑ'),
+ (0xA7D1, 'V'),
+ (0xA7D2, 'X'),
+ (0xA7D3, 'V'),
+ (0xA7D4, 'X'),
+ (0xA7D5, 'V'),
+ (0xA7D6, 'M', 'ꟗ'),
+ (0xA7D7, 'V'),
+ (0xA7D8, 'M', 'ꟙ'),
+ (0xA7D9, 'V'),
+ (0xA7DA, 'X'),
+ (0xA7F2, 'M', 'c'),
+ (0xA7F3, 'M', 'f'),
+ (0xA7F4, 'M', 'q'),
+ (0xA7F5, 'M', 'ꟶ'),
+ (0xA7F6, 'V'),
+ (0xA7F8, 'M', 'ħ'),
+ (0xA7F9, 'M', 'œ'),
+ (0xA7FA, 'V'),
+ (0xA82D, 'X'),
+ (0xA830, 'V'),
+ (0xA83A, 'X'),
+ (0xA840, 'V'),
+ (0xA878, 'X'),
+ (0xA880, 'V'),
+ (0xA8C6, 'X'),
+ (0xA8CE, 'V'),
+ (0xA8DA, 'X'),
+ (0xA8E0, 'V'),
+ (0xA954, 'X'),
+ ]
+
+def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA95F, 'V'),
+ (0xA97D, 'X'),
+ (0xA980, 'V'),
+ (0xA9CE, 'X'),
+ (0xA9CF, 'V'),
+ (0xA9DA, 'X'),
+ (0xA9DE, 'V'),
+ (0xA9FF, 'X'),
+ (0xAA00, 'V'),
+ (0xAA37, 'X'),
+ (0xAA40, 'V'),
+ (0xAA4E, 'X'),
+ (0xAA50, 'V'),
+ (0xAA5A, 'X'),
+ (0xAA5C, 'V'),
+ (0xAAC3, 'X'),
+ (0xAADB, 'V'),
+ (0xAAF7, 'X'),
+ (0xAB01, 'V'),
+ (0xAB07, 'X'),
+ (0xAB09, 'V'),
+ (0xAB0F, 'X'),
+ (0xAB11, 'V'),
+ (0xAB17, 'X'),
+ (0xAB20, 'V'),
+ (0xAB27, 'X'),
+ (0xAB28, 'V'),
+ (0xAB2F, 'X'),
+ (0xAB30, 'V'),
+ (0xAB5C, 'M', 'ꜧ'),
+ (0xAB5D, 'M', 'ꬷ'),
+ (0xAB5E, 'M', 'ɫ'),
+ (0xAB5F, 'M', 'ꭒ'),
+ (0xAB60, 'V'),
+ (0xAB69, 'M', 'ʍ'),
+ (0xAB6A, 'V'),
+ (0xAB6C, 'X'),
+ (0xAB70, 'M', 'Ꭰ'),
+ (0xAB71, 'M', 'Ꭱ'),
+ (0xAB72, 'M', 'Ꭲ'),
+ (0xAB73, 'M', 'Ꭳ'),
+ (0xAB74, 'M', 'Ꭴ'),
+ (0xAB75, 'M', 'Ꭵ'),
+ (0xAB76, 'M', 'Ꭶ'),
+ (0xAB77, 'M', 'Ꭷ'),
+ (0xAB78, 'M', 'Ꭸ'),
+ (0xAB79, 'M', 'Ꭹ'),
+ (0xAB7A, 'M', 'Ꭺ'),
+ (0xAB7B, 'M', 'Ꭻ'),
+ (0xAB7C, 'M', 'Ꭼ'),
+ (0xAB7D, 'M', 'Ꭽ'),
+ (0xAB7E, 'M', 'Ꭾ'),
+ (0xAB7F, 'M', 'Ꭿ'),
+ (0xAB80, 'M', 'Ꮀ'),
+ (0xAB81, 'M', 'Ꮁ'),
+ (0xAB82, 'M', 'Ꮂ'),
+ (0xAB83, 'M', 'Ꮃ'),
+ (0xAB84, 'M', 'Ꮄ'),
+ (0xAB85, 'M', 'Ꮅ'),
+ (0xAB86, 'M', 'Ꮆ'),
+ (0xAB87, 'M', 'Ꮇ'),
+ (0xAB88, 'M', 'Ꮈ'),
+ (0xAB89, 'M', 'Ꮉ'),
+ (0xAB8A, 'M', 'Ꮊ'),
+ (0xAB8B, 'M', 'Ꮋ'),
+ (0xAB8C, 'M', 'Ꮌ'),
+ (0xAB8D, 'M', 'Ꮍ'),
+ (0xAB8E, 'M', 'Ꮎ'),
+ (0xAB8F, 'M', 'Ꮏ'),
+ (0xAB90, 'M', 'Ꮐ'),
+ (0xAB91, 'M', 'Ꮑ'),
+ (0xAB92, 'M', 'Ꮒ'),
+ (0xAB93, 'M', 'Ꮓ'),
+ (0xAB94, 'M', 'Ꮔ'),
+ (0xAB95, 'M', 'Ꮕ'),
+ (0xAB96, 'M', 'Ꮖ'),
+ (0xAB97, 'M', 'Ꮗ'),
+ (0xAB98, 'M', 'Ꮘ'),
+ (0xAB99, 'M', 'Ꮙ'),
+ (0xAB9A, 'M', 'Ꮚ'),
+ (0xAB9B, 'M', 'Ꮛ'),
+ (0xAB9C, 'M', 'Ꮜ'),
+ (0xAB9D, 'M', 'Ꮝ'),
+ (0xAB9E, 'M', 'Ꮞ'),
+ (0xAB9F, 'M', 'Ꮟ'),
+ (0xABA0, 'M', 'Ꮠ'),
+ (0xABA1, 'M', 'Ꮡ'),
+ (0xABA2, 'M', 'Ꮢ'),
+ (0xABA3, 'M', 'Ꮣ'),
+ (0xABA4, 'M', 'Ꮤ'),
+ (0xABA5, 'M', 'Ꮥ'),
+ (0xABA6, 'M', 'Ꮦ'),
+ (0xABA7, 'M', 'Ꮧ'),
+ (0xABA8, 'M', 'Ꮨ'),
+ (0xABA9, 'M', 'Ꮩ'),
+ (0xABAA, 'M', 'Ꮪ'),
+ (0xABAB, 'M', 'Ꮫ'),
+ (0xABAC, 'M', 'Ꮬ'),
+ (0xABAD, 'M', 'Ꮭ'),
+ (0xABAE, 'M', 'Ꮮ'),
+ ]
+
+def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xABAF, 'M', 'Ꮯ'),
+ (0xABB0, 'M', 'Ꮰ'),
+ (0xABB1, 'M', 'Ꮱ'),
+ (0xABB2, 'M', 'Ꮲ'),
+ (0xABB3, 'M', 'Ꮳ'),
+ (0xABB4, 'M', 'Ꮴ'),
+ (0xABB5, 'M', 'Ꮵ'),
+ (0xABB6, 'M', 'Ꮶ'),
+ (0xABB7, 'M', 'Ꮷ'),
+ (0xABB8, 'M', 'Ꮸ'),
+ (0xABB9, 'M', 'Ꮹ'),
+ (0xABBA, 'M', 'Ꮺ'),
+ (0xABBB, 'M', 'Ꮻ'),
+ (0xABBC, 'M', 'Ꮼ'),
+ (0xABBD, 'M', 'Ꮽ'),
+ (0xABBE, 'M', 'Ꮾ'),
+ (0xABBF, 'M', 'Ꮿ'),
+ (0xABC0, 'V'),
+ (0xABEE, 'X'),
+ (0xABF0, 'V'),
+ (0xABFA, 'X'),
+ (0xAC00, 'V'),
+ (0xD7A4, 'X'),
+ (0xD7B0, 'V'),
+ (0xD7C7, 'X'),
+ (0xD7CB, 'V'),
+ (0xD7FC, 'X'),
+ (0xF900, 'M', '豈'),
+ (0xF901, 'M', '更'),
+ (0xF902, 'M', '車'),
+ (0xF903, 'M', '賈'),
+ (0xF904, 'M', '滑'),
+ (0xF905, 'M', '串'),
+ (0xF906, 'M', '句'),
+ (0xF907, 'M', '龜'),
+ (0xF909, 'M', '契'),
+ (0xF90A, 'M', '金'),
+ (0xF90B, 'M', '喇'),
+ (0xF90C, 'M', '奈'),
+ (0xF90D, 'M', '懶'),
+ (0xF90E, 'M', '癩'),
+ (0xF90F, 'M', '羅'),
+ (0xF910, 'M', '蘿'),
+ (0xF911, 'M', '螺'),
+ (0xF912, 'M', '裸'),
+ (0xF913, 'M', '邏'),
+ (0xF914, 'M', '樂'),
+ (0xF915, 'M', '洛'),
+ (0xF916, 'M', '烙'),
+ (0xF917, 'M', '珞'),
+ (0xF918, 'M', '落'),
+ (0xF919, 'M', '酪'),
+ (0xF91A, 'M', '駱'),
+ (0xF91B, 'M', '亂'),
+ (0xF91C, 'M', '卵'),
+ (0xF91D, 'M', '欄'),
+ (0xF91E, 'M', '爛'),
+ (0xF91F, 'M', '蘭'),
+ (0xF920, 'M', '鸞'),
+ (0xF921, 'M', '嵐'),
+ (0xF922, 'M', '濫'),
+ (0xF923, 'M', '藍'),
+ (0xF924, 'M', '襤'),
+ (0xF925, 'M', '拉'),
+ (0xF926, 'M', '臘'),
+ (0xF927, 'M', '蠟'),
+ (0xF928, 'M', '廊'),
+ (0xF929, 'M', '朗'),
+ (0xF92A, 'M', '浪'),
+ (0xF92B, 'M', '狼'),
+ (0xF92C, 'M', '郎'),
+ (0xF92D, 'M', '來'),
+ (0xF92E, 'M', '冷'),
+ (0xF92F, 'M', '勞'),
+ (0xF930, 'M', '擄'),
+ (0xF931, 'M', '櫓'),
+ (0xF932, 'M', '爐'),
+ (0xF933, 'M', '盧'),
+ (0xF934, 'M', '老'),
+ (0xF935, 'M', '蘆'),
+ (0xF936, 'M', '虜'),
+ (0xF937, 'M', '路'),
+ (0xF938, 'M', '露'),
+ (0xF939, 'M', '魯'),
+ (0xF93A, 'M', '鷺'),
+ (0xF93B, 'M', '碌'),
+ (0xF93C, 'M', '祿'),
+ (0xF93D, 'M', '綠'),
+ (0xF93E, 'M', '菉'),
+ (0xF93F, 'M', '錄'),
+ (0xF940, 'M', '鹿'),
+ (0xF941, 'M', '論'),
+ (0xF942, 'M', '壟'),
+ (0xF943, 'M', '弄'),
+ (0xF944, 'M', '籠'),
+ (0xF945, 'M', '聾'),
+ (0xF946, 'M', '牢'),
+ (0xF947, 'M', '磊'),
+ (0xF948, 'M', '賂'),
+ (0xF949, 'M', '雷'),
+ ]
+
+def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF94A, 'M', '壘'),
+ (0xF94B, 'M', '屢'),
+ (0xF94C, 'M', '樓'),
+ (0xF94D, 'M', '淚'),
+ (0xF94E, 'M', '漏'),
+ (0xF94F, 'M', '累'),
+ (0xF950, 'M', '縷'),
+ (0xF951, 'M', '陋'),
+ (0xF952, 'M', '勒'),
+ (0xF953, 'M', '肋'),
+ (0xF954, 'M', '凜'),
+ (0xF955, 'M', '凌'),
+ (0xF956, 'M', '稜'),
+ (0xF957, 'M', '綾'),
+ (0xF958, 'M', '菱'),
+ (0xF959, 'M', '陵'),
+ (0xF95A, 'M', '讀'),
+ (0xF95B, 'M', '拏'),
+ (0xF95C, 'M', '樂'),
+ (0xF95D, 'M', '諾'),
+ (0xF95E, 'M', '丹'),
+ (0xF95F, 'M', '寧'),
+ (0xF960, 'M', '怒'),
+ (0xF961, 'M', '率'),
+ (0xF962, 'M', '異'),
+ (0xF963, 'M', '北'),
+ (0xF964, 'M', '磻'),
+ (0xF965, 'M', '便'),
+ (0xF966, 'M', '復'),
+ (0xF967, 'M', '不'),
+ (0xF968, 'M', '泌'),
+ (0xF969, 'M', '數'),
+ (0xF96A, 'M', '索'),
+ (0xF96B, 'M', '參'),
+ (0xF96C, 'M', '塞'),
+ (0xF96D, 'M', '省'),
+ (0xF96E, 'M', '葉'),
+ (0xF96F, 'M', '說'),
+ (0xF970, 'M', '殺'),
+ (0xF971, 'M', '辰'),
+ (0xF972, 'M', '沈'),
+ (0xF973, 'M', '拾'),
+ (0xF974, 'M', '若'),
+ (0xF975, 'M', '掠'),
+ (0xF976, 'M', '略'),
+ (0xF977, 'M', '亮'),
+ (0xF978, 'M', '兩'),
+ (0xF979, 'M', '凉'),
+ (0xF97A, 'M', '梁'),
+ (0xF97B, 'M', '糧'),
+ (0xF97C, 'M', '良'),
+ (0xF97D, 'M', '諒'),
+ (0xF97E, 'M', '量'),
+ (0xF97F, 'M', '勵'),
+ (0xF980, 'M', '呂'),
+ (0xF981, 'M', '女'),
+ (0xF982, 'M', '廬'),
+ (0xF983, 'M', '旅'),
+ (0xF984, 'M', '濾'),
+ (0xF985, 'M', '礪'),
+ (0xF986, 'M', '閭'),
+ (0xF987, 'M', '驪'),
+ (0xF988, 'M', '麗'),
+ (0xF989, 'M', '黎'),
+ (0xF98A, 'M', '力'),
+ (0xF98B, 'M', '曆'),
+ (0xF98C, 'M', '歷'),
+ (0xF98D, 'M', '轢'),
+ (0xF98E, 'M', '年'),
+ (0xF98F, 'M', '憐'),
+ (0xF990, 'M', '戀'),
+ (0xF991, 'M', '撚'),
+ (0xF992, 'M', '漣'),
+ (0xF993, 'M', '煉'),
+ (0xF994, 'M', '璉'),
+ (0xF995, 'M', '秊'),
+ (0xF996, 'M', '練'),
+ (0xF997, 'M', '聯'),
+ (0xF998, 'M', '輦'),
+ (0xF999, 'M', '蓮'),
+ (0xF99A, 'M', '連'),
+ (0xF99B, 'M', '鍊'),
+ (0xF99C, 'M', '列'),
+ (0xF99D, 'M', '劣'),
+ (0xF99E, 'M', '咽'),
+ (0xF99F, 'M', '烈'),
+ (0xF9A0, 'M', '裂'),
+ (0xF9A1, 'M', '說'),
+ (0xF9A2, 'M', '廉'),
+ (0xF9A3, 'M', '念'),
+ (0xF9A4, 'M', '捻'),
+ (0xF9A5, 'M', '殮'),
+ (0xF9A6, 'M', '簾'),
+ (0xF9A7, 'M', '獵'),
+ (0xF9A8, 'M', '令'),
+ (0xF9A9, 'M', '囹'),
+ (0xF9AA, 'M', '寧'),
+ (0xF9AB, 'M', '嶺'),
+ (0xF9AC, 'M', '怜'),
+ (0xF9AD, 'M', '玲'),
+ ]
+
+def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF9AE, 'M', '瑩'),
+ (0xF9AF, 'M', '羚'),
+ (0xF9B0, 'M', '聆'),
+ (0xF9B1, 'M', '鈴'),
+ (0xF9B2, 'M', '零'),
+ (0xF9B3, 'M', '靈'),
+ (0xF9B4, 'M', '領'),
+ (0xF9B5, 'M', '例'),
+ (0xF9B6, 'M', '禮'),
+ (0xF9B7, 'M', '醴'),
+ (0xF9B8, 'M', '隸'),
+ (0xF9B9, 'M', '惡'),
+ (0xF9BA, 'M', '了'),
+ (0xF9BB, 'M', '僚'),
+ (0xF9BC, 'M', '寮'),
+ (0xF9BD, 'M', '尿'),
+ (0xF9BE, 'M', '料'),
+ (0xF9BF, 'M', '樂'),
+ (0xF9C0, 'M', '燎'),
+ (0xF9C1, 'M', '療'),
+ (0xF9C2, 'M', '蓼'),
+ (0xF9C3, 'M', '遼'),
+ (0xF9C4, 'M', '龍'),
+ (0xF9C5, 'M', '暈'),
+ (0xF9C6, 'M', '阮'),
+ (0xF9C7, 'M', '劉'),
+ (0xF9C8, 'M', '杻'),
+ (0xF9C9, 'M', '柳'),
+ (0xF9CA, 'M', '流'),
+ (0xF9CB, 'M', '溜'),
+ (0xF9CC, 'M', '琉'),
+ (0xF9CD, 'M', '留'),
+ (0xF9CE, 'M', '硫'),
+ (0xF9CF, 'M', '紐'),
+ (0xF9D0, 'M', '類'),
+ (0xF9D1, 'M', '六'),
+ (0xF9D2, 'M', '戮'),
+ (0xF9D3, 'M', '陸'),
+ (0xF9D4, 'M', '倫'),
+ (0xF9D5, 'M', '崙'),
+ (0xF9D6, 'M', '淪'),
+ (0xF9D7, 'M', '輪'),
+ (0xF9D8, 'M', '律'),
+ (0xF9D9, 'M', '慄'),
+ (0xF9DA, 'M', '栗'),
+ (0xF9DB, 'M', '率'),
+ (0xF9DC, 'M', '隆'),
+ (0xF9DD, 'M', '利'),
+ (0xF9DE, 'M', '吏'),
+ (0xF9DF, 'M', '履'),
+ (0xF9E0, 'M', '易'),
+ (0xF9E1, 'M', '李'),
+ (0xF9E2, 'M', '梨'),
+ (0xF9E3, 'M', '泥'),
+ (0xF9E4, 'M', '理'),
+ (0xF9E5, 'M', '痢'),
+ (0xF9E6, 'M', '罹'),
+ (0xF9E7, 'M', '裏'),
+ (0xF9E8, 'M', '裡'),
+ (0xF9E9, 'M', '里'),
+ (0xF9EA, 'M', '離'),
+ (0xF9EB, 'M', '匿'),
+ (0xF9EC, 'M', '溺'),
+ (0xF9ED, 'M', '吝'),
+ (0xF9EE, 'M', '燐'),
+ (0xF9EF, 'M', '璘'),
+ (0xF9F0, 'M', '藺'),
+ (0xF9F1, 'M', '隣'),
+ (0xF9F2, 'M', '鱗'),
+ (0xF9F3, 'M', '麟'),
+ (0xF9F4, 'M', '林'),
+ (0xF9F5, 'M', '淋'),
+ (0xF9F6, 'M', '臨'),
+ (0xF9F7, 'M', '立'),
+ (0xF9F8, 'M', '笠'),
+ (0xF9F9, 'M', '粒'),
+ (0xF9FA, 'M', '狀'),
+ (0xF9FB, 'M', '炙'),
+ (0xF9FC, 'M', '識'),
+ (0xF9FD, 'M', '什'),
+ (0xF9FE, 'M', '茶'),
+ (0xF9FF, 'M', '刺'),
+ (0xFA00, 'M', '切'),
+ (0xFA01, 'M', '度'),
+ (0xFA02, 'M', '拓'),
+ (0xFA03, 'M', '糖'),
+ (0xFA04, 'M', '宅'),
+ (0xFA05, 'M', '洞'),
+ (0xFA06, 'M', '暴'),
+ (0xFA07, 'M', '輻'),
+ (0xFA08, 'M', '行'),
+ (0xFA09, 'M', '降'),
+ (0xFA0A, 'M', '見'),
+ (0xFA0B, 'M', '廓'),
+ (0xFA0C, 'M', '兀'),
+ (0xFA0D, 'M', '嗀'),
+ (0xFA0E, 'V'),
+ (0xFA10, 'M', '塚'),
+ (0xFA11, 'V'),
+ (0xFA12, 'M', '晴'),
+ ]
+
+def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFA13, 'V'),
+ (0xFA15, 'M', '凞'),
+ (0xFA16, 'M', '猪'),
+ (0xFA17, 'M', '益'),
+ (0xFA18, 'M', '礼'),
+ (0xFA19, 'M', '神'),
+ (0xFA1A, 'M', '祥'),
+ (0xFA1B, 'M', '福'),
+ (0xFA1C, 'M', '靖'),
+ (0xFA1D, 'M', '精'),
+ (0xFA1E, 'M', '羽'),
+ (0xFA1F, 'V'),
+ (0xFA20, 'M', '蘒'),
+ (0xFA21, 'V'),
+ (0xFA22, 'M', '諸'),
+ (0xFA23, 'V'),
+ (0xFA25, 'M', '逸'),
+ (0xFA26, 'M', '都'),
+ (0xFA27, 'V'),
+ (0xFA2A, 'M', '飯'),
+ (0xFA2B, 'M', '飼'),
+ (0xFA2C, 'M', '館'),
+ (0xFA2D, 'M', '鶴'),
+ (0xFA2E, 'M', '郞'),
+ (0xFA2F, 'M', '隷'),
+ (0xFA30, 'M', '侮'),
+ (0xFA31, 'M', '僧'),
+ (0xFA32, 'M', '免'),
+ (0xFA33, 'M', '勉'),
+ (0xFA34, 'M', '勤'),
+ (0xFA35, 'M', '卑'),
+ (0xFA36, 'M', '喝'),
+ (0xFA37, 'M', '嘆'),
+ (0xFA38, 'M', '器'),
+ (0xFA39, 'M', '塀'),
+ (0xFA3A, 'M', '墨'),
+ (0xFA3B, 'M', '層'),
+ (0xFA3C, 'M', '屮'),
+ (0xFA3D, 'M', '悔'),
+ (0xFA3E, 'M', '慨'),
+ (0xFA3F, 'M', '憎'),
+ (0xFA40, 'M', '懲'),
+ (0xFA41, 'M', '敏'),
+ (0xFA42, 'M', '既'),
+ (0xFA43, 'M', '暑'),
+ (0xFA44, 'M', '梅'),
+ (0xFA45, 'M', '海'),
+ (0xFA46, 'M', '渚'),
+ (0xFA47, 'M', '漢'),
+ (0xFA48, 'M', '煮'),
+ (0xFA49, 'M', '爫'),
+ (0xFA4A, 'M', '琢'),
+ (0xFA4B, 'M', '碑'),
+ (0xFA4C, 'M', '社'),
+ (0xFA4D, 'M', '祉'),
+ (0xFA4E, 'M', '祈'),
+ (0xFA4F, 'M', '祐'),
+ (0xFA50, 'M', '祖'),
+ (0xFA51, 'M', '祝'),
+ (0xFA52, 'M', '禍'),
+ (0xFA53, 'M', '禎'),
+ (0xFA54, 'M', '穀'),
+ (0xFA55, 'M', '突'),
+ (0xFA56, 'M', '節'),
+ (0xFA57, 'M', '練'),
+ (0xFA58, 'M', '縉'),
+ (0xFA59, 'M', '繁'),
+ (0xFA5A, 'M', '署'),
+ (0xFA5B, 'M', '者'),
+ (0xFA5C, 'M', '臭'),
+ (0xFA5D, 'M', '艹'),
+ (0xFA5F, 'M', '著'),
+ (0xFA60, 'M', '褐'),
+ (0xFA61, 'M', '視'),
+ (0xFA62, 'M', '謁'),
+ (0xFA63, 'M', '謹'),
+ (0xFA64, 'M', '賓'),
+ (0xFA65, 'M', '贈'),
+ (0xFA66, 'M', '辶'),
+ (0xFA67, 'M', '逸'),
+ (0xFA68, 'M', '難'),
+ (0xFA69, 'M', '響'),
+ (0xFA6A, 'M', '頻'),
+ (0xFA6B, 'M', '恵'),
+ (0xFA6C, 'M', '𤋮'),
+ (0xFA6D, 'M', '舘'),
+ (0xFA6E, 'X'),
+ (0xFA70, 'M', '並'),
+ (0xFA71, 'M', '况'),
+ (0xFA72, 'M', '全'),
+ (0xFA73, 'M', '侀'),
+ (0xFA74, 'M', '充'),
+ (0xFA75, 'M', '冀'),
+ (0xFA76, 'M', '勇'),
+ (0xFA77, 'M', '勺'),
+ (0xFA78, 'M', '喝'),
+ (0xFA79, 'M', '啕'),
+ (0xFA7A, 'M', '喙'),
+ (0xFA7B, 'M', '嗢'),
+ (0xFA7C, 'M', '塚'),
+ ]
+
+def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFA7D, 'M', '墳'),
+ (0xFA7E, 'M', '奄'),
+ (0xFA7F, 'M', '奔'),
+ (0xFA80, 'M', '婢'),
+ (0xFA81, 'M', '嬨'),
+ (0xFA82, 'M', '廒'),
+ (0xFA83, 'M', '廙'),
+ (0xFA84, 'M', '彩'),
+ (0xFA85, 'M', '徭'),
+ (0xFA86, 'M', '惘'),
+ (0xFA87, 'M', '慎'),
+ (0xFA88, 'M', '愈'),
+ (0xFA89, 'M', '憎'),
+ (0xFA8A, 'M', '慠'),
+ (0xFA8B, 'M', '懲'),
+ (0xFA8C, 'M', '戴'),
+ (0xFA8D, 'M', '揄'),
+ (0xFA8E, 'M', '搜'),
+ (0xFA8F, 'M', '摒'),
+ (0xFA90, 'M', '敖'),
+ (0xFA91, 'M', '晴'),
+ (0xFA92, 'M', '朗'),
+ (0xFA93, 'M', '望'),
+ (0xFA94, 'M', '杖'),
+ (0xFA95, 'M', '歹'),
+ (0xFA96, 'M', '殺'),
+ (0xFA97, 'M', '流'),
+ (0xFA98, 'M', '滛'),
+ (0xFA99, 'M', '滋'),
+ (0xFA9A, 'M', '漢'),
+ (0xFA9B, 'M', '瀞'),
+ (0xFA9C, 'M', '煮'),
+ (0xFA9D, 'M', '瞧'),
+ (0xFA9E, 'M', '爵'),
+ (0xFA9F, 'M', '犯'),
+ (0xFAA0, 'M', '猪'),
+ (0xFAA1, 'M', '瑱'),
+ (0xFAA2, 'M', '甆'),
+ (0xFAA3, 'M', '画'),
+ (0xFAA4, 'M', '瘝'),
+ (0xFAA5, 'M', '瘟'),
+ (0xFAA6, 'M', '益'),
+ (0xFAA7, 'M', '盛'),
+ (0xFAA8, 'M', '直'),
+ (0xFAA9, 'M', '睊'),
+ (0xFAAA, 'M', '着'),
+ (0xFAAB, 'M', '磌'),
+ (0xFAAC, 'M', '窱'),
+ (0xFAAD, 'M', '節'),
+ (0xFAAE, 'M', '类'),
+ (0xFAAF, 'M', '絛'),
+ (0xFAB0, 'M', '練'),
+ (0xFAB1, 'M', '缾'),
+ (0xFAB2, 'M', '者'),
+ (0xFAB3, 'M', '荒'),
+ (0xFAB4, 'M', '華'),
+ (0xFAB5, 'M', '蝹'),
+ (0xFAB6, 'M', '襁'),
+ (0xFAB7, 'M', '覆'),
+ (0xFAB8, 'M', '視'),
+ (0xFAB9, 'M', '調'),
+ (0xFABA, 'M', '諸'),
+ (0xFABB, 'M', '請'),
+ (0xFABC, 'M', '謁'),
+ (0xFABD, 'M', '諾'),
+ (0xFABE, 'M', '諭'),
+ (0xFABF, 'M', '謹'),
+ (0xFAC0, 'M', '變'),
+ (0xFAC1, 'M', '贈'),
+ (0xFAC2, 'M', '輸'),
+ (0xFAC3, 'M', '遲'),
+ (0xFAC4, 'M', '醙'),
+ (0xFAC5, 'M', '鉶'),
+ (0xFAC6, 'M', '陼'),
+ (0xFAC7, 'M', '難'),
+ (0xFAC8, 'M', '靖'),
+ (0xFAC9, 'M', '韛'),
+ (0xFACA, 'M', '響'),
+ (0xFACB, 'M', '頋'),
+ (0xFACC, 'M', '頻'),
+ (0xFACD, 'M', '鬒'),
+ (0xFACE, 'M', '龜'),
+ (0xFACF, 'M', '𢡊'),
+ (0xFAD0, 'M', '𢡄'),
+ (0xFAD1, 'M', '𣏕'),
+ (0xFAD2, 'M', '㮝'),
+ (0xFAD3, 'M', '䀘'),
+ (0xFAD4, 'M', '䀹'),
+ (0xFAD5, 'M', '𥉉'),
+ (0xFAD6, 'M', '𥳐'),
+ (0xFAD7, 'M', '𧻓'),
+ (0xFAD8, 'M', '齃'),
+ (0xFAD9, 'M', '龎'),
+ (0xFADA, 'X'),
+ (0xFB00, 'M', 'ff'),
+ (0xFB01, 'M', 'fi'),
+ (0xFB02, 'M', 'fl'),
+ (0xFB03, 'M', 'ffi'),
+ (0xFB04, 'M', 'ffl'),
+ (0xFB05, 'M', 'st'),
+ ]
+
+def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFB07, 'X'),
+ (0xFB13, 'M', 'մն'),
+ (0xFB14, 'M', 'մե'),
+ (0xFB15, 'M', 'մի'),
+ (0xFB16, 'M', 'վն'),
+ (0xFB17, 'M', 'մխ'),
+ (0xFB18, 'X'),
+ (0xFB1D, 'M', 'יִ'),
+ (0xFB1E, 'V'),
+ (0xFB1F, 'M', 'ײַ'),
+ (0xFB20, 'M', 'ע'),
+ (0xFB21, 'M', 'א'),
+ (0xFB22, 'M', 'ד'),
+ (0xFB23, 'M', 'ה'),
+ (0xFB24, 'M', 'כ'),
+ (0xFB25, 'M', 'ל'),
+ (0xFB26, 'M', 'ם'),
+ (0xFB27, 'M', 'ר'),
+ (0xFB28, 'M', 'ת'),
+ (0xFB29, '3', '+'),
+ (0xFB2A, 'M', 'שׁ'),
+ (0xFB2B, 'M', 'שׂ'),
+ (0xFB2C, 'M', 'שּׁ'),
+ (0xFB2D, 'M', 'שּׂ'),
+ (0xFB2E, 'M', 'אַ'),
+ (0xFB2F, 'M', 'אָ'),
+ (0xFB30, 'M', 'אּ'),
+ (0xFB31, 'M', 'בּ'),
+ (0xFB32, 'M', 'גּ'),
+ (0xFB33, 'M', 'דּ'),
+ (0xFB34, 'M', 'הּ'),
+ (0xFB35, 'M', 'וּ'),
+ (0xFB36, 'M', 'זּ'),
+ (0xFB37, 'X'),
+ (0xFB38, 'M', 'טּ'),
+ (0xFB39, 'M', 'יּ'),
+ (0xFB3A, 'M', 'ךּ'),
+ (0xFB3B, 'M', 'כּ'),
+ (0xFB3C, 'M', 'לּ'),
+ (0xFB3D, 'X'),
+ (0xFB3E, 'M', 'מּ'),
+ (0xFB3F, 'X'),
+ (0xFB40, 'M', 'נּ'),
+ (0xFB41, 'M', 'סּ'),
+ (0xFB42, 'X'),
+ (0xFB43, 'M', 'ףּ'),
+ (0xFB44, 'M', 'פּ'),
+ (0xFB45, 'X'),
+ (0xFB46, 'M', 'צּ'),
+ (0xFB47, 'M', 'קּ'),
+ (0xFB48, 'M', 'רּ'),
+ (0xFB49, 'M', 'שּ'),
+ (0xFB4A, 'M', 'תּ'),
+ (0xFB4B, 'M', 'וֹ'),
+ (0xFB4C, 'M', 'בֿ'),
+ (0xFB4D, 'M', 'כֿ'),
+ (0xFB4E, 'M', 'פֿ'),
+ (0xFB4F, 'M', 'אל'),
+ (0xFB50, 'M', 'ٱ'),
+ (0xFB52, 'M', 'ٻ'),
+ (0xFB56, 'M', 'پ'),
+ (0xFB5A, 'M', 'ڀ'),
+ (0xFB5E, 'M', 'ٺ'),
+ (0xFB62, 'M', 'ٿ'),
+ (0xFB66, 'M', 'ٹ'),
+ (0xFB6A, 'M', 'ڤ'),
+ (0xFB6E, 'M', 'ڦ'),
+ (0xFB72, 'M', 'ڄ'),
+ (0xFB76, 'M', 'ڃ'),
+ (0xFB7A, 'M', 'چ'),
+ (0xFB7E, 'M', 'ڇ'),
+ (0xFB82, 'M', 'ڍ'),
+ (0xFB84, 'M', 'ڌ'),
+ (0xFB86, 'M', 'ڎ'),
+ (0xFB88, 'M', 'ڈ'),
+ (0xFB8A, 'M', 'ژ'),
+ (0xFB8C, 'M', 'ڑ'),
+ (0xFB8E, 'M', 'ک'),
+ (0xFB92, 'M', 'گ'),
+ (0xFB96, 'M', 'ڳ'),
+ (0xFB9A, 'M', 'ڱ'),
+ (0xFB9E, 'M', 'ں'),
+ (0xFBA0, 'M', 'ڻ'),
+ (0xFBA4, 'M', 'ۀ'),
+ (0xFBA6, 'M', 'ہ'),
+ (0xFBAA, 'M', 'ھ'),
+ (0xFBAE, 'M', 'ے'),
+ (0xFBB0, 'M', 'ۓ'),
+ (0xFBB2, 'V'),
+ (0xFBC3, 'X'),
+ (0xFBD3, 'M', 'ڭ'),
+ (0xFBD7, 'M', 'ۇ'),
+ (0xFBD9, 'M', 'ۆ'),
+ (0xFBDB, 'M', 'ۈ'),
+ (0xFBDD, 'M', 'ۇٴ'),
+ (0xFBDE, 'M', 'ۋ'),
+ (0xFBE0, 'M', 'ۅ'),
+ (0xFBE2, 'M', 'ۉ'),
+ (0xFBE4, 'M', 'ې'),
+ (0xFBE8, 'M', 'ى'),
+ ]
+
+def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFBEA, 'M', 'ئا'),
+ (0xFBEC, 'M', 'ئە'),
+ (0xFBEE, 'M', 'ئو'),
+ (0xFBF0, 'M', 'ئۇ'),
+ (0xFBF2, 'M', 'ئۆ'),
+ (0xFBF4, 'M', 'ئۈ'),
+ (0xFBF6, 'M', 'ئې'),
+ (0xFBF9, 'M', 'ئى'),
+ (0xFBFC, 'M', 'ی'),
+ (0xFC00, 'M', 'ئج'),
+ (0xFC01, 'M', 'ئح'),
+ (0xFC02, 'M', 'ئم'),
+ (0xFC03, 'M', 'ئى'),
+ (0xFC04, 'M', 'ئي'),
+ (0xFC05, 'M', 'بج'),
+ (0xFC06, 'M', 'بح'),
+ (0xFC07, 'M', 'بخ'),
+ (0xFC08, 'M', 'بم'),
+ (0xFC09, 'M', 'بى'),
+ (0xFC0A, 'M', 'بي'),
+ (0xFC0B, 'M', 'تج'),
+ (0xFC0C, 'M', 'تح'),
+ (0xFC0D, 'M', 'تخ'),
+ (0xFC0E, 'M', 'تم'),
+ (0xFC0F, 'M', 'تى'),
+ (0xFC10, 'M', 'تي'),
+ (0xFC11, 'M', 'ثج'),
+ (0xFC12, 'M', 'ثم'),
+ (0xFC13, 'M', 'ثى'),
+ (0xFC14, 'M', 'ثي'),
+ (0xFC15, 'M', 'جح'),
+ (0xFC16, 'M', 'جم'),
+ (0xFC17, 'M', 'حج'),
+ (0xFC18, 'M', 'حم'),
+ (0xFC19, 'M', 'خج'),
+ (0xFC1A, 'M', 'خح'),
+ (0xFC1B, 'M', 'خم'),
+ (0xFC1C, 'M', 'سج'),
+ (0xFC1D, 'M', 'سح'),
+ (0xFC1E, 'M', 'سخ'),
+ (0xFC1F, 'M', 'سم'),
+ (0xFC20, 'M', 'صح'),
+ (0xFC21, 'M', 'صم'),
+ (0xFC22, 'M', 'ضج'),
+ (0xFC23, 'M', 'ضح'),
+ (0xFC24, 'M', 'ضخ'),
+ (0xFC25, 'M', 'ضم'),
+ (0xFC26, 'M', 'طح'),
+ (0xFC27, 'M', 'طم'),
+ (0xFC28, 'M', 'ظم'),
+ (0xFC29, 'M', 'عج'),
+ (0xFC2A, 'M', 'عم'),
+ (0xFC2B, 'M', 'غج'),
+ (0xFC2C, 'M', 'غم'),
+ (0xFC2D, 'M', 'فج'),
+ (0xFC2E, 'M', 'فح'),
+ (0xFC2F, 'M', 'فخ'),
+ (0xFC30, 'M', 'فم'),
+ (0xFC31, 'M', 'فى'),
+ (0xFC32, 'M', 'في'),
+ (0xFC33, 'M', 'قح'),
+ (0xFC34, 'M', 'قم'),
+ (0xFC35, 'M', 'قى'),
+ (0xFC36, 'M', 'قي'),
+ (0xFC37, 'M', 'كا'),
+ (0xFC38, 'M', 'كج'),
+ (0xFC39, 'M', 'كح'),
+ (0xFC3A, 'M', 'كخ'),
+ (0xFC3B, 'M', 'كل'),
+ (0xFC3C, 'M', 'كم'),
+ (0xFC3D, 'M', 'كى'),
+ (0xFC3E, 'M', 'كي'),
+ (0xFC3F, 'M', 'لج'),
+ (0xFC40, 'M', 'لح'),
+ (0xFC41, 'M', 'لخ'),
+ (0xFC42, 'M', 'لم'),
+ (0xFC43, 'M', 'لى'),
+ (0xFC44, 'M', 'لي'),
+ (0xFC45, 'M', 'مج'),
+ (0xFC46, 'M', 'مح'),
+ (0xFC47, 'M', 'مخ'),
+ (0xFC48, 'M', 'مم'),
+ (0xFC49, 'M', 'مى'),
+ (0xFC4A, 'M', 'مي'),
+ (0xFC4B, 'M', 'نج'),
+ (0xFC4C, 'M', 'نح'),
+ (0xFC4D, 'M', 'نخ'),
+ (0xFC4E, 'M', 'نم'),
+ (0xFC4F, 'M', 'نى'),
+ (0xFC50, 'M', 'ني'),
+ (0xFC51, 'M', 'هج'),
+ (0xFC52, 'M', 'هم'),
+ (0xFC53, 'M', 'هى'),
+ (0xFC54, 'M', 'هي'),
+ (0xFC55, 'M', 'يج'),
+ (0xFC56, 'M', 'يح'),
+ (0xFC57, 'M', 'يخ'),
+ (0xFC58, 'M', 'يم'),
+ (0xFC59, 'M', 'يى'),
+ (0xFC5A, 'M', 'يي'),
+ ]
+
+def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFC5B, 'M', 'ذٰ'),
+ (0xFC5C, 'M', 'رٰ'),
+ (0xFC5D, 'M', 'ىٰ'),
+ (0xFC5E, '3', ' ٌّ'),
+ (0xFC5F, '3', ' ٍّ'),
+ (0xFC60, '3', ' َّ'),
+ (0xFC61, '3', ' ُّ'),
+ (0xFC62, '3', ' ِّ'),
+ (0xFC63, '3', ' ّٰ'),
+ (0xFC64, 'M', 'ئر'),
+ (0xFC65, 'M', 'ئز'),
+ (0xFC66, 'M', 'ئم'),
+ (0xFC67, 'M', 'ئن'),
+ (0xFC68, 'M', 'ئى'),
+ (0xFC69, 'M', 'ئي'),
+ (0xFC6A, 'M', 'بر'),
+ (0xFC6B, 'M', 'بز'),
+ (0xFC6C, 'M', 'بم'),
+ (0xFC6D, 'M', 'بن'),
+ (0xFC6E, 'M', 'بى'),
+ (0xFC6F, 'M', 'بي'),
+ (0xFC70, 'M', 'تر'),
+ (0xFC71, 'M', 'تز'),
+ (0xFC72, 'M', 'تم'),
+ (0xFC73, 'M', 'تن'),
+ (0xFC74, 'M', 'تى'),
+ (0xFC75, 'M', 'تي'),
+ (0xFC76, 'M', 'ثر'),
+ (0xFC77, 'M', 'ثز'),
+ (0xFC78, 'M', 'ثم'),
+ (0xFC79, 'M', 'ثن'),
+ (0xFC7A, 'M', 'ثى'),
+ (0xFC7B, 'M', 'ثي'),
+ (0xFC7C, 'M', 'فى'),
+ (0xFC7D, 'M', 'في'),
+ (0xFC7E, 'M', 'قى'),
+ (0xFC7F, 'M', 'قي'),
+ (0xFC80, 'M', 'كا'),
+ (0xFC81, 'M', 'كل'),
+ (0xFC82, 'M', 'كم'),
+ (0xFC83, 'M', 'كى'),
+ (0xFC84, 'M', 'كي'),
+ (0xFC85, 'M', 'لم'),
+ (0xFC86, 'M', 'لى'),
+ (0xFC87, 'M', 'لي'),
+ (0xFC88, 'M', 'ما'),
+ (0xFC89, 'M', 'مم'),
+ (0xFC8A, 'M', 'نر'),
+ (0xFC8B, 'M', 'نز'),
+ (0xFC8C, 'M', 'نم'),
+ (0xFC8D, 'M', 'نن'),
+ (0xFC8E, 'M', 'نى'),
+ (0xFC8F, 'M', 'ني'),
+ (0xFC90, 'M', 'ىٰ'),
+ (0xFC91, 'M', 'ير'),
+ (0xFC92, 'M', 'يز'),
+ (0xFC93, 'M', 'يم'),
+ (0xFC94, 'M', 'ين'),
+ (0xFC95, 'M', 'يى'),
+ (0xFC96, 'M', 'يي'),
+ (0xFC97, 'M', 'ئج'),
+ (0xFC98, 'M', 'ئح'),
+ (0xFC99, 'M', 'ئخ'),
+ (0xFC9A, 'M', 'ئم'),
+ (0xFC9B, 'M', 'ئه'),
+ (0xFC9C, 'M', 'بج'),
+ (0xFC9D, 'M', 'بح'),
+ (0xFC9E, 'M', 'بخ'),
+ (0xFC9F, 'M', 'بم'),
+ (0xFCA0, 'M', 'به'),
+ (0xFCA1, 'M', 'تج'),
+ (0xFCA2, 'M', 'تح'),
+ (0xFCA3, 'M', 'تخ'),
+ (0xFCA4, 'M', 'تم'),
+ (0xFCA5, 'M', 'ته'),
+ (0xFCA6, 'M', 'ثم'),
+ (0xFCA7, 'M', 'جح'),
+ (0xFCA8, 'M', 'جم'),
+ (0xFCA9, 'M', 'حج'),
+ (0xFCAA, 'M', 'حم'),
+ (0xFCAB, 'M', 'خج'),
+ (0xFCAC, 'M', 'خم'),
+ (0xFCAD, 'M', 'سج'),
+ (0xFCAE, 'M', 'سح'),
+ (0xFCAF, 'M', 'سخ'),
+ (0xFCB0, 'M', 'سم'),
+ (0xFCB1, 'M', 'صح'),
+ (0xFCB2, 'M', 'صخ'),
+ (0xFCB3, 'M', 'صم'),
+ (0xFCB4, 'M', 'ضج'),
+ (0xFCB5, 'M', 'ضح'),
+ (0xFCB6, 'M', 'ضخ'),
+ (0xFCB7, 'M', 'ضم'),
+ (0xFCB8, 'M', 'طح'),
+ (0xFCB9, 'M', 'ظم'),
+ (0xFCBA, 'M', 'عج'),
+ (0xFCBB, 'M', 'عم'),
+ (0xFCBC, 'M', 'غج'),
+ (0xFCBD, 'M', 'غم'),
+ (0xFCBE, 'M', 'فج'),
+ ]
+
+def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFCBF, 'M', 'فح'),
+ (0xFCC0, 'M', 'فخ'),
+ (0xFCC1, 'M', 'فم'),
+ (0xFCC2, 'M', 'قح'),
+ (0xFCC3, 'M', 'قم'),
+ (0xFCC4, 'M', 'كج'),
+ (0xFCC5, 'M', 'كح'),
+ (0xFCC6, 'M', 'كخ'),
+ (0xFCC7, 'M', 'كل'),
+ (0xFCC8, 'M', 'كم'),
+ (0xFCC9, 'M', 'لج'),
+ (0xFCCA, 'M', 'لح'),
+ (0xFCCB, 'M', 'لخ'),
+ (0xFCCC, 'M', 'لم'),
+ (0xFCCD, 'M', 'له'),
+ (0xFCCE, 'M', 'مج'),
+ (0xFCCF, 'M', 'مح'),
+ (0xFCD0, 'M', 'مخ'),
+ (0xFCD1, 'M', 'مم'),
+ (0xFCD2, 'M', 'نج'),
+ (0xFCD3, 'M', 'نح'),
+ (0xFCD4, 'M', 'نخ'),
+ (0xFCD5, 'M', 'نم'),
+ (0xFCD6, 'M', 'نه'),
+ (0xFCD7, 'M', 'هج'),
+ (0xFCD8, 'M', 'هم'),
+ (0xFCD9, 'M', 'هٰ'),
+ (0xFCDA, 'M', 'يج'),
+ (0xFCDB, 'M', 'يح'),
+ (0xFCDC, 'M', 'يخ'),
+ (0xFCDD, 'M', 'يم'),
+ (0xFCDE, 'M', 'يه'),
+ (0xFCDF, 'M', 'ئم'),
+ (0xFCE0, 'M', 'ئه'),
+ (0xFCE1, 'M', 'بم'),
+ (0xFCE2, 'M', 'به'),
+ (0xFCE3, 'M', 'تم'),
+ (0xFCE4, 'M', 'ته'),
+ (0xFCE5, 'M', 'ثم'),
+ (0xFCE6, 'M', 'ثه'),
+ (0xFCE7, 'M', 'سم'),
+ (0xFCE8, 'M', 'سه'),
+ (0xFCE9, 'M', 'شم'),
+ (0xFCEA, 'M', 'شه'),
+ (0xFCEB, 'M', 'كل'),
+ (0xFCEC, 'M', 'كم'),
+ (0xFCED, 'M', 'لم'),
+ (0xFCEE, 'M', 'نم'),
+ (0xFCEF, 'M', 'نه'),
+ (0xFCF0, 'M', 'يم'),
+ (0xFCF1, 'M', 'يه'),
+ (0xFCF2, 'M', 'ـَّ'),
+ (0xFCF3, 'M', 'ـُّ'),
+ (0xFCF4, 'M', 'ـِّ'),
+ (0xFCF5, 'M', 'طى'),
+ (0xFCF6, 'M', 'طي'),
+ (0xFCF7, 'M', 'عى'),
+ (0xFCF8, 'M', 'عي'),
+ (0xFCF9, 'M', 'غى'),
+ (0xFCFA, 'M', 'غي'),
+ (0xFCFB, 'M', 'سى'),
+ (0xFCFC, 'M', 'سي'),
+ (0xFCFD, 'M', 'شى'),
+ (0xFCFE, 'M', 'شي'),
+ (0xFCFF, 'M', 'حى'),
+ (0xFD00, 'M', 'حي'),
+ (0xFD01, 'M', 'جى'),
+ (0xFD02, 'M', 'جي'),
+ (0xFD03, 'M', 'خى'),
+ (0xFD04, 'M', 'خي'),
+ (0xFD05, 'M', 'صى'),
+ (0xFD06, 'M', 'صي'),
+ (0xFD07, 'M', 'ضى'),
+ (0xFD08, 'M', 'ضي'),
+ (0xFD09, 'M', 'شج'),
+ (0xFD0A, 'M', 'شح'),
+ (0xFD0B, 'M', 'شخ'),
+ (0xFD0C, 'M', 'شم'),
+ (0xFD0D, 'M', 'شر'),
+ (0xFD0E, 'M', 'سر'),
+ (0xFD0F, 'M', 'صر'),
+ (0xFD10, 'M', 'ضر'),
+ (0xFD11, 'M', 'طى'),
+ (0xFD12, 'M', 'طي'),
+ (0xFD13, 'M', 'عى'),
+ (0xFD14, 'M', 'عي'),
+ (0xFD15, 'M', 'غى'),
+ (0xFD16, 'M', 'غي'),
+ (0xFD17, 'M', 'سى'),
+ (0xFD18, 'M', 'سي'),
+ (0xFD19, 'M', 'شى'),
+ (0xFD1A, 'M', 'شي'),
+ (0xFD1B, 'M', 'حى'),
+ (0xFD1C, 'M', 'حي'),
+ (0xFD1D, 'M', 'جى'),
+ (0xFD1E, 'M', 'جي'),
+ (0xFD1F, 'M', 'خى'),
+ (0xFD20, 'M', 'خي'),
+ (0xFD21, 'M', 'صى'),
+ (0xFD22, 'M', 'صي'),
+ ]
+
+def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFD23, 'M', 'ضى'),
+ (0xFD24, 'M', 'ضي'),
+ (0xFD25, 'M', 'شج'),
+ (0xFD26, 'M', 'شح'),
+ (0xFD27, 'M', 'شخ'),
+ (0xFD28, 'M', 'شم'),
+ (0xFD29, 'M', 'شر'),
+ (0xFD2A, 'M', 'سر'),
+ (0xFD2B, 'M', 'صر'),
+ (0xFD2C, 'M', 'ضر'),
+ (0xFD2D, 'M', 'شج'),
+ (0xFD2E, 'M', 'شح'),
+ (0xFD2F, 'M', 'شخ'),
+ (0xFD30, 'M', 'شم'),
+ (0xFD31, 'M', 'سه'),
+ (0xFD32, 'M', 'شه'),
+ (0xFD33, 'M', 'طم'),
+ (0xFD34, 'M', 'سج'),
+ (0xFD35, 'M', 'سح'),
+ (0xFD36, 'M', 'سخ'),
+ (0xFD37, 'M', 'شج'),
+ (0xFD38, 'M', 'شح'),
+ (0xFD39, 'M', 'شخ'),
+ (0xFD3A, 'M', 'طم'),
+ (0xFD3B, 'M', 'ظم'),
+ (0xFD3C, 'M', 'اً'),
+ (0xFD3E, 'V'),
+ (0xFD50, 'M', 'تجم'),
+ (0xFD51, 'M', 'تحج'),
+ (0xFD53, 'M', 'تحم'),
+ (0xFD54, 'M', 'تخم'),
+ (0xFD55, 'M', 'تمج'),
+ (0xFD56, 'M', 'تمح'),
+ (0xFD57, 'M', 'تمخ'),
+ (0xFD58, 'M', 'جمح'),
+ (0xFD5A, 'M', 'حمي'),
+ (0xFD5B, 'M', 'حمى'),
+ (0xFD5C, 'M', 'سحج'),
+ (0xFD5D, 'M', 'سجح'),
+ (0xFD5E, 'M', 'سجى'),
+ (0xFD5F, 'M', 'سمح'),
+ (0xFD61, 'M', 'سمج'),
+ (0xFD62, 'M', 'سمم'),
+ (0xFD64, 'M', 'صحح'),
+ (0xFD66, 'M', 'صمم'),
+ (0xFD67, 'M', 'شحم'),
+ (0xFD69, 'M', 'شجي'),
+ (0xFD6A, 'M', 'شمخ'),
+ (0xFD6C, 'M', 'شمم'),
+ (0xFD6E, 'M', 'ضحى'),
+ (0xFD6F, 'M', 'ضخم'),
+ (0xFD71, 'M', 'طمح'),
+ (0xFD73, 'M', 'طمم'),
+ (0xFD74, 'M', 'طمي'),
+ (0xFD75, 'M', 'عجم'),
+ (0xFD76, 'M', 'عمم'),
+ (0xFD78, 'M', 'عمى'),
+ (0xFD79, 'M', 'غمم'),
+ (0xFD7A, 'M', 'غمي'),
+ (0xFD7B, 'M', 'غمى'),
+ (0xFD7C, 'M', 'فخم'),
+ (0xFD7E, 'M', 'قمح'),
+ (0xFD7F, 'M', 'قمم'),
+ (0xFD80, 'M', 'لحم'),
+ (0xFD81, 'M', 'لحي'),
+ (0xFD82, 'M', 'لحى'),
+ (0xFD83, 'M', 'لجج'),
+ (0xFD85, 'M', 'لخم'),
+ (0xFD87, 'M', 'لمح'),
+ (0xFD89, 'M', 'محج'),
+ (0xFD8A, 'M', 'محم'),
+ (0xFD8B, 'M', 'محي'),
+ (0xFD8C, 'M', 'مجح'),
+ (0xFD8D, 'M', 'مجم'),
+ (0xFD8E, 'M', 'مخج'),
+ (0xFD8F, 'M', 'مخم'),
+ (0xFD90, 'X'),
+ (0xFD92, 'M', 'مجخ'),
+ (0xFD93, 'M', 'همج'),
+ (0xFD94, 'M', 'همم'),
+ (0xFD95, 'M', 'نحم'),
+ (0xFD96, 'M', 'نحى'),
+ (0xFD97, 'M', 'نجم'),
+ (0xFD99, 'M', 'نجى'),
+ (0xFD9A, 'M', 'نمي'),
+ (0xFD9B, 'M', 'نمى'),
+ (0xFD9C, 'M', 'يمم'),
+ (0xFD9E, 'M', 'بخي'),
+ (0xFD9F, 'M', 'تجي'),
+ (0xFDA0, 'M', 'تجى'),
+ (0xFDA1, 'M', 'تخي'),
+ (0xFDA2, 'M', 'تخى'),
+ (0xFDA3, 'M', 'تمي'),
+ (0xFDA4, 'M', 'تمى'),
+ (0xFDA5, 'M', 'جمي'),
+ (0xFDA6, 'M', 'جحى'),
+ (0xFDA7, 'M', 'جمى'),
+ (0xFDA8, 'M', 'سخى'),
+ (0xFDA9, 'M', 'صحي'),
+ (0xFDAA, 'M', 'شحي'),
+ ]
+
+def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFDAB, 'M', 'ضحي'),
+ (0xFDAC, 'M', 'لجي'),
+ (0xFDAD, 'M', 'لمي'),
+ (0xFDAE, 'M', 'يحي'),
+ (0xFDAF, 'M', 'يجي'),
+ (0xFDB0, 'M', 'يمي'),
+ (0xFDB1, 'M', 'ممي'),
+ (0xFDB2, 'M', 'قمي'),
+ (0xFDB3, 'M', 'نحي'),
+ (0xFDB4, 'M', 'قمح'),
+ (0xFDB5, 'M', 'لحم'),
+ (0xFDB6, 'M', 'عمي'),
+ (0xFDB7, 'M', 'كمي'),
+ (0xFDB8, 'M', 'نجح'),
+ (0xFDB9, 'M', 'مخي'),
+ (0xFDBA, 'M', 'لجم'),
+ (0xFDBB, 'M', 'كمم'),
+ (0xFDBC, 'M', 'لجم'),
+ (0xFDBD, 'M', 'نجح'),
+ (0xFDBE, 'M', 'جحي'),
+ (0xFDBF, 'M', 'حجي'),
+ (0xFDC0, 'M', 'مجي'),
+ (0xFDC1, 'M', 'فمي'),
+ (0xFDC2, 'M', 'بحي'),
+ (0xFDC3, 'M', 'كمم'),
+ (0xFDC4, 'M', 'عجم'),
+ (0xFDC5, 'M', 'صمم'),
+ (0xFDC6, 'M', 'سخي'),
+ (0xFDC7, 'M', 'نجي'),
+ (0xFDC8, 'X'),
+ (0xFDCF, 'V'),
+ (0xFDD0, 'X'),
+ (0xFDF0, 'M', 'صلے'),
+ (0xFDF1, 'M', 'قلے'),
+ (0xFDF2, 'M', 'الله'),
+ (0xFDF3, 'M', 'اكبر'),
+ (0xFDF4, 'M', 'محمد'),
+ (0xFDF5, 'M', 'صلعم'),
+ (0xFDF6, 'M', 'رسول'),
+ (0xFDF7, 'M', 'عليه'),
+ (0xFDF8, 'M', 'وسلم'),
+ (0xFDF9, 'M', 'صلى'),
+ (0xFDFA, '3', 'صلى الله عليه وسلم'),
+ (0xFDFB, '3', 'جل جلاله'),
+ (0xFDFC, 'M', 'ریال'),
+ (0xFDFD, 'V'),
+ (0xFE00, 'I'),
+ (0xFE10, '3', ','),
+ (0xFE11, 'M', '、'),
+ (0xFE12, 'X'),
+ (0xFE13, '3', ':'),
+ (0xFE14, '3', ';'),
+ (0xFE15, '3', '!'),
+ (0xFE16, '3', '?'),
+ (0xFE17, 'M', '〖'),
+ (0xFE18, 'M', '〗'),
+ (0xFE19, 'X'),
+ (0xFE20, 'V'),
+ (0xFE30, 'X'),
+ (0xFE31, 'M', '—'),
+ (0xFE32, 'M', '–'),
+ (0xFE33, '3', '_'),
+ (0xFE35, '3', '('),
+ (0xFE36, '3', ')'),
+ (0xFE37, '3', '{'),
+ (0xFE38, '3', '}'),
+ (0xFE39, 'M', '〔'),
+ (0xFE3A, 'M', '〕'),
+ (0xFE3B, 'M', '【'),
+ (0xFE3C, 'M', '】'),
+ (0xFE3D, 'M', '《'),
+ (0xFE3E, 'M', '》'),
+ (0xFE3F, 'M', '〈'),
+ (0xFE40, 'M', '〉'),
+ (0xFE41, 'M', '「'),
+ (0xFE42, 'M', '」'),
+ (0xFE43, 'M', '『'),
+ (0xFE44, 'M', '』'),
+ (0xFE45, 'V'),
+ (0xFE47, '3', '['),
+ (0xFE48, '3', ']'),
+ (0xFE49, '3', ' ̅'),
+ (0xFE4D, '3', '_'),
+ (0xFE50, '3', ','),
+ (0xFE51, 'M', '、'),
+ (0xFE52, 'X'),
+ (0xFE54, '3', ';'),
+ (0xFE55, '3', ':'),
+ (0xFE56, '3', '?'),
+ (0xFE57, '3', '!'),
+ (0xFE58, 'M', '—'),
+ (0xFE59, '3', '('),
+ (0xFE5A, '3', ')'),
+ (0xFE5B, '3', '{'),
+ (0xFE5C, '3', '}'),
+ (0xFE5D, 'M', '〔'),
+ (0xFE5E, 'M', '〕'),
+ (0xFE5F, '3', '#'),
+ (0xFE60, '3', '&'),
+ (0xFE61, '3', '*'),
+ ]
+
+def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFE62, '3', '+'),
+ (0xFE63, 'M', '-'),
+ (0xFE64, '3', '<'),
+ (0xFE65, '3', '>'),
+ (0xFE66, '3', '='),
+ (0xFE67, 'X'),
+ (0xFE68, '3', '\\'),
+ (0xFE69, '3', '$'),
+ (0xFE6A, '3', '%'),
+ (0xFE6B, '3', '@'),
+ (0xFE6C, 'X'),
+ (0xFE70, '3', ' ً'),
+ (0xFE71, 'M', 'ـً'),
+ (0xFE72, '3', ' ٌ'),
+ (0xFE73, 'V'),
+ (0xFE74, '3', ' ٍ'),
+ (0xFE75, 'X'),
+ (0xFE76, '3', ' َ'),
+ (0xFE77, 'M', 'ـَ'),
+ (0xFE78, '3', ' ُ'),
+ (0xFE79, 'M', 'ـُ'),
+ (0xFE7A, '3', ' ِ'),
+ (0xFE7B, 'M', 'ـِ'),
+ (0xFE7C, '3', ' ّ'),
+ (0xFE7D, 'M', 'ـّ'),
+ (0xFE7E, '3', ' ْ'),
+ (0xFE7F, 'M', 'ـْ'),
+ (0xFE80, 'M', 'ء'),
+ (0xFE81, 'M', 'آ'),
+ (0xFE83, 'M', 'أ'),
+ (0xFE85, 'M', 'ؤ'),
+ (0xFE87, 'M', 'إ'),
+ (0xFE89, 'M', 'ئ'),
+ (0xFE8D, 'M', 'ا'),
+ (0xFE8F, 'M', 'ب'),
+ (0xFE93, 'M', 'ة'),
+ (0xFE95, 'M', 'ت'),
+ (0xFE99, 'M', 'ث'),
+ (0xFE9D, 'M', 'ج'),
+ (0xFEA1, 'M', 'ح'),
+ (0xFEA5, 'M', 'خ'),
+ (0xFEA9, 'M', 'د'),
+ (0xFEAB, 'M', 'ذ'),
+ (0xFEAD, 'M', 'ر'),
+ (0xFEAF, 'M', 'ز'),
+ (0xFEB1, 'M', 'س'),
+ (0xFEB5, 'M', 'ش'),
+ (0xFEB9, 'M', 'ص'),
+ (0xFEBD, 'M', 'ض'),
+ (0xFEC1, 'M', 'ط'),
+ (0xFEC5, 'M', 'ظ'),
+ (0xFEC9, 'M', 'ع'),
+ (0xFECD, 'M', 'غ'),
+ (0xFED1, 'M', 'ف'),
+ (0xFED5, 'M', 'ق'),
+ (0xFED9, 'M', 'ك'),
+ (0xFEDD, 'M', 'ل'),
+ (0xFEE1, 'M', 'م'),
+ (0xFEE5, 'M', 'ن'),
+ (0xFEE9, 'M', 'ه'),
+ (0xFEED, 'M', 'و'),
+ (0xFEEF, 'M', 'ى'),
+ (0xFEF1, 'M', 'ي'),
+ (0xFEF5, 'M', 'لآ'),
+ (0xFEF7, 'M', 'لأ'),
+ (0xFEF9, 'M', 'لإ'),
+ (0xFEFB, 'M', 'لا'),
+ (0xFEFD, 'X'),
+ (0xFEFF, 'I'),
+ (0xFF00, 'X'),
+ (0xFF01, '3', '!'),
+ (0xFF02, '3', '"'),
+ (0xFF03, '3', '#'),
+ (0xFF04, '3', '$'),
+ (0xFF05, '3', '%'),
+ (0xFF06, '3', '&'),
+ (0xFF07, '3', '\''),
+ (0xFF08, '3', '('),
+ (0xFF09, '3', ')'),
+ (0xFF0A, '3', '*'),
+ (0xFF0B, '3', '+'),
+ (0xFF0C, '3', ','),
+ (0xFF0D, 'M', '-'),
+ (0xFF0E, 'M', '.'),
+ (0xFF0F, '3', '/'),
+ (0xFF10, 'M', '0'),
+ (0xFF11, 'M', '1'),
+ (0xFF12, 'M', '2'),
+ (0xFF13, 'M', '3'),
+ (0xFF14, 'M', '4'),
+ (0xFF15, 'M', '5'),
+ (0xFF16, 'M', '6'),
+ (0xFF17, 'M', '7'),
+ (0xFF18, 'M', '8'),
+ (0xFF19, 'M', '9'),
+ (0xFF1A, '3', ':'),
+ (0xFF1B, '3', ';'),
+ (0xFF1C, '3', '<'),
+ (0xFF1D, '3', '='),
+ (0xFF1E, '3', '>'),
+ ]
+
+def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFF1F, '3', '?'),
+ (0xFF20, '3', '@'),
+ (0xFF21, 'M', 'a'),
+ (0xFF22, 'M', 'b'),
+ (0xFF23, 'M', 'c'),
+ (0xFF24, 'M', 'd'),
+ (0xFF25, 'M', 'e'),
+ (0xFF26, 'M', 'f'),
+ (0xFF27, 'M', 'g'),
+ (0xFF28, 'M', 'h'),
+ (0xFF29, 'M', 'i'),
+ (0xFF2A, 'M', 'j'),
+ (0xFF2B, 'M', 'k'),
+ (0xFF2C, 'M', 'l'),
+ (0xFF2D, 'M', 'm'),
+ (0xFF2E, 'M', 'n'),
+ (0xFF2F, 'M', 'o'),
+ (0xFF30, 'M', 'p'),
+ (0xFF31, 'M', 'q'),
+ (0xFF32, 'M', 'r'),
+ (0xFF33, 'M', 's'),
+ (0xFF34, 'M', 't'),
+ (0xFF35, 'M', 'u'),
+ (0xFF36, 'M', 'v'),
+ (0xFF37, 'M', 'w'),
+ (0xFF38, 'M', 'x'),
+ (0xFF39, 'M', 'y'),
+ (0xFF3A, 'M', 'z'),
+ (0xFF3B, '3', '['),
+ (0xFF3C, '3', '\\'),
+ (0xFF3D, '3', ']'),
+ (0xFF3E, '3', '^'),
+ (0xFF3F, '3', '_'),
+ (0xFF40, '3', '`'),
+ (0xFF41, 'M', 'a'),
+ (0xFF42, 'M', 'b'),
+ (0xFF43, 'M', 'c'),
+ (0xFF44, 'M', 'd'),
+ (0xFF45, 'M', 'e'),
+ (0xFF46, 'M', 'f'),
+ (0xFF47, 'M', 'g'),
+ (0xFF48, 'M', 'h'),
+ (0xFF49, 'M', 'i'),
+ (0xFF4A, 'M', 'j'),
+ (0xFF4B, 'M', 'k'),
+ (0xFF4C, 'M', 'l'),
+ (0xFF4D, 'M', 'm'),
+ (0xFF4E, 'M', 'n'),
+ (0xFF4F, 'M', 'o'),
+ (0xFF50, 'M', 'p'),
+ (0xFF51, 'M', 'q'),
+ (0xFF52, 'M', 'r'),
+ (0xFF53, 'M', 's'),
+ (0xFF54, 'M', 't'),
+ (0xFF55, 'M', 'u'),
+ (0xFF56, 'M', 'v'),
+ (0xFF57, 'M', 'w'),
+ (0xFF58, 'M', 'x'),
+ (0xFF59, 'M', 'y'),
+ (0xFF5A, 'M', 'z'),
+ (0xFF5B, '3', '{'),
+ (0xFF5C, '3', '|'),
+ (0xFF5D, '3', '}'),
+ (0xFF5E, '3', '~'),
+ (0xFF5F, 'M', '⦅'),
+ (0xFF60, 'M', '⦆'),
+ (0xFF61, 'M', '.'),
+ (0xFF62, 'M', '「'),
+ (0xFF63, 'M', '」'),
+ (0xFF64, 'M', '、'),
+ (0xFF65, 'M', '・'),
+ (0xFF66, 'M', 'ヲ'),
+ (0xFF67, 'M', 'ァ'),
+ (0xFF68, 'M', 'ィ'),
+ (0xFF69, 'M', 'ゥ'),
+ (0xFF6A, 'M', 'ェ'),
+ (0xFF6B, 'M', 'ォ'),
+ (0xFF6C, 'M', 'ャ'),
+ (0xFF6D, 'M', 'ュ'),
+ (0xFF6E, 'M', 'ョ'),
+ (0xFF6F, 'M', 'ッ'),
+ (0xFF70, 'M', 'ー'),
+ (0xFF71, 'M', 'ア'),
+ (0xFF72, 'M', 'イ'),
+ (0xFF73, 'M', 'ウ'),
+ (0xFF74, 'M', 'エ'),
+ (0xFF75, 'M', 'オ'),
+ (0xFF76, 'M', 'カ'),
+ (0xFF77, 'M', 'キ'),
+ (0xFF78, 'M', 'ク'),
+ (0xFF79, 'M', 'ケ'),
+ (0xFF7A, 'M', 'コ'),
+ (0xFF7B, 'M', 'サ'),
+ (0xFF7C, 'M', 'シ'),
+ (0xFF7D, 'M', 'ス'),
+ (0xFF7E, 'M', 'セ'),
+ (0xFF7F, 'M', 'ソ'),
+ (0xFF80, 'M', 'タ'),
+ (0xFF81, 'M', 'チ'),
+ (0xFF82, 'M', 'ツ'),
+ ]
+
+def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFF83, 'M', 'テ'),
+ (0xFF84, 'M', 'ト'),
+ (0xFF85, 'M', 'ナ'),
+ (0xFF86, 'M', 'ニ'),
+ (0xFF87, 'M', 'ヌ'),
+ (0xFF88, 'M', 'ネ'),
+ (0xFF89, 'M', 'ノ'),
+ (0xFF8A, 'M', 'ハ'),
+ (0xFF8B, 'M', 'ヒ'),
+ (0xFF8C, 'M', 'フ'),
+ (0xFF8D, 'M', 'ヘ'),
+ (0xFF8E, 'M', 'ホ'),
+ (0xFF8F, 'M', 'マ'),
+ (0xFF90, 'M', 'ミ'),
+ (0xFF91, 'M', 'ム'),
+ (0xFF92, 'M', 'メ'),
+ (0xFF93, 'M', 'モ'),
+ (0xFF94, 'M', 'ヤ'),
+ (0xFF95, 'M', 'ユ'),
+ (0xFF96, 'M', 'ヨ'),
+ (0xFF97, 'M', 'ラ'),
+ (0xFF98, 'M', 'リ'),
+ (0xFF99, 'M', 'ル'),
+ (0xFF9A, 'M', 'レ'),
+ (0xFF9B, 'M', 'ロ'),
+ (0xFF9C, 'M', 'ワ'),
+ (0xFF9D, 'M', 'ン'),
+ (0xFF9E, 'M', '゙'),
+ (0xFF9F, 'M', '゚'),
+ (0xFFA0, 'X'),
+ (0xFFA1, 'M', 'ᄀ'),
+ (0xFFA2, 'M', 'ᄁ'),
+ (0xFFA3, 'M', 'ᆪ'),
+ (0xFFA4, 'M', 'ᄂ'),
+ (0xFFA5, 'M', 'ᆬ'),
+ (0xFFA6, 'M', 'ᆭ'),
+ (0xFFA7, 'M', 'ᄃ'),
+ (0xFFA8, 'M', 'ᄄ'),
+ (0xFFA9, 'M', 'ᄅ'),
+ (0xFFAA, 'M', 'ᆰ'),
+ (0xFFAB, 'M', 'ᆱ'),
+ (0xFFAC, 'M', 'ᆲ'),
+ (0xFFAD, 'M', 'ᆳ'),
+ (0xFFAE, 'M', 'ᆴ'),
+ (0xFFAF, 'M', 'ᆵ'),
+ (0xFFB0, 'M', 'ᄚ'),
+ (0xFFB1, 'M', 'ᄆ'),
+ (0xFFB2, 'M', 'ᄇ'),
+ (0xFFB3, 'M', 'ᄈ'),
+ (0xFFB4, 'M', 'ᄡ'),
+ (0xFFB5, 'M', 'ᄉ'),
+ (0xFFB6, 'M', 'ᄊ'),
+ (0xFFB7, 'M', 'ᄋ'),
+ (0xFFB8, 'M', 'ᄌ'),
+ (0xFFB9, 'M', 'ᄍ'),
+ (0xFFBA, 'M', 'ᄎ'),
+ (0xFFBB, 'M', 'ᄏ'),
+ (0xFFBC, 'M', 'ᄐ'),
+ (0xFFBD, 'M', 'ᄑ'),
+ (0xFFBE, 'M', 'ᄒ'),
+ (0xFFBF, 'X'),
+ (0xFFC2, 'M', 'ᅡ'),
+ (0xFFC3, 'M', 'ᅢ'),
+ (0xFFC4, 'M', 'ᅣ'),
+ (0xFFC5, 'M', 'ᅤ'),
+ (0xFFC6, 'M', 'ᅥ'),
+ (0xFFC7, 'M', 'ᅦ'),
+ (0xFFC8, 'X'),
+ (0xFFCA, 'M', 'ᅧ'),
+ (0xFFCB, 'M', 'ᅨ'),
+ (0xFFCC, 'M', 'ᅩ'),
+ (0xFFCD, 'M', 'ᅪ'),
+ (0xFFCE, 'M', 'ᅫ'),
+ (0xFFCF, 'M', 'ᅬ'),
+ (0xFFD0, 'X'),
+ (0xFFD2, 'M', 'ᅭ'),
+ (0xFFD3, 'M', 'ᅮ'),
+ (0xFFD4, 'M', 'ᅯ'),
+ (0xFFD5, 'M', 'ᅰ'),
+ (0xFFD6, 'M', 'ᅱ'),
+ (0xFFD7, 'M', 'ᅲ'),
+ (0xFFD8, 'X'),
+ (0xFFDA, 'M', 'ᅳ'),
+ (0xFFDB, 'M', 'ᅴ'),
+ (0xFFDC, 'M', 'ᅵ'),
+ (0xFFDD, 'X'),
+ (0xFFE0, 'M', '¢'),
+ (0xFFE1, 'M', '£'),
+ (0xFFE2, 'M', '¬'),
+ (0xFFE3, '3', ' ̄'),
+ (0xFFE4, 'M', '¦'),
+ (0xFFE5, 'M', '¥'),
+ (0xFFE6, 'M', '₩'),
+ (0xFFE7, 'X'),
+ (0xFFE8, 'M', '│'),
+ (0xFFE9, 'M', '←'),
+ (0xFFEA, 'M', '↑'),
+ (0xFFEB, 'M', '→'),
+ (0xFFEC, 'M', '↓'),
+ (0xFFED, 'M', '■'),
+ ]
+
+def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFFEE, 'M', '○'),
+ (0xFFEF, 'X'),
+ (0x10000, 'V'),
+ (0x1000C, 'X'),
+ (0x1000D, 'V'),
+ (0x10027, 'X'),
+ (0x10028, 'V'),
+ (0x1003B, 'X'),
+ (0x1003C, 'V'),
+ (0x1003E, 'X'),
+ (0x1003F, 'V'),
+ (0x1004E, 'X'),
+ (0x10050, 'V'),
+ (0x1005E, 'X'),
+ (0x10080, 'V'),
+ (0x100FB, 'X'),
+ (0x10100, 'V'),
+ (0x10103, 'X'),
+ (0x10107, 'V'),
+ (0x10134, 'X'),
+ (0x10137, 'V'),
+ (0x1018F, 'X'),
+ (0x10190, 'V'),
+ (0x1019D, 'X'),
+ (0x101A0, 'V'),
+ (0x101A1, 'X'),
+ (0x101D0, 'V'),
+ (0x101FE, 'X'),
+ (0x10280, 'V'),
+ (0x1029D, 'X'),
+ (0x102A0, 'V'),
+ (0x102D1, 'X'),
+ (0x102E0, 'V'),
+ (0x102FC, 'X'),
+ (0x10300, 'V'),
+ (0x10324, 'X'),
+ (0x1032D, 'V'),
+ (0x1034B, 'X'),
+ (0x10350, 'V'),
+ (0x1037B, 'X'),
+ (0x10380, 'V'),
+ (0x1039E, 'X'),
+ (0x1039F, 'V'),
+ (0x103C4, 'X'),
+ (0x103C8, 'V'),
+ (0x103D6, 'X'),
+ (0x10400, 'M', '𐐨'),
+ (0x10401, 'M', '𐐩'),
+ (0x10402, 'M', '𐐪'),
+ (0x10403, 'M', '𐐫'),
+ (0x10404, 'M', '𐐬'),
+ (0x10405, 'M', '𐐭'),
+ (0x10406, 'M', '𐐮'),
+ (0x10407, 'M', '𐐯'),
+ (0x10408, 'M', '𐐰'),
+ (0x10409, 'M', '𐐱'),
+ (0x1040A, 'M', '𐐲'),
+ (0x1040B, 'M', '𐐳'),
+ (0x1040C, 'M', '𐐴'),
+ (0x1040D, 'M', '𐐵'),
+ (0x1040E, 'M', '𐐶'),
+ (0x1040F, 'M', '𐐷'),
+ (0x10410, 'M', '𐐸'),
+ (0x10411, 'M', '𐐹'),
+ (0x10412, 'M', '𐐺'),
+ (0x10413, 'M', '𐐻'),
+ (0x10414, 'M', '𐐼'),
+ (0x10415, 'M', '𐐽'),
+ (0x10416, 'M', '𐐾'),
+ (0x10417, 'M', '𐐿'),
+ (0x10418, 'M', '𐑀'),
+ (0x10419, 'M', '𐑁'),
+ (0x1041A, 'M', '𐑂'),
+ (0x1041B, 'M', '𐑃'),
+ (0x1041C, 'M', '𐑄'),
+ (0x1041D, 'M', '𐑅'),
+ (0x1041E, 'M', '𐑆'),
+ (0x1041F, 'M', '𐑇'),
+ (0x10420, 'M', '𐑈'),
+ (0x10421, 'M', '𐑉'),
+ (0x10422, 'M', '𐑊'),
+ (0x10423, 'M', '𐑋'),
+ (0x10424, 'M', '𐑌'),
+ (0x10425, 'M', '𐑍'),
+ (0x10426, 'M', '𐑎'),
+ (0x10427, 'M', '𐑏'),
+ (0x10428, 'V'),
+ (0x1049E, 'X'),
+ (0x104A0, 'V'),
+ (0x104AA, 'X'),
+ (0x104B0, 'M', '𐓘'),
+ (0x104B1, 'M', '𐓙'),
+ (0x104B2, 'M', '𐓚'),
+ (0x104B3, 'M', '𐓛'),
+ (0x104B4, 'M', '𐓜'),
+ (0x104B5, 'M', '𐓝'),
+ (0x104B6, 'M', '𐓞'),
+ (0x104B7, 'M', '𐓟'),
+ (0x104B8, 'M', '𐓠'),
+ (0x104B9, 'M', '𐓡'),
+ ]
+
+def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x104BA, 'M', '𐓢'),
+ (0x104BB, 'M', '𐓣'),
+ (0x104BC, 'M', '𐓤'),
+ (0x104BD, 'M', '𐓥'),
+ (0x104BE, 'M', '𐓦'),
+ (0x104BF, 'M', '𐓧'),
+ (0x104C0, 'M', '𐓨'),
+ (0x104C1, 'M', '𐓩'),
+ (0x104C2, 'M', '𐓪'),
+ (0x104C3, 'M', '𐓫'),
+ (0x104C4, 'M', '𐓬'),
+ (0x104C5, 'M', '𐓭'),
+ (0x104C6, 'M', '𐓮'),
+ (0x104C7, 'M', '𐓯'),
+ (0x104C8, 'M', '𐓰'),
+ (0x104C9, 'M', '𐓱'),
+ (0x104CA, 'M', '𐓲'),
+ (0x104CB, 'M', '𐓳'),
+ (0x104CC, 'M', '𐓴'),
+ (0x104CD, 'M', '𐓵'),
+ (0x104CE, 'M', '𐓶'),
+ (0x104CF, 'M', '𐓷'),
+ (0x104D0, 'M', '𐓸'),
+ (0x104D1, 'M', '𐓹'),
+ (0x104D2, 'M', '𐓺'),
+ (0x104D3, 'M', '𐓻'),
+ (0x104D4, 'X'),
+ (0x104D8, 'V'),
+ (0x104FC, 'X'),
+ (0x10500, 'V'),
+ (0x10528, 'X'),
+ (0x10530, 'V'),
+ (0x10564, 'X'),
+ (0x1056F, 'V'),
+ (0x10570, 'M', '𐖗'),
+ (0x10571, 'M', '𐖘'),
+ (0x10572, 'M', '𐖙'),
+ (0x10573, 'M', '𐖚'),
+ (0x10574, 'M', '𐖛'),
+ (0x10575, 'M', '𐖜'),
+ (0x10576, 'M', '𐖝'),
+ (0x10577, 'M', '𐖞'),
+ (0x10578, 'M', '𐖟'),
+ (0x10579, 'M', '𐖠'),
+ (0x1057A, 'M', '𐖡'),
+ (0x1057B, 'X'),
+ (0x1057C, 'M', '𐖣'),
+ (0x1057D, 'M', '𐖤'),
+ (0x1057E, 'M', '𐖥'),
+ (0x1057F, 'M', '𐖦'),
+ (0x10580, 'M', '𐖧'),
+ (0x10581, 'M', '𐖨'),
+ (0x10582, 'M', '𐖩'),
+ (0x10583, 'M', '𐖪'),
+ (0x10584, 'M', '𐖫'),
+ (0x10585, 'M', '𐖬'),
+ (0x10586, 'M', '𐖭'),
+ (0x10587, 'M', '𐖮'),
+ (0x10588, 'M', '𐖯'),
+ (0x10589, 'M', '𐖰'),
+ (0x1058A, 'M', '𐖱'),
+ (0x1058B, 'X'),
+ (0x1058C, 'M', '𐖳'),
+ (0x1058D, 'M', '𐖴'),
+ (0x1058E, 'M', '𐖵'),
+ (0x1058F, 'M', '𐖶'),
+ (0x10590, 'M', '𐖷'),
+ (0x10591, 'M', '𐖸'),
+ (0x10592, 'M', '𐖹'),
+ (0x10593, 'X'),
+ (0x10594, 'M', '𐖻'),
+ (0x10595, 'M', '𐖼'),
+ (0x10596, 'X'),
+ (0x10597, 'V'),
+ (0x105A2, 'X'),
+ (0x105A3, 'V'),
+ (0x105B2, 'X'),
+ (0x105B3, 'V'),
+ (0x105BA, 'X'),
+ (0x105BB, 'V'),
+ (0x105BD, 'X'),
+ (0x10600, 'V'),
+ (0x10737, 'X'),
+ (0x10740, 'V'),
+ (0x10756, 'X'),
+ (0x10760, 'V'),
+ (0x10768, 'X'),
+ (0x10780, 'V'),
+ (0x10781, 'M', 'ː'),
+ (0x10782, 'M', 'ˑ'),
+ (0x10783, 'M', 'æ'),
+ (0x10784, 'M', 'ʙ'),
+ (0x10785, 'M', 'ɓ'),
+ (0x10786, 'X'),
+ (0x10787, 'M', 'ʣ'),
+ (0x10788, 'M', 'ꭦ'),
+ (0x10789, 'M', 'ʥ'),
+ (0x1078A, 'M', 'ʤ'),
+ (0x1078B, 'M', 'ɖ'),
+ (0x1078C, 'M', 'ɗ'),
+ ]
+
+def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1078D, 'M', 'ᶑ'),
+ (0x1078E, 'M', 'ɘ'),
+ (0x1078F, 'M', 'ɞ'),
+ (0x10790, 'M', 'ʩ'),
+ (0x10791, 'M', 'ɤ'),
+ (0x10792, 'M', 'ɢ'),
+ (0x10793, 'M', 'ɠ'),
+ (0x10794, 'M', 'ʛ'),
+ (0x10795, 'M', 'ħ'),
+ (0x10796, 'M', 'ʜ'),
+ (0x10797, 'M', 'ɧ'),
+ (0x10798, 'M', 'ʄ'),
+ (0x10799, 'M', 'ʪ'),
+ (0x1079A, 'M', 'ʫ'),
+ (0x1079B, 'M', 'ɬ'),
+ (0x1079C, 'M', '𝼄'),
+ (0x1079D, 'M', 'ꞎ'),
+ (0x1079E, 'M', 'ɮ'),
+ (0x1079F, 'M', '𝼅'),
+ (0x107A0, 'M', 'ʎ'),
+ (0x107A1, 'M', '𝼆'),
+ (0x107A2, 'M', 'ø'),
+ (0x107A3, 'M', 'ɶ'),
+ (0x107A4, 'M', 'ɷ'),
+ (0x107A5, 'M', 'q'),
+ (0x107A6, 'M', 'ɺ'),
+ (0x107A7, 'M', '𝼈'),
+ (0x107A8, 'M', 'ɽ'),
+ (0x107A9, 'M', 'ɾ'),
+ (0x107AA, 'M', 'ʀ'),
+ (0x107AB, 'M', 'ʨ'),
+ (0x107AC, 'M', 'ʦ'),
+ (0x107AD, 'M', 'ꭧ'),
+ (0x107AE, 'M', 'ʧ'),
+ (0x107AF, 'M', 'ʈ'),
+ (0x107B0, 'M', 'ⱱ'),
+ (0x107B1, 'X'),
+ (0x107B2, 'M', 'ʏ'),
+ (0x107B3, 'M', 'ʡ'),
+ (0x107B4, 'M', 'ʢ'),
+ (0x107B5, 'M', 'ʘ'),
+ (0x107B6, 'M', 'ǀ'),
+ (0x107B7, 'M', 'ǁ'),
+ (0x107B8, 'M', 'ǂ'),
+ (0x107B9, 'M', '𝼊'),
+ (0x107BA, 'M', '𝼞'),
+ (0x107BB, 'X'),
+ (0x10800, 'V'),
+ (0x10806, 'X'),
+ (0x10808, 'V'),
+ (0x10809, 'X'),
+ (0x1080A, 'V'),
+ (0x10836, 'X'),
+ (0x10837, 'V'),
+ (0x10839, 'X'),
+ (0x1083C, 'V'),
+ (0x1083D, 'X'),
+ (0x1083F, 'V'),
+ (0x10856, 'X'),
+ (0x10857, 'V'),
+ (0x1089F, 'X'),
+ (0x108A7, 'V'),
+ (0x108B0, 'X'),
+ (0x108E0, 'V'),
+ (0x108F3, 'X'),
+ (0x108F4, 'V'),
+ (0x108F6, 'X'),
+ (0x108FB, 'V'),
+ (0x1091C, 'X'),
+ (0x1091F, 'V'),
+ (0x1093A, 'X'),
+ (0x1093F, 'V'),
+ (0x10940, 'X'),
+ (0x10980, 'V'),
+ (0x109B8, 'X'),
+ (0x109BC, 'V'),
+ (0x109D0, 'X'),
+ (0x109D2, 'V'),
+ (0x10A04, 'X'),
+ (0x10A05, 'V'),
+ (0x10A07, 'X'),
+ (0x10A0C, 'V'),
+ (0x10A14, 'X'),
+ (0x10A15, 'V'),
+ (0x10A18, 'X'),
+ (0x10A19, 'V'),
+ (0x10A36, 'X'),
+ (0x10A38, 'V'),
+ (0x10A3B, 'X'),
+ (0x10A3F, 'V'),
+ (0x10A49, 'X'),
+ (0x10A50, 'V'),
+ (0x10A59, 'X'),
+ (0x10A60, 'V'),
+ (0x10AA0, 'X'),
+ (0x10AC0, 'V'),
+ (0x10AE7, 'X'),
+ (0x10AEB, 'V'),
+ (0x10AF7, 'X'),
+ (0x10B00, 'V'),
+ ]
+
+def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10B36, 'X'),
+ (0x10B39, 'V'),
+ (0x10B56, 'X'),
+ (0x10B58, 'V'),
+ (0x10B73, 'X'),
+ (0x10B78, 'V'),
+ (0x10B92, 'X'),
+ (0x10B99, 'V'),
+ (0x10B9D, 'X'),
+ (0x10BA9, 'V'),
+ (0x10BB0, 'X'),
+ (0x10C00, 'V'),
+ (0x10C49, 'X'),
+ (0x10C80, 'M', '𐳀'),
+ (0x10C81, 'M', '𐳁'),
+ (0x10C82, 'M', '𐳂'),
+ (0x10C83, 'M', '𐳃'),
+ (0x10C84, 'M', '𐳄'),
+ (0x10C85, 'M', '𐳅'),
+ (0x10C86, 'M', '𐳆'),
+ (0x10C87, 'M', '𐳇'),
+ (0x10C88, 'M', '𐳈'),
+ (0x10C89, 'M', '𐳉'),
+ (0x10C8A, 'M', '𐳊'),
+ (0x10C8B, 'M', '𐳋'),
+ (0x10C8C, 'M', '𐳌'),
+ (0x10C8D, 'M', '𐳍'),
+ (0x10C8E, 'M', '𐳎'),
+ (0x10C8F, 'M', '𐳏'),
+ (0x10C90, 'M', '𐳐'),
+ (0x10C91, 'M', '𐳑'),
+ (0x10C92, 'M', '𐳒'),
+ (0x10C93, 'M', '𐳓'),
+ (0x10C94, 'M', '𐳔'),
+ (0x10C95, 'M', '𐳕'),
+ (0x10C96, 'M', '𐳖'),
+ (0x10C97, 'M', '𐳗'),
+ (0x10C98, 'M', '𐳘'),
+ (0x10C99, 'M', '𐳙'),
+ (0x10C9A, 'M', '𐳚'),
+ (0x10C9B, 'M', '𐳛'),
+ (0x10C9C, 'M', '𐳜'),
+ (0x10C9D, 'M', '𐳝'),
+ (0x10C9E, 'M', '𐳞'),
+ (0x10C9F, 'M', '𐳟'),
+ (0x10CA0, 'M', '𐳠'),
+ (0x10CA1, 'M', '𐳡'),
+ (0x10CA2, 'M', '𐳢'),
+ (0x10CA3, 'M', '𐳣'),
+ (0x10CA4, 'M', '𐳤'),
+ (0x10CA5, 'M', '𐳥'),
+ (0x10CA6, 'M', '𐳦'),
+ (0x10CA7, 'M', '𐳧'),
+ (0x10CA8, 'M', '𐳨'),
+ (0x10CA9, 'M', '𐳩'),
+ (0x10CAA, 'M', '𐳪'),
+ (0x10CAB, 'M', '𐳫'),
+ (0x10CAC, 'M', '𐳬'),
+ (0x10CAD, 'M', '𐳭'),
+ (0x10CAE, 'M', '𐳮'),
+ (0x10CAF, 'M', '𐳯'),
+ (0x10CB0, 'M', '𐳰'),
+ (0x10CB1, 'M', '𐳱'),
+ (0x10CB2, 'M', '𐳲'),
+ (0x10CB3, 'X'),
+ (0x10CC0, 'V'),
+ (0x10CF3, 'X'),
+ (0x10CFA, 'V'),
+ (0x10D28, 'X'),
+ (0x10D30, 'V'),
+ (0x10D3A, 'X'),
+ (0x10E60, 'V'),
+ (0x10E7F, 'X'),
+ (0x10E80, 'V'),
+ (0x10EAA, 'X'),
+ (0x10EAB, 'V'),
+ (0x10EAE, 'X'),
+ (0x10EB0, 'V'),
+ (0x10EB2, 'X'),
+ (0x10EFD, 'V'),
+ (0x10F28, 'X'),
+ (0x10F30, 'V'),
+ (0x10F5A, 'X'),
+ (0x10F70, 'V'),
+ (0x10F8A, 'X'),
+ (0x10FB0, 'V'),
+ (0x10FCC, 'X'),
+ (0x10FE0, 'V'),
+ (0x10FF7, 'X'),
+ (0x11000, 'V'),
+ (0x1104E, 'X'),
+ (0x11052, 'V'),
+ (0x11076, 'X'),
+ (0x1107F, 'V'),
+ (0x110BD, 'X'),
+ (0x110BE, 'V'),
+ (0x110C3, 'X'),
+ (0x110D0, 'V'),
+ (0x110E9, 'X'),
+ (0x110F0, 'V'),
+ ]
+
+def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x110FA, 'X'),
+ (0x11100, 'V'),
+ (0x11135, 'X'),
+ (0x11136, 'V'),
+ (0x11148, 'X'),
+ (0x11150, 'V'),
+ (0x11177, 'X'),
+ (0x11180, 'V'),
+ (0x111E0, 'X'),
+ (0x111E1, 'V'),
+ (0x111F5, 'X'),
+ (0x11200, 'V'),
+ (0x11212, 'X'),
+ (0x11213, 'V'),
+ (0x11242, 'X'),
+ (0x11280, 'V'),
+ (0x11287, 'X'),
+ (0x11288, 'V'),
+ (0x11289, 'X'),
+ (0x1128A, 'V'),
+ (0x1128E, 'X'),
+ (0x1128F, 'V'),
+ (0x1129E, 'X'),
+ (0x1129F, 'V'),
+ (0x112AA, 'X'),
+ (0x112B0, 'V'),
+ (0x112EB, 'X'),
+ (0x112F0, 'V'),
+ (0x112FA, 'X'),
+ (0x11300, 'V'),
+ (0x11304, 'X'),
+ (0x11305, 'V'),
+ (0x1130D, 'X'),
+ (0x1130F, 'V'),
+ (0x11311, 'X'),
+ (0x11313, 'V'),
+ (0x11329, 'X'),
+ (0x1132A, 'V'),
+ (0x11331, 'X'),
+ (0x11332, 'V'),
+ (0x11334, 'X'),
+ (0x11335, 'V'),
+ (0x1133A, 'X'),
+ (0x1133B, 'V'),
+ (0x11345, 'X'),
+ (0x11347, 'V'),
+ (0x11349, 'X'),
+ (0x1134B, 'V'),
+ (0x1134E, 'X'),
+ (0x11350, 'V'),
+ (0x11351, 'X'),
+ (0x11357, 'V'),
+ (0x11358, 'X'),
+ (0x1135D, 'V'),
+ (0x11364, 'X'),
+ (0x11366, 'V'),
+ (0x1136D, 'X'),
+ (0x11370, 'V'),
+ (0x11375, 'X'),
+ (0x11400, 'V'),
+ (0x1145C, 'X'),
+ (0x1145D, 'V'),
+ (0x11462, 'X'),
+ (0x11480, 'V'),
+ (0x114C8, 'X'),
+ (0x114D0, 'V'),
+ (0x114DA, 'X'),
+ (0x11580, 'V'),
+ (0x115B6, 'X'),
+ (0x115B8, 'V'),
+ (0x115DE, 'X'),
+ (0x11600, 'V'),
+ (0x11645, 'X'),
+ (0x11650, 'V'),
+ (0x1165A, 'X'),
+ (0x11660, 'V'),
+ (0x1166D, 'X'),
+ (0x11680, 'V'),
+ (0x116BA, 'X'),
+ (0x116C0, 'V'),
+ (0x116CA, 'X'),
+ (0x11700, 'V'),
+ (0x1171B, 'X'),
+ (0x1171D, 'V'),
+ (0x1172C, 'X'),
+ (0x11730, 'V'),
+ (0x11747, 'X'),
+ (0x11800, 'V'),
+ (0x1183C, 'X'),
+ (0x118A0, 'M', '𑣀'),
+ (0x118A1, 'M', '𑣁'),
+ (0x118A2, 'M', '𑣂'),
+ (0x118A3, 'M', '𑣃'),
+ (0x118A4, 'M', '𑣄'),
+ (0x118A5, 'M', '𑣅'),
+ (0x118A6, 'M', '𑣆'),
+ (0x118A7, 'M', '𑣇'),
+ (0x118A8, 'M', '𑣈'),
+ (0x118A9, 'M', '𑣉'),
+ (0x118AA, 'M', '𑣊'),
+ ]
+
+def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x118AB, 'M', '𑣋'),
+ (0x118AC, 'M', '𑣌'),
+ (0x118AD, 'M', '𑣍'),
+ (0x118AE, 'M', '𑣎'),
+ (0x118AF, 'M', '𑣏'),
+ (0x118B0, 'M', '𑣐'),
+ (0x118B1, 'M', '𑣑'),
+ (0x118B2, 'M', '𑣒'),
+ (0x118B3, 'M', '𑣓'),
+ (0x118B4, 'M', '𑣔'),
+ (0x118B5, 'M', '𑣕'),
+ (0x118B6, 'M', '𑣖'),
+ (0x118B7, 'M', '𑣗'),
+ (0x118B8, 'M', '𑣘'),
+ (0x118B9, 'M', '𑣙'),
+ (0x118BA, 'M', '𑣚'),
+ (0x118BB, 'M', '𑣛'),
+ (0x118BC, 'M', '𑣜'),
+ (0x118BD, 'M', '𑣝'),
+ (0x118BE, 'M', '𑣞'),
+ (0x118BF, 'M', '𑣟'),
+ (0x118C0, 'V'),
+ (0x118F3, 'X'),
+ (0x118FF, 'V'),
+ (0x11907, 'X'),
+ (0x11909, 'V'),
+ (0x1190A, 'X'),
+ (0x1190C, 'V'),
+ (0x11914, 'X'),
+ (0x11915, 'V'),
+ (0x11917, 'X'),
+ (0x11918, 'V'),
+ (0x11936, 'X'),
+ (0x11937, 'V'),
+ (0x11939, 'X'),
+ (0x1193B, 'V'),
+ (0x11947, 'X'),
+ (0x11950, 'V'),
+ (0x1195A, 'X'),
+ (0x119A0, 'V'),
+ (0x119A8, 'X'),
+ (0x119AA, 'V'),
+ (0x119D8, 'X'),
+ (0x119DA, 'V'),
+ (0x119E5, 'X'),
+ (0x11A00, 'V'),
+ (0x11A48, 'X'),
+ (0x11A50, 'V'),
+ (0x11AA3, 'X'),
+ (0x11AB0, 'V'),
+ (0x11AF9, 'X'),
+ (0x11B00, 'V'),
+ (0x11B0A, 'X'),
+ (0x11C00, 'V'),
+ (0x11C09, 'X'),
+ (0x11C0A, 'V'),
+ (0x11C37, 'X'),
+ (0x11C38, 'V'),
+ (0x11C46, 'X'),
+ (0x11C50, 'V'),
+ (0x11C6D, 'X'),
+ (0x11C70, 'V'),
+ (0x11C90, 'X'),
+ (0x11C92, 'V'),
+ (0x11CA8, 'X'),
+ (0x11CA9, 'V'),
+ (0x11CB7, 'X'),
+ (0x11D00, 'V'),
+ (0x11D07, 'X'),
+ (0x11D08, 'V'),
+ (0x11D0A, 'X'),
+ (0x11D0B, 'V'),
+ (0x11D37, 'X'),
+ (0x11D3A, 'V'),
+ (0x11D3B, 'X'),
+ (0x11D3C, 'V'),
+ (0x11D3E, 'X'),
+ (0x11D3F, 'V'),
+ (0x11D48, 'X'),
+ (0x11D50, 'V'),
+ (0x11D5A, 'X'),
+ (0x11D60, 'V'),
+ (0x11D66, 'X'),
+ (0x11D67, 'V'),
+ (0x11D69, 'X'),
+ (0x11D6A, 'V'),
+ (0x11D8F, 'X'),
+ (0x11D90, 'V'),
+ (0x11D92, 'X'),
+ (0x11D93, 'V'),
+ (0x11D99, 'X'),
+ (0x11DA0, 'V'),
+ (0x11DAA, 'X'),
+ (0x11EE0, 'V'),
+ (0x11EF9, 'X'),
+ (0x11F00, 'V'),
+ (0x11F11, 'X'),
+ (0x11F12, 'V'),
+ (0x11F3B, 'X'),
+ (0x11F3E, 'V'),
+ ]
+
+def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x11F5A, 'X'),
+ (0x11FB0, 'V'),
+ (0x11FB1, 'X'),
+ (0x11FC0, 'V'),
+ (0x11FF2, 'X'),
+ (0x11FFF, 'V'),
+ (0x1239A, 'X'),
+ (0x12400, 'V'),
+ (0x1246F, 'X'),
+ (0x12470, 'V'),
+ (0x12475, 'X'),
+ (0x12480, 'V'),
+ (0x12544, 'X'),
+ (0x12F90, 'V'),
+ (0x12FF3, 'X'),
+ (0x13000, 'V'),
+ (0x13430, 'X'),
+ (0x13440, 'V'),
+ (0x13456, 'X'),
+ (0x14400, 'V'),
+ (0x14647, 'X'),
+ (0x16800, 'V'),
+ (0x16A39, 'X'),
+ (0x16A40, 'V'),
+ (0x16A5F, 'X'),
+ (0x16A60, 'V'),
+ (0x16A6A, 'X'),
+ (0x16A6E, 'V'),
+ (0x16ABF, 'X'),
+ (0x16AC0, 'V'),
+ (0x16ACA, 'X'),
+ (0x16AD0, 'V'),
+ (0x16AEE, 'X'),
+ (0x16AF0, 'V'),
+ (0x16AF6, 'X'),
+ (0x16B00, 'V'),
+ (0x16B46, 'X'),
+ (0x16B50, 'V'),
+ (0x16B5A, 'X'),
+ (0x16B5B, 'V'),
+ (0x16B62, 'X'),
+ (0x16B63, 'V'),
+ (0x16B78, 'X'),
+ (0x16B7D, 'V'),
+ (0x16B90, 'X'),
+ (0x16E40, 'M', '𖹠'),
+ (0x16E41, 'M', '𖹡'),
+ (0x16E42, 'M', '𖹢'),
+ (0x16E43, 'M', '𖹣'),
+ (0x16E44, 'M', '𖹤'),
+ (0x16E45, 'M', '𖹥'),
+ (0x16E46, 'M', '𖹦'),
+ (0x16E47, 'M', '𖹧'),
+ (0x16E48, 'M', '𖹨'),
+ (0x16E49, 'M', '𖹩'),
+ (0x16E4A, 'M', '𖹪'),
+ (0x16E4B, 'M', '𖹫'),
+ (0x16E4C, 'M', '𖹬'),
+ (0x16E4D, 'M', '𖹭'),
+ (0x16E4E, 'M', '𖹮'),
+ (0x16E4F, 'M', '𖹯'),
+ (0x16E50, 'M', '𖹰'),
+ (0x16E51, 'M', '𖹱'),
+ (0x16E52, 'M', '𖹲'),
+ (0x16E53, 'M', '𖹳'),
+ (0x16E54, 'M', '𖹴'),
+ (0x16E55, 'M', '𖹵'),
+ (0x16E56, 'M', '𖹶'),
+ (0x16E57, 'M', '𖹷'),
+ (0x16E58, 'M', '𖹸'),
+ (0x16E59, 'M', '𖹹'),
+ (0x16E5A, 'M', '𖹺'),
+ (0x16E5B, 'M', '𖹻'),
+ (0x16E5C, 'M', '𖹼'),
+ (0x16E5D, 'M', '𖹽'),
+ (0x16E5E, 'M', '𖹾'),
+ (0x16E5F, 'M', '𖹿'),
+ (0x16E60, 'V'),
+ (0x16E9B, 'X'),
+ (0x16F00, 'V'),
+ (0x16F4B, 'X'),
+ (0x16F4F, 'V'),
+ (0x16F88, 'X'),
+ (0x16F8F, 'V'),
+ (0x16FA0, 'X'),
+ (0x16FE0, 'V'),
+ (0x16FE5, 'X'),
+ (0x16FF0, 'V'),
+ (0x16FF2, 'X'),
+ (0x17000, 'V'),
+ (0x187F8, 'X'),
+ (0x18800, 'V'),
+ (0x18CD6, 'X'),
+ (0x18D00, 'V'),
+ (0x18D09, 'X'),
+ (0x1AFF0, 'V'),
+ (0x1AFF4, 'X'),
+ (0x1AFF5, 'V'),
+ (0x1AFFC, 'X'),
+ (0x1AFFD, 'V'),
+ ]
+
+def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1AFFF, 'X'),
+ (0x1B000, 'V'),
+ (0x1B123, 'X'),
+ (0x1B132, 'V'),
+ (0x1B133, 'X'),
+ (0x1B150, 'V'),
+ (0x1B153, 'X'),
+ (0x1B155, 'V'),
+ (0x1B156, 'X'),
+ (0x1B164, 'V'),
+ (0x1B168, 'X'),
+ (0x1B170, 'V'),
+ (0x1B2FC, 'X'),
+ (0x1BC00, 'V'),
+ (0x1BC6B, 'X'),
+ (0x1BC70, 'V'),
+ (0x1BC7D, 'X'),
+ (0x1BC80, 'V'),
+ (0x1BC89, 'X'),
+ (0x1BC90, 'V'),
+ (0x1BC9A, 'X'),
+ (0x1BC9C, 'V'),
+ (0x1BCA0, 'I'),
+ (0x1BCA4, 'X'),
+ (0x1CF00, 'V'),
+ (0x1CF2E, 'X'),
+ (0x1CF30, 'V'),
+ (0x1CF47, 'X'),
+ (0x1CF50, 'V'),
+ (0x1CFC4, 'X'),
+ (0x1D000, 'V'),
+ (0x1D0F6, 'X'),
+ (0x1D100, 'V'),
+ (0x1D127, 'X'),
+ (0x1D129, 'V'),
+ (0x1D15E, 'M', '𝅗𝅥'),
+ (0x1D15F, 'M', '𝅘𝅥'),
+ (0x1D160, 'M', '𝅘𝅥𝅮'),
+ (0x1D161, 'M', '𝅘𝅥𝅯'),
+ (0x1D162, 'M', '𝅘𝅥𝅰'),
+ (0x1D163, 'M', '𝅘𝅥𝅱'),
+ (0x1D164, 'M', '𝅘𝅥𝅲'),
+ (0x1D165, 'V'),
+ (0x1D173, 'X'),
+ (0x1D17B, 'V'),
+ (0x1D1BB, 'M', '𝆹𝅥'),
+ (0x1D1BC, 'M', '𝆺𝅥'),
+ (0x1D1BD, 'M', '𝆹𝅥𝅮'),
+ (0x1D1BE, 'M', '𝆺𝅥𝅮'),
+ (0x1D1BF, 'M', '𝆹𝅥𝅯'),
+ (0x1D1C0, 'M', '𝆺𝅥𝅯'),
+ (0x1D1C1, 'V'),
+ (0x1D1EB, 'X'),
+ (0x1D200, 'V'),
+ (0x1D246, 'X'),
+ (0x1D2C0, 'V'),
+ (0x1D2D4, 'X'),
+ (0x1D2E0, 'V'),
+ (0x1D2F4, 'X'),
+ (0x1D300, 'V'),
+ (0x1D357, 'X'),
+ (0x1D360, 'V'),
+ (0x1D379, 'X'),
+ (0x1D400, 'M', 'a'),
+ (0x1D401, 'M', 'b'),
+ (0x1D402, 'M', 'c'),
+ (0x1D403, 'M', 'd'),
+ (0x1D404, 'M', 'e'),
+ (0x1D405, 'M', 'f'),
+ (0x1D406, 'M', 'g'),
+ (0x1D407, 'M', 'h'),
+ (0x1D408, 'M', 'i'),
+ (0x1D409, 'M', 'j'),
+ (0x1D40A, 'M', 'k'),
+ (0x1D40B, 'M', 'l'),
+ (0x1D40C, 'M', 'm'),
+ (0x1D40D, 'M', 'n'),
+ (0x1D40E, 'M', 'o'),
+ (0x1D40F, 'M', 'p'),
+ (0x1D410, 'M', 'q'),
+ (0x1D411, 'M', 'r'),
+ (0x1D412, 'M', 's'),
+ (0x1D413, 'M', 't'),
+ (0x1D414, 'M', 'u'),
+ (0x1D415, 'M', 'v'),
+ (0x1D416, 'M', 'w'),
+ (0x1D417, 'M', 'x'),
+ (0x1D418, 'M', 'y'),
+ (0x1D419, 'M', 'z'),
+ (0x1D41A, 'M', 'a'),
+ (0x1D41B, 'M', 'b'),
+ (0x1D41C, 'M', 'c'),
+ (0x1D41D, 'M', 'd'),
+ (0x1D41E, 'M', 'e'),
+ (0x1D41F, 'M', 'f'),
+ (0x1D420, 'M', 'g'),
+ (0x1D421, 'M', 'h'),
+ (0x1D422, 'M', 'i'),
+ (0x1D423, 'M', 'j'),
+ (0x1D424, 'M', 'k'),
+ ]
+
+def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D425, 'M', 'l'),
+ (0x1D426, 'M', 'm'),
+ (0x1D427, 'M', 'n'),
+ (0x1D428, 'M', 'o'),
+ (0x1D429, 'M', 'p'),
+ (0x1D42A, 'M', 'q'),
+ (0x1D42B, 'M', 'r'),
+ (0x1D42C, 'M', 's'),
+ (0x1D42D, 'M', 't'),
+ (0x1D42E, 'M', 'u'),
+ (0x1D42F, 'M', 'v'),
+ (0x1D430, 'M', 'w'),
+ (0x1D431, 'M', 'x'),
+ (0x1D432, 'M', 'y'),
+ (0x1D433, 'M', 'z'),
+ (0x1D434, 'M', 'a'),
+ (0x1D435, 'M', 'b'),
+ (0x1D436, 'M', 'c'),
+ (0x1D437, 'M', 'd'),
+ (0x1D438, 'M', 'e'),
+ (0x1D439, 'M', 'f'),
+ (0x1D43A, 'M', 'g'),
+ (0x1D43B, 'M', 'h'),
+ (0x1D43C, 'M', 'i'),
+ (0x1D43D, 'M', 'j'),
+ (0x1D43E, 'M', 'k'),
+ (0x1D43F, 'M', 'l'),
+ (0x1D440, 'M', 'm'),
+ (0x1D441, 'M', 'n'),
+ (0x1D442, 'M', 'o'),
+ (0x1D443, 'M', 'p'),
+ (0x1D444, 'M', 'q'),
+ (0x1D445, 'M', 'r'),
+ (0x1D446, 'M', 's'),
+ (0x1D447, 'M', 't'),
+ (0x1D448, 'M', 'u'),
+ (0x1D449, 'M', 'v'),
+ (0x1D44A, 'M', 'w'),
+ (0x1D44B, 'M', 'x'),
+ (0x1D44C, 'M', 'y'),
+ (0x1D44D, 'M', 'z'),
+ (0x1D44E, 'M', 'a'),
+ (0x1D44F, 'M', 'b'),
+ (0x1D450, 'M', 'c'),
+ (0x1D451, 'M', 'd'),
+ (0x1D452, 'M', 'e'),
+ (0x1D453, 'M', 'f'),
+ (0x1D454, 'M', 'g'),
+ (0x1D455, 'X'),
+ (0x1D456, 'M', 'i'),
+ (0x1D457, 'M', 'j'),
+ (0x1D458, 'M', 'k'),
+ (0x1D459, 'M', 'l'),
+ (0x1D45A, 'M', 'm'),
+ (0x1D45B, 'M', 'n'),
+ (0x1D45C, 'M', 'o'),
+ (0x1D45D, 'M', 'p'),
+ (0x1D45E, 'M', 'q'),
+ (0x1D45F, 'M', 'r'),
+ (0x1D460, 'M', 's'),
+ (0x1D461, 'M', 't'),
+ (0x1D462, 'M', 'u'),
+ (0x1D463, 'M', 'v'),
+ (0x1D464, 'M', 'w'),
+ (0x1D465, 'M', 'x'),
+ (0x1D466, 'M', 'y'),
+ (0x1D467, 'M', 'z'),
+ (0x1D468, 'M', 'a'),
+ (0x1D469, 'M', 'b'),
+ (0x1D46A, 'M', 'c'),
+ (0x1D46B, 'M', 'd'),
+ (0x1D46C, 'M', 'e'),
+ (0x1D46D, 'M', 'f'),
+ (0x1D46E, 'M', 'g'),
+ (0x1D46F, 'M', 'h'),
+ (0x1D470, 'M', 'i'),
+ (0x1D471, 'M', 'j'),
+ (0x1D472, 'M', 'k'),
+ (0x1D473, 'M', 'l'),
+ (0x1D474, 'M', 'm'),
+ (0x1D475, 'M', 'n'),
+ (0x1D476, 'M', 'o'),
+ (0x1D477, 'M', 'p'),
+ (0x1D478, 'M', 'q'),
+ (0x1D479, 'M', 'r'),
+ (0x1D47A, 'M', 's'),
+ (0x1D47B, 'M', 't'),
+ (0x1D47C, 'M', 'u'),
+ (0x1D47D, 'M', 'v'),
+ (0x1D47E, 'M', 'w'),
+ (0x1D47F, 'M', 'x'),
+ (0x1D480, 'M', 'y'),
+ (0x1D481, 'M', 'z'),
+ (0x1D482, 'M', 'a'),
+ (0x1D483, 'M', 'b'),
+ (0x1D484, 'M', 'c'),
+ (0x1D485, 'M', 'd'),
+ (0x1D486, 'M', 'e'),
+ (0x1D487, 'M', 'f'),
+ (0x1D488, 'M', 'g'),
+ ]
+
+def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D489, 'M', 'h'),
+ (0x1D48A, 'M', 'i'),
+ (0x1D48B, 'M', 'j'),
+ (0x1D48C, 'M', 'k'),
+ (0x1D48D, 'M', 'l'),
+ (0x1D48E, 'M', 'm'),
+ (0x1D48F, 'M', 'n'),
+ (0x1D490, 'M', 'o'),
+ (0x1D491, 'M', 'p'),
+ (0x1D492, 'M', 'q'),
+ (0x1D493, 'M', 'r'),
+ (0x1D494, 'M', 's'),
+ (0x1D495, 'M', 't'),
+ (0x1D496, 'M', 'u'),
+ (0x1D497, 'M', 'v'),
+ (0x1D498, 'M', 'w'),
+ (0x1D499, 'M', 'x'),
+ (0x1D49A, 'M', 'y'),
+ (0x1D49B, 'M', 'z'),
+ (0x1D49C, 'M', 'a'),
+ (0x1D49D, 'X'),
+ (0x1D49E, 'M', 'c'),
+ (0x1D49F, 'M', 'd'),
+ (0x1D4A0, 'X'),
+ (0x1D4A2, 'M', 'g'),
+ (0x1D4A3, 'X'),
+ (0x1D4A5, 'M', 'j'),
+ (0x1D4A6, 'M', 'k'),
+ (0x1D4A7, 'X'),
+ (0x1D4A9, 'M', 'n'),
+ (0x1D4AA, 'M', 'o'),
+ (0x1D4AB, 'M', 'p'),
+ (0x1D4AC, 'M', 'q'),
+ (0x1D4AD, 'X'),
+ (0x1D4AE, 'M', 's'),
+ (0x1D4AF, 'M', 't'),
+ (0x1D4B0, 'M', 'u'),
+ (0x1D4B1, 'M', 'v'),
+ (0x1D4B2, 'M', 'w'),
+ (0x1D4B3, 'M', 'x'),
+ (0x1D4B4, 'M', 'y'),
+ (0x1D4B5, 'M', 'z'),
+ (0x1D4B6, 'M', 'a'),
+ (0x1D4B7, 'M', 'b'),
+ (0x1D4B8, 'M', 'c'),
+ (0x1D4B9, 'M', 'd'),
+ (0x1D4BA, 'X'),
+ (0x1D4BB, 'M', 'f'),
+ (0x1D4BC, 'X'),
+ (0x1D4BD, 'M', 'h'),
+ (0x1D4BE, 'M', 'i'),
+ (0x1D4BF, 'M', 'j'),
+ (0x1D4C0, 'M', 'k'),
+ (0x1D4C1, 'M', 'l'),
+ (0x1D4C2, 'M', 'm'),
+ (0x1D4C3, 'M', 'n'),
+ (0x1D4C4, 'X'),
+ (0x1D4C5, 'M', 'p'),
+ (0x1D4C6, 'M', 'q'),
+ (0x1D4C7, 'M', 'r'),
+ (0x1D4C8, 'M', 's'),
+ (0x1D4C9, 'M', 't'),
+ (0x1D4CA, 'M', 'u'),
+ (0x1D4CB, 'M', 'v'),
+ (0x1D4CC, 'M', 'w'),
+ (0x1D4CD, 'M', 'x'),
+ (0x1D4CE, 'M', 'y'),
+ (0x1D4CF, 'M', 'z'),
+ (0x1D4D0, 'M', 'a'),
+ (0x1D4D1, 'M', 'b'),
+ (0x1D4D2, 'M', 'c'),
+ (0x1D4D3, 'M', 'd'),
+ (0x1D4D4, 'M', 'e'),
+ (0x1D4D5, 'M', 'f'),
+ (0x1D4D6, 'M', 'g'),
+ (0x1D4D7, 'M', 'h'),
+ (0x1D4D8, 'M', 'i'),
+ (0x1D4D9, 'M', 'j'),
+ (0x1D4DA, 'M', 'k'),
+ (0x1D4DB, 'M', 'l'),
+ (0x1D4DC, 'M', 'm'),
+ (0x1D4DD, 'M', 'n'),
+ (0x1D4DE, 'M', 'o'),
+ (0x1D4DF, 'M', 'p'),
+ (0x1D4E0, 'M', 'q'),
+ (0x1D4E1, 'M', 'r'),
+ (0x1D4E2, 'M', 's'),
+ (0x1D4E3, 'M', 't'),
+ (0x1D4E4, 'M', 'u'),
+ (0x1D4E5, 'M', 'v'),
+ (0x1D4E6, 'M', 'w'),
+ (0x1D4E7, 'M', 'x'),
+ (0x1D4E8, 'M', 'y'),
+ (0x1D4E9, 'M', 'z'),
+ (0x1D4EA, 'M', 'a'),
+ (0x1D4EB, 'M', 'b'),
+ (0x1D4EC, 'M', 'c'),
+ (0x1D4ED, 'M', 'd'),
+ (0x1D4EE, 'M', 'e'),
+ (0x1D4EF, 'M', 'f'),
+ ]
+
+def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D4F0, 'M', 'g'),
+ (0x1D4F1, 'M', 'h'),
+ (0x1D4F2, 'M', 'i'),
+ (0x1D4F3, 'M', 'j'),
+ (0x1D4F4, 'M', 'k'),
+ (0x1D4F5, 'M', 'l'),
+ (0x1D4F6, 'M', 'm'),
+ (0x1D4F7, 'M', 'n'),
+ (0x1D4F8, 'M', 'o'),
+ (0x1D4F9, 'M', 'p'),
+ (0x1D4FA, 'M', 'q'),
+ (0x1D4FB, 'M', 'r'),
+ (0x1D4FC, 'M', 's'),
+ (0x1D4FD, 'M', 't'),
+ (0x1D4FE, 'M', 'u'),
+ (0x1D4FF, 'M', 'v'),
+ (0x1D500, 'M', 'w'),
+ (0x1D501, 'M', 'x'),
+ (0x1D502, 'M', 'y'),
+ (0x1D503, 'M', 'z'),
+ (0x1D504, 'M', 'a'),
+ (0x1D505, 'M', 'b'),
+ (0x1D506, 'X'),
+ (0x1D507, 'M', 'd'),
+ (0x1D508, 'M', 'e'),
+ (0x1D509, 'M', 'f'),
+ (0x1D50A, 'M', 'g'),
+ (0x1D50B, 'X'),
+ (0x1D50D, 'M', 'j'),
+ (0x1D50E, 'M', 'k'),
+ (0x1D50F, 'M', 'l'),
+ (0x1D510, 'M', 'm'),
+ (0x1D511, 'M', 'n'),
+ (0x1D512, 'M', 'o'),
+ (0x1D513, 'M', 'p'),
+ (0x1D514, 'M', 'q'),
+ (0x1D515, 'X'),
+ (0x1D516, 'M', 's'),
+ (0x1D517, 'M', 't'),
+ (0x1D518, 'M', 'u'),
+ (0x1D519, 'M', 'v'),
+ (0x1D51A, 'M', 'w'),
+ (0x1D51B, 'M', 'x'),
+ (0x1D51C, 'M', 'y'),
+ (0x1D51D, 'X'),
+ (0x1D51E, 'M', 'a'),
+ (0x1D51F, 'M', 'b'),
+ (0x1D520, 'M', 'c'),
+ (0x1D521, 'M', 'd'),
+ (0x1D522, 'M', 'e'),
+ (0x1D523, 'M', 'f'),
+ (0x1D524, 'M', 'g'),
+ (0x1D525, 'M', 'h'),
+ (0x1D526, 'M', 'i'),
+ (0x1D527, 'M', 'j'),
+ (0x1D528, 'M', 'k'),
+ (0x1D529, 'M', 'l'),
+ (0x1D52A, 'M', 'm'),
+ (0x1D52B, 'M', 'n'),
+ (0x1D52C, 'M', 'o'),
+ (0x1D52D, 'M', 'p'),
+ (0x1D52E, 'M', 'q'),
+ (0x1D52F, 'M', 'r'),
+ (0x1D530, 'M', 's'),
+ (0x1D531, 'M', 't'),
+ (0x1D532, 'M', 'u'),
+ (0x1D533, 'M', 'v'),
+ (0x1D534, 'M', 'w'),
+ (0x1D535, 'M', 'x'),
+ (0x1D536, 'M', 'y'),
+ (0x1D537, 'M', 'z'),
+ (0x1D538, 'M', 'a'),
+ (0x1D539, 'M', 'b'),
+ (0x1D53A, 'X'),
+ (0x1D53B, 'M', 'd'),
+ (0x1D53C, 'M', 'e'),
+ (0x1D53D, 'M', 'f'),
+ (0x1D53E, 'M', 'g'),
+ (0x1D53F, 'X'),
+ (0x1D540, 'M', 'i'),
+ (0x1D541, 'M', 'j'),
+ (0x1D542, 'M', 'k'),
+ (0x1D543, 'M', 'l'),
+ (0x1D544, 'M', 'm'),
+ (0x1D545, 'X'),
+ (0x1D546, 'M', 'o'),
+ (0x1D547, 'X'),
+ (0x1D54A, 'M', 's'),
+ (0x1D54B, 'M', 't'),
+ (0x1D54C, 'M', 'u'),
+ (0x1D54D, 'M', 'v'),
+ (0x1D54E, 'M', 'w'),
+ (0x1D54F, 'M', 'x'),
+ (0x1D550, 'M', 'y'),
+ (0x1D551, 'X'),
+ (0x1D552, 'M', 'a'),
+ (0x1D553, 'M', 'b'),
+ (0x1D554, 'M', 'c'),
+ (0x1D555, 'M', 'd'),
+ (0x1D556, 'M', 'e'),
+ ]
+
+def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D557, 'M', 'f'),
+ (0x1D558, 'M', 'g'),
+ (0x1D559, 'M', 'h'),
+ (0x1D55A, 'M', 'i'),
+ (0x1D55B, 'M', 'j'),
+ (0x1D55C, 'M', 'k'),
+ (0x1D55D, 'M', 'l'),
+ (0x1D55E, 'M', 'm'),
+ (0x1D55F, 'M', 'n'),
+ (0x1D560, 'M', 'o'),
+ (0x1D561, 'M', 'p'),
+ (0x1D562, 'M', 'q'),
+ (0x1D563, 'M', 'r'),
+ (0x1D564, 'M', 's'),
+ (0x1D565, 'M', 't'),
+ (0x1D566, 'M', 'u'),
+ (0x1D567, 'M', 'v'),
+ (0x1D568, 'M', 'w'),
+ (0x1D569, 'M', 'x'),
+ (0x1D56A, 'M', 'y'),
+ (0x1D56B, 'M', 'z'),
+ (0x1D56C, 'M', 'a'),
+ (0x1D56D, 'M', 'b'),
+ (0x1D56E, 'M', 'c'),
+ (0x1D56F, 'M', 'd'),
+ (0x1D570, 'M', 'e'),
+ (0x1D571, 'M', 'f'),
+ (0x1D572, 'M', 'g'),
+ (0x1D573, 'M', 'h'),
+ (0x1D574, 'M', 'i'),
+ (0x1D575, 'M', 'j'),
+ (0x1D576, 'M', 'k'),
+ (0x1D577, 'M', 'l'),
+ (0x1D578, 'M', 'm'),
+ (0x1D579, 'M', 'n'),
+ (0x1D57A, 'M', 'o'),
+ (0x1D57B, 'M', 'p'),
+ (0x1D57C, 'M', 'q'),
+ (0x1D57D, 'M', 'r'),
+ (0x1D57E, 'M', 's'),
+ (0x1D57F, 'M', 't'),
+ (0x1D580, 'M', 'u'),
+ (0x1D581, 'M', 'v'),
+ (0x1D582, 'M', 'w'),
+ (0x1D583, 'M', 'x'),
+ (0x1D584, 'M', 'y'),
+ (0x1D585, 'M', 'z'),
+ (0x1D586, 'M', 'a'),
+ (0x1D587, 'M', 'b'),
+ (0x1D588, 'M', 'c'),
+ (0x1D589, 'M', 'd'),
+ (0x1D58A, 'M', 'e'),
+ (0x1D58B, 'M', 'f'),
+ (0x1D58C, 'M', 'g'),
+ (0x1D58D, 'M', 'h'),
+ (0x1D58E, 'M', 'i'),
+ (0x1D58F, 'M', 'j'),
+ (0x1D590, 'M', 'k'),
+ (0x1D591, 'M', 'l'),
+ (0x1D592, 'M', 'm'),
+ (0x1D593, 'M', 'n'),
+ (0x1D594, 'M', 'o'),
+ (0x1D595, 'M', 'p'),
+ (0x1D596, 'M', 'q'),
+ (0x1D597, 'M', 'r'),
+ (0x1D598, 'M', 's'),
+ (0x1D599, 'M', 't'),
+ (0x1D59A, 'M', 'u'),
+ (0x1D59B, 'M', 'v'),
+ (0x1D59C, 'M', 'w'),
+ (0x1D59D, 'M', 'x'),
+ (0x1D59E, 'M', 'y'),
+ (0x1D59F, 'M', 'z'),
+ (0x1D5A0, 'M', 'a'),
+ (0x1D5A1, 'M', 'b'),
+ (0x1D5A2, 'M', 'c'),
+ (0x1D5A3, 'M', 'd'),
+ (0x1D5A4, 'M', 'e'),
+ (0x1D5A5, 'M', 'f'),
+ (0x1D5A6, 'M', 'g'),
+ (0x1D5A7, 'M', 'h'),
+ (0x1D5A8, 'M', 'i'),
+ (0x1D5A9, 'M', 'j'),
+ (0x1D5AA, 'M', 'k'),
+ (0x1D5AB, 'M', 'l'),
+ (0x1D5AC, 'M', 'm'),
+ (0x1D5AD, 'M', 'n'),
+ (0x1D5AE, 'M', 'o'),
+ (0x1D5AF, 'M', 'p'),
+ (0x1D5B0, 'M', 'q'),
+ (0x1D5B1, 'M', 'r'),
+ (0x1D5B2, 'M', 's'),
+ (0x1D5B3, 'M', 't'),
+ (0x1D5B4, 'M', 'u'),
+ (0x1D5B5, 'M', 'v'),
+ (0x1D5B6, 'M', 'w'),
+ (0x1D5B7, 'M', 'x'),
+ (0x1D5B8, 'M', 'y'),
+ (0x1D5B9, 'M', 'z'),
+ (0x1D5BA, 'M', 'a'),
+ ]
+
+def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D5BB, 'M', 'b'),
+ (0x1D5BC, 'M', 'c'),
+ (0x1D5BD, 'M', 'd'),
+ (0x1D5BE, 'M', 'e'),
+ (0x1D5BF, 'M', 'f'),
+ (0x1D5C0, 'M', 'g'),
+ (0x1D5C1, 'M', 'h'),
+ (0x1D5C2, 'M', 'i'),
+ (0x1D5C3, 'M', 'j'),
+ (0x1D5C4, 'M', 'k'),
+ (0x1D5C5, 'M', 'l'),
+ (0x1D5C6, 'M', 'm'),
+ (0x1D5C7, 'M', 'n'),
+ (0x1D5C8, 'M', 'o'),
+ (0x1D5C9, 'M', 'p'),
+ (0x1D5CA, 'M', 'q'),
+ (0x1D5CB, 'M', 'r'),
+ (0x1D5CC, 'M', 's'),
+ (0x1D5CD, 'M', 't'),
+ (0x1D5CE, 'M', 'u'),
+ (0x1D5CF, 'M', 'v'),
+ (0x1D5D0, 'M', 'w'),
+ (0x1D5D1, 'M', 'x'),
+ (0x1D5D2, 'M', 'y'),
+ (0x1D5D3, 'M', 'z'),
+ (0x1D5D4, 'M', 'a'),
+ (0x1D5D5, 'M', 'b'),
+ (0x1D5D6, 'M', 'c'),
+ (0x1D5D7, 'M', 'd'),
+ (0x1D5D8, 'M', 'e'),
+ (0x1D5D9, 'M', 'f'),
+ (0x1D5DA, 'M', 'g'),
+ (0x1D5DB, 'M', 'h'),
+ (0x1D5DC, 'M', 'i'),
+ (0x1D5DD, 'M', 'j'),
+ (0x1D5DE, 'M', 'k'),
+ (0x1D5DF, 'M', 'l'),
+ (0x1D5E0, 'M', 'm'),
+ (0x1D5E1, 'M', 'n'),
+ (0x1D5E2, 'M', 'o'),
+ (0x1D5E3, 'M', 'p'),
+ (0x1D5E4, 'M', 'q'),
+ (0x1D5E5, 'M', 'r'),
+ (0x1D5E6, 'M', 's'),
+ (0x1D5E7, 'M', 't'),
+ (0x1D5E8, 'M', 'u'),
+ (0x1D5E9, 'M', 'v'),
+ (0x1D5EA, 'M', 'w'),
+ (0x1D5EB, 'M', 'x'),
+ (0x1D5EC, 'M', 'y'),
+ (0x1D5ED, 'M', 'z'),
+ (0x1D5EE, 'M', 'a'),
+ (0x1D5EF, 'M', 'b'),
+ (0x1D5F0, 'M', 'c'),
+ (0x1D5F1, 'M', 'd'),
+ (0x1D5F2, 'M', 'e'),
+ (0x1D5F3, 'M', 'f'),
+ (0x1D5F4, 'M', 'g'),
+ (0x1D5F5, 'M', 'h'),
+ (0x1D5F6, 'M', 'i'),
+ (0x1D5F7, 'M', 'j'),
+ (0x1D5F8, 'M', 'k'),
+ (0x1D5F9, 'M', 'l'),
+ (0x1D5FA, 'M', 'm'),
+ (0x1D5FB, 'M', 'n'),
+ (0x1D5FC, 'M', 'o'),
+ (0x1D5FD, 'M', 'p'),
+ (0x1D5FE, 'M', 'q'),
+ (0x1D5FF, 'M', 'r'),
+ (0x1D600, 'M', 's'),
+ (0x1D601, 'M', 't'),
+ (0x1D602, 'M', 'u'),
+ (0x1D603, 'M', 'v'),
+ (0x1D604, 'M', 'w'),
+ (0x1D605, 'M', 'x'),
+ (0x1D606, 'M', 'y'),
+ (0x1D607, 'M', 'z'),
+ (0x1D608, 'M', 'a'),
+ (0x1D609, 'M', 'b'),
+ (0x1D60A, 'M', 'c'),
+ (0x1D60B, 'M', 'd'),
+ (0x1D60C, 'M', 'e'),
+ (0x1D60D, 'M', 'f'),
+ (0x1D60E, 'M', 'g'),
+ (0x1D60F, 'M', 'h'),
+ (0x1D610, 'M', 'i'),
+ (0x1D611, 'M', 'j'),
+ (0x1D612, 'M', 'k'),
+ (0x1D613, 'M', 'l'),
+ (0x1D614, 'M', 'm'),
+ (0x1D615, 'M', 'n'),
+ (0x1D616, 'M', 'o'),
+ (0x1D617, 'M', 'p'),
+ (0x1D618, 'M', 'q'),
+ (0x1D619, 'M', 'r'),
+ (0x1D61A, 'M', 's'),
+ (0x1D61B, 'M', 't'),
+ (0x1D61C, 'M', 'u'),
+ (0x1D61D, 'M', 'v'),
+ (0x1D61E, 'M', 'w'),
+ ]
+
+def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D61F, 'M', 'x'),
+ (0x1D620, 'M', 'y'),
+ (0x1D621, 'M', 'z'),
+ (0x1D622, 'M', 'a'),
+ (0x1D623, 'M', 'b'),
+ (0x1D624, 'M', 'c'),
+ (0x1D625, 'M', 'd'),
+ (0x1D626, 'M', 'e'),
+ (0x1D627, 'M', 'f'),
+ (0x1D628, 'M', 'g'),
+ (0x1D629, 'M', 'h'),
+ (0x1D62A, 'M', 'i'),
+ (0x1D62B, 'M', 'j'),
+ (0x1D62C, 'M', 'k'),
+ (0x1D62D, 'M', 'l'),
+ (0x1D62E, 'M', 'm'),
+ (0x1D62F, 'M', 'n'),
+ (0x1D630, 'M', 'o'),
+ (0x1D631, 'M', 'p'),
+ (0x1D632, 'M', 'q'),
+ (0x1D633, 'M', 'r'),
+ (0x1D634, 'M', 's'),
+ (0x1D635, 'M', 't'),
+ (0x1D636, 'M', 'u'),
+ (0x1D637, 'M', 'v'),
+ (0x1D638, 'M', 'w'),
+ (0x1D639, 'M', 'x'),
+ (0x1D63A, 'M', 'y'),
+ (0x1D63B, 'M', 'z'),
+ (0x1D63C, 'M', 'a'),
+ (0x1D63D, 'M', 'b'),
+ (0x1D63E, 'M', 'c'),
+ (0x1D63F, 'M', 'd'),
+ (0x1D640, 'M', 'e'),
+ (0x1D641, 'M', 'f'),
+ (0x1D642, 'M', 'g'),
+ (0x1D643, 'M', 'h'),
+ (0x1D644, 'M', 'i'),
+ (0x1D645, 'M', 'j'),
+ (0x1D646, 'M', 'k'),
+ (0x1D647, 'M', 'l'),
+ (0x1D648, 'M', 'm'),
+ (0x1D649, 'M', 'n'),
+ (0x1D64A, 'M', 'o'),
+ (0x1D64B, 'M', 'p'),
+ (0x1D64C, 'M', 'q'),
+ (0x1D64D, 'M', 'r'),
+ (0x1D64E, 'M', 's'),
+ (0x1D64F, 'M', 't'),
+ (0x1D650, 'M', 'u'),
+ (0x1D651, 'M', 'v'),
+ (0x1D652, 'M', 'w'),
+ (0x1D653, 'M', 'x'),
+ (0x1D654, 'M', 'y'),
+ (0x1D655, 'M', 'z'),
+ (0x1D656, 'M', 'a'),
+ (0x1D657, 'M', 'b'),
+ (0x1D658, 'M', 'c'),
+ (0x1D659, 'M', 'd'),
+ (0x1D65A, 'M', 'e'),
+ (0x1D65B, 'M', 'f'),
+ (0x1D65C, 'M', 'g'),
+ (0x1D65D, 'M', 'h'),
+ (0x1D65E, 'M', 'i'),
+ (0x1D65F, 'M', 'j'),
+ (0x1D660, 'M', 'k'),
+ (0x1D661, 'M', 'l'),
+ (0x1D662, 'M', 'm'),
+ (0x1D663, 'M', 'n'),
+ (0x1D664, 'M', 'o'),
+ (0x1D665, 'M', 'p'),
+ (0x1D666, 'M', 'q'),
+ (0x1D667, 'M', 'r'),
+ (0x1D668, 'M', 's'),
+ (0x1D669, 'M', 't'),
+ (0x1D66A, 'M', 'u'),
+ (0x1D66B, 'M', 'v'),
+ (0x1D66C, 'M', 'w'),
+ (0x1D66D, 'M', 'x'),
+ (0x1D66E, 'M', 'y'),
+ (0x1D66F, 'M', 'z'),
+ (0x1D670, 'M', 'a'),
+ (0x1D671, 'M', 'b'),
+ (0x1D672, 'M', 'c'),
+ (0x1D673, 'M', 'd'),
+ (0x1D674, 'M', 'e'),
+ (0x1D675, 'M', 'f'),
+ (0x1D676, 'M', 'g'),
+ (0x1D677, 'M', 'h'),
+ (0x1D678, 'M', 'i'),
+ (0x1D679, 'M', 'j'),
+ (0x1D67A, 'M', 'k'),
+ (0x1D67B, 'M', 'l'),
+ (0x1D67C, 'M', 'm'),
+ (0x1D67D, 'M', 'n'),
+ (0x1D67E, 'M', 'o'),
+ (0x1D67F, 'M', 'p'),
+ (0x1D680, 'M', 'q'),
+ (0x1D681, 'M', 'r'),
+ (0x1D682, 'M', 's'),
+ ]
+
+def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D683, 'M', 't'),
+ (0x1D684, 'M', 'u'),
+ (0x1D685, 'M', 'v'),
+ (0x1D686, 'M', 'w'),
+ (0x1D687, 'M', 'x'),
+ (0x1D688, 'M', 'y'),
+ (0x1D689, 'M', 'z'),
+ (0x1D68A, 'M', 'a'),
+ (0x1D68B, 'M', 'b'),
+ (0x1D68C, 'M', 'c'),
+ (0x1D68D, 'M', 'd'),
+ (0x1D68E, 'M', 'e'),
+ (0x1D68F, 'M', 'f'),
+ (0x1D690, 'M', 'g'),
+ (0x1D691, 'M', 'h'),
+ (0x1D692, 'M', 'i'),
+ (0x1D693, 'M', 'j'),
+ (0x1D694, 'M', 'k'),
+ (0x1D695, 'M', 'l'),
+ (0x1D696, 'M', 'm'),
+ (0x1D697, 'M', 'n'),
+ (0x1D698, 'M', 'o'),
+ (0x1D699, 'M', 'p'),
+ (0x1D69A, 'M', 'q'),
+ (0x1D69B, 'M', 'r'),
+ (0x1D69C, 'M', 's'),
+ (0x1D69D, 'M', 't'),
+ (0x1D69E, 'M', 'u'),
+ (0x1D69F, 'M', 'v'),
+ (0x1D6A0, 'M', 'w'),
+ (0x1D6A1, 'M', 'x'),
+ (0x1D6A2, 'M', 'y'),
+ (0x1D6A3, 'M', 'z'),
+ (0x1D6A4, 'M', 'ı'),
+ (0x1D6A5, 'M', 'ȷ'),
+ (0x1D6A6, 'X'),
+ (0x1D6A8, 'M', 'α'),
+ (0x1D6A9, 'M', 'β'),
+ (0x1D6AA, 'M', 'γ'),
+ (0x1D6AB, 'M', 'δ'),
+ (0x1D6AC, 'M', 'ε'),
+ (0x1D6AD, 'M', 'ζ'),
+ (0x1D6AE, 'M', 'η'),
+ (0x1D6AF, 'M', 'θ'),
+ (0x1D6B0, 'M', 'ι'),
+ (0x1D6B1, 'M', 'κ'),
+ (0x1D6B2, 'M', 'λ'),
+ (0x1D6B3, 'M', 'μ'),
+ (0x1D6B4, 'M', 'ν'),
+ (0x1D6B5, 'M', 'ξ'),
+ (0x1D6B6, 'M', 'ο'),
+ (0x1D6B7, 'M', 'π'),
+ (0x1D6B8, 'M', 'ρ'),
+ (0x1D6B9, 'M', 'θ'),
+ (0x1D6BA, 'M', 'σ'),
+ (0x1D6BB, 'M', 'τ'),
+ (0x1D6BC, 'M', 'υ'),
+ (0x1D6BD, 'M', 'φ'),
+ (0x1D6BE, 'M', 'χ'),
+ (0x1D6BF, 'M', 'ψ'),
+ (0x1D6C0, 'M', 'ω'),
+ (0x1D6C1, 'M', '∇'),
+ (0x1D6C2, 'M', 'α'),
+ (0x1D6C3, 'M', 'β'),
+ (0x1D6C4, 'M', 'γ'),
+ (0x1D6C5, 'M', 'δ'),
+ (0x1D6C6, 'M', 'ε'),
+ (0x1D6C7, 'M', 'ζ'),
+ (0x1D6C8, 'M', 'η'),
+ (0x1D6C9, 'M', 'θ'),
+ (0x1D6CA, 'M', 'ι'),
+ (0x1D6CB, 'M', 'κ'),
+ (0x1D6CC, 'M', 'λ'),
+ (0x1D6CD, 'M', 'μ'),
+ (0x1D6CE, 'M', 'ν'),
+ (0x1D6CF, 'M', 'ξ'),
+ (0x1D6D0, 'M', 'ο'),
+ (0x1D6D1, 'M', 'π'),
+ (0x1D6D2, 'M', 'ρ'),
+ (0x1D6D3, 'M', 'σ'),
+ (0x1D6D5, 'M', 'τ'),
+ (0x1D6D6, 'M', 'υ'),
+ (0x1D6D7, 'M', 'φ'),
+ (0x1D6D8, 'M', 'χ'),
+ (0x1D6D9, 'M', 'ψ'),
+ (0x1D6DA, 'M', 'ω'),
+ (0x1D6DB, 'M', '∂'),
+ (0x1D6DC, 'M', 'ε'),
+ (0x1D6DD, 'M', 'θ'),
+ (0x1D6DE, 'M', 'κ'),
+ (0x1D6DF, 'M', 'φ'),
+ (0x1D6E0, 'M', 'ρ'),
+ (0x1D6E1, 'M', 'π'),
+ (0x1D6E2, 'M', 'α'),
+ (0x1D6E3, 'M', 'β'),
+ (0x1D6E4, 'M', 'γ'),
+ (0x1D6E5, 'M', 'δ'),
+ (0x1D6E6, 'M', 'ε'),
+ (0x1D6E7, 'M', 'ζ'),
+ (0x1D6E8, 'M', 'η'),
+ ]
+
+def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D6E9, 'M', 'θ'),
+ (0x1D6EA, 'M', 'ι'),
+ (0x1D6EB, 'M', 'κ'),
+ (0x1D6EC, 'M', 'λ'),
+ (0x1D6ED, 'M', 'μ'),
+ (0x1D6EE, 'M', 'ν'),
+ (0x1D6EF, 'M', 'ξ'),
+ (0x1D6F0, 'M', 'ο'),
+ (0x1D6F1, 'M', 'π'),
+ (0x1D6F2, 'M', 'ρ'),
+ (0x1D6F3, 'M', 'θ'),
+ (0x1D6F4, 'M', 'σ'),
+ (0x1D6F5, 'M', 'τ'),
+ (0x1D6F6, 'M', 'υ'),
+ (0x1D6F7, 'M', 'φ'),
+ (0x1D6F8, 'M', 'χ'),
+ (0x1D6F9, 'M', 'ψ'),
+ (0x1D6FA, 'M', 'ω'),
+ (0x1D6FB, 'M', '∇'),
+ (0x1D6FC, 'M', 'α'),
+ (0x1D6FD, 'M', 'β'),
+ (0x1D6FE, 'M', 'γ'),
+ (0x1D6FF, 'M', 'δ'),
+ (0x1D700, 'M', 'ε'),
+ (0x1D701, 'M', 'ζ'),
+ (0x1D702, 'M', 'η'),
+ (0x1D703, 'M', 'θ'),
+ (0x1D704, 'M', 'ι'),
+ (0x1D705, 'M', 'κ'),
+ (0x1D706, 'M', 'λ'),
+ (0x1D707, 'M', 'μ'),
+ (0x1D708, 'M', 'ν'),
+ (0x1D709, 'M', 'ξ'),
+ (0x1D70A, 'M', 'ο'),
+ (0x1D70B, 'M', 'π'),
+ (0x1D70C, 'M', 'ρ'),
+ (0x1D70D, 'M', 'σ'),
+ (0x1D70F, 'M', 'τ'),
+ (0x1D710, 'M', 'υ'),
+ (0x1D711, 'M', 'φ'),
+ (0x1D712, 'M', 'χ'),
+ (0x1D713, 'M', 'ψ'),
+ (0x1D714, 'M', 'ω'),
+ (0x1D715, 'M', '∂'),
+ (0x1D716, 'M', 'ε'),
+ (0x1D717, 'M', 'θ'),
+ (0x1D718, 'M', 'κ'),
+ (0x1D719, 'M', 'φ'),
+ (0x1D71A, 'M', 'ρ'),
+ (0x1D71B, 'M', 'π'),
+ (0x1D71C, 'M', 'α'),
+ (0x1D71D, 'M', 'β'),
+ (0x1D71E, 'M', 'γ'),
+ (0x1D71F, 'M', 'δ'),
+ (0x1D720, 'M', 'ε'),
+ (0x1D721, 'M', 'ζ'),
+ (0x1D722, 'M', 'η'),
+ (0x1D723, 'M', 'θ'),
+ (0x1D724, 'M', 'ι'),
+ (0x1D725, 'M', 'κ'),
+ (0x1D726, 'M', 'λ'),
+ (0x1D727, 'M', 'μ'),
+ (0x1D728, 'M', 'ν'),
+ (0x1D729, 'M', 'ξ'),
+ (0x1D72A, 'M', 'ο'),
+ (0x1D72B, 'M', 'π'),
+ (0x1D72C, 'M', 'ρ'),
+ (0x1D72D, 'M', 'θ'),
+ (0x1D72E, 'M', 'σ'),
+ (0x1D72F, 'M', 'τ'),
+ (0x1D730, 'M', 'υ'),
+ (0x1D731, 'M', 'φ'),
+ (0x1D732, 'M', 'χ'),
+ (0x1D733, 'M', 'ψ'),
+ (0x1D734, 'M', 'ω'),
+ (0x1D735, 'M', '∇'),
+ (0x1D736, 'M', 'α'),
+ (0x1D737, 'M', 'β'),
+ (0x1D738, 'M', 'γ'),
+ (0x1D739, 'M', 'δ'),
+ (0x1D73A, 'M', 'ε'),
+ (0x1D73B, 'M', 'ζ'),
+ (0x1D73C, 'M', 'η'),
+ (0x1D73D, 'M', 'θ'),
+ (0x1D73E, 'M', 'ι'),
+ (0x1D73F, 'M', 'κ'),
+ (0x1D740, 'M', 'λ'),
+ (0x1D741, 'M', 'μ'),
+ (0x1D742, 'M', 'ν'),
+ (0x1D743, 'M', 'ξ'),
+ (0x1D744, 'M', 'ο'),
+ (0x1D745, 'M', 'π'),
+ (0x1D746, 'M', 'ρ'),
+ (0x1D747, 'M', 'σ'),
+ (0x1D749, 'M', 'τ'),
+ (0x1D74A, 'M', 'υ'),
+ (0x1D74B, 'M', 'φ'),
+ (0x1D74C, 'M', 'χ'),
+ (0x1D74D, 'M', 'ψ'),
+ (0x1D74E, 'M', 'ω'),
+ ]
+
+def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D74F, 'M', '∂'),
+ (0x1D750, 'M', 'ε'),
+ (0x1D751, 'M', 'θ'),
+ (0x1D752, 'M', 'κ'),
+ (0x1D753, 'M', 'φ'),
+ (0x1D754, 'M', 'ρ'),
+ (0x1D755, 'M', 'π'),
+ (0x1D756, 'M', 'α'),
+ (0x1D757, 'M', 'β'),
+ (0x1D758, 'M', 'γ'),
+ (0x1D759, 'M', 'δ'),
+ (0x1D75A, 'M', 'ε'),
+ (0x1D75B, 'M', 'ζ'),
+ (0x1D75C, 'M', 'η'),
+ (0x1D75D, 'M', 'θ'),
+ (0x1D75E, 'M', 'ι'),
+ (0x1D75F, 'M', 'κ'),
+ (0x1D760, 'M', 'λ'),
+ (0x1D761, 'M', 'μ'),
+ (0x1D762, 'M', 'ν'),
+ (0x1D763, 'M', 'ξ'),
+ (0x1D764, 'M', 'ο'),
+ (0x1D765, 'M', 'π'),
+ (0x1D766, 'M', 'ρ'),
+ (0x1D767, 'M', 'θ'),
+ (0x1D768, 'M', 'σ'),
+ (0x1D769, 'M', 'τ'),
+ (0x1D76A, 'M', 'υ'),
+ (0x1D76B, 'M', 'φ'),
+ (0x1D76C, 'M', 'χ'),
+ (0x1D76D, 'M', 'ψ'),
+ (0x1D76E, 'M', 'ω'),
+ (0x1D76F, 'M', '∇'),
+ (0x1D770, 'M', 'α'),
+ (0x1D771, 'M', 'β'),
+ (0x1D772, 'M', 'γ'),
+ (0x1D773, 'M', 'δ'),
+ (0x1D774, 'M', 'ε'),
+ (0x1D775, 'M', 'ζ'),
+ (0x1D776, 'M', 'η'),
+ (0x1D777, 'M', 'θ'),
+ (0x1D778, 'M', 'ι'),
+ (0x1D779, 'M', 'κ'),
+ (0x1D77A, 'M', 'λ'),
+ (0x1D77B, 'M', 'μ'),
+ (0x1D77C, 'M', 'ν'),
+ (0x1D77D, 'M', 'ξ'),
+ (0x1D77E, 'M', 'ο'),
+ (0x1D77F, 'M', 'π'),
+ (0x1D780, 'M', 'ρ'),
+ (0x1D781, 'M', 'σ'),
+ (0x1D783, 'M', 'τ'),
+ (0x1D784, 'M', 'υ'),
+ (0x1D785, 'M', 'φ'),
+ (0x1D786, 'M', 'χ'),
+ (0x1D787, 'M', 'ψ'),
+ (0x1D788, 'M', 'ω'),
+ (0x1D789, 'M', '∂'),
+ (0x1D78A, 'M', 'ε'),
+ (0x1D78B, 'M', 'θ'),
+ (0x1D78C, 'M', 'κ'),
+ (0x1D78D, 'M', 'φ'),
+ (0x1D78E, 'M', 'ρ'),
+ (0x1D78F, 'M', 'π'),
+ (0x1D790, 'M', 'α'),
+ (0x1D791, 'M', 'β'),
+ (0x1D792, 'M', 'γ'),
+ (0x1D793, 'M', 'δ'),
+ (0x1D794, 'M', 'ε'),
+ (0x1D795, 'M', 'ζ'),
+ (0x1D796, 'M', 'η'),
+ (0x1D797, 'M', 'θ'),
+ (0x1D798, 'M', 'ι'),
+ (0x1D799, 'M', 'κ'),
+ (0x1D79A, 'M', 'λ'),
+ (0x1D79B, 'M', 'μ'),
+ (0x1D79C, 'M', 'ν'),
+ (0x1D79D, 'M', 'ξ'),
+ (0x1D79E, 'M', 'ο'),
+ (0x1D79F, 'M', 'π'),
+ (0x1D7A0, 'M', 'ρ'),
+ (0x1D7A1, 'M', 'θ'),
+ (0x1D7A2, 'M', 'σ'),
+ (0x1D7A3, 'M', 'τ'),
+ (0x1D7A4, 'M', 'υ'),
+ (0x1D7A5, 'M', 'φ'),
+ (0x1D7A6, 'M', 'χ'),
+ (0x1D7A7, 'M', 'ψ'),
+ (0x1D7A8, 'M', 'ω'),
+ (0x1D7A9, 'M', '∇'),
+ (0x1D7AA, 'M', 'α'),
+ (0x1D7AB, 'M', 'β'),
+ (0x1D7AC, 'M', 'γ'),
+ (0x1D7AD, 'M', 'δ'),
+ (0x1D7AE, 'M', 'ε'),
+ (0x1D7AF, 'M', 'ζ'),
+ (0x1D7B0, 'M', 'η'),
+ (0x1D7B1, 'M', 'θ'),
+ (0x1D7B2, 'M', 'ι'),
+ (0x1D7B3, 'M', 'κ'),
+ ]
+
+def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D7B4, 'M', 'λ'),
+ (0x1D7B5, 'M', 'μ'),
+ (0x1D7B6, 'M', 'ν'),
+ (0x1D7B7, 'M', 'ξ'),
+ (0x1D7B8, 'M', 'ο'),
+ (0x1D7B9, 'M', 'π'),
+ (0x1D7BA, 'M', 'ρ'),
+ (0x1D7BB, 'M', 'σ'),
+ (0x1D7BD, 'M', 'τ'),
+ (0x1D7BE, 'M', 'υ'),
+ (0x1D7BF, 'M', 'φ'),
+ (0x1D7C0, 'M', 'χ'),
+ (0x1D7C1, 'M', 'ψ'),
+ (0x1D7C2, 'M', 'ω'),
+ (0x1D7C3, 'M', '∂'),
+ (0x1D7C4, 'M', 'ε'),
+ (0x1D7C5, 'M', 'θ'),
+ (0x1D7C6, 'M', 'κ'),
+ (0x1D7C7, 'M', 'φ'),
+ (0x1D7C8, 'M', 'ρ'),
+ (0x1D7C9, 'M', 'π'),
+ (0x1D7CA, 'M', 'ϝ'),
+ (0x1D7CC, 'X'),
+ (0x1D7CE, 'M', '0'),
+ (0x1D7CF, 'M', '1'),
+ (0x1D7D0, 'M', '2'),
+ (0x1D7D1, 'M', '3'),
+ (0x1D7D2, 'M', '4'),
+ (0x1D7D3, 'M', '5'),
+ (0x1D7D4, 'M', '6'),
+ (0x1D7D5, 'M', '7'),
+ (0x1D7D6, 'M', '8'),
+ (0x1D7D7, 'M', '9'),
+ (0x1D7D8, 'M', '0'),
+ (0x1D7D9, 'M', '1'),
+ (0x1D7DA, 'M', '2'),
+ (0x1D7DB, 'M', '3'),
+ (0x1D7DC, 'M', '4'),
+ (0x1D7DD, 'M', '5'),
+ (0x1D7DE, 'M', '6'),
+ (0x1D7DF, 'M', '7'),
+ (0x1D7E0, 'M', '8'),
+ (0x1D7E1, 'M', '9'),
+ (0x1D7E2, 'M', '0'),
+ (0x1D7E3, 'M', '1'),
+ (0x1D7E4, 'M', '2'),
+ (0x1D7E5, 'M', '3'),
+ (0x1D7E6, 'M', '4'),
+ (0x1D7E7, 'M', '5'),
+ (0x1D7E8, 'M', '6'),
+ (0x1D7E9, 'M', '7'),
+ (0x1D7EA, 'M', '8'),
+ (0x1D7EB, 'M', '9'),
+ (0x1D7EC, 'M', '0'),
+ (0x1D7ED, 'M', '1'),
+ (0x1D7EE, 'M', '2'),
+ (0x1D7EF, 'M', '3'),
+ (0x1D7F0, 'M', '4'),
+ (0x1D7F1, 'M', '5'),
+ (0x1D7F2, 'M', '6'),
+ (0x1D7F3, 'M', '7'),
+ (0x1D7F4, 'M', '8'),
+ (0x1D7F5, 'M', '9'),
+ (0x1D7F6, 'M', '0'),
+ (0x1D7F7, 'M', '1'),
+ (0x1D7F8, 'M', '2'),
+ (0x1D7F9, 'M', '3'),
+ (0x1D7FA, 'M', '4'),
+ (0x1D7FB, 'M', '5'),
+ (0x1D7FC, 'M', '6'),
+ (0x1D7FD, 'M', '7'),
+ (0x1D7FE, 'M', '8'),
+ (0x1D7FF, 'M', '9'),
+ (0x1D800, 'V'),
+ (0x1DA8C, 'X'),
+ (0x1DA9B, 'V'),
+ (0x1DAA0, 'X'),
+ (0x1DAA1, 'V'),
+ (0x1DAB0, 'X'),
+ (0x1DF00, 'V'),
+ (0x1DF1F, 'X'),
+ (0x1DF25, 'V'),
+ (0x1DF2B, 'X'),
+ (0x1E000, 'V'),
+ (0x1E007, 'X'),
+ (0x1E008, 'V'),
+ (0x1E019, 'X'),
+ (0x1E01B, 'V'),
+ (0x1E022, 'X'),
+ (0x1E023, 'V'),
+ (0x1E025, 'X'),
+ (0x1E026, 'V'),
+ (0x1E02B, 'X'),
+ (0x1E030, 'M', 'а'),
+ (0x1E031, 'M', 'б'),
+ (0x1E032, 'M', 'в'),
+ (0x1E033, 'M', 'г'),
+ (0x1E034, 'M', 'д'),
+ (0x1E035, 'M', 'е'),
+ (0x1E036, 'M', 'ж'),
+ ]
+
+def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E037, 'M', 'з'),
+ (0x1E038, 'M', 'и'),
+ (0x1E039, 'M', 'к'),
+ (0x1E03A, 'M', 'л'),
+ (0x1E03B, 'M', 'м'),
+ (0x1E03C, 'M', 'о'),
+ (0x1E03D, 'M', 'п'),
+ (0x1E03E, 'M', 'р'),
+ (0x1E03F, 'M', 'с'),
+ (0x1E040, 'M', 'т'),
+ (0x1E041, 'M', 'у'),
+ (0x1E042, 'M', 'ф'),
+ (0x1E043, 'M', 'х'),
+ (0x1E044, 'M', 'ц'),
+ (0x1E045, 'M', 'ч'),
+ (0x1E046, 'M', 'ш'),
+ (0x1E047, 'M', 'ы'),
+ (0x1E048, 'M', 'э'),
+ (0x1E049, 'M', 'ю'),
+ (0x1E04A, 'M', 'ꚉ'),
+ (0x1E04B, 'M', 'ә'),
+ (0x1E04C, 'M', 'і'),
+ (0x1E04D, 'M', 'ј'),
+ (0x1E04E, 'M', 'ө'),
+ (0x1E04F, 'M', 'ү'),
+ (0x1E050, 'M', 'ӏ'),
+ (0x1E051, 'M', 'а'),
+ (0x1E052, 'M', 'б'),
+ (0x1E053, 'M', 'в'),
+ (0x1E054, 'M', 'г'),
+ (0x1E055, 'M', 'д'),
+ (0x1E056, 'M', 'е'),
+ (0x1E057, 'M', 'ж'),
+ (0x1E058, 'M', 'з'),
+ (0x1E059, 'M', 'и'),
+ (0x1E05A, 'M', 'к'),
+ (0x1E05B, 'M', 'л'),
+ (0x1E05C, 'M', 'о'),
+ (0x1E05D, 'M', 'п'),
+ (0x1E05E, 'M', 'с'),
+ (0x1E05F, 'M', 'у'),
+ (0x1E060, 'M', 'ф'),
+ (0x1E061, 'M', 'х'),
+ (0x1E062, 'M', 'ц'),
+ (0x1E063, 'M', 'ч'),
+ (0x1E064, 'M', 'ш'),
+ (0x1E065, 'M', 'ъ'),
+ (0x1E066, 'M', 'ы'),
+ (0x1E067, 'M', 'ґ'),
+ (0x1E068, 'M', 'і'),
+ (0x1E069, 'M', 'ѕ'),
+ (0x1E06A, 'M', 'џ'),
+ (0x1E06B, 'M', 'ҫ'),
+ (0x1E06C, 'M', 'ꙑ'),
+ (0x1E06D, 'M', 'ұ'),
+ (0x1E06E, 'X'),
+ (0x1E08F, 'V'),
+ (0x1E090, 'X'),
+ (0x1E100, 'V'),
+ (0x1E12D, 'X'),
+ (0x1E130, 'V'),
+ (0x1E13E, 'X'),
+ (0x1E140, 'V'),
+ (0x1E14A, 'X'),
+ (0x1E14E, 'V'),
+ (0x1E150, 'X'),
+ (0x1E290, 'V'),
+ (0x1E2AF, 'X'),
+ (0x1E2C0, 'V'),
+ (0x1E2FA, 'X'),
+ (0x1E2FF, 'V'),
+ (0x1E300, 'X'),
+ (0x1E4D0, 'V'),
+ (0x1E4FA, 'X'),
+ (0x1E7E0, 'V'),
+ (0x1E7E7, 'X'),
+ (0x1E7E8, 'V'),
+ (0x1E7EC, 'X'),
+ (0x1E7ED, 'V'),
+ (0x1E7EF, 'X'),
+ (0x1E7F0, 'V'),
+ (0x1E7FF, 'X'),
+ (0x1E800, 'V'),
+ (0x1E8C5, 'X'),
+ (0x1E8C7, 'V'),
+ (0x1E8D7, 'X'),
+ (0x1E900, 'M', '𞤢'),
+ (0x1E901, 'M', '𞤣'),
+ (0x1E902, 'M', '𞤤'),
+ (0x1E903, 'M', '𞤥'),
+ (0x1E904, 'M', '𞤦'),
+ (0x1E905, 'M', '𞤧'),
+ (0x1E906, 'M', '𞤨'),
+ (0x1E907, 'M', '𞤩'),
+ (0x1E908, 'M', '𞤪'),
+ (0x1E909, 'M', '𞤫'),
+ (0x1E90A, 'M', '𞤬'),
+ (0x1E90B, 'M', '𞤭'),
+ (0x1E90C, 'M', '𞤮'),
+ (0x1E90D, 'M', '𞤯'),
+ ]
+
+def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E90E, 'M', '𞤰'),
+ (0x1E90F, 'M', '𞤱'),
+ (0x1E910, 'M', '𞤲'),
+ (0x1E911, 'M', '𞤳'),
+ (0x1E912, 'M', '𞤴'),
+ (0x1E913, 'M', '𞤵'),
+ (0x1E914, 'M', '𞤶'),
+ (0x1E915, 'M', '𞤷'),
+ (0x1E916, 'M', '𞤸'),
+ (0x1E917, 'M', '𞤹'),
+ (0x1E918, 'M', '𞤺'),
+ (0x1E919, 'M', '𞤻'),
+ (0x1E91A, 'M', '𞤼'),
+ (0x1E91B, 'M', '𞤽'),
+ (0x1E91C, 'M', '𞤾'),
+ (0x1E91D, 'M', '𞤿'),
+ (0x1E91E, 'M', '𞥀'),
+ (0x1E91F, 'M', '𞥁'),
+ (0x1E920, 'M', '𞥂'),
+ (0x1E921, 'M', '𞥃'),
+ (0x1E922, 'V'),
+ (0x1E94C, 'X'),
+ (0x1E950, 'V'),
+ (0x1E95A, 'X'),
+ (0x1E95E, 'V'),
+ (0x1E960, 'X'),
+ (0x1EC71, 'V'),
+ (0x1ECB5, 'X'),
+ (0x1ED01, 'V'),
+ (0x1ED3E, 'X'),
+ (0x1EE00, 'M', 'ا'),
+ (0x1EE01, 'M', 'ب'),
+ (0x1EE02, 'M', 'ج'),
+ (0x1EE03, 'M', 'د'),
+ (0x1EE04, 'X'),
+ (0x1EE05, 'M', 'و'),
+ (0x1EE06, 'M', 'ز'),
+ (0x1EE07, 'M', 'ح'),
+ (0x1EE08, 'M', 'ط'),
+ (0x1EE09, 'M', 'ي'),
+ (0x1EE0A, 'M', 'ك'),
+ (0x1EE0B, 'M', 'ل'),
+ (0x1EE0C, 'M', 'م'),
+ (0x1EE0D, 'M', 'ن'),
+ (0x1EE0E, 'M', 'س'),
+ (0x1EE0F, 'M', 'ع'),
+ (0x1EE10, 'M', 'ف'),
+ (0x1EE11, 'M', 'ص'),
+ (0x1EE12, 'M', 'ق'),
+ (0x1EE13, 'M', 'ر'),
+ (0x1EE14, 'M', 'ش'),
+ (0x1EE15, 'M', 'ت'),
+ (0x1EE16, 'M', 'ث'),
+ (0x1EE17, 'M', 'خ'),
+ (0x1EE18, 'M', 'ذ'),
+ (0x1EE19, 'M', 'ض'),
+ (0x1EE1A, 'M', 'ظ'),
+ (0x1EE1B, 'M', 'غ'),
+ (0x1EE1C, 'M', 'ٮ'),
+ (0x1EE1D, 'M', 'ں'),
+ (0x1EE1E, 'M', 'ڡ'),
+ (0x1EE1F, 'M', 'ٯ'),
+ (0x1EE20, 'X'),
+ (0x1EE21, 'M', 'ب'),
+ (0x1EE22, 'M', 'ج'),
+ (0x1EE23, 'X'),
+ (0x1EE24, 'M', 'ه'),
+ (0x1EE25, 'X'),
+ (0x1EE27, 'M', 'ح'),
+ (0x1EE28, 'X'),
+ (0x1EE29, 'M', 'ي'),
+ (0x1EE2A, 'M', 'ك'),
+ (0x1EE2B, 'M', 'ل'),
+ (0x1EE2C, 'M', 'م'),
+ (0x1EE2D, 'M', 'ن'),
+ (0x1EE2E, 'M', 'س'),
+ (0x1EE2F, 'M', 'ع'),
+ (0x1EE30, 'M', 'ف'),
+ (0x1EE31, 'M', 'ص'),
+ (0x1EE32, 'M', 'ق'),
+ (0x1EE33, 'X'),
+ (0x1EE34, 'M', 'ش'),
+ (0x1EE35, 'M', 'ت'),
+ (0x1EE36, 'M', 'ث'),
+ (0x1EE37, 'M', 'خ'),
+ (0x1EE38, 'X'),
+ (0x1EE39, 'M', 'ض'),
+ (0x1EE3A, 'X'),
+ (0x1EE3B, 'M', 'غ'),
+ (0x1EE3C, 'X'),
+ (0x1EE42, 'M', 'ج'),
+ (0x1EE43, 'X'),
+ (0x1EE47, 'M', 'ح'),
+ (0x1EE48, 'X'),
+ (0x1EE49, 'M', 'ي'),
+ (0x1EE4A, 'X'),
+ (0x1EE4B, 'M', 'ل'),
+ (0x1EE4C, 'X'),
+ (0x1EE4D, 'M', 'ن'),
+ (0x1EE4E, 'M', 'س'),
+ ]
+
+def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EE4F, 'M', 'ع'),
+ (0x1EE50, 'X'),
+ (0x1EE51, 'M', 'ص'),
+ (0x1EE52, 'M', 'ق'),
+ (0x1EE53, 'X'),
+ (0x1EE54, 'M', 'ش'),
+ (0x1EE55, 'X'),
+ (0x1EE57, 'M', 'خ'),
+ (0x1EE58, 'X'),
+ (0x1EE59, 'M', 'ض'),
+ (0x1EE5A, 'X'),
+ (0x1EE5B, 'M', 'غ'),
+ (0x1EE5C, 'X'),
+ (0x1EE5D, 'M', 'ں'),
+ (0x1EE5E, 'X'),
+ (0x1EE5F, 'M', 'ٯ'),
+ (0x1EE60, 'X'),
+ (0x1EE61, 'M', 'ب'),
+ (0x1EE62, 'M', 'ج'),
+ (0x1EE63, 'X'),
+ (0x1EE64, 'M', 'ه'),
+ (0x1EE65, 'X'),
+ (0x1EE67, 'M', 'ح'),
+ (0x1EE68, 'M', 'ط'),
+ (0x1EE69, 'M', 'ي'),
+ (0x1EE6A, 'M', 'ك'),
+ (0x1EE6B, 'X'),
+ (0x1EE6C, 'M', 'م'),
+ (0x1EE6D, 'M', 'ن'),
+ (0x1EE6E, 'M', 'س'),
+ (0x1EE6F, 'M', 'ع'),
+ (0x1EE70, 'M', 'ف'),
+ (0x1EE71, 'M', 'ص'),
+ (0x1EE72, 'M', 'ق'),
+ (0x1EE73, 'X'),
+ (0x1EE74, 'M', 'ش'),
+ (0x1EE75, 'M', 'ت'),
+ (0x1EE76, 'M', 'ث'),
+ (0x1EE77, 'M', 'خ'),
+ (0x1EE78, 'X'),
+ (0x1EE79, 'M', 'ض'),
+ (0x1EE7A, 'M', 'ظ'),
+ (0x1EE7B, 'M', 'غ'),
+ (0x1EE7C, 'M', 'ٮ'),
+ (0x1EE7D, 'X'),
+ (0x1EE7E, 'M', 'ڡ'),
+ (0x1EE7F, 'X'),
+ (0x1EE80, 'M', 'ا'),
+ (0x1EE81, 'M', 'ب'),
+ (0x1EE82, 'M', 'ج'),
+ (0x1EE83, 'M', 'د'),
+ (0x1EE84, 'M', 'ه'),
+ (0x1EE85, 'M', 'و'),
+ (0x1EE86, 'M', 'ز'),
+ (0x1EE87, 'M', 'ح'),
+ (0x1EE88, 'M', 'ط'),
+ (0x1EE89, 'M', 'ي'),
+ (0x1EE8A, 'X'),
+ (0x1EE8B, 'M', 'ل'),
+ (0x1EE8C, 'M', 'م'),
+ (0x1EE8D, 'M', 'ن'),
+ (0x1EE8E, 'M', 'س'),
+ (0x1EE8F, 'M', 'ع'),
+ (0x1EE90, 'M', 'ف'),
+ (0x1EE91, 'M', 'ص'),
+ (0x1EE92, 'M', 'ق'),
+ (0x1EE93, 'M', 'ر'),
+ (0x1EE94, 'M', 'ش'),
+ (0x1EE95, 'M', 'ت'),
+ (0x1EE96, 'M', 'ث'),
+ (0x1EE97, 'M', 'خ'),
+ (0x1EE98, 'M', 'ذ'),
+ (0x1EE99, 'M', 'ض'),
+ (0x1EE9A, 'M', 'ظ'),
+ (0x1EE9B, 'M', 'غ'),
+ (0x1EE9C, 'X'),
+ (0x1EEA1, 'M', 'ب'),
+ (0x1EEA2, 'M', 'ج'),
+ (0x1EEA3, 'M', 'د'),
+ (0x1EEA4, 'X'),
+ (0x1EEA5, 'M', 'و'),
+ (0x1EEA6, 'M', 'ز'),
+ (0x1EEA7, 'M', 'ح'),
+ (0x1EEA8, 'M', 'ط'),
+ (0x1EEA9, 'M', 'ي'),
+ (0x1EEAA, 'X'),
+ (0x1EEAB, 'M', 'ل'),
+ (0x1EEAC, 'M', 'م'),
+ (0x1EEAD, 'M', 'ن'),
+ (0x1EEAE, 'M', 'س'),
+ (0x1EEAF, 'M', 'ع'),
+ (0x1EEB0, 'M', 'ف'),
+ (0x1EEB1, 'M', 'ص'),
+ (0x1EEB2, 'M', 'ق'),
+ (0x1EEB3, 'M', 'ر'),
+ (0x1EEB4, 'M', 'ش'),
+ (0x1EEB5, 'M', 'ت'),
+ (0x1EEB6, 'M', 'ث'),
+ (0x1EEB7, 'M', 'خ'),
+ (0x1EEB8, 'M', 'ذ'),
+ ]
+
+def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EEB9, 'M', 'ض'),
+ (0x1EEBA, 'M', 'ظ'),
+ (0x1EEBB, 'M', 'غ'),
+ (0x1EEBC, 'X'),
+ (0x1EEF0, 'V'),
+ (0x1EEF2, 'X'),
+ (0x1F000, 'V'),
+ (0x1F02C, 'X'),
+ (0x1F030, 'V'),
+ (0x1F094, 'X'),
+ (0x1F0A0, 'V'),
+ (0x1F0AF, 'X'),
+ (0x1F0B1, 'V'),
+ (0x1F0C0, 'X'),
+ (0x1F0C1, 'V'),
+ (0x1F0D0, 'X'),
+ (0x1F0D1, 'V'),
+ (0x1F0F6, 'X'),
+ (0x1F101, '3', '0,'),
+ (0x1F102, '3', '1,'),
+ (0x1F103, '3', '2,'),
+ (0x1F104, '3', '3,'),
+ (0x1F105, '3', '4,'),
+ (0x1F106, '3', '5,'),
+ (0x1F107, '3', '6,'),
+ (0x1F108, '3', '7,'),
+ (0x1F109, '3', '8,'),
+ (0x1F10A, '3', '9,'),
+ (0x1F10B, 'V'),
+ (0x1F110, '3', '(a)'),
+ (0x1F111, '3', '(b)'),
+ (0x1F112, '3', '(c)'),
+ (0x1F113, '3', '(d)'),
+ (0x1F114, '3', '(e)'),
+ (0x1F115, '3', '(f)'),
+ (0x1F116, '3', '(g)'),
+ (0x1F117, '3', '(h)'),
+ (0x1F118, '3', '(i)'),
+ (0x1F119, '3', '(j)'),
+ (0x1F11A, '3', '(k)'),
+ (0x1F11B, '3', '(l)'),
+ (0x1F11C, '3', '(m)'),
+ (0x1F11D, '3', '(n)'),
+ (0x1F11E, '3', '(o)'),
+ (0x1F11F, '3', '(p)'),
+ (0x1F120, '3', '(q)'),
+ (0x1F121, '3', '(r)'),
+ (0x1F122, '3', '(s)'),
+ (0x1F123, '3', '(t)'),
+ (0x1F124, '3', '(u)'),
+ (0x1F125, '3', '(v)'),
+ (0x1F126, '3', '(w)'),
+ (0x1F127, '3', '(x)'),
+ (0x1F128, '3', '(y)'),
+ (0x1F129, '3', '(z)'),
+ (0x1F12A, 'M', '〔s〕'),
+ (0x1F12B, 'M', 'c'),
+ (0x1F12C, 'M', 'r'),
+ (0x1F12D, 'M', 'cd'),
+ (0x1F12E, 'M', 'wz'),
+ (0x1F12F, 'V'),
+ (0x1F130, 'M', 'a'),
+ (0x1F131, 'M', 'b'),
+ (0x1F132, 'M', 'c'),
+ (0x1F133, 'M', 'd'),
+ (0x1F134, 'M', 'e'),
+ (0x1F135, 'M', 'f'),
+ (0x1F136, 'M', 'g'),
+ (0x1F137, 'M', 'h'),
+ (0x1F138, 'M', 'i'),
+ (0x1F139, 'M', 'j'),
+ (0x1F13A, 'M', 'k'),
+ (0x1F13B, 'M', 'l'),
+ (0x1F13C, 'M', 'm'),
+ (0x1F13D, 'M', 'n'),
+ (0x1F13E, 'M', 'o'),
+ (0x1F13F, 'M', 'p'),
+ (0x1F140, 'M', 'q'),
+ (0x1F141, 'M', 'r'),
+ (0x1F142, 'M', 's'),
+ (0x1F143, 'M', 't'),
+ (0x1F144, 'M', 'u'),
+ (0x1F145, 'M', 'v'),
+ (0x1F146, 'M', 'w'),
+ (0x1F147, 'M', 'x'),
+ (0x1F148, 'M', 'y'),
+ (0x1F149, 'M', 'z'),
+ (0x1F14A, 'M', 'hv'),
+ (0x1F14B, 'M', 'mv'),
+ (0x1F14C, 'M', 'sd'),
+ (0x1F14D, 'M', 'ss'),
+ (0x1F14E, 'M', 'ppv'),
+ (0x1F14F, 'M', 'wc'),
+ (0x1F150, 'V'),
+ (0x1F16A, 'M', 'mc'),
+ (0x1F16B, 'M', 'md'),
+ (0x1F16C, 'M', 'mr'),
+ (0x1F16D, 'V'),
+ (0x1F190, 'M', 'dj'),
+ (0x1F191, 'V'),
+ ]
+
+def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F1AE, 'X'),
+ (0x1F1E6, 'V'),
+ (0x1F200, 'M', 'ほか'),
+ (0x1F201, 'M', 'ココ'),
+ (0x1F202, 'M', 'サ'),
+ (0x1F203, 'X'),
+ (0x1F210, 'M', '手'),
+ (0x1F211, 'M', '字'),
+ (0x1F212, 'M', '双'),
+ (0x1F213, 'M', 'デ'),
+ (0x1F214, 'M', '二'),
+ (0x1F215, 'M', '多'),
+ (0x1F216, 'M', '解'),
+ (0x1F217, 'M', '天'),
+ (0x1F218, 'M', '交'),
+ (0x1F219, 'M', '映'),
+ (0x1F21A, 'M', '無'),
+ (0x1F21B, 'M', '料'),
+ (0x1F21C, 'M', '前'),
+ (0x1F21D, 'M', '後'),
+ (0x1F21E, 'M', '再'),
+ (0x1F21F, 'M', '新'),
+ (0x1F220, 'M', '初'),
+ (0x1F221, 'M', '終'),
+ (0x1F222, 'M', '生'),
+ (0x1F223, 'M', '販'),
+ (0x1F224, 'M', '声'),
+ (0x1F225, 'M', '吹'),
+ (0x1F226, 'M', '演'),
+ (0x1F227, 'M', '投'),
+ (0x1F228, 'M', '捕'),
+ (0x1F229, 'M', '一'),
+ (0x1F22A, 'M', '三'),
+ (0x1F22B, 'M', '遊'),
+ (0x1F22C, 'M', '左'),
+ (0x1F22D, 'M', '中'),
+ (0x1F22E, 'M', '右'),
+ (0x1F22F, 'M', '指'),
+ (0x1F230, 'M', '走'),
+ (0x1F231, 'M', '打'),
+ (0x1F232, 'M', '禁'),
+ (0x1F233, 'M', '空'),
+ (0x1F234, 'M', '合'),
+ (0x1F235, 'M', '満'),
+ (0x1F236, 'M', '有'),
+ (0x1F237, 'M', '月'),
+ (0x1F238, 'M', '申'),
+ (0x1F239, 'M', '割'),
+ (0x1F23A, 'M', '営'),
+ (0x1F23B, 'M', '配'),
+ (0x1F23C, 'X'),
+ (0x1F240, 'M', '〔本〕'),
+ (0x1F241, 'M', '〔三〕'),
+ (0x1F242, 'M', '〔二〕'),
+ (0x1F243, 'M', '〔安〕'),
+ (0x1F244, 'M', '〔点〕'),
+ (0x1F245, 'M', '〔打〕'),
+ (0x1F246, 'M', '〔盗〕'),
+ (0x1F247, 'M', '〔勝〕'),
+ (0x1F248, 'M', '〔敗〕'),
+ (0x1F249, 'X'),
+ (0x1F250, 'M', '得'),
+ (0x1F251, 'M', '可'),
+ (0x1F252, 'X'),
+ (0x1F260, 'V'),
+ (0x1F266, 'X'),
+ (0x1F300, 'V'),
+ (0x1F6D8, 'X'),
+ (0x1F6DC, 'V'),
+ (0x1F6ED, 'X'),
+ (0x1F6F0, 'V'),
+ (0x1F6FD, 'X'),
+ (0x1F700, 'V'),
+ (0x1F777, 'X'),
+ (0x1F77B, 'V'),
+ (0x1F7DA, 'X'),
+ (0x1F7E0, 'V'),
+ (0x1F7EC, 'X'),
+ (0x1F7F0, 'V'),
+ (0x1F7F1, 'X'),
+ (0x1F800, 'V'),
+ (0x1F80C, 'X'),
+ (0x1F810, 'V'),
+ (0x1F848, 'X'),
+ (0x1F850, 'V'),
+ (0x1F85A, 'X'),
+ (0x1F860, 'V'),
+ (0x1F888, 'X'),
+ (0x1F890, 'V'),
+ (0x1F8AE, 'X'),
+ (0x1F8B0, 'V'),
+ (0x1F8B2, 'X'),
+ (0x1F900, 'V'),
+ (0x1FA54, 'X'),
+ (0x1FA60, 'V'),
+ (0x1FA6E, 'X'),
+ (0x1FA70, 'V'),
+ (0x1FA7D, 'X'),
+ (0x1FA80, 'V'),
+ (0x1FA89, 'X'),
+ ]
+
+def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1FA90, 'V'),
+ (0x1FABE, 'X'),
+ (0x1FABF, 'V'),
+ (0x1FAC6, 'X'),
+ (0x1FACE, 'V'),
+ (0x1FADC, 'X'),
+ (0x1FAE0, 'V'),
+ (0x1FAE9, 'X'),
+ (0x1FAF0, 'V'),
+ (0x1FAF9, 'X'),
+ (0x1FB00, 'V'),
+ (0x1FB93, 'X'),
+ (0x1FB94, 'V'),
+ (0x1FBCB, 'X'),
+ (0x1FBF0, 'M', '0'),
+ (0x1FBF1, 'M', '1'),
+ (0x1FBF2, 'M', '2'),
+ (0x1FBF3, 'M', '3'),
+ (0x1FBF4, 'M', '4'),
+ (0x1FBF5, 'M', '5'),
+ (0x1FBF6, 'M', '6'),
+ (0x1FBF7, 'M', '7'),
+ (0x1FBF8, 'M', '8'),
+ (0x1FBF9, 'M', '9'),
+ (0x1FBFA, 'X'),
+ (0x20000, 'V'),
+ (0x2A6E0, 'X'),
+ (0x2A700, 'V'),
+ (0x2B73A, 'X'),
+ (0x2B740, 'V'),
+ (0x2B81E, 'X'),
+ (0x2B820, 'V'),
+ (0x2CEA2, 'X'),
+ (0x2CEB0, 'V'),
+ (0x2EBE1, 'X'),
+ (0x2EBF0, 'V'),
+ (0x2EE5E, 'X'),
+ (0x2F800, 'M', '丽'),
+ (0x2F801, 'M', '丸'),
+ (0x2F802, 'M', '乁'),
+ (0x2F803, 'M', '𠄢'),
+ (0x2F804, 'M', '你'),
+ (0x2F805, 'M', '侮'),
+ (0x2F806, 'M', '侻'),
+ (0x2F807, 'M', '倂'),
+ (0x2F808, 'M', '偺'),
+ (0x2F809, 'M', '備'),
+ (0x2F80A, 'M', '僧'),
+ (0x2F80B, 'M', '像'),
+ (0x2F80C, 'M', '㒞'),
+ (0x2F80D, 'M', '𠘺'),
+ (0x2F80E, 'M', '免'),
+ (0x2F80F, 'M', '兔'),
+ (0x2F810, 'M', '兤'),
+ (0x2F811, 'M', '具'),
+ (0x2F812, 'M', '𠔜'),
+ (0x2F813, 'M', '㒹'),
+ (0x2F814, 'M', '內'),
+ (0x2F815, 'M', '再'),
+ (0x2F816, 'M', '𠕋'),
+ (0x2F817, 'M', '冗'),
+ (0x2F818, 'M', '冤'),
+ (0x2F819, 'M', '仌'),
+ (0x2F81A, 'M', '冬'),
+ (0x2F81B, 'M', '况'),
+ (0x2F81C, 'M', '𩇟'),
+ (0x2F81D, 'M', '凵'),
+ (0x2F81E, 'M', '刃'),
+ (0x2F81F, 'M', '㓟'),
+ (0x2F820, 'M', '刻'),
+ (0x2F821, 'M', '剆'),
+ (0x2F822, 'M', '割'),
+ (0x2F823, 'M', '剷'),
+ (0x2F824, 'M', '㔕'),
+ (0x2F825, 'M', '勇'),
+ (0x2F826, 'M', '勉'),
+ (0x2F827, 'M', '勤'),
+ (0x2F828, 'M', '勺'),
+ (0x2F829, 'M', '包'),
+ (0x2F82A, 'M', '匆'),
+ (0x2F82B, 'M', '北'),
+ (0x2F82C, 'M', '卉'),
+ (0x2F82D, 'M', '卑'),
+ (0x2F82E, 'M', '博'),
+ (0x2F82F, 'M', '即'),
+ (0x2F830, 'M', '卽'),
+ (0x2F831, 'M', '卿'),
+ (0x2F834, 'M', '𠨬'),
+ (0x2F835, 'M', '灰'),
+ (0x2F836, 'M', '及'),
+ (0x2F837, 'M', '叟'),
+ (0x2F838, 'M', '𠭣'),
+ (0x2F839, 'M', '叫'),
+ (0x2F83A, 'M', '叱'),
+ (0x2F83B, 'M', '吆'),
+ (0x2F83C, 'M', '咞'),
+ (0x2F83D, 'M', '吸'),
+ (0x2F83E, 'M', '呈'),
+ (0x2F83F, 'M', '周'),
+ (0x2F840, 'M', '咢'),
+ ]
+
+def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F841, 'M', '哶'),
+ (0x2F842, 'M', '唐'),
+ (0x2F843, 'M', '啓'),
+ (0x2F844, 'M', '啣'),
+ (0x2F845, 'M', '善'),
+ (0x2F847, 'M', '喙'),
+ (0x2F848, 'M', '喫'),
+ (0x2F849, 'M', '喳'),
+ (0x2F84A, 'M', '嗂'),
+ (0x2F84B, 'M', '圖'),
+ (0x2F84C, 'M', '嘆'),
+ (0x2F84D, 'M', '圗'),
+ (0x2F84E, 'M', '噑'),
+ (0x2F84F, 'M', '噴'),
+ (0x2F850, 'M', '切'),
+ (0x2F851, 'M', '壮'),
+ (0x2F852, 'M', '城'),
+ (0x2F853, 'M', '埴'),
+ (0x2F854, 'M', '堍'),
+ (0x2F855, 'M', '型'),
+ (0x2F856, 'M', '堲'),
+ (0x2F857, 'M', '報'),
+ (0x2F858, 'M', '墬'),
+ (0x2F859, 'M', '𡓤'),
+ (0x2F85A, 'M', '売'),
+ (0x2F85B, 'M', '壷'),
+ (0x2F85C, 'M', '夆'),
+ (0x2F85D, 'M', '多'),
+ (0x2F85E, 'M', '夢'),
+ (0x2F85F, 'M', '奢'),
+ (0x2F860, 'M', '𡚨'),
+ (0x2F861, 'M', '𡛪'),
+ (0x2F862, 'M', '姬'),
+ (0x2F863, 'M', '娛'),
+ (0x2F864, 'M', '娧'),
+ (0x2F865, 'M', '姘'),
+ (0x2F866, 'M', '婦'),
+ (0x2F867, 'M', '㛮'),
+ (0x2F868, 'X'),
+ (0x2F869, 'M', '嬈'),
+ (0x2F86A, 'M', '嬾'),
+ (0x2F86C, 'M', '𡧈'),
+ (0x2F86D, 'M', '寃'),
+ (0x2F86E, 'M', '寘'),
+ (0x2F86F, 'M', '寧'),
+ (0x2F870, 'M', '寳'),
+ (0x2F871, 'M', '𡬘'),
+ (0x2F872, 'M', '寿'),
+ (0x2F873, 'M', '将'),
+ (0x2F874, 'X'),
+ (0x2F875, 'M', '尢'),
+ (0x2F876, 'M', '㞁'),
+ (0x2F877, 'M', '屠'),
+ (0x2F878, 'M', '屮'),
+ (0x2F879, 'M', '峀'),
+ (0x2F87A, 'M', '岍'),
+ (0x2F87B, 'M', '𡷤'),
+ (0x2F87C, 'M', '嵃'),
+ (0x2F87D, 'M', '𡷦'),
+ (0x2F87E, 'M', '嵮'),
+ (0x2F87F, 'M', '嵫'),
+ (0x2F880, 'M', '嵼'),
+ (0x2F881, 'M', '巡'),
+ (0x2F882, 'M', '巢'),
+ (0x2F883, 'M', '㠯'),
+ (0x2F884, 'M', '巽'),
+ (0x2F885, 'M', '帨'),
+ (0x2F886, 'M', '帽'),
+ (0x2F887, 'M', '幩'),
+ (0x2F888, 'M', '㡢'),
+ (0x2F889, 'M', '𢆃'),
+ (0x2F88A, 'M', '㡼'),
+ (0x2F88B, 'M', '庰'),
+ (0x2F88C, 'M', '庳'),
+ (0x2F88D, 'M', '庶'),
+ (0x2F88E, 'M', '廊'),
+ (0x2F88F, 'M', '𪎒'),
+ (0x2F890, 'M', '廾'),
+ (0x2F891, 'M', '𢌱'),
+ (0x2F893, 'M', '舁'),
+ (0x2F894, 'M', '弢'),
+ (0x2F896, 'M', '㣇'),
+ (0x2F897, 'M', '𣊸'),
+ (0x2F898, 'M', '𦇚'),
+ (0x2F899, 'M', '形'),
+ (0x2F89A, 'M', '彫'),
+ (0x2F89B, 'M', '㣣'),
+ (0x2F89C, 'M', '徚'),
+ (0x2F89D, 'M', '忍'),
+ (0x2F89E, 'M', '志'),
+ (0x2F89F, 'M', '忹'),
+ (0x2F8A0, 'M', '悁'),
+ (0x2F8A1, 'M', '㤺'),
+ (0x2F8A2, 'M', '㤜'),
+ (0x2F8A3, 'M', '悔'),
+ (0x2F8A4, 'M', '𢛔'),
+ (0x2F8A5, 'M', '惇'),
+ (0x2F8A6, 'M', '慈'),
+ (0x2F8A7, 'M', '慌'),
+ (0x2F8A8, 'M', '慎'),
+ ]
+
+def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F8A9, 'M', '慌'),
+ (0x2F8AA, 'M', '慺'),
+ (0x2F8AB, 'M', '憎'),
+ (0x2F8AC, 'M', '憲'),
+ (0x2F8AD, 'M', '憤'),
+ (0x2F8AE, 'M', '憯'),
+ (0x2F8AF, 'M', '懞'),
+ (0x2F8B0, 'M', '懲'),
+ (0x2F8B1, 'M', '懶'),
+ (0x2F8B2, 'M', '成'),
+ (0x2F8B3, 'M', '戛'),
+ (0x2F8B4, 'M', '扝'),
+ (0x2F8B5, 'M', '抱'),
+ (0x2F8B6, 'M', '拔'),
+ (0x2F8B7, 'M', '捐'),
+ (0x2F8B8, 'M', '𢬌'),
+ (0x2F8B9, 'M', '挽'),
+ (0x2F8BA, 'M', '拼'),
+ (0x2F8BB, 'M', '捨'),
+ (0x2F8BC, 'M', '掃'),
+ (0x2F8BD, 'M', '揤'),
+ (0x2F8BE, 'M', '𢯱'),
+ (0x2F8BF, 'M', '搢'),
+ (0x2F8C0, 'M', '揅'),
+ (0x2F8C1, 'M', '掩'),
+ (0x2F8C2, 'M', '㨮'),
+ (0x2F8C3, 'M', '摩'),
+ (0x2F8C4, 'M', '摾'),
+ (0x2F8C5, 'M', '撝'),
+ (0x2F8C6, 'M', '摷'),
+ (0x2F8C7, 'M', '㩬'),
+ (0x2F8C8, 'M', '敏'),
+ (0x2F8C9, 'M', '敬'),
+ (0x2F8CA, 'M', '𣀊'),
+ (0x2F8CB, 'M', '旣'),
+ (0x2F8CC, 'M', '書'),
+ (0x2F8CD, 'M', '晉'),
+ (0x2F8CE, 'M', '㬙'),
+ (0x2F8CF, 'M', '暑'),
+ (0x2F8D0, 'M', '㬈'),
+ (0x2F8D1, 'M', '㫤'),
+ (0x2F8D2, 'M', '冒'),
+ (0x2F8D3, 'M', '冕'),
+ (0x2F8D4, 'M', '最'),
+ (0x2F8D5, 'M', '暜'),
+ (0x2F8D6, 'M', '肭'),
+ (0x2F8D7, 'M', '䏙'),
+ (0x2F8D8, 'M', '朗'),
+ (0x2F8D9, 'M', '望'),
+ (0x2F8DA, 'M', '朡'),
+ (0x2F8DB, 'M', '杞'),
+ (0x2F8DC, 'M', '杓'),
+ (0x2F8DD, 'M', '𣏃'),
+ (0x2F8DE, 'M', '㭉'),
+ (0x2F8DF, 'M', '柺'),
+ (0x2F8E0, 'M', '枅'),
+ (0x2F8E1, 'M', '桒'),
+ (0x2F8E2, 'M', '梅'),
+ (0x2F8E3, 'M', '𣑭'),
+ (0x2F8E4, 'M', '梎'),
+ (0x2F8E5, 'M', '栟'),
+ (0x2F8E6, 'M', '椔'),
+ (0x2F8E7, 'M', '㮝'),
+ (0x2F8E8, 'M', '楂'),
+ (0x2F8E9, 'M', '榣'),
+ (0x2F8EA, 'M', '槪'),
+ (0x2F8EB, 'M', '檨'),
+ (0x2F8EC, 'M', '𣚣'),
+ (0x2F8ED, 'M', '櫛'),
+ (0x2F8EE, 'M', '㰘'),
+ (0x2F8EF, 'M', '次'),
+ (0x2F8F0, 'M', '𣢧'),
+ (0x2F8F1, 'M', '歔'),
+ (0x2F8F2, 'M', '㱎'),
+ (0x2F8F3, 'M', '歲'),
+ (0x2F8F4, 'M', '殟'),
+ (0x2F8F5, 'M', '殺'),
+ (0x2F8F6, 'M', '殻'),
+ (0x2F8F7, 'M', '𣪍'),
+ (0x2F8F8, 'M', '𡴋'),
+ (0x2F8F9, 'M', '𣫺'),
+ (0x2F8FA, 'M', '汎'),
+ (0x2F8FB, 'M', '𣲼'),
+ (0x2F8FC, 'M', '沿'),
+ (0x2F8FD, 'M', '泍'),
+ (0x2F8FE, 'M', '汧'),
+ (0x2F8FF, 'M', '洖'),
+ (0x2F900, 'M', '派'),
+ (0x2F901, 'M', '海'),
+ (0x2F902, 'M', '流'),
+ (0x2F903, 'M', '浩'),
+ (0x2F904, 'M', '浸'),
+ (0x2F905, 'M', '涅'),
+ (0x2F906, 'M', '𣴞'),
+ (0x2F907, 'M', '洴'),
+ (0x2F908, 'M', '港'),
+ (0x2F909, 'M', '湮'),
+ (0x2F90A, 'M', '㴳'),
+ (0x2F90B, 'M', '滋'),
+ (0x2F90C, 'M', '滇'),
+ ]
+
+def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F90D, 'M', '𣻑'),
+ (0x2F90E, 'M', '淹'),
+ (0x2F90F, 'M', '潮'),
+ (0x2F910, 'M', '𣽞'),
+ (0x2F911, 'M', '𣾎'),
+ (0x2F912, 'M', '濆'),
+ (0x2F913, 'M', '瀹'),
+ (0x2F914, 'M', '瀞'),
+ (0x2F915, 'M', '瀛'),
+ (0x2F916, 'M', '㶖'),
+ (0x2F917, 'M', '灊'),
+ (0x2F918, 'M', '災'),
+ (0x2F919, 'M', '灷'),
+ (0x2F91A, 'M', '炭'),
+ (0x2F91B, 'M', '𠔥'),
+ (0x2F91C, 'M', '煅'),
+ (0x2F91D, 'M', '𤉣'),
+ (0x2F91E, 'M', '熜'),
+ (0x2F91F, 'X'),
+ (0x2F920, 'M', '爨'),
+ (0x2F921, 'M', '爵'),
+ (0x2F922, 'M', '牐'),
+ (0x2F923, 'M', '𤘈'),
+ (0x2F924, 'M', '犀'),
+ (0x2F925, 'M', '犕'),
+ (0x2F926, 'M', '𤜵'),
+ (0x2F927, 'M', '𤠔'),
+ (0x2F928, 'M', '獺'),
+ (0x2F929, 'M', '王'),
+ (0x2F92A, 'M', '㺬'),
+ (0x2F92B, 'M', '玥'),
+ (0x2F92C, 'M', '㺸'),
+ (0x2F92E, 'M', '瑇'),
+ (0x2F92F, 'M', '瑜'),
+ (0x2F930, 'M', '瑱'),
+ (0x2F931, 'M', '璅'),
+ (0x2F932, 'M', '瓊'),
+ (0x2F933, 'M', '㼛'),
+ (0x2F934, 'M', '甤'),
+ (0x2F935, 'M', '𤰶'),
+ (0x2F936, 'M', '甾'),
+ (0x2F937, 'M', '𤲒'),
+ (0x2F938, 'M', '異'),
+ (0x2F939, 'M', '𢆟'),
+ (0x2F93A, 'M', '瘐'),
+ (0x2F93B, 'M', '𤾡'),
+ (0x2F93C, 'M', '𤾸'),
+ (0x2F93D, 'M', '𥁄'),
+ (0x2F93E, 'M', '㿼'),
+ (0x2F93F, 'M', '䀈'),
+ (0x2F940, 'M', '直'),
+ (0x2F941, 'M', '𥃳'),
+ (0x2F942, 'M', '𥃲'),
+ (0x2F943, 'M', '𥄙'),
+ (0x2F944, 'M', '𥄳'),
+ (0x2F945, 'M', '眞'),
+ (0x2F946, 'M', '真'),
+ (0x2F948, 'M', '睊'),
+ (0x2F949, 'M', '䀹'),
+ (0x2F94A, 'M', '瞋'),
+ (0x2F94B, 'M', '䁆'),
+ (0x2F94C, 'M', '䂖'),
+ (0x2F94D, 'M', '𥐝'),
+ (0x2F94E, 'M', '硎'),
+ (0x2F94F, 'M', '碌'),
+ (0x2F950, 'M', '磌'),
+ (0x2F951, 'M', '䃣'),
+ (0x2F952, 'M', '𥘦'),
+ (0x2F953, 'M', '祖'),
+ (0x2F954, 'M', '𥚚'),
+ (0x2F955, 'M', '𥛅'),
+ (0x2F956, 'M', '福'),
+ (0x2F957, 'M', '秫'),
+ (0x2F958, 'M', '䄯'),
+ (0x2F959, 'M', '穀'),
+ (0x2F95A, 'M', '穊'),
+ (0x2F95B, 'M', '穏'),
+ (0x2F95C, 'M', '𥥼'),
+ (0x2F95D, 'M', '𥪧'),
+ (0x2F95F, 'X'),
+ (0x2F960, 'M', '䈂'),
+ (0x2F961, 'M', '𥮫'),
+ (0x2F962, 'M', '篆'),
+ (0x2F963, 'M', '築'),
+ (0x2F964, 'M', '䈧'),
+ (0x2F965, 'M', '𥲀'),
+ (0x2F966, 'M', '糒'),
+ (0x2F967, 'M', '䊠'),
+ (0x2F968, 'M', '糨'),
+ (0x2F969, 'M', '糣'),
+ (0x2F96A, 'M', '紀'),
+ (0x2F96B, 'M', '𥾆'),
+ (0x2F96C, 'M', '絣'),
+ (0x2F96D, 'M', '䌁'),
+ (0x2F96E, 'M', '緇'),
+ (0x2F96F, 'M', '縂'),
+ (0x2F970, 'M', '繅'),
+ (0x2F971, 'M', '䌴'),
+ (0x2F972, 'M', '𦈨'),
+ (0x2F973, 'M', '𦉇'),
+ ]
+
+def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F974, 'M', '䍙'),
+ (0x2F975, 'M', '𦋙'),
+ (0x2F976, 'M', '罺'),
+ (0x2F977, 'M', '𦌾'),
+ (0x2F978, 'M', '羕'),
+ (0x2F979, 'M', '翺'),
+ (0x2F97A, 'M', '者'),
+ (0x2F97B, 'M', '𦓚'),
+ (0x2F97C, 'M', '𦔣'),
+ (0x2F97D, 'M', '聠'),
+ (0x2F97E, 'M', '𦖨'),
+ (0x2F97F, 'M', '聰'),
+ (0x2F980, 'M', '𣍟'),
+ (0x2F981, 'M', '䏕'),
+ (0x2F982, 'M', '育'),
+ (0x2F983, 'M', '脃'),
+ (0x2F984, 'M', '䐋'),
+ (0x2F985, 'M', '脾'),
+ (0x2F986, 'M', '媵'),
+ (0x2F987, 'M', '𦞧'),
+ (0x2F988, 'M', '𦞵'),
+ (0x2F989, 'M', '𣎓'),
+ (0x2F98A, 'M', '𣎜'),
+ (0x2F98B, 'M', '舁'),
+ (0x2F98C, 'M', '舄'),
+ (0x2F98D, 'M', '辞'),
+ (0x2F98E, 'M', '䑫'),
+ (0x2F98F, 'M', '芑'),
+ (0x2F990, 'M', '芋'),
+ (0x2F991, 'M', '芝'),
+ (0x2F992, 'M', '劳'),
+ (0x2F993, 'M', '花'),
+ (0x2F994, 'M', '芳'),
+ (0x2F995, 'M', '芽'),
+ (0x2F996, 'M', '苦'),
+ (0x2F997, 'M', '𦬼'),
+ (0x2F998, 'M', '若'),
+ (0x2F999, 'M', '茝'),
+ (0x2F99A, 'M', '荣'),
+ (0x2F99B, 'M', '莭'),
+ (0x2F99C, 'M', '茣'),
+ (0x2F99D, 'M', '莽'),
+ (0x2F99E, 'M', '菧'),
+ (0x2F99F, 'M', '著'),
+ (0x2F9A0, 'M', '荓'),
+ (0x2F9A1, 'M', '菊'),
+ (0x2F9A2, 'M', '菌'),
+ (0x2F9A3, 'M', '菜'),
+ (0x2F9A4, 'M', '𦰶'),
+ (0x2F9A5, 'M', '𦵫'),
+ (0x2F9A6, 'M', '𦳕'),
+ (0x2F9A7, 'M', '䔫'),
+ (0x2F9A8, 'M', '蓱'),
+ (0x2F9A9, 'M', '蓳'),
+ (0x2F9AA, 'M', '蔖'),
+ (0x2F9AB, 'M', '𧏊'),
+ (0x2F9AC, 'M', '蕤'),
+ (0x2F9AD, 'M', '𦼬'),
+ (0x2F9AE, 'M', '䕝'),
+ (0x2F9AF, 'M', '䕡'),
+ (0x2F9B0, 'M', '𦾱'),
+ (0x2F9B1, 'M', '𧃒'),
+ (0x2F9B2, 'M', '䕫'),
+ (0x2F9B3, 'M', '虐'),
+ (0x2F9B4, 'M', '虜'),
+ (0x2F9B5, 'M', '虧'),
+ (0x2F9B6, 'M', '虩'),
+ (0x2F9B7, 'M', '蚩'),
+ (0x2F9B8, 'M', '蚈'),
+ (0x2F9B9, 'M', '蜎'),
+ (0x2F9BA, 'M', '蛢'),
+ (0x2F9BB, 'M', '蝹'),
+ (0x2F9BC, 'M', '蜨'),
+ (0x2F9BD, 'M', '蝫'),
+ (0x2F9BE, 'M', '螆'),
+ (0x2F9BF, 'X'),
+ (0x2F9C0, 'M', '蟡'),
+ (0x2F9C1, 'M', '蠁'),
+ (0x2F9C2, 'M', '䗹'),
+ (0x2F9C3, 'M', '衠'),
+ (0x2F9C4, 'M', '衣'),
+ (0x2F9C5, 'M', '𧙧'),
+ (0x2F9C6, 'M', '裗'),
+ (0x2F9C7, 'M', '裞'),
+ (0x2F9C8, 'M', '䘵'),
+ (0x2F9C9, 'M', '裺'),
+ (0x2F9CA, 'M', '㒻'),
+ (0x2F9CB, 'M', '𧢮'),
+ (0x2F9CC, 'M', '𧥦'),
+ (0x2F9CD, 'M', '䚾'),
+ (0x2F9CE, 'M', '䛇'),
+ (0x2F9CF, 'M', '誠'),
+ (0x2F9D0, 'M', '諭'),
+ (0x2F9D1, 'M', '變'),
+ (0x2F9D2, 'M', '豕'),
+ (0x2F9D3, 'M', '𧲨'),
+ (0x2F9D4, 'M', '貫'),
+ (0x2F9D5, 'M', '賁'),
+ (0x2F9D6, 'M', '贛'),
+ (0x2F9D7, 'M', '起'),
+ ]
+
+def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F9D8, 'M', '𧼯'),
+ (0x2F9D9, 'M', '𠠄'),
+ (0x2F9DA, 'M', '跋'),
+ (0x2F9DB, 'M', '趼'),
+ (0x2F9DC, 'M', '跰'),
+ (0x2F9DD, 'M', '𠣞'),
+ (0x2F9DE, 'M', '軔'),
+ (0x2F9DF, 'M', '輸'),
+ (0x2F9E0, 'M', '𨗒'),
+ (0x2F9E1, 'M', '𨗭'),
+ (0x2F9E2, 'M', '邔'),
+ (0x2F9E3, 'M', '郱'),
+ (0x2F9E4, 'M', '鄑'),
+ (0x2F9E5, 'M', '𨜮'),
+ (0x2F9E6, 'M', '鄛'),
+ (0x2F9E7, 'M', '鈸'),
+ (0x2F9E8, 'M', '鋗'),
+ (0x2F9E9, 'M', '鋘'),
+ (0x2F9EA, 'M', '鉼'),
+ (0x2F9EB, 'M', '鏹'),
+ (0x2F9EC, 'M', '鐕'),
+ (0x2F9ED, 'M', '𨯺'),
+ (0x2F9EE, 'M', '開'),
+ (0x2F9EF, 'M', '䦕'),
+ (0x2F9F0, 'M', '閷'),
+ (0x2F9F1, 'M', '𨵷'),
+ (0x2F9F2, 'M', '䧦'),
+ (0x2F9F3, 'M', '雃'),
+ (0x2F9F4, 'M', '嶲'),
+ (0x2F9F5, 'M', '霣'),
+ (0x2F9F6, 'M', '𩅅'),
+ (0x2F9F7, 'M', '𩈚'),
+ (0x2F9F8, 'M', '䩮'),
+ (0x2F9F9, 'M', '䩶'),
+ (0x2F9FA, 'M', '韠'),
+ (0x2F9FB, 'M', '𩐊'),
+ (0x2F9FC, 'M', '䪲'),
+ (0x2F9FD, 'M', '𩒖'),
+ (0x2F9FE, 'M', '頋'),
+ (0x2FA00, 'M', '頩'),
+ (0x2FA01, 'M', '𩖶'),
+ (0x2FA02, 'M', '飢'),
+ (0x2FA03, 'M', '䬳'),
+ (0x2FA04, 'M', '餩'),
+ (0x2FA05, 'M', '馧'),
+ (0x2FA06, 'M', '駂'),
+ (0x2FA07, 'M', '駾'),
+ (0x2FA08, 'M', '䯎'),
+ (0x2FA09, 'M', '𩬰'),
+ (0x2FA0A, 'M', '鬒'),
+ (0x2FA0B, 'M', '鱀'),
+ (0x2FA0C, 'M', '鳽'),
+ (0x2FA0D, 'M', '䳎'),
+ (0x2FA0E, 'M', '䳭'),
+ (0x2FA0F, 'M', '鵧'),
+ (0x2FA10, 'M', '𪃎'),
+ (0x2FA11, 'M', '䳸'),
+ (0x2FA12, 'M', '𪄅'),
+ (0x2FA13, 'M', '𪈎'),
+ (0x2FA14, 'M', '𪊑'),
+ (0x2FA15, 'M', '麻'),
+ (0x2FA16, 'M', '䵖'),
+ (0x2FA17, 'M', '黹'),
+ (0x2FA18, 'M', '黾'),
+ (0x2FA19, 'M', '鼅'),
+ (0x2FA1A, 'M', '鼏'),
+ (0x2FA1B, 'M', '鼖'),
+ (0x2FA1C, 'M', '鼻'),
+ (0x2FA1D, 'M', '𪘀'),
+ (0x2FA1E, 'X'),
+ (0x30000, 'V'),
+ (0x3134B, 'X'),
+ (0x31350, 'V'),
+ (0x323B0, 'X'),
+ (0xE0100, 'I'),
+ (0xE01F0, 'X'),
+ ]
+
+uts46data = tuple(
+ _seg_0()
+ + _seg_1()
+ + _seg_2()
+ + _seg_3()
+ + _seg_4()
+ + _seg_5()
+ + _seg_6()
+ + _seg_7()
+ + _seg_8()
+ + _seg_9()
+ + _seg_10()
+ + _seg_11()
+ + _seg_12()
+ + _seg_13()
+ + _seg_14()
+ + _seg_15()
+ + _seg_16()
+ + _seg_17()
+ + _seg_18()
+ + _seg_19()
+ + _seg_20()
+ + _seg_21()
+ + _seg_22()
+ + _seg_23()
+ + _seg_24()
+ + _seg_25()
+ + _seg_26()
+ + _seg_27()
+ + _seg_28()
+ + _seg_29()
+ + _seg_30()
+ + _seg_31()
+ + _seg_32()
+ + _seg_33()
+ + _seg_34()
+ + _seg_35()
+ + _seg_36()
+ + _seg_37()
+ + _seg_38()
+ + _seg_39()
+ + _seg_40()
+ + _seg_41()
+ + _seg_42()
+ + _seg_43()
+ + _seg_44()
+ + _seg_45()
+ + _seg_46()
+ + _seg_47()
+ + _seg_48()
+ + _seg_49()
+ + _seg_50()
+ + _seg_51()
+ + _seg_52()
+ + _seg_53()
+ + _seg_54()
+ + _seg_55()
+ + _seg_56()
+ + _seg_57()
+ + _seg_58()
+ + _seg_59()
+ + _seg_60()
+ + _seg_61()
+ + _seg_62()
+ + _seg_63()
+ + _seg_64()
+ + _seg_65()
+ + _seg_66()
+ + _seg_67()
+ + _seg_68()
+ + _seg_69()
+ + _seg_70()
+ + _seg_71()
+ + _seg_72()
+ + _seg_73()
+ + _seg_74()
+ + _seg_75()
+ + _seg_76()
+ + _seg_77()
+ + _seg_78()
+ + _seg_79()
+ + _seg_80()
+ + _seg_81()
+) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...]
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/LICENSE b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..2dc0587ffdbd0c5d15bf94dbc3e0b46a5785d632
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2018 Kaggle Inc
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/METADATA b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..28721b0a68e815903a9d550923a583d71f576808
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/METADATA
@@ -0,0 +1,23 @@
+Metadata-Version: 2.1
+Name: kaggle
+Version: 1.6.12
+Summary: Kaggle API
+Home-page: https://github.com/Kaggle/kaggle-api
+Author: Kaggle
+Author-email: support@kaggle.com
+License: Apache 2.0
+Project-URL: Documentation, https://www.kaggle.com/docs/api
+Project-URL: GitHub, https://github.com/Kaggle/kaggle-api
+Project-URL: Tracker, https://github.com/Kaggle/kaggle-api/issues
+Keywords: Kaggle,API
+License-File: LICENSE
+Requires-Dist: six >=1.10
+Requires-Dist: certifi >=2023.7.22
+Requires-Dist: python-dateutil
+Requires-Dist: requests
+Requires-Dist: tqdm
+Requires-Dist: python-slugify
+Requires-Dist: urllib3
+Requires-Dist: bleach
+
+Official API for https://www.kaggle.com, accessible using a command line tool implemented in Python. Beta release - Kaggle reserves the right to modify the API functionality currently offered.
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/RECORD b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..db3941d7fde25ab42bc4020ce18aa0b3223bb299
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/RECORD
@@ -0,0 +1,39 @@
+../../../bin/kaggle,sha256=O4Ql9nd01WROsVXYwtaocsgCiF5pKSl7h1XB6nlABcY,240
+kaggle/__init__.py,sha256=B89ugVaosY0z1g245Gi6-WPJSXAzZVXTi8ACEuUDMf4,203
+kaggle/api_client.py,sha256=_dwrtsFK8GC6X6U3uqH_LJ7Yhxr1MLksrqbm16YCa7w,24862
+kaggle/cli.py,sha256=Fu7XTgysIamGTTY-EfrhrPTkIyHFooZU53xfZwUH6G0,75151
+kaggle/configuration.py,sha256=7e3yiaFIYz-408n_t6XRuCHP5aJTZc4_j2BP1h7ExCw,8567
+kaggle/rest.py,sha256=iXYX9ivdrm9bGaKmdrVfLUBRR8K4_wt_dCmGj1nKkKs,13331
+kaggle/api/__init__.py,sha256=LfuQK_tdzsjKvMMh81px3Ut86N534_HT64ndJpdx4es,146
+kaggle/api/kaggle_api.py,sha256=E_4ig76HMk9BT6q49LwfIHgR8pGLcdnu-otS5anqK-g,189294
+kaggle/api/kaggle_api_extended.py,sha256=j00joLjNRT7Vk5TnLNc4sXR3b9v600XcRAwFpaOr3iQ,176542
+kaggle/models/__init__.py,sha256=ROEn7RSIVYje45mE0fj6CiU8kEeHmOh4llJ6pS6GkSQ,1536
+kaggle/models/api_blob_type.py,sha256=jSCJxPPUXYY-3rZrI7N8HFHSM33jNG_qJ3RL8paoJeo,2426
+kaggle/models/collaborator.py,sha256=5jSiOIxGq-9vq6srorHG5M_V-jYOUDq-KJBiuFX3UAg,4255
+kaggle/models/create_inbox_file_request.py,sha256=XdkZwVYdJxUNyBp-FgvpBFGJQb50GXlUP6ovHccDBVg,4570
+kaggle/models/dataset_column.py,sha256=2q_Zt_o6G7TRw3dT_gVkUdSosHwTWYE0thkrz0bCQRs,6828
+kaggle/models/dataset_new_request.py,sha256=ba9Vl_z8JhicjdV5qGJefMwvWvUB0hy1iD9l2w4bWqE,11973
+kaggle/models/dataset_new_version_request.py,sha256=LGgwbTJ-FEYPDV3roHnZLmPbpOf2tUhS18vTBM3pqSc,9395
+kaggle/models/dataset_update_settings_request.py,sha256=S0QAr_iX7lGTzY4T6b1278-smUI5vTZZ-5RjxlI0QKM,9518
+kaggle/models/error.py,sha256=-5T6OmmIpFmbQ8OME9Sq5TmJFSfbEe0PFwprhj2NriA,3746
+kaggle/models/kaggle_models_extended.py,sha256=jFuDwaHqhJf6X4vF9rZYGeHvehWYhRDDs5H3gkAgceM,7195
+kaggle/models/kernel_push_request.py,sha256=uAYNTHnWcoIztwvGb4j6RUhceN2Ef9Ldz17yweLBx5Q,19603
+kaggle/models/license.py,sha256=RS4ODvTMwzykz5hbymnZl_oXJrOzOdvOFN4vBlKAttQ,3769
+kaggle/models/model_instance_new_version_request.py,sha256=y9HOVqmbjR2XHJqKhb6YoeQE8qpCYAJ5O0RcH_jwg-8,4455
+kaggle/models/model_instance_update_request.py,sha256=liI3tKpvjRmg71-wKGX0CqUQfS-oqGFTP6gfvYkjA_4,13125
+kaggle/models/model_new_instance_request.py,sha256=it7Cq4BQOxMOep3J78cCNG4lTcLspUMPwsocNiyCrXo,15596
+kaggle/models/model_new_request.py,sha256=lwkdZtxe1ecpI0UCQHzGk5bA8LPbsiwxc8LjwYjkMbY,9459
+kaggle/models/model_update_request.py,sha256=i5UlKOOOMZtKRRFAwyhwzau7mmEjL3ILMxGxBBeF6vE,8500
+kaggle/models/result.py,sha256=Ft9gyP74_pZw4m1yctbKA-P0ODxBEDZLM7rUBTgUGOs,2299
+kaggle/models/start_blob_upload_request.py,sha256=kpX3VHqpMuUoK8ADl5akbA88TElvrqGB4HtBLBRgKpQ,7248
+kaggle/models/start_blob_upload_response.py,sha256=AQa8O3ICtnhxL_OcyJ25e8RwTiS4dkxtXhgVF_1u3Tw,4230
+kaggle/models/upload_file.py,sha256=TxF2sJeEUBFzY7uVRZltGIxjZNiQA5L226q5vwpkqw4,4843
+kaggle/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+kaggle/test/test_authenticate.py,sha256=M2-aeVQd8pPgUHYNUMLs5RiQrjc0G-k3pJfztcAS5L4,1186
+kaggle-1.6.12.dist-info/LICENSE,sha256=IzhfWrxIlFAci3lzZCg5WWHZJBI1QSzsHxO0plI63vk,11541
+kaggle-1.6.12.dist-info/METADATA,sha256=3RoyqCScxdQl7TLcQvarCnKDmx776pQXHgddT9k6_w0,819
+kaggle-1.6.12.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
+kaggle-1.6.12.dist-info/entry_points.txt,sha256=hl3dQAS4qpVkC5FWiqfnU6qKJU924OEN6PsHEiw-FGg,43
+kaggle-1.6.12.dist-info/top_level.txt,sha256=Y-2G5HyGxBsP1wVIKTJK5OB4qbSlC-MaA8-PjkezJzo,7
+kaggle-1.6.12.dist-info/INSTALLER,sha256=4EobgVZEtoZym__e-MIhNYRUXcWFMMbrrt6xRpKyZoQ,12
+kaggle-1.6.12.dist-info/RECORD,,
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/WHEEL b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..bab98d675883cc7567a79df485cd7b4f015e376f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.43.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/entry_points.txt b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/entry_points.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8548c5c1cb604cb6ab7020d0a31560881107e59e
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/entry_points.txt
@@ -0,0 +1,2 @@
+[console_scripts]
+kaggle = kaggle.cli:main
diff --git a/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/top_level.txt b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..83ef4c0e57a17105ed73af28db23cedf609db366
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle-1.6.12.dist-info/top_level.txt
@@ -0,0 +1 @@
+kaggle
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__init__.py b/.venv/lib/python3.10/site-packages/kaggle/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..94a24d94ddee9db47db4cf9acd5bd6f4fc6ffb19
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/__init__.py
@@ -0,0 +1,7 @@
+# coding=utf-8
+from __future__ import absolute_import
+from kaggle.api.kaggle_api_extended import KaggleApi
+from kaggle.api_client import ApiClient
+
+api = KaggleApi(ApiClient())
+api.authenticate()
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4e71adc458dbce4676bbc75929de19d9bed51865
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__pycache__/api_client.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/api_client.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..424d1cc6fc0059a8b5f62b2e92f8d7302ecb88d3
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/api_client.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__pycache__/cli.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/cli.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e8e709700a782e6457f1b77fff5ba9824d927722
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/cli.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__pycache__/configuration.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/configuration.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1945195cfe7fbca4143a0055f045a4fd8d166e41
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/configuration.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/__pycache__/rest.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/rest.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b244624d0f6e81ca64285e4909ca3671ef1bf81e
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/__pycache__/rest.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/__init__.py b/.venv/lib/python3.10/site-packages/kaggle/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bf68816d5a9b85836d9edcbda1deb1a53429ea4
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/api/__init__.py
@@ -0,0 +1,6 @@
+from __future__ import absolute_import
+
+# flake8: noqa
+
+# import apis into api package
+from kaggle.api.kaggle_api_extended import KaggleApi
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f86fef3c12a6e035dd78e7afe95818310b8f7695
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..22dbab2bdcfe38128a0b4d4be5644ebe62ba05a6
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api_extended.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api_extended.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..926077cf2e5f033224797c73db7ab37898f6726f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/api/__pycache__/kaggle_api_extended.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api.py b/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3c050747ec341c57f68845411e2f91209e05233
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api.py
@@ -0,0 +1,4241 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+from __future__ import absolute_import
+
+import re # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+
+from kaggle.api_client import ApiClient
+
+
+class KaggleApi(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ Ref: https://github.com/swagger-api/swagger-codegen
+ """
+
+ def __init__(self, api_client=None):
+ if api_client is None:
+ api_client = ApiClient()
+ self.api_client = api_client
+
+ def competition_download_leaderboard(self, id, **kwargs): # noqa: E501
+ """Download competition leaderboard # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competition_download_leaderboard(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competition_download_leaderboard_with_http_info(id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competition_download_leaderboard_with_http_info(id, **kwargs) # noqa: E501
+ return data
+
+ def competition_download_leaderboard_with_http_info(self, id, **kwargs): # noqa: E501
+ """Download competition leaderboard # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competition_download_leaderboard_with_http_info(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competition_download_leaderboard" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competition_download_leaderboard`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/{id}/leaderboard/download', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competition_view_leaderboard(self, id, **kwargs): # noqa: E501
+ """VIew competition leaderboard # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competition_view_leaderboard(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competition_view_leaderboard_with_http_info(id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competition_view_leaderboard_with_http_info(id, **kwargs) # noqa: E501
+ return data
+
+ def competition_view_leaderboard_with_http_info(self, id, **kwargs): # noqa: E501
+ """VIew competition leaderboard # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competition_view_leaderboard_with_http_info(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competition_view_leaderboard" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competition_view_leaderboard`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/{id}/leaderboard/view', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_data_download_file(self, id, file_name, **kwargs): # noqa: E501
+ """Download competition data file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_download_file(id, file_name, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :param str file_name: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_data_download_file_with_http_info(id, file_name, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_data_download_file_with_http_info(id, file_name, **kwargs) # noqa: E501
+ return data
+
+ def competitions_data_download_file_with_http_info(self, id, file_name, **kwargs): # noqa: E501
+ """Download competition data file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_download_file_with_http_info(id, file_name, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :param str file_name: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id', 'file_name'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_data_download_file" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_data_download_file`") # noqa: E501
+ # verify the required parameter 'file_name' is set
+ if ('file_name' not in params or
+ params['file_name'] is None):
+ raise ValueError("Missing the required parameter `file_name` when calling `competitions_data_download_file`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+ if 'file_name' in params:
+ path_params['fileName'] = params['file_name'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/data/download/{id}/{fileName}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_data_download_files(self, id, **kwargs): # noqa: E501
+ """Download all competition data files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_download_files(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_data_download_files_with_http_info(id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_data_download_files_with_http_info(id, **kwargs) # noqa: E501
+ return data
+
+ def competitions_data_download_files_with_http_info(self, id, **kwargs): # noqa: E501
+ """Download all competition data files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_download_files_with_http_info(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_data_download_files" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_data_download_files`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/data/download-all/{id}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_data_list_files(self, id, **kwargs): # noqa: E501
+ """List competition data files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_list_files(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_data_list_files_with_http_info(id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_data_list_files_with_http_info(id, **kwargs) # noqa: E501
+ return data
+
+ def competitions_data_list_files_with_http_info(self, id, **kwargs): # noqa: E501
+ """List competition data files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_data_list_files_with_http_info(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_data_list_files" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_data_list_files`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/data/list/{id}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_list(self, **kwargs): # noqa: E501
+ """List competitions # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_list(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str group: Filter competitions by a particular group
+ :param str category: Filter competitions by a particular category
+ :param str sort_by: Sort the results
+ :param int page: Page number
+ :param str search: Search terms
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_list_with_http_info(**kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_list_with_http_info(**kwargs) # noqa: E501
+ return data
+
+ def competitions_list_with_http_info(self, **kwargs): # noqa: E501
+ """List competitions # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_list_with_http_info(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str group: Filter competitions by a particular group
+ :param str category: Filter competitions by a particular category
+ :param str sort_by: Sort the results
+ :param int page: Page number
+ :param str search: Search terms
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['group', 'category', 'sort_by', 'page', 'search'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_list" % key
+ )
+ params[key] = val
+ del params['kwargs']
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'group' in params:
+ query_params.append(('group', params['group'])) # noqa: E501
+ if 'category' in params:
+ query_params.append(('category', params['category'])) # noqa: E501
+ if 'sort_by' in params:
+ query_params.append(('sortBy', params['sort_by'])) # noqa: E501
+ if 'page' in params:
+ query_params.append(('page', params['page'])) # noqa: E501
+ if 'search' in params:
+ query_params.append(('search', params['search'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/list', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_submissions_list(self, id, **kwargs): # noqa: E501
+ """List competition submissions # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_list(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :param int page: Page number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_submissions_list_with_http_info(id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_submissions_list_with_http_info(id, **kwargs) # noqa: E501
+ return data
+
+ def competitions_submissions_list_with_http_info(self, id, **kwargs): # noqa: E501
+ """List competition submissions # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_list_with_http_info(id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name (required)
+ :param int page: Page number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id', 'page'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_submissions_list" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_submissions_list`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+ if 'page' in params:
+ query_params.append(('page', params['page'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/submissions/list/{id}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_submissions_submit(self, blob_file_tokens, submission_description, id, **kwargs): # noqa: E501
+ """Submit to competition # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_submit(blob_file_tokens, submission_description, id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str blob_file_tokens: Token identifying location of uploaded submission file (required)
+ :param str submission_description: Description of competition submission (required)
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_submissions_submit_with_http_info(blob_file_tokens, submission_description, id, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_submissions_submit_with_http_info(blob_file_tokens, submission_description, id, **kwargs) # noqa: E501
+ return data
+
+ def competitions_submissions_submit_with_http_info(self, blob_file_tokens, submission_description, id, **kwargs): # noqa: E501
+ """Submit to competition # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_submit_with_http_info(blob_file_tokens, submission_description, id, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str blob_file_tokens: Token identifying location of uploaded submission file (required)
+ :param str submission_description: Description of competition submission (required)
+ :param str id: Competition name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['blob_file_tokens', 'submission_description', 'id'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_submissions_submit" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'blob_file_tokens' is set
+ if ('blob_file_tokens' not in params or
+ params['blob_file_tokens'] is None):
+ raise ValueError("Missing the required parameter `blob_file_tokens` when calling `competitions_submissions_submit`") # noqa: E501
+ # verify the required parameter 'submission_description' is set
+ if ('submission_description' not in params or
+ params['submission_description'] is None):
+ raise ValueError("Missing the required parameter `submission_description` when calling `competitions_submissions_submit`") # noqa: E501
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_submissions_submit`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+ if 'blob_file_tokens' in params:
+ form_params.append(('blobFileTokens', params['blob_file_tokens'])) # noqa: E501
+ if 'submission_description' in params:
+ form_params.append(('submissionDescription', params['submission_description'])) # noqa: E501
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['multipart/form-data']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/submissions/submit/{id}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_submissions_upload(self, file, guid, content_length, last_modified_date_utc, **kwargs): # noqa: E501
+ """Upload competition submission file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_upload(file, guid, content_length, last_modified_date_utc, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param file file: Competition submission file (required)
+ :param str guid: Location where submission should be uploaded (required)
+ :param int content_length: Content length of file in bytes (required)
+ :param int last_modified_date_utc: Last modified date of file in seconds since epoch in UTC (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_submissions_upload_with_http_info(file, guid, content_length, last_modified_date_utc, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_submissions_upload_with_http_info(file, guid, content_length, last_modified_date_utc, **kwargs) # noqa: E501
+ return data
+
+ def competitions_submissions_upload_with_http_info(self, file, guid, content_length, last_modified_date_utc, **kwargs): # noqa: E501
+ """Upload competition submission file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_upload_with_http_info(file, guid, content_length, last_modified_date_utc, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param file file: Competition submission file (required)
+ :param str guid: Location where submission should be uploaded (required)
+ :param int content_length: Content length of file in bytes (required)
+ :param int last_modified_date_utc: Last modified date of file in seconds since epoch in UTC (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['file', 'guid', 'content_length', 'last_modified_date_utc'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_submissions_upload" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'file' is set
+ if ('file' not in params or
+ params['file'] is None):
+ raise ValueError("Missing the required parameter `file` when calling `competitions_submissions_upload`") # noqa: E501
+ # verify the required parameter 'guid' is set
+ if ('guid' not in params or
+ params['guid'] is None):
+ raise ValueError("Missing the required parameter `guid` when calling `competitions_submissions_upload`") # noqa: E501
+ # verify the required parameter 'content_length' is set
+ if ('content_length' not in params or
+ params['content_length'] is None):
+ raise ValueError("Missing the required parameter `content_length` when calling `competitions_submissions_upload`") # noqa: E501
+ # verify the required parameter 'last_modified_date_utc' is set
+ if ('last_modified_date_utc' not in params or
+ params['last_modified_date_utc'] is None):
+ raise ValueError("Missing the required parameter `last_modified_date_utc` when calling `competitions_submissions_upload`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'guid' in params:
+ path_params['guid'] = params['guid'] # noqa: E501
+ if 'content_length' in params:
+ path_params['contentLength'] = params['content_length'] # noqa: E501
+ if 'last_modified_date_utc' in params:
+ path_params['lastModifiedDateUtc'] = params['last_modified_date_utc'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+ if 'file' in params:
+ local_var_files['file'] = params['file'] # noqa: E501
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['multipart/form-data']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/submissions/upload/{guid}/{contentLength}/{lastModifiedDateUtc}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def competitions_submissions_url(self, id, content_length, last_modified_date_utc, **kwargs): # noqa: E501
+ """Generate competition submission URL # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_url(id, content_length, last_modified_date_utc, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name, as it appears in the competition's URL (required)
+ :param int content_length: Content length of file in bytes (required)
+ :param int last_modified_date_utc: Last modified date of file in seconds since epoch in UTC (required)
+ :param str file_name: Competition submission file name
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.competitions_submissions_url_with_http_info(id, content_length, last_modified_date_utc, **kwargs) # noqa: E501
+ else:
+ (data) = self.competitions_submissions_url_with_http_info(id, content_length, last_modified_date_utc, **kwargs) # noqa: E501
+ return data
+
+ def competitions_submissions_url_with_http_info(self, id, content_length, last_modified_date_utc, **kwargs): # noqa: E501
+ """Generate competition submission URL # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.competitions_submissions_url_with_http_info(id, content_length, last_modified_date_utc, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str id: Competition name, as it appears in the competition's URL (required)
+ :param int content_length: Content length of file in bytes (required)
+ :param int last_modified_date_utc: Last modified date of file in seconds since epoch in UTC (required)
+ :param str file_name: Competition submission file name
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id', 'content_length', 'last_modified_date_utc', 'file_name'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method competitions_submissions_url" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `competitions_submissions_url`") # noqa: E501
+ # verify the required parameter 'content_length' is set
+ if ('content_length' not in params or
+ params['content_length'] is None):
+ raise ValueError("Missing the required parameter `content_length` when calling `competitions_submissions_url`") # noqa: E501
+ # verify the required parameter 'last_modified_date_utc' is set
+ if ('last_modified_date_utc' not in params or
+ params['last_modified_date_utc'] is None):
+ raise ValueError("Missing the required parameter `last_modified_date_utc` when calling `competitions_submissions_url`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+ if 'content_length' in params:
+ path_params['contentLength'] = params['content_length'] # noqa: E501
+ if 'last_modified_date_utc' in params:
+ path_params['lastModifiedDateUtc'] = params['last_modified_date_utc'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+ if 'file_name' in params:
+ form_params.append(('fileName', params['file_name'])) # noqa: E501
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['multipart/form-data']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/competitions/{id}/submissions/url/{contentLength}/{lastModifiedDateUtc}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def create_inbox_file(self, create_inbox_file_request, **kwargs): # noqa: E501
+ """Creates (aka \"drops\") a new file into the inbox. # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.create_inbox_file(create_inbox_file_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param CreateInboxFileRequest create_inbox_file_request: (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.create_inbox_file_with_http_info(create_inbox_file_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.create_inbox_file_with_http_info(create_inbox_file_request, **kwargs) # noqa: E501
+ return data
+
+ def create_inbox_file_with_http_info(self, create_inbox_file_request, **kwargs): # noqa: E501
+ """Creates (aka \"drops\") a new file into the inbox. # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.create_inbox_file_with_http_info(create_inbox_file_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param CreateInboxFileRequest create_inbox_file_request: (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['create_inbox_file_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method create_inbox_file" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'create_inbox_file_request' is set
+ if ('create_inbox_file_request' not in params or
+ params['create_inbox_file_request'] is None):
+ raise ValueError("Missing the required parameter `create_inbox_file_request` when calling `create_inbox_file`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'create_inbox_file_request' in params:
+ body_params = params['create_inbox_file_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/inbox/files/create', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_create_new(self, dataset_new_request, **kwargs): # noqa: E501
+ """Create a new dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_new(dataset_new_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param DatasetNewRequest dataset_new_request: Information for creating a new dataset (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_create_new_with_http_info(dataset_new_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_create_new_with_http_info(dataset_new_request, **kwargs) # noqa: E501
+ return data
+
+ def datasets_create_new_with_http_info(self, dataset_new_request, **kwargs): # noqa: E501
+ """Create a new dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_new_with_http_info(dataset_new_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param DatasetNewRequest dataset_new_request: Information for creating a new dataset (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['dataset_new_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_create_new" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'dataset_new_request' is set
+ if ('dataset_new_request' not in params or
+ params['dataset_new_request'] is None):
+ raise ValueError("Missing the required parameter `dataset_new_request` when calling `datasets_create_new`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'dataset_new_request' in params:
+ body_params = params['dataset_new_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/create/new', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_create_version(self, owner_slug, dataset_slug, dataset_new_version_request, **kwargs): # noqa: E501
+ """Create a new dataset version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_version(owner_slug, dataset_slug, dataset_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param DatasetNewVersionRequest dataset_new_version_request: Information for creating a new dataset version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_create_version_with_http_info(owner_slug, dataset_slug, dataset_new_version_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_create_version_with_http_info(owner_slug, dataset_slug, dataset_new_version_request, **kwargs) # noqa: E501
+ return data
+
+ def datasets_create_version_with_http_info(self, owner_slug, dataset_slug, dataset_new_version_request, **kwargs): # noqa: E501
+ """Create a new dataset version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_version_with_http_info(owner_slug, dataset_slug, dataset_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param DatasetNewVersionRequest dataset_new_version_request: Information for creating a new dataset version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug', 'dataset_new_version_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_create_version" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `datasets_create_version`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `datasets_create_version`") # noqa: E501
+ # verify the required parameter 'dataset_new_version_request' is set
+ if ('dataset_new_version_request' not in params or
+ params['dataset_new_version_request'] is None):
+ raise ValueError("Missing the required parameter `dataset_new_version_request` when calling `datasets_create_version`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'dataset_new_version_request' in params:
+ body_params = params['dataset_new_version_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/create/version/{ownerSlug}/{datasetSlug}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_create_version_by_id(self, id, dataset_new_version_request, **kwargs): # noqa: E501
+ """Create a new dataset version by id # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_version_by_id(id, dataset_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param int id: Dataset ID (required)
+ :param DatasetNewVersionRequest dataset_new_version_request: Information for creating a new dataset version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_create_version_by_id_with_http_info(id, dataset_new_version_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_create_version_by_id_with_http_info(id, dataset_new_version_request, **kwargs) # noqa: E501
+ return data
+
+ def datasets_create_version_by_id_with_http_info(self, id, dataset_new_version_request, **kwargs): # noqa: E501
+ """Create a new dataset version by id # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_create_version_by_id_with_http_info(id, dataset_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param int id: Dataset ID (required)
+ :param DatasetNewVersionRequest dataset_new_version_request: Information for creating a new dataset version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['id', 'dataset_new_version_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_create_version_by_id" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'id' is set
+ if ('id' not in params or
+ params['id'] is None):
+ raise ValueError("Missing the required parameter `id` when calling `datasets_create_version_by_id`") # noqa: E501
+ # verify the required parameter 'dataset_new_version_request' is set
+ if ('dataset_new_version_request' not in params or
+ params['dataset_new_version_request'] is None):
+ raise ValueError("Missing the required parameter `dataset_new_version_request` when calling `datasets_create_version_by_id`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'id' in params:
+ path_params['id'] = params['id'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'dataset_new_version_request' in params:
+ body_params = params['dataset_new_version_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/create/version/{id}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_download(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Download dataset file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_download(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_download_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_download_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ return data
+
+ def datasets_download_with_http_info(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Download dataset file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_download_with_http_info(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug', 'dataset_version_number'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_download" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `datasets_download`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `datasets_download`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+ if 'dataset_version_number' in params:
+ query_params.append(('datasetVersionNumber', params['dataset_version_number'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['file']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/download/{ownerSlug}/{datasetSlug}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_download_file(self, owner_slug, dataset_slug, file_name, **kwargs): # noqa: E501
+ """Download dataset file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_download_file(owner_slug, dataset_slug, file_name, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str file_name: File name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_download_file_with_http_info(owner_slug, dataset_slug, file_name, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_download_file_with_http_info(owner_slug, dataset_slug, file_name, **kwargs) # noqa: E501
+ return data
+
+ def datasets_download_file_with_http_info(self, owner_slug, dataset_slug, file_name, **kwargs): # noqa: E501
+ """Download dataset file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_download_file_with_http_info(owner_slug, dataset_slug, file_name, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str file_name: File name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug', 'file_name', 'dataset_version_number'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_download_file" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `datasets_download_file`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `datasets_download_file`") # noqa: E501
+ # verify the required parameter 'file_name' is set
+ if ('file_name' not in params or
+ params['file_name'] is None):
+ raise ValueError("Missing the required parameter `file_name` when calling `datasets_download_file`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+ if 'file_name' in params:
+ path_params['fileName'] = params['file_name'] # noqa: E501
+
+ query_params = []
+ if 'dataset_version_number' in params:
+ query_params.append(('datasetVersionNumber', params['dataset_version_number'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['file']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/download/{ownerSlug}/{datasetSlug}/{fileName}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_list(self, **kwargs): # noqa: E501
+ """List datasets # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_list(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str group: Display datasets by a particular group
+ :param str sort_by: Sort the results
+ :param str size: (DEPRECATED). Please use --max-size and --min-size to filter dataset sizes.
+ :param str filetype: Display datasets of a specific file type
+ :param str license: Display datasets with a specific license
+ :param str tagids: A comma separated list of tags to filter by
+ :param str search: Search terms
+ :param str user: Display datasets by a specific user or organization
+ :param int page: Page number
+ :param int max_size: Max Dataset Size (bytes)
+ :param int min_size: Max Dataset Size (bytes)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_list_with_http_info(**kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_list_with_http_info(**kwargs) # noqa: E501
+ return data
+
+ def datasets_list_with_http_info(self, **kwargs): # noqa: E501
+ """List datasets # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_list_with_http_info(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str group: Display datasets by a particular group
+ :param str sort_by: Sort the results
+ :param str size: (DEPRECATED). Please use --max-size and --min-size to filter dataset sizes.
+ :param str filetype: Display datasets of a specific file type
+ :param str license: Display datasets with a specific license
+ :param str tagids: A comma separated list of tags to filter by
+ :param str search: Search terms
+ :param str user: Display datasets by a specific user or organization
+ :param int page: Page number
+ :param int max_size: Max Dataset Size (bytes)
+ :param int min_size: Max Dataset Size (bytes)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['group', 'sort_by', 'size', 'filetype', 'license', 'tagids', 'search', 'user', 'page', 'max_size', 'min_size'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_list" % key
+ )
+ params[key] = val
+ del params['kwargs']
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'group' in params:
+ query_params.append(('group', params['group'])) # noqa: E501
+ if 'sort_by' in params:
+ query_params.append(('sortBy', params['sort_by'])) # noqa: E501
+ if 'size' in params:
+ query_params.append(('size', params['size'])) # noqa: E501
+ if 'filetype' in params:
+ query_params.append(('filetype', params['filetype'])) # noqa: E501
+ if 'license' in params:
+ query_params.append(('license', params['license'])) # noqa: E501
+ if 'tagids' in params:
+ query_params.append(('tagids', params['tagids'])) # noqa: E501
+ if 'search' in params:
+ query_params.append(('search', params['search'])) # noqa: E501
+ if 'user' in params:
+ query_params.append(('user', params['user'])) # noqa: E501
+ if 'page' in params:
+ query_params.append(('page', params['page'])) # noqa: E501
+ if 'max_size' in params:
+ query_params.append(('maxSize', params['max_size'])) # noqa: E501
+ if 'min_size' in params:
+ query_params.append(('minSize', params['min_size'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/list', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_list_files(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """List dataset files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_list_files(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_list_files_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_list_files_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ return data
+
+ def datasets_list_files_with_http_info(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """List dataset files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_list_files_with_http_info(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param str dataset_version_number: Dataset version number
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug', 'dataset_version_number'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_list_files" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `datasets_list_files`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `datasets_list_files`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+ if 'dataset_version_number' in params:
+ query_params.append(('datasetVersionNumber', params['dataset_version_number'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/list/{ownerSlug}/{datasetSlug}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def datasets_status(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Get dataset creation status # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_status(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.datasets_status_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.datasets_status_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ return data
+
+ def datasets_status_with_http_info(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Get dataset creation status # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.datasets_status_with_http_info(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method datasets_status" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `datasets_status`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `datasets_status`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/status/{ownerSlug}/{datasetSlug}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def delete_model(self, owner_slug, model_slug, **kwargs): # noqa: E501
+ """Delete a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model(owner_slug, model_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.delete_model_with_http_info(owner_slug, model_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.delete_model_with_http_info(owner_slug, model_slug, **kwargs) # noqa: E501
+ return data
+
+ def delete_model_with_http_info(self, owner_slug, model_slug, **kwargs): # noqa: E501
+ """Delete a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model_with_http_info(owner_slug, model_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method delete_model" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `delete_model`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `delete_model`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/delete', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def delete_model_instance(self, owner_slug, model_slug, framework, instance_slug, **kwargs): # noqa: E501
+ """Delete a model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model_instance(owner_slug, model_slug, framework, instance_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.delete_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.delete_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, **kwargs) # noqa: E501
+ return data
+
+ def delete_model_instance_with_http_info(self, owner_slug, model_slug, framework, instance_slug, **kwargs): # noqa: E501
+ """Delete a model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method delete_model_instance" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `delete_model_instance`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `delete_model_instance`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `delete_model_instance`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `delete_model_instance`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/delete', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def delete_model_instance_version(self, owner_slug, model_slug, framework, instance_slug, version_number, **kwargs): # noqa: E501
+ """Delete a model instance version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model_instance_version(owner_slug, model_slug, framework, instance_slug, version_number, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param str version_number: Model instance version number (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.delete_model_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, **kwargs) # noqa: E501
+ else:
+ (data) = self.delete_model_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, **kwargs) # noqa: E501
+ return data
+
+ def delete_model_instance_version_with_http_info(self, owner_slug, model_slug, framework, instance_slug, version_number, **kwargs): # noqa: E501
+ """Delete a model instance version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.delete_model_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param str version_number: Model instance version number (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug', 'version_number'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method delete_model_instance_version" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `delete_model_instance_version`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `delete_model_instance_version`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `delete_model_instance_version`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `delete_model_instance_version`") # noqa: E501
+ # verify the required parameter 'version_number' is set
+ if ('version_number' not in params or
+ params['version_number'] is None):
+ raise ValueError("Missing the required parameter `version_number` when calling `delete_model_instance_version`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+ if 'version_number' in params:
+ path_params['versionNumber'] = params['version_number'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/{versionNumber}/delete', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def get_model(self, owner_slug, model_slug, **kwargs): # noqa: E501
+ """Get a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.get_model(owner_slug, model_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.get_model_with_http_info(owner_slug, model_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.get_model_with_http_info(owner_slug, model_slug, **kwargs) # noqa: E501
+ return data
+
+ def get_model_with_http_info(self, owner_slug, model_slug, **kwargs): # noqa: E501
+ """Get a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.get_model_with_http_info(owner_slug, model_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method get_model" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `get_model`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `get_model`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/get', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def get_model_instance(self, owner_slug, model_slug, framework, instance_slug, **kwargs): # noqa: E501
+ """Get a model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.get_model_instance(owner_slug, model_slug, framework, instance_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.get_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.get_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, **kwargs) # noqa: E501
+ return data
+
+ def get_model_instance_with_http_info(self, owner_slug, model_slug, framework, instance_slug, **kwargs): # noqa: E501
+ """Get a model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.get_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method get_model_instance" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `get_model_instance`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `get_model_instance`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `get_model_instance`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `get_model_instance`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/get', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def kernel_output(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Download the latest output from a kernel # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_output(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.kernel_output_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.kernel_output_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ return data
+
+ def kernel_output_with_http_info(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Download the latest output from a kernel # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_output_with_http_info(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['user_name', 'kernel_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method kernel_output" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'user_name' is set
+ if ('user_name' not in params or
+ params['user_name'] is None):
+ raise ValueError("Missing the required parameter `user_name` when calling `kernel_output`") # noqa: E501
+ # verify the required parameter 'kernel_slug' is set
+ if ('kernel_slug' not in params or
+ params['kernel_slug'] is None):
+ raise ValueError("Missing the required parameter `kernel_slug` when calling `kernel_output`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'user_name' in params:
+ query_params.append(('userName', params['user_name'])) # noqa: E501
+ if 'kernel_slug' in params:
+ query_params.append(('kernelSlug', params['kernel_slug'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/kernels/output', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def kernel_pull(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Pull the latest code from a kernel # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_pull(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.kernel_pull_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.kernel_pull_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ return data
+
+ def kernel_pull_with_http_info(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Pull the latest code from a kernel # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_pull_with_http_info(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['user_name', 'kernel_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method kernel_pull" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'user_name' is set
+ if ('user_name' not in params or
+ params['user_name'] is None):
+ raise ValueError("Missing the required parameter `user_name` when calling `kernel_pull`") # noqa: E501
+ # verify the required parameter 'kernel_slug' is set
+ if ('kernel_slug' not in params or
+ params['kernel_slug'] is None):
+ raise ValueError("Missing the required parameter `kernel_slug` when calling `kernel_pull`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'user_name' in params:
+ query_params.append(('userName', params['user_name'])) # noqa: E501
+ if 'kernel_slug' in params:
+ query_params.append(('kernelSlug', params['kernel_slug'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/kernels/pull', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def kernel_push(self, kernel_push_request, **kwargs): # noqa: E501
+ """Push a new kernel version. Can be used to create a new kernel and update an existing one. # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_push(kernel_push_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param KernelPushRequest kernel_push_request: Information for pushing a new kernel version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.kernel_push_with_http_info(kernel_push_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.kernel_push_with_http_info(kernel_push_request, **kwargs) # noqa: E501
+ return data
+
+ def kernel_push_with_http_info(self, kernel_push_request, **kwargs): # noqa: E501
+ """Push a new kernel version. Can be used to create a new kernel and update an existing one. # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_push_with_http_info(kernel_push_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param KernelPushRequest kernel_push_request: Information for pushing a new kernel version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['kernel_push_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method kernel_push" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'kernel_push_request' is set
+ if ('kernel_push_request' not in params or
+ params['kernel_push_request'] is None):
+ raise ValueError("Missing the required parameter `kernel_push_request` when calling `kernel_push`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'kernel_push_request' in params:
+ body_params = params['kernel_push_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/kernels/push', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def kernel_status(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Get the status of the latest kernel version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_status(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.kernel_status_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.kernel_status_with_http_info(user_name, kernel_slug, **kwargs) # noqa: E501
+ return data
+
+ def kernel_status_with_http_info(self, user_name, kernel_slug, **kwargs): # noqa: E501
+ """Get the status of the latest kernel version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernel_status_with_http_info(user_name, kernel_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str user_name: Kernel owner (required)
+ :param str kernel_slug: Kernel name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['user_name', 'kernel_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method kernel_status" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'user_name' is set
+ if ('user_name' not in params or
+ params['user_name'] is None):
+ raise ValueError("Missing the required parameter `user_name` when calling `kernel_status`") # noqa: E501
+ # verify the required parameter 'kernel_slug' is set
+ if ('kernel_slug' not in params or
+ params['kernel_slug'] is None):
+ raise ValueError("Missing the required parameter `kernel_slug` when calling `kernel_status`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'user_name' in params:
+ query_params.append(('userName', params['user_name'])) # noqa: E501
+ if 'kernel_slug' in params:
+ query_params.append(('kernelSlug', params['kernel_slug'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/kernels/status', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def kernels_list(self, **kwargs): # noqa: E501
+ """List kernels # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernels_list(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param int page: Page number
+ :param int page_size: Page size
+ :param str search: Search terms
+ :param str group: Display only your kernels
+ :param str user: Display kernels by a particular group
+ :param str language: Display kernels in a specific language
+ :param str kernel_type: Display kernels of a specific type
+ :param str output_type: Display kernels with a specific output type
+ :param str sort_by: Sort the results. 'relevance' only works if there is a search query
+ :param str dataset: Display kernels using the specified dataset
+ :param str competition: Display kernels using the specified competition
+ :param str parent_kernel: Display kernels that have forked the specified kernel
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.kernels_list_with_http_info(**kwargs) # noqa: E501
+ else:
+ (data) = self.kernels_list_with_http_info(**kwargs) # noqa: E501
+ return data
+
+ def kernels_list_with_http_info(self, **kwargs): # noqa: E501
+ """List kernels # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.kernels_list_with_http_info(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param int page: Page number
+ :param int page_size: Page size
+ :param str search: Search terms
+ :param str group: Display only your kernels
+ :param str user: Display kernels by a particular group
+ :param str language: Display kernels in a specific language
+ :param str kernel_type: Display kernels of a specific type
+ :param str output_type: Display kernels with a specific output type
+ :param str sort_by: Sort the results. 'relevance' only works if there is a search query
+ :param str dataset: Display kernels using the specified dataset
+ :param str competition: Display kernels using the specified competition
+ :param str parent_kernel: Display kernels that have forked the specified kernel
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['page', 'page_size', 'search', 'group', 'user', 'language', 'kernel_type', 'output_type', 'sort_by', 'dataset', 'competition', 'parent_kernel'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method kernels_list" % key
+ )
+ params[key] = val
+ del params['kwargs']
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'page' in params:
+ query_params.append(('page', params['page'])) # noqa: E501
+ if 'page_size' in params:
+ query_params.append(('pageSize', params['page_size'])) # noqa: E501
+ if 'search' in params:
+ query_params.append(('search', params['search'])) # noqa: E501
+ if 'group' in params:
+ query_params.append(('group', params['group'])) # noqa: E501
+ if 'user' in params:
+ query_params.append(('user', params['user'])) # noqa: E501
+ if 'language' in params:
+ query_params.append(('language', params['language'])) # noqa: E501
+ if 'kernel_type' in params:
+ query_params.append(('kernelType', params['kernel_type'])) # noqa: E501
+ if 'output_type' in params:
+ query_params.append(('outputType', params['output_type'])) # noqa: E501
+ if 'sort_by' in params:
+ query_params.append(('sortBy', params['sort_by'])) # noqa: E501
+ if 'dataset' in params:
+ query_params.append(('dataset', params['dataset'])) # noqa: E501
+ if 'competition' in params:
+ query_params.append(('competition', params['competition'])) # noqa: E501
+ if 'parent_kernel' in params:
+ query_params.append(('parentKernel', params['parent_kernel'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/kernels/list', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def metadata_get(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Get the metadata for a dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.metadata_get(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.metadata_get_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ else:
+ (data) = self.metadata_get_with_http_info(owner_slug, dataset_slug, **kwargs) # noqa: E501
+ return data
+
+ def metadata_get_with_http_info(self, owner_slug, dataset_slug, **kwargs): # noqa: E501
+ """Get the metadata for a dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.metadata_get_with_http_info(owner_slug, dataset_slug, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method metadata_get" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `metadata_get`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `metadata_get`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/metadata/{ownerSlug}/{datasetSlug}', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def metadata_post(self, owner_slug, dataset_slug, settings, **kwargs): # noqa: E501
+ """Update the metadata for a dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.metadata_post(owner_slug, dataset_slug, settings, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param DatasetUpdateSettingsRequest settings: Dataset metadata to update (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.metadata_post_with_http_info(owner_slug, dataset_slug, settings, **kwargs) # noqa: E501
+ else:
+ (data) = self.metadata_post_with_http_info(owner_slug, dataset_slug, settings, **kwargs) # noqa: E501
+ return data
+
+ def metadata_post_with_http_info(self, owner_slug, dataset_slug, settings, **kwargs): # noqa: E501
+ """Update the metadata for a dataset # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.metadata_post_with_http_info(owner_slug, dataset_slug, settings, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Dataset owner (required)
+ :param str dataset_slug: Dataset name (required)
+ :param DatasetUpdateSettingsRequest settings: Dataset metadata to update (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'dataset_slug', 'settings'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method metadata_post" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `metadata_post`") # noqa: E501
+ # verify the required parameter 'dataset_slug' is set
+ if ('dataset_slug' not in params or
+ params['dataset_slug'] is None):
+ raise ValueError("Missing the required parameter `dataset_slug` when calling `metadata_post`") # noqa: E501
+ # verify the required parameter 'settings' is set
+ if ('settings' not in params or
+ params['settings'] is None):
+ raise ValueError("Missing the required parameter `settings` when calling `metadata_post`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'dataset_slug' in params:
+ path_params['datasetSlug'] = params['dataset_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'settings' in params:
+ body_params = params['settings']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/datasets/metadata/{ownerSlug}/{datasetSlug}', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def model_instance_versions_download(self, owner_slug, model_slug, framework, instance_slug, version_number, **kwargs): # noqa: E501
+ """Download model instance version files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.model_instance_versions_download(owner_slug, model_slug, framework, instance_slug, version_number, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param str version_number: Model instance version number (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.model_instance_versions_download_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, **kwargs) # noqa: E501
+ else:
+ (data) = self.model_instance_versions_download_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, **kwargs) # noqa: E501
+ return data
+
+ def model_instance_versions_download_with_http_info(self, owner_slug, model_slug, framework, instance_slug, version_number, **kwargs): # noqa: E501
+ """Download model instance version files # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.model_instance_versions_download_with_http_info(owner_slug, model_slug, framework, instance_slug, version_number, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param str version_number: Model instance version number (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug', 'version_number'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method model_instance_versions_download" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `model_instance_versions_download`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `model_instance_versions_download`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `model_instance_versions_download`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `model_instance_versions_download`") # noqa: E501
+ # verify the required parameter 'version_number' is set
+ if ('version_number' not in params or
+ params['version_number'] is None):
+ raise ValueError("Missing the required parameter `version_number` when calling `model_instance_versions_download`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+ if 'version_number' in params:
+ path_params['versionNumber'] = params['version_number'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['file']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/{versionNumber}/download', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def models_create_instance(self, owner_slug, model_slug, model_new_instance_request, **kwargs): # noqa: E501
+ """Create a new model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_instance(owner_slug, model_slug, model_new_instance_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model slug (required)
+ :param ModelNewInstanceRequest model_new_instance_request: Information for creating a new model instance (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.models_create_instance_with_http_info(owner_slug, model_slug, model_new_instance_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.models_create_instance_with_http_info(owner_slug, model_slug, model_new_instance_request, **kwargs) # noqa: E501
+ return data
+
+ def models_create_instance_with_http_info(self, owner_slug, model_slug, model_new_instance_request, **kwargs): # noqa: E501
+ """Create a new model instance # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_instance_with_http_info(owner_slug, model_slug, model_new_instance_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model slug (required)
+ :param ModelNewInstanceRequest model_new_instance_request: Information for creating a new model instance (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'model_new_instance_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method models_create_instance" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `models_create_instance`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `models_create_instance`") # noqa: E501
+ # verify the required parameter 'model_new_instance_request' is set
+ if ('model_new_instance_request' not in params or
+ params['model_new_instance_request'] is None):
+ raise ValueError("Missing the required parameter `model_new_instance_request` when calling `models_create_instance`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'model_new_instance_request' in params:
+ body_params = params['model_new_instance_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/create/instance', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def models_create_instance_version(self, owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, **kwargs): # noqa: E501
+ """Create a new model instance version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_instance_version(owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model slug (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param ModelInstanceNewVersionRequest model_instance_new_version_request: Information for creating a new model instance version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.models_create_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.models_create_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, **kwargs) # noqa: E501
+ return data
+
+ def models_create_instance_version_with_http_info(self, owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, **kwargs): # noqa: E501
+ """Create a new model instance version # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_instance_version_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_new_version_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model slug (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param ModelInstanceNewVersionRequest model_instance_new_version_request: Information for creating a new model instance version (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug', 'model_instance_new_version_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method models_create_instance_version" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `models_create_instance_version`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `models_create_instance_version`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `models_create_instance_version`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `models_create_instance_version`") # noqa: E501
+ # verify the required parameter 'model_instance_new_version_request' is set
+ if ('model_instance_new_version_request' not in params or
+ params['model_instance_new_version_request'] is None):
+ raise ValueError("Missing the required parameter `model_instance_new_version_request` when calling `models_create_instance_version`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'model_instance_new_version_request' in params:
+ body_params = params['model_instance_new_version_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/create/version', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def models_create_new(self, model_new_request, **kwargs): # noqa: E501
+ """Create a new model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_new(model_new_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param ModelNewRequest model_new_request: Information for creating a new model (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.models_create_new_with_http_info(model_new_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.models_create_new_with_http_info(model_new_request, **kwargs) # noqa: E501
+ return data
+
+ def models_create_new_with_http_info(self, model_new_request, **kwargs): # noqa: E501
+ """Create a new model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_create_new_with_http_info(model_new_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param ModelNewRequest model_new_request: Information for creating a new model (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['model_new_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method models_create_new" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'model_new_request' is set
+ if ('model_new_request' not in params or
+ params['model_new_request'] is None):
+ raise ValueError("Missing the required parameter `model_new_request` when calling `models_create_new`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'model_new_request' in params:
+ body_params = params['model_new_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/create/new', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def models_list(self, **kwargs): # noqa: E501
+ """Lists models # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_list(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str search: Search terms
+ :param str sort_by: Sort the results
+ :param str owner: Display models by a specific user or organization
+ :param int page_size: Page size
+ :param str page_token: Page token for pagination
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.models_list_with_http_info(**kwargs) # noqa: E501
+ else:
+ (data) = self.models_list_with_http_info(**kwargs) # noqa: E501
+ return data
+
+ def models_list_with_http_info(self, **kwargs): # noqa: E501
+ """Lists models # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.models_list_with_http_info(async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str search: Search terms
+ :param str sort_by: Sort the results
+ :param str owner: Display models by a specific user or organization
+ :param int page_size: Page size
+ :param str page_token: Page token for pagination
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['search', 'sort_by', 'owner', 'page_size', 'page_token'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method models_list" % key
+ )
+ params[key] = val
+ del params['kwargs']
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+ if 'search' in params:
+ query_params.append(('search', params['search'])) # noqa: E501
+ if 'sort_by' in params:
+ query_params.append(('sortBy', params['sort_by'])) # noqa: E501
+ if 'owner' in params:
+ query_params.append(('owner', params['owner'])) # noqa: E501
+ if 'page_size' in params:
+ query_params.append(('pageSize', params['page_size'])) # noqa: E501
+ if 'page_token' in params:
+ query_params.append(('pageToken', params['page_token'])) # noqa: E501
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/list', 'GET',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def update_model(self, owner_slug, model_slug, model_update_request, **kwargs): # noqa: E501
+ """Update a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.update_model(owner_slug, model_slug, model_update_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param ModelUpdateRequest model_update_request: Information for updating a model (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.update_model_with_http_info(owner_slug, model_slug, model_update_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.update_model_with_http_info(owner_slug, model_slug, model_update_request, **kwargs) # noqa: E501
+ return data
+
+ def update_model_with_http_info(self, owner_slug, model_slug, model_update_request, **kwargs): # noqa: E501
+ """Update a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.update_model_with_http_info(owner_slug, model_slug, model_update_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param ModelUpdateRequest model_update_request: Information for updating a model (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'model_update_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method update_model" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `update_model`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `update_model`") # noqa: E501
+ # verify the required parameter 'model_update_request' is set
+ if ('model_update_request' not in params or
+ params['model_update_request'] is None):
+ raise ValueError("Missing the required parameter `model_update_request` when calling `update_model`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'model_update_request' in params:
+ body_params = params['model_update_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/update', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def update_model_instance(self, owner_slug, model_slug, framework, instance_slug, model_instance_update_request, **kwargs): # noqa: E501
+ """Update a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.update_model_instance(owner_slug, model_slug, framework, instance_slug, model_instance_update_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param ModelInstanceUpdateRequest model_instance_update_request: Information for updating a model instance (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.update_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_update_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.update_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_update_request, **kwargs) # noqa: E501
+ return data
+
+ def update_model_instance_with_http_info(self, owner_slug, model_slug, framework, instance_slug, model_instance_update_request, **kwargs): # noqa: E501
+ """Update a model # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.update_model_instance_with_http_info(owner_slug, model_slug, framework, instance_slug, model_instance_update_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param str owner_slug: Model owner (required)
+ :param str model_slug: Model name (required)
+ :param str framework: Model instance framework (required)
+ :param str instance_slug: Model instance slug (required)
+ :param ModelInstanceUpdateRequest model_instance_update_request: Information for updating a model instance (required)
+ :return: Result
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['owner_slug', 'model_slug', 'framework', 'instance_slug', 'model_instance_update_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method update_model_instance" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'owner_slug' is set
+ if ('owner_slug' not in params or
+ params['owner_slug'] is None):
+ raise ValueError("Missing the required parameter `owner_slug` when calling `update_model_instance`") # noqa: E501
+ # verify the required parameter 'model_slug' is set
+ if ('model_slug' not in params or
+ params['model_slug'] is None):
+ raise ValueError("Missing the required parameter `model_slug` when calling `update_model_instance`") # noqa: E501
+ # verify the required parameter 'framework' is set
+ if ('framework' not in params or
+ params['framework'] is None):
+ raise ValueError("Missing the required parameter `framework` when calling `update_model_instance`") # noqa: E501
+ # verify the required parameter 'instance_slug' is set
+ if ('instance_slug' not in params or
+ params['instance_slug'] is None):
+ raise ValueError("Missing the required parameter `instance_slug` when calling `update_model_instance`") # noqa: E501
+ # verify the required parameter 'model_instance_update_request' is set
+ if ('model_instance_update_request' not in params or
+ params['model_instance_update_request'] is None):
+ raise ValueError("Missing the required parameter `model_instance_update_request` when calling `update_model_instance`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+ if 'owner_slug' in params:
+ path_params['ownerSlug'] = params['owner_slug'] # noqa: E501
+ if 'model_slug' in params:
+ path_params['modelSlug'] = params['model_slug'] # noqa: E501
+ if 'framework' in params:
+ path_params['framework'] = params['framework'] # noqa: E501
+ if 'instance_slug' in params:
+ path_params['instanceSlug'] = params['instance_slug'] # noqa: E501
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'model_instance_update_request' in params:
+ body_params = params['model_instance_update_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/models/{ownerSlug}/{modelSlug}/{framework}/{instanceSlug}/update', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='Result', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
+
+ def upload_file(self, start_blob_upload_request, **kwargs): # noqa: E501
+ """Start uploading a file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.upload_file(start_blob_upload_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param StartBlobUploadRequest start_blob_upload_request: (required)
+ :return: StartBlobUploadResponse
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+ kwargs['_return_http_data_only'] = True
+ if kwargs.get('async_req'):
+ return self.upload_file_with_http_info(start_blob_upload_request, **kwargs) # noqa: E501
+ else:
+ (data) = self.upload_file_with_http_info(start_blob_upload_request, **kwargs) # noqa: E501
+ return data
+
+ def upload_file_with_http_info(self, start_blob_upload_request, **kwargs): # noqa: E501
+ """Start uploading a file # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+ >>> thread = api.upload_file_with_http_info(start_blob_upload_request, async_req=True)
+ >>> result = thread.get()
+
+ :param async_req bool
+ :param StartBlobUploadRequest start_blob_upload_request: (required)
+ :return: StartBlobUploadResponse
+ If the method is called asynchronously,
+ returns the request thread.
+ """
+
+ all_params = ['start_blob_upload_request'] # noqa: E501
+ all_params.append('async_req')
+ all_params.append('_return_http_data_only')
+ all_params.append('_preload_content')
+ all_params.append('_request_timeout')
+
+ params = locals()
+ for key, val in six.iteritems(params['kwargs']):
+ if key not in all_params:
+ raise TypeError(
+ "Got an unexpected keyword argument '%s'"
+ " to method upload_file" % key
+ )
+ params[key] = val
+ del params['kwargs']
+ # verify the required parameter 'start_blob_upload_request' is set
+ if ('start_blob_upload_request' not in params or
+ params['start_blob_upload_request'] is None):
+ raise ValueError("Missing the required parameter `start_blob_upload_request` when calling `upload_file`") # noqa: E501
+
+ collection_formats = {}
+
+ path_params = {}
+
+ query_params = []
+
+ header_params = {}
+
+ form_params = []
+ local_var_files = {}
+
+ body_params = None
+ if 'start_blob_upload_request' in params:
+ body_params = params['start_blob_upload_request']
+ # HTTP header `Accept`
+ header_params['Accept'] = self.api_client.select_header_accept(
+ ['application/json']) # noqa: E501
+
+ # HTTP header `Content-Type`
+ header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501
+ ['application/json']) # noqa: E501
+
+ # Authentication setting
+ auth_settings = ['basicAuth'] # noqa: E501
+
+ return self.api_client.call_api(
+ '/blobs/upload', 'POST',
+ path_params,
+ query_params,
+ header_params,
+ body=body_params,
+ post_params=form_params,
+ files=local_var_files,
+ response_type='StartBlobUploadResponse', # noqa: E501
+ auth_settings=auth_settings,
+ async_req=params.get('async_req'),
+ _return_http_data_only=params.get('_return_http_data_only'),
+ _preload_content=params.get('_preload_content', True),
+ _request_timeout=params.get('_request_timeout'),
+ collection_formats=collection_formats)
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api_extended.py b/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api_extended.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b29f30d79410f374c1e57b3339fb3f681ced852
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/api/kaggle_api_extended.py
@@ -0,0 +1,4169 @@
+#!/usr/bin/python
+#
+# Copyright 2019 Kaggle Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding=utf-8
+from __future__ import print_function
+import csv
+from datetime import datetime
+import io
+import json
+import os
+from os.path import expanduser
+from random import random
+import sys
+import shutil
+import tarfile
+import time
+import zipfile
+import tempfile
+from ..api_client import ApiClient
+from kaggle.configuration import Configuration
+from .kaggle_api import KaggleApi
+from ..models.api_blob_type import ApiBlobType
+from ..models.collaborator import Collaborator
+from ..models.create_inbox_file_request import CreateInboxFileRequest
+from ..models.dataset_column import DatasetColumn
+from ..models.dataset_new_request import DatasetNewRequest
+from ..models.dataset_new_version_request import DatasetNewVersionRequest
+from ..models.dataset_update_settings_request import DatasetUpdateSettingsRequest
+from ..models.kaggle_models_extended import Competition
+from ..models.kaggle_models_extended import Dataset
+from ..models.kaggle_models_extended import DatasetNewResponse
+from ..models.kaggle_models_extended import DatasetNewVersionResponse
+from ..models.kaggle_models_extended import File
+from ..models.kaggle_models_extended import Kernel
+from ..models.kaggle_models_extended import KernelPushResponse
+from ..models.kaggle_models_extended import LeaderboardEntry
+from ..models.kaggle_models_extended import ListFilesResult
+from ..models.kaggle_models_extended import Metadata
+from ..models.kaggle_models_extended import Model
+from ..models.kaggle_models_extended import ModelNewResponse
+from ..models.kaggle_models_extended import ModelDeleteResponse
+from ..models.kaggle_models_extended import ResumableUploadResult
+from ..models.kaggle_models_extended import Submission
+from ..models.kaggle_models_extended import SubmitResult
+from ..models.kernel_push_request import KernelPushRequest
+from ..models.license import License
+from ..models.model_new_request import ModelNewRequest
+from ..models.model_new_instance_request import ModelNewInstanceRequest
+from ..models.model_instance_new_version_request import ModelInstanceNewVersionRequest
+from ..models.model_update_request import ModelUpdateRequest
+from ..models.model_instance_update_request import ModelInstanceUpdateRequest
+from ..models.start_blob_upload_request import StartBlobUploadRequest
+from ..models.start_blob_upload_response import StartBlobUploadResponse
+from ..models.upload_file import UploadFile
+import requests
+from requests.adapters import HTTPAdapter
+import requests.packages.urllib3.exceptions as urllib3_exceptions
+from requests.packages.urllib3.util.retry import Retry
+from ..rest import ApiException
+import six
+from slugify import slugify
+from tqdm import tqdm
+import bleach
+import time
+
+try:
+ unicode # Python 2
+except NameError:
+ unicode = str # Python 3
+
+
+class DirectoryArchive(object):
+
+ def __init__(self, fullpath, format):
+ self._fullpath = fullpath
+ self._format = format
+ self.name = None
+ self.path = None
+
+ def __enter__(self):
+ self._temp_dir = tempfile.mkdtemp()
+ _, dir_name = os.path.split(self._fullpath)
+ self.path = shutil.make_archive(os.path.join(self._temp_dir, dir_name),
+ self._format, self._fullpath)
+ _, self.name = os.path.split(self.path)
+ return self
+
+ def __exit__(self, *args):
+ shutil.rmtree(self._temp_dir)
+
+
+class ResumableUploadContext(object):
+
+ def __init__(self, no_resume=False):
+ self.no_resume = no_resume
+ self._temp_dir = os.path.join(tempfile.gettempdir(), '.kaggle/uploads')
+ self._file_uploads = []
+
+ def __enter__(self):
+ if self.no_resume:
+ return
+ self._create_temp_dir()
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ if self.no_resume:
+ return
+ if exc_type is not None:
+ # Don't delete the upload file info when there is an error
+ # to give it a chance to retry/resume on the next invocation.
+ return
+ for file_upload in self._file_uploads:
+ file_upload.cleanup()
+
+ def get_upload_info_file_path(self, path):
+ return os.path.join(
+ self._temp_dir,
+ '%s.json' % path.replace(os.path.sep, '_').replace(':', '_'))
+
+ def new_resumable_file_upload(self, path, start_blob_upload_request):
+ file_upload = ResumableFileUpload(path, start_blob_upload_request,
+ self)
+ self._file_uploads.append(file_upload)
+ file_upload.load()
+ return file_upload
+
+ def _create_temp_dir(self):
+ try:
+ os.makedirs(self._temp_dir)
+ except FileExistsError:
+ pass
+
+
+class ResumableFileUpload(object):
+ # Reference: https://cloud.google.com/storage/docs/resumable-uploads
+ # A resumable upload must be completed within a week of being initiated
+ RESUMABLE_UPLOAD_EXPIRY_SECONDS = 6 * 24 * 3600
+
+ def __init__(self, path, start_blob_upload_request, context):
+ self.path = path
+ self.start_blob_upload_request = start_blob_upload_request
+ self.context = context
+ self.timestamp = int(time.time())
+ self.start_blob_upload_response = None
+ self.can_resume = False
+ self.upload_complete = False
+ if self.context.no_resume:
+ return
+ self._upload_info_file_path = self.context.get_upload_info_file_path(
+ path)
+
+ def get_token(self):
+ if self.upload_complete:
+ return self.start_blob_upload_response.token
+ return None
+
+ def load(self):
+ if self.context.no_resume:
+ return
+ self._load_previous_if_any()
+
+ def _load_previous_if_any(self):
+ if not os.path.exists(self._upload_info_file_path):
+ return False
+
+ try:
+ with io.open(self._upload_info_file_path, 'r') as f:
+ previous = ResumableFileUpload.from_dict(
+ json.load(f), self.context)
+ if self._is_previous_valid(previous):
+ self.start_blob_upload_response = previous.start_blob_upload_response
+ self.timestamp = previous.timestamp
+ self.can_resume = True
+ except Exception as e:
+ print('Error while trying to load upload info:', e)
+
+ def _is_previous_valid(self, previous):
+ return previous.path == self.path and \
+ previous.start_blob_upload_request == self.start_blob_upload_request and \
+ previous.timestamp > time.time() - ResumableFileUpload.RESUMABLE_UPLOAD_EXPIRY_SECONDS
+
+ def upload_initiated(self, start_blob_upload_response):
+ if self.context.no_resume:
+ return
+
+ self.start_blob_upload_response = start_blob_upload_response
+ with io.open(self._upload_info_file_path, 'w') as f:
+ json.dump(self.to_dict(), f, indent=True)
+
+ def upload_completed(self):
+ if self.context.no_resume:
+ return
+
+ self.upload_complete = True
+ self._save()
+
+ def _save(self):
+ with io.open(self._upload_info_file_path, 'w') as f:
+ json.dump(self.to_dict(), f, indent=True)
+
+ def cleanup(self):
+ if self.context.no_resume:
+ return
+
+ try:
+ os.remove(self._upload_info_file_path)
+ except OSError:
+ pass
+
+ def to_dict(self):
+ return {
+ 'path':
+ self.path,
+ 'start_blob_upload_request':
+ self.start_blob_upload_request.to_dict(),
+ 'timestamp':
+ self.timestamp,
+ 'start_blob_upload_response':
+ self.start_blob_upload_response.to_dict()
+ if self.start_blob_upload_response is not None else None,
+ 'upload_complete':
+ self.upload_complete,
+ }
+
+ def from_dict(other, context):
+ new = ResumableFileUpload(
+ other['path'],
+ StartBlobUploadRequest(**other['start_blob_upload_request']),
+ context)
+ new.timestamp = other.get('timestamp')
+ start_blob_upload_response = other.get('start_blob_upload_response')
+ if start_blob_upload_response is not None:
+ new.start_blob_upload_response = StartBlobUploadResponse(
+ **start_blob_upload_response)
+ new.upload_complete = other.get('upload_complete') or False
+ return new
+
+ def to_str(self):
+ return str(self.to_dict())
+
+ def __repr__(self):
+ return self.to_str()
+
+
+class KaggleApi(KaggleApi):
+ __version__ = '1.6.12'
+
+ CONFIG_NAME_PROXY = 'proxy'
+ CONFIG_NAME_COMPETITION = 'competition'
+ CONFIG_NAME_PATH = 'path'
+ CONFIG_NAME_USER = 'username'
+ CONFIG_NAME_KEY = 'key'
+ CONFIG_NAME_SSL_CA_CERT = 'ssl_ca_cert'
+
+ HEADER_API_VERSION = 'X-Kaggle-ApiVersion'
+ DATASET_METADATA_FILE = 'dataset-metadata.json'
+ OLD_DATASET_METADATA_FILE = 'datapackage.json'
+ KERNEL_METADATA_FILE = 'kernel-metadata.json'
+ MODEL_METADATA_FILE = 'model-metadata.json'
+ MODEL_INSTANCE_METADATA_FILE = 'model-instance-metadata.json'
+ MAX_NUM_INBOX_FILES_TO_UPLOAD = 1000
+ MAX_UPLOAD_RESUME_ATTEMPTS = 10
+
+ config_dir = os.environ.get('KAGGLE_CONFIG_DIR') or os.path.join(
+ expanduser('~'), '.kaggle')
+ if not os.path.exists(config_dir):
+ os.makedirs(config_dir)
+
+ config_file = 'kaggle.json'
+ config = os.path.join(config_dir, config_file)
+ config_values = {}
+ already_printed_version_warning = False
+
+ # Kernels valid types
+ valid_push_kernel_types = ['script', 'notebook']
+ valid_push_language_types = ['python', 'r', 'rmarkdown']
+ valid_push_pinning_types = ['original', 'latest']
+ valid_list_languages = ['all', 'python', 'r', 'sqlite', 'julia']
+ valid_list_kernel_types = ['all', 'script', 'notebook']
+ valid_list_output_types = ['all', 'visualization', 'data']
+ valid_list_sort_by = [
+ 'hotness', 'commentCount', 'dateCreated', 'dateRun', 'relevance',
+ 'scoreAscending', 'scoreDescending', 'viewCount', 'voteCount'
+ ]
+
+ # Competitions valid types
+ valid_competition_groups = ['general', 'entered', 'inClass']
+ valid_competition_categories = [
+ 'all', 'featured', 'research', 'recruitment', 'gettingStarted',
+ 'masters', 'playground'
+ ]
+ valid_competition_sort_by = [
+ 'grouped', 'prize', 'earliestDeadline', 'latestDeadline',
+ 'numberOfTeams', 'recentlyCreated'
+ ]
+
+ # Datasets valid types
+ valid_dataset_file_types = ['all', 'csv', 'sqlite', 'json', 'bigQuery']
+ valid_dataset_license_names = ['all', 'cc', 'gpl', 'odb', 'other']
+ valid_dataset_sort_bys = [
+ 'hottest', 'votes', 'updated', 'active', 'published'
+ ]
+
+ # Models valid types
+ valid_model_sort_bys = [
+ 'hotness', 'downloadCount', 'voteCount', 'notebookCount', 'createTime'
+ ]
+
+ # Command prefixes that are valid without authentication.
+ command_prefixes_allowing_anonymous_access = ('datasets download',
+ 'datasets files')
+
+ # Hack for https://github.com/Kaggle/kaggle-api/issues/22 / b/78194015
+ if six.PY2:
+ reload(sys)
+ sys.setdefaultencoding('latin1')
+
+ def _is_retriable(self, e):
+ return issubclass(type(e), ConnectionError) or \
+ issubclass(type(e), urllib3_exceptions.ConnectionError) or \
+ issubclass(type(e), urllib3_exceptions.ConnectTimeoutError) or \
+ issubclass(type(e), urllib3_exceptions.ProtocolError) or \
+ issubclass(type(e), requests.exceptions.ConnectionError) or \
+ issubclass(type(e), requests.exceptions.ConnectTimeout)
+
+ def _calculate_backoff_delay(self, attempt, initial_delay_millis,
+ retry_multiplier, randomness_factor):
+ delay_ms = initial_delay_millis * (retry_multiplier**attempt)
+ random_wait_ms = int(random() - 0.5) * 2 * delay_ms * randomness_factor
+ total_delay = (delay_ms + random_wait_ms) / 1000.0
+ return total_delay
+
+ def with_retry(self,
+ func,
+ max_retries=10,
+ initial_delay_millis=500,
+ retry_multiplier=1.7,
+ randomness_factor=0.5):
+
+ def retriable_func(*args):
+ for i in range(1, max_retries + 1):
+ try:
+ return func(*args)
+ except Exception as e:
+ if self._is_retriable(e) and i < max_retries:
+ total_delay = self._calculate_backoff_delay(
+ i, initial_delay_millis, retry_multiplier,
+ randomness_factor)
+ print(
+ 'Request failed: %s. Will retry in %2.1f seconds' %
+ (e, total_delay))
+ time.sleep(total_delay)
+ continue
+ raise
+
+ return retriable_func
+
+ ## Authentication
+
+ def authenticate(self):
+ """authenticate the user with the Kaggle API. This method will generate
+ a configuration, first checking the environment for credential
+ variables, and falling back to looking for the .kaggle/kaggle.json
+ configuration file.
+ """
+
+ config_data = {}
+ # Ex: 'datasets list', 'competitions files', 'models instances get', etc.
+ api_command = ' '.join(sys.argv[1:])
+
+ # Step 1: try getting username/password from environment
+ config_data = self.read_config_environment(config_data)
+
+ # Step 2: if credentials were not in env read in configuration file
+ if self.CONFIG_NAME_USER not in config_data \
+ or self.CONFIG_NAME_KEY not in config_data:
+ if os.path.exists(self.config):
+ config_data = self.read_config_file(config_data)
+ elif self._is_help_or_version_command(api_command) or (
+ len(sys.argv) > 2 and api_command.startswith(
+ self.command_prefixes_allowing_anonymous_access)):
+ # Some API commands should be allowed without authentication.
+ return
+ else:
+ raise IOError('Could not find {}. Make sure it\'s located in'
+ ' {}. Or use the environment method.'.format(
+ self.config_file, self.config_dir))
+
+ # Step 3: load into configuration!
+ self._load_config(config_data)
+
+ def _is_help_or_version_command(self, api_command):
+ """determines if the string command passed in is for a help or version
+ command.
+ Parameters
+ ==========
+ api_command: a string, 'datasets list', 'competitions files',
+ 'models instances get', etc.
+ """
+ return api_command.endswith(('-h', '--help', '-v', '--version'))
+
+ def read_config_environment(self, config_data=None, quiet=False):
+ """read_config_environment is the second effort to get a username
+ and key to authenticate to the Kaggle API. The environment keys
+ are equivalent to the kaggle.json file, but with "KAGGLE_" prefix
+ to define a unique namespace.
+
+ Parameters
+ ==========
+ config_data: a partially loaded configuration dictionary (optional)
+ quiet: suppress verbose print of output (default is False)
+ """
+
+ # Add all variables that start with KAGGLE_ to config data
+
+ if config_data is None:
+ config_data = {}
+ for key, val in os.environ.items():
+ if key.startswith('KAGGLE_'):
+ config_key = key.replace('KAGGLE_', '', 1).lower()
+ config_data[config_key] = val
+
+ return config_data
+
+ ## Configuration
+
+ def _load_config(self, config_data):
+ """the final step of the authenticate steps, where we load the values
+ from config_data into the Configuration object.
+
+ Parameters
+ ==========
+ config_data: a dictionary with configuration values (keys) to read
+ into self.config_values
+
+ """
+ # Username and password are required.
+
+ for item in [self.CONFIG_NAME_USER, self.CONFIG_NAME_KEY]:
+ if item not in config_data:
+ raise ValueError('Error: Missing %s in configuration.' % item)
+
+ configuration = Configuration()
+
+ # Add to the final configuration (required)
+
+ configuration.username = config_data[self.CONFIG_NAME_USER]
+ configuration.password = config_data[self.CONFIG_NAME_KEY]
+
+ # Proxy
+
+ if self.CONFIG_NAME_PROXY in config_data:
+ configuration.proxy = config_data[self.CONFIG_NAME_PROXY]
+
+ # Cert File
+
+ if self.CONFIG_NAME_SSL_CA_CERT in config_data:
+ configuration.ssl_ca_cert = config_data[
+ self.CONFIG_NAME_SSL_CA_CERT]
+
+ # Keep config values with class instance, and load api client!
+
+ self.config_values = config_data
+
+ try:
+ self.api_client = ApiClient(configuration)
+
+ except Exception as error:
+
+ if 'Proxy' in type(error).__name__:
+ raise ValueError(
+ 'The specified proxy ' +
+ config_data[self.CONFIG_NAME_PROXY] +
+ ' is not valid, please check your proxy settings')
+ else:
+ raise ValueError(
+ 'Unauthorized: you must download an API key or export '
+ 'credentials to the environment. Please see\n ' +
+ 'https://github.com/Kaggle/kaggle-api#api-credentials ' +
+ 'for instructions.')
+
+ def read_config_file(self, config_data=None, quiet=False):
+ """read_config_file is the first effort to get a username
+ and key to authenticate to the Kaggle API. Since we can get the
+ username and password from the environment, it's not required.
+
+ Parameters
+ ==========
+ config_data: the Configuration object to save a username and
+ password, if defined
+ quiet: suppress verbose print of output (default is False)
+ """
+ if config_data is None:
+ config_data = {}
+
+ if os.path.exists(self.config):
+
+ try:
+ if os.name != 'nt':
+ permissions = os.stat(self.config).st_mode
+ if (permissions & 4) or (permissions & 32):
+ print(
+ 'Warning: Your Kaggle API key is readable by other '
+ 'users on this system! To fix this, you can run ' +
+ '\'chmod 600 {}\''.format(self.config))
+
+ with open(self.config) as f:
+ config_data = json.load(f)
+ except:
+ pass
+
+ else:
+
+ # Warn the user that configuration will be reliant on environment
+ if not quiet:
+ print('No Kaggle API config file found, will use environment.')
+
+ return config_data
+
+ def _read_config_file(self):
+ """read in the configuration file, a json file defined at self.config"""
+
+ try:
+ with open(self.config, 'r') as f:
+ config_data = json.load(f)
+ except FileNotFoundError:
+ config_data = {}
+
+ return config_data
+
+ def _write_config_file(self, config_data, indent=2):
+ """write config data to file.
+
+ Parameters
+ ==========
+ config_data: the Configuration object to save a username and
+ password, if defined
+ indent: number of tab indentations to use when writing json
+ """
+ with open(self.config, 'w') as f:
+ json.dump(config_data, f, indent=indent)
+
+ def set_config_value(self, name, value, quiet=False):
+ """a client helper function to set a configuration value, meaning
+ reading in the configuration file (if it exists), saving a new
+ config value, and then writing back
+
+ Parameters
+ ==========
+ name: the name of the value to set (key in dictionary)
+ value: the value to set at the key
+ quiet: disable verbose output if True (default is False)
+ """
+
+ config_data = self._read_config_file()
+
+ if value is not None:
+
+ # Update the config file with the value
+ config_data[name] = value
+
+ # Update the instance with the value
+ self.config_values[name] = value
+
+ # If defined by client, set and save!
+ self._write_config_file(config_data)
+
+ if not quiet:
+ self.print_config_value(name, separator=' is now set to: ')
+
+ def unset_config_value(self, name, quiet=False):
+ """unset a configuration value
+ Parameters
+ ==========
+ name: the name of the value to unset (remove key in dictionary)
+ quiet: disable verbose output if True (default is False)
+ """
+
+ config_data = self._read_config_file()
+
+ if name in config_data:
+
+ del config_data[name]
+
+ self._write_config_file(config_data)
+
+ if not quiet:
+ self.print_config_value(name, separator=' is now set to: ')
+
+ def get_config_value(self, name):
+ """ return a config value (with key name) if it's in the config_values,
+ otherwise return None
+
+ Parameters
+ ==========
+ name: the config value key to get
+
+ """
+ if name in self.config_values:
+ return self.config_values[name]
+
+ def get_default_download_dir(self, *subdirs):
+ """ Get the download path for a file. If not defined, return default
+ from config.
+
+ Parameters
+ ==========
+ subdirs: a single (or list of) subfolders under the basepath
+ """
+ # Look up value for key "path" in the config
+ path = self.get_config_value(self.CONFIG_NAME_PATH)
+
+ # If not set in config, default to present working directory
+ if path is None:
+ return os.getcwd()
+
+ return os.path.join(path, *subdirs)
+
+ def print_config_value(self, name, prefix='- ', separator=': '):
+ """print a single configuration value, based on a prefix and separator
+
+ Parameters
+ ==========
+ name: the key of the config valur in self.config_values to print
+ prefix: the prefix to print
+ separator: the separator to use (default is : )
+ """
+
+ value_out = 'None'
+ if name in self.config_values and self.config_values[name] is not None:
+ value_out = self.config_values[name]
+ print(prefix + name + separator + value_out)
+
+ def print_config_values(self, prefix='- '):
+ """a wrapper to print_config_value to print all configuration values
+ Parameters
+ ==========
+ prefix: the character prefix to put before the printed config value
+ defaults to "- "
+ """
+ print('Configuration values from ' + self.config_dir)
+ self.print_config_value(self.CONFIG_NAME_USER, prefix=prefix)
+ self.print_config_value(self.CONFIG_NAME_PATH, prefix=prefix)
+ self.print_config_value(self.CONFIG_NAME_PROXY, prefix=prefix)
+ self.print_config_value(self.CONFIG_NAME_COMPETITION, prefix=prefix)
+
+ ## Competitions
+
+ def competitions_list(self,
+ group=None,
+ category=None,
+ sort_by=None,
+ page=1,
+ search=None):
+ """ make call to list competitions, format the response, and return
+ a list of Competition instances
+
+ Parameters
+ ==========
+
+ page: the page to return (default is 1)
+ search: a search term to use (default is empty string)
+ sort_by: how to sort the result, see valid_competition_sort_by for options
+ category: category to filter result to
+ group: group to filter result to
+ """
+ if group and group not in self.valid_competition_groups:
+ raise ValueError('Invalid group specified. Valid options are ' +
+ str(self.valid_competition_groups))
+
+ if category and category not in self.valid_competition_categories:
+ raise ValueError('Invalid category specified. Valid options are ' +
+ str(self.valid_competition_categories))
+
+ if sort_by and sort_by not in self.valid_competition_sort_by:
+ raise ValueError('Invalid sort_by specified. Valid options are ' +
+ str(self.valid_competition_sort_by))
+
+ competitions_list_result = self.process_response(
+ self.competitions_list_with_http_info(group=group or '',
+ category=category or '',
+ sort_by=sort_by or '',
+ page=page,
+ search=search or ''))
+ return [Competition(c) for c in competitions_list_result]
+
+ def competitions_list_cli(self,
+ group=None,
+ category=None,
+ sort_by=None,
+ page=1,
+ search=None,
+ csv_display=False):
+ """ a wrapper for competitions_list for the client.
+
+ Parameters
+ ==========
+ group: group to filter result to
+ category: category to filter result to
+ sort_by: how to sort the result, see valid_sort_by for options
+ page: the page to return (default is 1)
+ search: a search term to use (default is empty string)
+ csv_display: if True, print comma separated values
+ """
+ competitions = self.competitions_list(group=group,
+ category=category,
+ sort_by=sort_by,
+ page=page,
+ search=search)
+ fields = [
+ 'ref', 'deadline', 'category', 'reward', 'teamCount',
+ 'userHasEntered'
+ ]
+ if competitions:
+ if csv_display:
+ self.print_csv(competitions, fields)
+ else:
+ self.print_table(competitions, fields)
+ else:
+ print('No competitions found')
+
+ def competition_submit(self, file_name, message, competition, quiet=False):
+ """ submit a competition!
+
+ Parameters
+ ==========
+ file_name: the competition metadata file
+ message: the submission description
+ competition: the competition name
+ quiet: suppress verbose output (default is False)
+ """
+ if competition is None:
+ competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
+ if competition is not None and not quiet:
+ print('Using competition: ' + competition)
+
+ if competition is None:
+ raise ValueError('No competition specified')
+ else:
+ url_result = self.process_response(
+ self.competitions_submissions_url_with_http_info(
+ id=competition,
+ file_name=os.path.basename(file_name),
+ content_length=os.path.getsize(file_name),
+ last_modified_date_utc=int(os.path.getmtime(file_name))))
+
+ # Temporary while new worker is gradually turned on. 'isComplete'
+ # exists on the old DTO but not the new, so this is an hacky but
+ # easy solution to figure out which submission logic to use
+ if 'isComplete' in url_result:
+ # Old submissions path
+ url_result_list = url_result['createUrl'].split('/')
+ upload_result = self.process_response(
+ self.competitions_submissions_upload_with_http_info(
+ file=file_name,
+ guid=url_result_list[-3],
+ content_length=url_result_list[-2],
+ last_modified_date_utc=url_result_list[-1]))
+ upload_result_token = upload_result['token']
+ else:
+ # New submissions path!
+ upload_status = self.upload_complete(file_name,
+ url_result['createUrl'],
+ quiet)
+ if upload_status != ResumableUploadResult.COMPLETE:
+ # Actual error is printed during upload_complete. Not
+ # ideal but changing would not be backwards compatible
+ return "Could not submit to competition"
+
+ upload_result_token = url_result['token']
+
+ submit_result = self.process_response(
+ self.competitions_submissions_submit_with_http_info(
+ id=competition,
+ blob_file_tokens=upload_result_token,
+ submission_description=message))
+ return SubmitResult(submit_result)
+
+ def competition_submit_cli(self,
+ file_name,
+ message,
+ competition,
+ competition_opt=None,
+ quiet=False):
+ """ submit a competition using the client. Arguments are same as for
+ competition_submit, except for extra arguments provided here.
+ Parameters
+ ==========
+ competition_opt: an alternative competition option provided by cli
+ """
+ competition = competition or competition_opt
+ try:
+ submit_result = self.competition_submit(file_name, message,
+ competition, quiet)
+ except ApiException as e:
+ if e.status == 404:
+ print('Could not find competition - please verify that you '
+ 'entered the correct competition ID and that the '
+ 'competition is still accepting submissions.')
+ return None
+ else:
+ raise e
+ return submit_result
+
+ def competition_submissions(self, competition):
+ """ get the list of Submission for a particular competition
+
+ Parameters
+ ==========
+ competition: the name of the competition
+ """
+ submissions_result = self.process_response(
+ self.competitions_submissions_list_with_http_info(id=competition))
+ return [Submission(s) for s in submissions_result]
+
+ def competition_submissions_cli(self,
+ competition=None,
+ competition_opt=None,
+ csv_display=False,
+ quiet=False):
+ """ wrapper to competition_submission, will return either json or csv
+ to the user. Additional parameters are listed below, see
+ competition_submissions for rest.
+
+ Parameters
+ ==========
+ competition: the name of the competition. If None, look to config
+ competition_opt: an alternative competition option provided by cli
+ csv_display: if True, print comma separated values
+ quiet: suppress verbose output (default is False)
+ """
+ competition = competition or competition_opt
+ if competition is None:
+ competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
+ if competition is not None and not quiet:
+ print('Using competition: ' + competition)
+
+ if competition is None:
+ raise ValueError('No competition specified')
+ else:
+ submissions = self.competition_submissions(competition)
+ fields = [
+ 'fileName', 'date', 'description', 'status', 'publicScore',
+ 'privateScore'
+ ]
+ if submissions:
+ if csv_display:
+ self.print_csv(submissions, fields)
+ else:
+ self.print_table(submissions, fields)
+ else:
+ print('No submissions found')
+
+ def competition_list_files(self, competition):
+ """ list files for competition
+ Parameters
+ ==========
+ competition: the name of the competition
+ """
+ competition_list_files_result = self.process_response(
+ self.competitions_data_list_files_with_http_info(id=competition))
+ return [File(f) for f in competition_list_files_result]
+
+ def competition_list_files_cli(self,
+ competition,
+ competition_opt=None,
+ csv_display=False,
+ quiet=False):
+ """ List files for a competition, if it exists
+
+ Parameters
+ ==========
+ competition: the name of the competition. If None, look to config
+ competition_opt: an alternative competition option provided by cli
+ csv_display: if True, print comma separated values
+ quiet: suppress verbose output (default is False)
+ """
+ competition = competition or competition_opt
+ if competition is None:
+ competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
+ if competition is not None and not quiet:
+ print('Using competition: ' + competition)
+
+ if competition is None:
+ raise ValueError('No competition specified')
+ else:
+ files = self.competition_list_files(competition)
+ fields = ['name', 'size', 'creationDate']
+ if files:
+ if csv_display:
+ self.print_csv(files, fields)
+ else:
+ self.print_table(files, fields)
+ else:
+ print('No files found')
+
+ def competition_download_file(self,
+ competition,
+ file_name,
+ path=None,
+ force=False,
+ quiet=False):
+ """ download a competition file to a designated location, or use
+ a default location
+
+ Parameters
+ =========
+ competition: the name of the competition
+ file_name: the configuration file name
+ path: a path to download the file to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is False)
+ """
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'competitions', competition)
+ else:
+ effective_path = path
+
+ response = self.process_response(
+ self.competitions_data_download_file_with_http_info(
+ id=competition, file_name=file_name, _preload_content=False))
+ url = response.retries.history[0].redirect_location.split('?')[0]
+ outfile = os.path.join(effective_path, url.split('/')[-1])
+
+ if force or self.download_needed(response, outfile, quiet):
+ self.download_file(response, outfile, quiet, not force)
+
+ def competition_download_files(self,
+ competition,
+ path=None,
+ force=False,
+ quiet=True):
+ """ downloads all competition files.
+
+ Parameters
+ =========
+ competition: the name of the competition
+ path: a path to download the file to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is True)
+ """
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'competitions', competition)
+ else:
+ effective_path = path
+
+ response = self.process_response(
+ self.competitions_data_download_files_with_http_info(
+ id=competition, _preload_content=False))
+ url = response.retries.history[0].redirect_location.split('?')[0]
+ outfile = os.path.join(effective_path,
+ competition + '.' + url.split('.')[-1])
+
+ if force or self.download_needed(response, outfile, quiet):
+ self.download_file(response, outfile, quiet, not force)
+
+ def competition_download_cli(self,
+ competition,
+ competition_opt=None,
+ file_name=None,
+ path=None,
+ force=False,
+ quiet=False):
+ """ a wrapper to competition_download_files, but first will parse input
+ from API client. Additional parameters are listed here, see
+ competition_download for remaining.
+
+ Parameters
+ =========
+ competition: the name of the competition
+ competition_opt: an alternative competition option provided by cli
+ file_name: the configuration file name
+ path: a path to download the file to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is False)
+ """
+ competition = competition or competition_opt
+ if competition is None:
+ competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
+ if competition is not None and not quiet:
+ print('Using competition: ' + competition)
+
+ if competition is None:
+ raise ValueError('No competition specified')
+ else:
+ if file_name is None:
+ self.competition_download_files(competition, path, force,
+ quiet)
+ else:
+ self.competition_download_file(competition, file_name, path,
+ force, quiet)
+
+ def competition_leaderboard_download(self, competition, path, quiet=True):
+ """ Download competition leaderboards
+
+ Parameters
+ =========
+ competition: the name of the competition
+ path: a path to download the file to
+ quiet: suppress verbose output (default is True)
+ """
+ response = self.process_response(
+ self.competition_download_leaderboard_with_http_info(
+ competition, _preload_content=False))
+
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'competitions', competition)
+ else:
+ effective_path = path
+
+ file_name = competition + '.zip'
+ outfile = os.path.join(effective_path, file_name)
+ self.download_file(response, outfile, quiet)
+
+ def competition_leaderboard_view(self, competition):
+ """ view a leaderboard based on a competition name
+
+ Parameters
+ ==========
+ competition: the competition name to view leadboard for
+ """
+ result = self.process_response(
+ self.competition_view_leaderboard_with_http_info(competition))
+ return [LeaderboardEntry(e) for e in result['submissions']]
+
+ def competition_leaderboard_cli(self,
+ competition,
+ competition_opt=None,
+ path=None,
+ view=False,
+ download=False,
+ csv_display=False,
+ quiet=False):
+ """ a wrapper for competition_leaderbord_view that will print the
+ results as a table or comma separated values
+
+ Parameters
+ ==========
+ competition: the competition name to view leadboard for
+ competition_opt: an alternative competition option provided by cli
+ path: a path to download to, if download is True
+ view: if True, show the results in the terminal as csv or table
+ download: if True, download the entire leaderboard
+ csv_display: if True, print comma separated values instead of table
+ quiet: suppress verbose output (default is False)
+ """
+ competition = competition or competition_opt
+ if not view and not download:
+ raise ValueError('Either --show or --download must be specified')
+
+ if competition is None:
+ competition = self.get_config_value(self.CONFIG_NAME_COMPETITION)
+ if competition is not None and not quiet:
+ print('Using competition: ' + competition)
+
+ if competition is None:
+ raise ValueError('No competition specified')
+
+ if download:
+ self.competition_leaderboard_download(competition, path, quiet)
+
+ if view:
+ results = self.competition_leaderboard_view(competition)
+ fields = ['teamId', 'teamName', 'submissionDate', 'score']
+ if results:
+ if csv_display:
+ self.print_csv(results, fields)
+ else:
+ self.print_table(results, fields)
+ else:
+ print('No results found')
+
+ def dataset_list(self,
+ sort_by=None,
+ size=None,
+ file_type=None,
+ license_name=None,
+ tag_ids=None,
+ search=None,
+ user=None,
+ mine=False,
+ page=1,
+ max_size=None,
+ min_size=None):
+ """ return a list of datasets!
+
+ Parameters
+ ==========
+ sort_by: how to sort the result, see valid_dataset_sort_bys for options
+ size: Deprecated
+ file_type: the format, see valid_dataset_file_types for string options
+ license_name: string descriptor for license, see valid_dataset_license_names
+ tag_ids: tag identifiers to filter the search
+ search: a search term to use (default is empty string)
+ user: username to filter the search to
+ mine: boolean if True, group is changed to "my" to return personal
+ page: the page to return (default is 1)
+ max_size: the maximum size of the dataset to return (bytes)
+ min_size: the minimum size of the dataset to return (bytes)
+ """
+ if sort_by and sort_by not in self.valid_dataset_sort_bys:
+ raise ValueError('Invalid sort by specified. Valid options are ' +
+ str(self.valid_dataset_sort_bys))
+
+ if size:
+ raise ValueError(
+ 'The --size parameter has been deprecated. ' +
+ 'Please use --max-size and --min-size to filter dataset sizes.'
+ )
+
+ if file_type and file_type not in self.valid_dataset_file_types:
+ raise ValueError(
+ 'Invalid file type specified. Valid options are ' +
+ str(self.valid_dataset_file_types))
+
+ if license_name and license_name not in self.valid_dataset_license_names:
+ raise ValueError('Invalid license specified. Valid options are ' +
+ str(self.valid_dataset_license_names))
+
+ if int(page) <= 0:
+ raise ValueError('Page number must be >= 1')
+
+ if max_size and min_size:
+ if (int(max_size) < int(min_size)):
+ raise ValueError('Max Size must be max_size >= min_size')
+ if (max_size and int(max_size) <= 0):
+ raise ValueError('Max Size must be > 0')
+ elif (min_size and int(min_size) < 0):
+ raise ValueError('Min Size must be >= 0')
+
+ group = 'public'
+ if mine:
+ group = 'my'
+ if user:
+ raise ValueError('Cannot specify both mine and a user')
+ if user:
+ group = 'user'
+
+ datasets_list_result = self.process_response(
+ self.datasets_list_with_http_info(group=group,
+ sort_by=sort_by or 'hottest',
+ size=size,
+ filetype=file_type or 'all',
+ license=license_name or 'all',
+ tagids=tag_ids or '',
+ search=search or '',
+ user=user or '',
+ page=page,
+ max_size=max_size,
+ min_size=min_size))
+ return [Dataset(d) for d in datasets_list_result]
+
+ def dataset_list_cli(self,
+ sort_by=None,
+ size=None,
+ file_type=None,
+ license_name=None,
+ tag_ids=None,
+ search=None,
+ user=None,
+ mine=False,
+ page=1,
+ csv_display=False,
+ max_size=None,
+ min_size=None):
+ """ a wrapper to dataset_list for the client. Additional parameters
+ are described here, see dataset_list for others.
+
+ Parameters
+ ==========
+ sort_by: how to sort the result, see valid_dataset_sort_bys for options
+ size: DEPRECATED
+ file_type: the format, see valid_dataset_file_types for string options
+ license_name: string descriptor for license, see valid_dataset_license_names
+ tag_ids: tag identifiers to filter the search
+ search: a search term to use (default is empty string)
+ user: username to filter the search to
+ mine: boolean if True, group is changed to "my" to return personal
+ page: the page to return (default is 1)
+ csv_display: if True, print comma separated values instead of table
+ max_size: the maximum size of the dataset to return (bytes)
+ min_size: the minimum size of the dataset to return (bytes)
+ """
+ datasets = self.dataset_list(sort_by, size, file_type, license_name,
+ tag_ids, search, user, mine, page,
+ max_size, min_size)
+ fields = [
+ 'ref', 'title', 'size', 'lastUpdated', 'downloadCount',
+ 'voteCount', 'usabilityRating'
+ ]
+ if datasets:
+ if csv_display:
+ self.print_csv(datasets, fields)
+ else:
+ self.print_table(datasets, fields)
+ else:
+ print('No datasets found')
+
+ def dataset_metadata_prep(self, dataset, path):
+ if dataset is None:
+ raise ValueError('A dataset must be specified')
+ if '/' in dataset:
+ self.validate_dataset_string(dataset)
+ dataset_urls = dataset.split('/')
+ owner_slug = dataset_urls[0]
+ dataset_slug = dataset_urls[1]
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ dataset_slug = dataset
+
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'datasets', owner_slug, dataset_slug)
+ else:
+ effective_path = path
+
+ return (owner_slug, dataset_slug, effective_path)
+
+ def dataset_metadata_update(self, dataset, path):
+ (owner_slug, dataset_slug,
+ effective_path) = self.dataset_metadata_prep(dataset, path)
+ meta_file = self.get_dataset_metadata_file(effective_path)
+ with open(meta_file, 'r') as f:
+ metadata = json.load(f)
+ updateSettingsRequest = DatasetUpdateSettingsRequest(
+ title=metadata['title'],
+ subtitle=metadata['subtitle'],
+ description=metadata['description'],
+ is_private=metadata['isPrivate'],
+ licenses=[
+ License(name=l['name']) for l in metadata['licenses']
+ ],
+ keywords=metadata['keywords'],
+ collaborators=[
+ Collaborator(username=c['username'], role=c['role'])
+ for c in metadata['collaborators']
+ ],
+ data=metadata['data'])
+ result = self.process_response(
+ self.metadata_post_with_http_info(owner_slug, dataset_slug,
+ updateSettingsRequest))
+ if (len(result['errors']) > 0):
+ [print(e['message']) for e in result['errors']]
+ exit(1)
+
+ def dataset_metadata(self, dataset, path):
+ (owner_slug, dataset_slug,
+ effective_path) = self.dataset_metadata_prep(dataset, path)
+
+ if not os.path.exists(effective_path):
+ os.makedirs(effective_path)
+
+ result = self.process_response(
+ self.metadata_get_with_http_info(owner_slug, dataset_slug))
+ if (result['errorMessage']):
+ raise Exception(result['errorMessage'])
+
+ metadata = Metadata(result['info'])
+
+ meta_file = os.path.join(effective_path, self.DATASET_METADATA_FILE)
+ with open(meta_file, 'w') as f:
+ json.dump(metadata, f, indent=2, default=lambda o: o.__dict__)
+
+ return meta_file
+
+ def dataset_metadata_cli(self, dataset, path, update, dataset_opt=None):
+ dataset = dataset or dataset_opt
+ if (update):
+ print('updating dataset metadata')
+ self.dataset_metadata_update(dataset, path)
+ print('successfully updated dataset metadata')
+ else:
+ meta_file = self.dataset_metadata(dataset, path)
+ print('Downloaded metadata to ' + meta_file)
+
+ def dataset_list_files(self, dataset):
+ """ list files for a dataset
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ """
+ if dataset is None:
+ raise ValueError('A dataset must be specified')
+ owner_slug, dataset_slug, dataset_version_number = self.split_dataset_string(
+ dataset)
+
+ dataset_list_files_result = self.process_response(
+ self.datasets_list_files_with_http_info(
+ owner_slug=owner_slug,
+ dataset_slug=dataset_slug,
+ dataset_version_number=dataset_version_number))
+ return ListFilesResult(dataset_list_files_result)
+
+ def dataset_list_files_cli(self,
+ dataset,
+ dataset_opt=None,
+ csv_display=False):
+ """ a wrapper to dataset_list_files for the client
+ (list files for a dataset)
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ dataset_opt: an alternative option to providing a dataset
+ csv_display: if True, print comma separated values instead of table
+ """
+ dataset = dataset or dataset_opt
+ result = self.dataset_list_files(dataset)
+ if result:
+ if result.error_message:
+ print(result.error_message)
+ else:
+ fields = ['name', 'size', 'creationDate']
+ if csv_display:
+ self.print_csv(result.files, fields)
+ else:
+ self.print_table(result.files, fields)
+ else:
+ print('No files found')
+
+ def dataset_status(self, dataset):
+ """ call to get the status of a dataset from the API
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ """
+ if dataset is None:
+ raise ValueError('A dataset must be specified')
+ if '/' in dataset:
+ self.validate_dataset_string(dataset)
+ dataset_urls = dataset.split('/')
+ owner_slug = dataset_urls[0]
+ dataset_slug = dataset_urls[1]
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ dataset_slug = dataset
+ dataset_status_result = self.process_response(
+ self.datasets_status_with_http_info(owner_slug=owner_slug,
+ dataset_slug=dataset_slug))
+ return dataset_status_result
+
+ def dataset_status_cli(self, dataset, dataset_opt=None):
+ """ wrapper for client for dataset_status, with additional
+ dataset_opt to get the status of a dataset from the API
+ Parameters
+ ==========
+ dataset_opt: an alternative to dataset
+ """
+ dataset = dataset or dataset_opt
+ return self.dataset_status(dataset)
+
+ def dataset_download_file(self,
+ dataset,
+ file_name,
+ path=None,
+ force=False,
+ quiet=True,
+ licenses=[]):
+ """ download a single file for a dataset
+
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ file_name: the dataset configuration file
+ path: if defined, download to this location
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is True)
+ licenses: a list of license names, e.g. ['CC0-1.0']
+ """
+ if '/' in dataset:
+ self.validate_dataset_string(dataset)
+ owner_slug, dataset_slug, dataset_version_number = self.split_dataset_string(
+ dataset)
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ dataset_slug = dataset
+ dataset_version_number = None
+
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'datasets', owner_slug, dataset_slug)
+ else:
+ effective_path = path
+
+ self._print_dataset_url_and_license(owner_slug, dataset_slug,
+ dataset_version_number, licenses)
+
+ response = self.process_response(
+ self.datasets_download_file_with_http_info(
+ owner_slug=owner_slug,
+ dataset_slug=dataset_slug,
+ dataset_version_number=dataset_version_number,
+ file_name=file_name,
+ _preload_content=False))
+ url = response.retries.history[0].redirect_location.split('?')[0]
+ outfile = os.path.join(effective_path, url.split('/')[-1])
+ if force or self.download_needed(response, outfile, quiet):
+ self.download_file(response, outfile, quiet, not force)
+ return True
+ else:
+ return False
+
+ def dataset_download_files(self,
+ dataset,
+ path=None,
+ force=False,
+ quiet=True,
+ unzip=False,
+ licenses=[]):
+ """ download all files for a dataset
+
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ path: the path to download the dataset to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is True)
+ unzip: if True, unzip files upon download (default is False)
+ licenses: a list of license names, e.g. ['CC0-1.0']
+ """
+ if dataset is None:
+ raise ValueError('A dataset must be specified')
+ owner_slug, dataset_slug, dataset_version_number = self.split_dataset_string(
+ dataset)
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'datasets', owner_slug, dataset_slug)
+ else:
+ effective_path = path
+
+ self._print_dataset_url_and_license(owner_slug, dataset_slug,
+ dataset_version_number, licenses)
+
+ response = self.process_response(
+ self.datasets_download_with_http_info(
+ owner_slug=owner_slug,
+ dataset_slug=dataset_slug,
+ dataset_version_number=dataset_version_number,
+ _preload_content=False))
+
+ outfile = os.path.join(effective_path, dataset_slug + '.zip')
+ if force or self.download_needed(response, outfile, quiet):
+ self.download_file(response, outfile, quiet, not force)
+ downloaded = True
+ else:
+ downloaded = False
+
+ if downloaded:
+ outfile = os.path.join(effective_path, dataset_slug + '.zip')
+ if unzip:
+ try:
+ with zipfile.ZipFile(outfile) as z:
+ z.extractall(effective_path)
+ except zipfile.BadZipFile as e:
+ raise ValueError(
+ 'Bad zip file, please report on '
+ 'www.github.com/kaggle/kaggle-api', e)
+
+ try:
+ os.remove(outfile)
+ except OSError as e:
+ print('Could not delete zip file, got %s' % e)
+
+ def _print_dataset_url_and_license(self, owner_slug, dataset_slug,
+ dataset_version_number, licenses):
+ if dataset_version_number is None:
+ print('Dataset URL: https://www.kaggle.com/datasets/%s/%s' %
+ (owner_slug, dataset_slug))
+ else:
+ print(
+ 'Dataset URL: https://www.kaggle.com/datasets/%s/%s/versions/%s'
+ % (owner_slug, dataset_slug, dataset_version_number))
+
+ if len(licenses) > 0:
+ print('License(s): %s' % (','.join(licenses)))
+
+ def dataset_download_cli(self,
+ dataset,
+ dataset_opt=None,
+ file_name=None,
+ path=None,
+ unzip=False,
+ force=False,
+ quiet=False):
+ """ client wrapper for dataset_download_files and download dataset file,
+ either for a specific file (when file_name is provided),
+ or all files for a dataset (plural)
+
+ Parameters
+ ==========
+ dataset: the string identified of the dataset
+ should be in format [owner]/[dataset-name]
+ dataset_opt: an alternative option to providing a dataset
+ file_name: the dataset configuration file
+ path: the path to download the dataset to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is False)
+ unzip: if True, unzip files upon download (default is False)
+ """
+ dataset = dataset or dataset_opt
+
+ owner_slug, dataset_slug, _ = self.split_dataset_string(dataset)
+ metadata = self.process_response(
+ self.metadata_get_with_http_info(owner_slug, dataset_slug))
+
+ if 'info' in metadata and 'licenses' in metadata['info']:
+ # license_objs format is like: [{ 'name': 'CC0-1.0' }]
+ license_objs = metadata['info']['licenses']
+ licenses = [
+ license_obj['name'] for license_obj in license_objs
+ if 'name' in license_obj
+ ]
+ else:
+ licenses = [
+ 'Error retrieving license. Please visit the Dataset URL to view license information.'
+ ]
+
+ if file_name is None:
+ self.dataset_download_files(dataset,
+ path=path,
+ unzip=unzip,
+ force=force,
+ quiet=quiet,
+ licenses=licenses)
+ else:
+ self.dataset_download_file(dataset,
+ file_name,
+ path=path,
+ force=force,
+ quiet=quiet,
+ licenses=licenses)
+
+ def _upload_blob(self, path, quiet, blob_type, upload_context):
+ """ upload a file
+
+ Parameters
+ ==========
+ path: the complete path to upload
+ quiet: suppress verbose output (default is False)
+ blob_type (ApiBlobType): To which entity the file/blob refers
+ upload_context (ResumableUploadContext): Context for resumable uploads
+ """
+ file_name = os.path.basename(path)
+ content_length = os.path.getsize(path)
+ last_modified_epoch_seconds = int(os.path.getmtime(path))
+
+ start_blob_upload_request = StartBlobUploadRequest(
+ blob_type,
+ file_name,
+ content_length,
+ last_modified_epoch_seconds=last_modified_epoch_seconds)
+
+ file_upload = upload_context.new_resumable_file_upload(
+ path, start_blob_upload_request)
+
+ for i in range(0, self.MAX_UPLOAD_RESUME_ATTEMPTS):
+ if file_upload.upload_complete:
+ return file_upload
+
+ if not file_upload.can_resume:
+ # Initiate upload on Kaggle backend to get the url and token.
+ start_blob_upload_response = self.process_response(
+ self.with_retry(self.upload_file_with_http_info)(
+ file_upload.start_blob_upload_request))
+ file_upload.upload_initiated(start_blob_upload_response)
+
+ upload_result = self.upload_complete(
+ path,
+ file_upload.start_blob_upload_response.create_url,
+ quiet,
+ resume=file_upload.can_resume)
+ if upload_result == ResumableUploadResult.INCOMPLETE:
+ continue # Continue (i.e., retry/resume) only if the upload is incomplete.
+
+ if upload_result == ResumableUploadResult.COMPLETE:
+ file_upload.upload_completed()
+ break
+
+ return file_upload.get_token()
+
+ def dataset_create_version(self,
+ folder,
+ version_notes,
+ quiet=False,
+ convert_to_csv=True,
+ delete_old_versions=False,
+ dir_mode='skip'):
+ """ create a version of a dataset
+
+ Parameters
+ ==========
+ folder: the folder with the dataset configuration / data files
+ version_notes: notes to add for the version
+ quiet: suppress verbose output (default is False)
+ convert_to_csv: on upload, if data should be converted to csv
+ delete_old_versions: if True, do that (default False)
+ dir_mode: What to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_dataset_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ ref = self.get_or_default(meta_data, 'id', None)
+ id_no = self.get_or_default(meta_data, 'id_no', None)
+ if not ref and not id_no:
+ raise ValueError('ID or slug must be specified in the metadata')
+
+ subtitle = meta_data.get('subtitle')
+ if subtitle and (len(subtitle) < 20 or len(subtitle) > 80):
+ raise ValueError(
+ 'Subtitle length must be between 20 and 80 characters')
+ resources = meta_data.get('resources')
+ if resources:
+ self.validate_resources(folder, resources)
+
+ description = meta_data.get('description')
+ keywords = self.get_or_default(meta_data, 'keywords', [])
+
+ request = DatasetNewVersionRequest(
+ version_notes=version_notes,
+ subtitle=subtitle,
+ description=description,
+ files=[],
+ convert_to_csv=convert_to_csv,
+ category_ids=keywords,
+ delete_old_versions=delete_old_versions)
+
+ with ResumableUploadContext() as upload_context:
+ self.upload_files(request, resources, folder, ApiBlobType.DATASET,
+ upload_context, quiet, dir_mode)
+
+ if id_no:
+ result = DatasetNewVersionResponse(
+ self.process_response(
+ self.with_retry(
+ self.datasets_create_version_by_id_with_http_info)(
+ id_no, request)))
+ else:
+ if ref == self.config_values[
+ self.CONFIG_NAME_USER] + '/INSERT_SLUG_HERE':
+ raise ValueError(
+ 'Default slug detected, please change values before '
+ 'uploading')
+ self.validate_dataset_string(ref)
+ ref_list = ref.split('/')
+ owner_slug = ref_list[0]
+ dataset_slug = ref_list[1]
+ result = DatasetNewVersionResponse(
+ self.process_response(
+ self.datasets_create_version_with_http_info(
+ owner_slug, dataset_slug, request)))
+
+ return result
+
+ def dataset_create_version_cli(self,
+ folder,
+ version_notes,
+ quiet=False,
+ convert_to_csv=True,
+ delete_old_versions=False,
+ dir_mode='skip'):
+ """ client wrapper for creating a version of a dataset
+ Parameters
+ ==========
+ folder: the folder with the dataset configuration / data files
+ version_notes: notes to add for the version
+ quiet: suppress verbose output (default is False)
+ convert_to_csv: on upload, if data should be converted to csv
+ delete_old_versions: if True, do that (default False)
+ dir_mode: What to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ folder = folder or os.getcwd()
+ result = self.dataset_create_version(
+ folder,
+ version_notes,
+ quiet=quiet,
+ convert_to_csv=convert_to_csv,
+ delete_old_versions=delete_old_versions,
+ dir_mode=dir_mode)
+
+ if result is None:
+ print('Dataset version creation error: See previous output')
+ elif result.invalidTags:
+ print(
+ ('The following are not valid tags and could not be added to '
+ 'the dataset: ') + str(result.invalidTags))
+ elif result.status.lower() == 'ok':
+ print(
+ 'Dataset version is being created. Please check progress at ' +
+ result.url)
+ else:
+ print('Dataset version creation error: ' + result.error)
+
+ def dataset_initialize(self, folder):
+ """ initialize a folder with a a dataset configuration (metadata) file
+
+ Parameters
+ ==========
+ folder: the folder to initialize the metadata file in
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ ref = self.config_values[self.CONFIG_NAME_USER] + '/INSERT_SLUG_HERE'
+ licenses = []
+ default_license = {'name': 'CC0-1.0'}
+ licenses.append(default_license)
+
+ meta_data = {
+ 'title': 'INSERT_TITLE_HERE',
+ 'id': ref,
+ 'licenses': licenses
+ }
+ meta_file = os.path.join(folder, self.DATASET_METADATA_FILE)
+ with open(meta_file, 'w') as f:
+ json.dump(meta_data, f, indent=2)
+
+ print('Data package template written to: ' + meta_file)
+ return meta_file
+
+ def dataset_initialize_cli(self, folder=None):
+ folder = folder or os.getcwd()
+ self.dataset_initialize(folder)
+
+ def dataset_create_new(self,
+ folder,
+ public=False,
+ quiet=False,
+ convert_to_csv=True,
+ dir_mode='skip'):
+ """ create a new dataset, meaning the same as creating a version but
+ with extra metadata like license and user/owner.
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ public: should the dataset be public?
+ quiet: suppress verbose output (default is False)
+ convert_to_csv: if True, convert data to comma separated value
+ dir_mode: What to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_dataset_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ ref = self.get_or_fail(meta_data, 'id')
+ title = self.get_or_fail(meta_data, 'title')
+ licenses = self.get_or_fail(meta_data, 'licenses')
+ ref_list = ref.split('/')
+ owner_slug = ref_list[0]
+ dataset_slug = ref_list[1]
+
+ # validations
+ if ref == self.config_values[
+ self.CONFIG_NAME_USER] + '/INSERT_SLUG_HERE':
+ raise ValueError(
+ 'Default slug detected, please change values before uploading')
+ if title == 'INSERT_TITLE_HERE':
+ raise ValueError(
+ 'Default title detected, please change values before uploading'
+ )
+ if len(licenses) != 1:
+ raise ValueError('Please specify exactly one license')
+ if len(dataset_slug) < 6 or len(dataset_slug) > 50:
+ raise ValueError(
+ 'The dataset slug must be between 6 and 50 characters')
+ if len(title) < 6 or len(title) > 50:
+ raise ValueError(
+ 'The dataset title must be between 6 and 50 characters')
+ resources = meta_data.get('resources')
+ if resources:
+ self.validate_resources(folder, resources)
+
+ license_name = self.get_or_fail(licenses[0], 'name')
+ description = meta_data.get('description')
+ keywords = self.get_or_default(meta_data, 'keywords', [])
+
+ subtitle = meta_data.get('subtitle')
+ if subtitle and (len(subtitle) < 20 or len(subtitle) > 80):
+ raise ValueError(
+ 'Subtitle length must be between 20 and 80 characters')
+
+ request = DatasetNewRequest(title=title,
+ slug=dataset_slug,
+ owner_slug=owner_slug,
+ license_name=license_name,
+ subtitle=subtitle,
+ description=description,
+ files=[],
+ is_private=not public,
+ convert_to_csv=convert_to_csv,
+ category_ids=keywords)
+
+ with ResumableUploadContext() as upload_context:
+ self.upload_files(request, resources, folder, ApiBlobType.DATASET,
+ upload_context, quiet, dir_mode)
+ result = DatasetNewResponse(
+ self.process_response(
+ self.with_retry(
+ self.datasets_create_new_with_http_info)(request)))
+
+ return result
+
+ def dataset_create_new_cli(self,
+ folder=None,
+ public=False,
+ quiet=False,
+ convert_to_csv=True,
+ dir_mode='skip'):
+ """ client wrapper for creating a new dataset
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ public: should the dataset be public?
+ quiet: suppress verbose output (default is False)
+ convert_to_csv: if True, convert data to comma separated value
+ dir_mode: What to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ folder = folder or os.getcwd()
+ result = self.dataset_create_new(folder, public, quiet, convert_to_csv,
+ dir_mode)
+ if result.invalidTags:
+ print('The following are not valid tags and could not be added to '
+ 'the dataset: ' + str(result.invalidTags))
+ if result.status.lower() == 'ok':
+ if public:
+ print('Your public Dataset is being created. Please check '
+ 'progress at ' + result.url)
+ else:
+ print('Your private Dataset is being created. Please check '
+ 'progress at ' + result.url)
+ else:
+ print('Dataset creation error: ' + result.error)
+
+ def download_file(self,
+ response,
+ outfile,
+ quiet=True,
+ resume=False,
+ chunk_size=1048576):
+ """ download a file to an output file based on a chunk size
+
+ Parameters
+ ==========
+ response: the response to download
+ outfile: the output file to download to
+ quiet: suppress verbose output (default is True)
+ chunk_size: the size of the chunk to stream
+ resume: whether to resume an existing download
+ """
+
+ outpath = os.path.dirname(outfile)
+ if not os.path.exists(outpath):
+ os.makedirs(outpath)
+ size = int(response.headers['Content-Length'])
+ size_read = 0
+ open_mode = 'wb'
+ remote_date = datetime.strptime(response.headers['Last-Modified'],
+ '%a, %d %b %Y %H:%M:%S %Z')
+ remote_date_timestamp = time.mktime(remote_date.timetuple())
+
+ if not quiet:
+ print('Downloading ' + os.path.basename(outfile) + ' to ' +
+ outpath)
+
+ file_exists = os.path.isfile(outfile)
+ resumable = 'Accept-Ranges' in response.headers and response.headers[
+ 'Accept-Ranges'] == 'bytes'
+
+ if resume and resumable and file_exists:
+ size_read = os.path.getsize(outfile)
+ open_mode = 'ab'
+
+ if not quiet:
+ print("... resuming from %d bytes (%d bytes left) ..." % (
+ size_read,
+ size - size_read,
+ ))
+
+ request_history = response.retries.history[0]
+ response = self.api_client.request(
+ request_history.method,
+ request_history.redirect_location,
+ headers={'Range': 'bytes=%d-' % (size_read, )},
+ _preload_content=False)
+
+ with tqdm(total=size,
+ initial=size_read,
+ unit='B',
+ unit_scale=True,
+ unit_divisor=1024,
+ disable=quiet) as pbar:
+ with open(outfile, open_mode) as out:
+ while True:
+ data = response.read(chunk_size)
+ if not data:
+ break
+ out.write(data)
+ os.utime(outfile,
+ times=(remote_date_timestamp - 1,
+ remote_date_timestamp - 1))
+ size_read = min(size, size_read + chunk_size)
+ pbar.update(len(data))
+ if not quiet:
+ print('\n', end='')
+
+ os.utime(outfile,
+ times=(remote_date_timestamp, remote_date_timestamp))
+
+ def kernels_list(self,
+ page=1,
+ page_size=20,
+ dataset=None,
+ competition=None,
+ parent_kernel=None,
+ search=None,
+ mine=False,
+ user=None,
+ language=None,
+ kernel_type=None,
+ output_type=None,
+ sort_by=None):
+ """ list kernels based on a set of search criteria
+
+ Parameters
+ ==========
+ page: the page of results to return (default is 1)
+ page_size: results per page (default is 20)
+ dataset: if defined, filter to this dataset (default None)
+ competition: if defined, filter to this competition (default None)
+ parent_kernel: if defined, filter to those with specified parent
+ search: a custom search string to pass to the list query
+ mine: if true, group is specified as "my" to return personal kernels
+ user: filter results to a specific user
+ language: the programming language of the kernel
+ kernel_type: the type of kernel, one of valid_list_kernel_types (str)
+ output_type: the output type, one of valid_list_output_types (str)
+ sort_by: if defined, sort results by this string (valid_list_sort_by)
+ """
+ if int(page) <= 0:
+ raise ValueError('Page number must be >= 1')
+
+ page_size = int(page_size)
+ if page_size <= 0:
+ raise ValueError('Page size must be >= 1')
+ if page_size > 100:
+ page_size = 100
+
+ if language and language not in self.valid_list_languages:
+ raise ValueError('Invalid language specified. Valid options are ' +
+ str(self.valid_list_languages))
+
+ if kernel_type and kernel_type not in self.valid_list_kernel_types:
+ raise ValueError(
+ 'Invalid kernel type specified. Valid options are ' +
+ str(self.valid_list_kernel_types))
+
+ if output_type and output_type not in self.valid_list_output_types:
+ raise ValueError(
+ 'Invalid output type specified. Valid options are ' +
+ str(self.valid_list_output_types))
+
+ if sort_by and sort_by not in self.valid_list_sort_by:
+ raise ValueError(
+ 'Invalid sort by type specified. Valid options are ' +
+ str(self.valid_list_sort_by))
+
+ if sort_by == 'relevance' and search == '':
+ raise ValueError('Cannot sort by relevance without a search term.')
+
+ self.validate_dataset_string(dataset)
+ self.validate_kernel_string(parent_kernel)
+
+ group = 'everyone'
+ if mine:
+ group = 'profile'
+
+ kernels_list_result = self.process_response(
+ self.kernels_list_with_http_info(page=page,
+ page_size=page_size,
+ group=group,
+ user=user or '',
+ language=language or 'all',
+ kernel_type=kernel_type or 'all',
+ output_type=output_type or 'all',
+ sort_by=sort_by or 'hotness',
+ dataset=dataset or '',
+ competition=competition or '',
+ parent_kernel=parent_kernel or '',
+ search=search or ''))
+ return [Kernel(k) for k in kernels_list_result]
+
+ def kernels_list_cli(self,
+ mine=False,
+ page=1,
+ page_size=20,
+ search=None,
+ csv_display=False,
+ parent=None,
+ competition=None,
+ dataset=None,
+ user=None,
+ language=None,
+ kernel_type=None,
+ output_type=None,
+ sort_by=None):
+ """ client wrapper for kernels_list, see this function for arguments.
+ Additional arguments are provided here.
+ Parameters
+ ==========
+ csv_display: if True, print comma separated values instead of table
+ """
+ kernels = self.kernels_list(page=page,
+ page_size=page_size,
+ search=search,
+ mine=mine,
+ dataset=dataset,
+ competition=competition,
+ parent_kernel=parent,
+ user=user,
+ language=language,
+ kernel_type=kernel_type,
+ output_type=output_type,
+ sort_by=sort_by)
+ fields = ['ref', 'title', 'author', 'lastRunTime', 'totalVotes']
+ if kernels:
+ if csv_display:
+ self.print_csv(kernels, fields)
+ else:
+ self.print_table(kernels, fields)
+ else:
+ print('Not found')
+
+ def kernels_initialize(self, folder):
+ """ create a new kernel in a specified folder from template, including
+ json metadata that grabs values from the configuration.
+ Parameters
+ ==========
+ folder: the path of the folder
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ resources = []
+ resource = {'path': 'INSERT_SCRIPT_PATH_HERE'}
+ resources.append(resource)
+
+ username = self.get_config_value(self.CONFIG_NAME_USER)
+ meta_data = {
+ 'id':
+ username + '/INSERT_KERNEL_SLUG_HERE',
+ 'title':
+ 'INSERT_TITLE_HERE',
+ 'code_file':
+ 'INSERT_CODE_FILE_PATH_HERE',
+ 'language':
+ 'Pick one of: {' +
+ ','.join(x for x in self.valid_push_language_types) + '}',
+ 'kernel_type':
+ 'Pick one of: {' +
+ ','.join(x for x in self.valid_push_kernel_types) + '}',
+ 'is_private':
+ 'true',
+ 'enable_gpu':
+ 'false',
+ 'enable_tpu':
+ 'false',
+ 'enable_internet':
+ 'true',
+ 'dataset_sources': [],
+ 'competition_sources': [],
+ 'kernel_sources': [],
+ 'model_sources': [],
+ }
+ meta_file = os.path.join(folder, self.KERNEL_METADATA_FILE)
+ with open(meta_file, 'w') as f:
+ json.dump(meta_data, f, indent=2)
+
+ return meta_file
+
+ def kernels_initialize_cli(self, folder=None):
+ """ client wrapper for kernels_initialize, takes same arguments but
+ sets default folder to be None. If None, defaults to present
+ working directory.
+ Parameters
+ ==========
+ folder: the path of the folder (None defaults to ${PWD})
+ """
+ folder = folder or os.getcwd()
+ meta_file = self.kernels_initialize(folder)
+ print('Kernel metadata template written to: ' + meta_file)
+
+ def kernels_push(self, folder):
+ """ read the metadata file and kernel files from a notebook, validate
+ both, and use Kernel API to push to Kaggle if all is valid.
+ Parameters
+ ==========
+ folder: the path of the folder
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = os.path.join(folder, self.KERNEL_METADATA_FILE)
+ if not os.path.isfile(meta_file):
+ raise ValueError('Metadata file not found: ' +
+ self.KERNEL_METADATA_FILE)
+
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+
+ title = self.get_or_default(meta_data, 'title', None)
+ if title and len(title) < 5:
+ raise ValueError('Title must be at least five characters')
+
+ code_path = self.get_or_default(meta_data, 'code_file', '')
+ if not code_path:
+ raise ValueError('A source file must be specified in the metadata')
+
+ code_file = os.path.join(folder, code_path)
+ if not os.path.isfile(code_file):
+ raise ValueError('Source file not found: ' + code_file)
+
+ slug = meta_data.get('id')
+ id_no = meta_data.get('id_no')
+ if not slug and not id_no:
+ raise ValueError('ID or slug must be specified in the metadata')
+ if slug:
+ self.validate_kernel_string(slug)
+ if '/' in slug:
+ kernel_slug = slug.split('/')[1]
+ else:
+ kernel_slug = slug
+ if title:
+ as_slug = slugify(title)
+ if kernel_slug.lower() != as_slug:
+ print(
+ 'Your kernel title does not resolve to the specified '
+ 'id. This may result in surprising behavior. We '
+ 'suggest making your title something that resolves to '
+ 'the specified id. See %s for more information on '
+ 'how slugs are determined.' %
+ 'https://en.wikipedia.org/wiki/Clean_URL#Slug')
+
+ language = self.get_or_default(meta_data, 'language', '')
+ if language not in self.valid_push_language_types:
+ raise ValueError(
+ 'A valid language must be specified in the metadata. Valid '
+ 'options are ' + str(self.valid_push_language_types))
+
+ kernel_type = self.get_or_default(meta_data, 'kernel_type', '')
+ if kernel_type not in self.valid_push_kernel_types:
+ raise ValueError(
+ 'A valid kernel type must be specified in the metadata. Valid '
+ 'options are ' + str(self.valid_push_kernel_types))
+
+ if kernel_type == 'notebook' and language == 'rmarkdown':
+ language = 'r'
+
+ dataset_sources = self.get_or_default(meta_data, 'dataset_sources', [])
+ for source in dataset_sources:
+ self.validate_dataset_string(source)
+
+ kernel_sources = self.get_or_default(meta_data, 'kernel_sources', [])
+ for source in kernel_sources:
+ self.validate_kernel_string(source)
+
+ model_sources = self.get_or_default(meta_data, 'model_sources', [])
+ for source in model_sources:
+ self.validate_model_string(source)
+
+ docker_pinning_type = self.get_or_default(meta_data,
+ 'docker_image_pinning_type',
+ None)
+ if (docker_pinning_type is not None
+ and docker_pinning_type not in self.valid_push_pinning_types):
+ raise ValueError(
+ 'If specified, the docker_image_pinning_type must be '
+ 'one of ' + str(self.valid_push_pinning_types))
+
+ with open(code_file) as f:
+ script_body = f.read()
+
+ if kernel_type == 'notebook':
+ json_body = json.loads(script_body)
+ if 'cells' in json_body:
+ for cell in json_body['cells']:
+ if 'outputs' in cell and cell['cell_type'] == 'code':
+ cell['outputs'] = []
+ script_body = json.dumps(json_body)
+
+ kernel_push_request = KernelPushRequest(
+ id=id_no,
+ slug=slug,
+ new_title=self.get_or_default(meta_data, 'title', None),
+ text=script_body,
+ language=language,
+ kernel_type=kernel_type,
+ is_private=self.get_or_default(meta_data, 'is_private', None),
+ enable_gpu=self.get_or_default(meta_data, 'enable_gpu', None),
+ enable_tpu=self.get_or_default(meta_data, 'enable_tpu', None),
+ enable_internet=self.get_or_default(meta_data, 'enable_internet',
+ None),
+ dataset_data_sources=dataset_sources,
+ competition_data_sources=self.get_or_default(
+ meta_data, 'competition_sources', []),
+ kernel_data_sources=kernel_sources,
+ model_data_sources=model_sources,
+ category_ids=self.get_or_default(meta_data, 'keywords', []),
+ docker_image_pinning_type=docker_pinning_type)
+
+ result = KernelPushResponse(
+ self.process_response(
+ self.kernel_push_with_http_info(
+ kernel_push_request=kernel_push_request)))
+ return result
+
+ def kernels_push_cli(self, folder):
+ """ client wrapper for kernels_push, with same arguments.
+ """
+ folder = folder or os.getcwd()
+ result = self.kernels_push(folder)
+
+ if result is None:
+ print('Kernel push error: see previous output')
+ elif not result.error:
+ if result.invalidTags:
+ print(
+ 'The following are not valid tags and could not be added '
+ 'to the kernel: ' + str(result.invalidTags))
+ if result.invalidDatasetSources:
+ print(
+ 'The following are not valid dataset sources and could not '
+ 'be added to the kernel: ' +
+ str(result.invalidDatasetSources))
+ if result.invalidCompetitionSources:
+ print(
+ 'The following are not valid competition sources and could '
+ 'not be added to the kernel: ' +
+ str(result.invalidCompetitionSources))
+ if result.invalidKernelSources:
+ print(
+ 'The following are not valid kernel sources and could not '
+ 'be added to the kernel: ' +
+ str(result.invalidKernelSources))
+
+ if result.versionNumber:
+ print('Kernel version %s successfully pushed. Please check '
+ 'progress at %s' % (result.versionNumber, result.url))
+ else:
+ # Shouldn't happen but didn't test exhaustively
+ print('Kernel version successfully pushed. Please check '
+ 'progress at %s' % result.url)
+ else:
+ print('Kernel push error: ' + result.error)
+
+ def kernels_pull(self, kernel, path, metadata=False, quiet=True):
+ """ pull a kernel, including a metadata file (if metadata is True)
+ and associated files to a specified path.
+ Parameters
+ ==========
+ kernel: the kernel to pull
+ path: the path to pull files to on the filesystem
+ metadata: if True, also pull metadata
+ quiet: suppress verbosity (default is True)
+ """
+ existing_metadata = None
+ if kernel is None:
+ if path is None:
+ existing_metadata_path = os.path.join(
+ os.getcwd(), self.KERNEL_METADATA_FILE)
+ else:
+ existing_metadata_path = os.path.join(
+ path, self.KERNEL_METADATA_FILE)
+ if os.path.exists(existing_metadata_path):
+ with open(existing_metadata_path) as f:
+ existing_metadata = json.load(f)
+ kernel = existing_metadata['id']
+ if 'INSERT_KERNEL_SLUG_HERE' in kernel:
+ raise ValueError('A kernel must be specified')
+ else:
+ print('Using kernel ' + kernel)
+
+ if '/' in kernel:
+ self.validate_kernel_string(kernel)
+ kernel_url_list = kernel.split('/')
+ owner_slug = kernel_url_list[0]
+ kernel_slug = kernel_url_list[1]
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ kernel_slug = kernel
+
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'kernels', owner_slug, kernel_slug)
+ else:
+ effective_path = path
+
+ if not os.path.exists(effective_path):
+ os.makedirs(effective_path)
+
+ response = self.process_response(
+ self.kernel_pull_with_http_info(owner_slug, kernel_slug))
+ blob = response['blob']
+
+ if os.path.isfile(effective_path):
+ effective_dir = os.path.dirname(effective_path)
+ else:
+ effective_dir = effective_path
+ metadata_path = os.path.join(effective_dir, self.KERNEL_METADATA_FILE)
+
+ if not os.path.isfile(effective_path):
+ language = blob['language'].lower()
+ kernel_type = blob['kernelType'].lower()
+
+ file_name = None
+ if existing_metadata:
+ file_name = existing_metadata['code_file']
+ elif os.path.isfile(metadata_path):
+ with open(metadata_path) as f:
+ file_name = json.load(f)['code_file']
+
+ if not file_name or file_name == "INSERT_CODE_FILE_PATH_HERE":
+ extension = None
+ if kernel_type == 'script':
+ if language == 'python':
+ extension = '.py'
+ elif language == 'r':
+ extension = '.R'
+ elif language == 'rmarkdown':
+ extension = '.Rmd'
+ elif language == 'sqlite':
+ extension = '.sql'
+ elif language == 'julia':
+ extension = '.jl'
+ elif kernel_type == 'notebook':
+ if language == 'python':
+ extension = '.ipynb'
+ elif language == 'r':
+ extension = '.irnb'
+ elif language == 'julia':
+ extension = '.ijlnb'
+ file_name = blob['slug'] + extension
+
+ if file_name is None:
+ print(
+ 'Unknown language %s + kernel type %s - please report this '
+ 'on the kaggle-api github issues' %
+ (language, kernel_type))
+ print(
+ 'Saving as a python file, even though this may not be the '
+ 'correct language')
+ file_name = 'script.py'
+ script_path = os.path.join(effective_path, file_name)
+ else:
+ script_path = effective_path
+ file_name = os.path.basename(effective_path)
+
+ with open(script_path, 'w', encoding="utf-8") as f:
+ f.write(blob['source'])
+
+ if metadata:
+ data = {}
+
+ server_metadata = response['metadata']
+ data['id'] = server_metadata['ref']
+ data['id_no'] = server_metadata['id']
+ data['title'] = server_metadata['title']
+ data['code_file'] = file_name
+ data['language'] = server_metadata['language']
+ data['kernel_type'] = server_metadata['kernelType']
+ self.set_if_present(server_metadata, 'isPrivate', data,
+ 'is_private')
+ self.set_if_present(server_metadata, 'enableGpu', data,
+ 'enable_gpu')
+ self.set_if_present(server_metadata, 'enableTpu', data,
+ 'enable_tpu')
+ self.set_if_present(server_metadata, 'enableInternet', data,
+ 'enable_internet')
+ self.set_if_present(server_metadata, 'categoryIds', data,
+ 'keywords')
+ self.set_if_present(server_metadata, 'datasetDataSources', data,
+ 'dataset_sources')
+ self.set_if_present(server_metadata, 'kernelDataSources', data,
+ 'kernel_sources')
+ self.set_if_present(server_metadata, 'competitionDataSources',
+ data, 'competition_sources')
+ self.set_if_present(server_metadata, 'modelDataSources', data,
+ 'model_sources')
+ with open(metadata_path, 'w') as f:
+ json.dump(data, f, indent=2)
+
+ return effective_dir
+ else:
+ return script_path
+
+ def kernels_pull_cli(self,
+ kernel,
+ kernel_opt=None,
+ path=None,
+ metadata=False):
+ """ client wrapper for kernels_pull
+ """
+ kernel = kernel or kernel_opt
+ effective_path = self.kernels_pull(kernel,
+ path=path,
+ metadata=metadata,
+ quiet=False)
+ if metadata:
+ print('Source code and metadata downloaded to ' + effective_path)
+ else:
+ print('Source code downloaded to ' + effective_path)
+
+ def kernels_output(self, kernel, path, force=False, quiet=True):
+ """ retrieve output for a specified kernel
+ Parameters
+ ==========
+ kernel: the kernel to output
+ path: the path to pull files to on the filesystem
+ force: if output already exists, force overwrite (default False)
+ quiet: suppress verbosity (default is True)
+ """
+ if kernel is None:
+ raise ValueError('A kernel must be specified')
+ if '/' in kernel:
+ self.validate_kernel_string(kernel)
+ kernel_url_list = kernel.split('/')
+ owner_slug = kernel_url_list[0]
+ kernel_slug = kernel_url_list[1]
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ kernel_slug = kernel
+
+ if path is None:
+ target_dir = self.get_default_download_dir('kernels', owner_slug,
+ kernel_slug, 'output')
+ else:
+ target_dir = path
+
+ if not os.path.exists(target_dir):
+ os.makedirs(target_dir)
+
+ if not os.path.isdir(target_dir):
+ raise ValueError(
+ 'You must specify a directory for the kernels output')
+
+ response = self.process_response(
+ self.kernel_output_with_http_info(owner_slug, kernel_slug))
+ outfiles = []
+ for item in response['files']:
+ outfile = os.path.join(target_dir, item['fileName'])
+ outfiles.append(outfile)
+ download_response = requests.get(item['url'])
+ if force or self.download_needed(item, outfile, quiet):
+ os.makedirs(os.path.split(outfile)[0], exist_ok=True)
+ with open(outfile, 'wb') as out:
+ out.write(download_response.content)
+ if not quiet:
+ print('Output file downloaded to %s' % outfile)
+
+ log = response['log']
+ if log:
+ outfile = os.path.join(target_dir, kernel_slug + '.log')
+ outfiles.append(outfile)
+ with open(outfile, 'w') as out:
+ out.write(log)
+ if not quiet:
+ print('Kernel log downloaded to %s ' % outfile)
+
+ return outfiles
+
+ def kernels_output_cli(self,
+ kernel,
+ kernel_opt=None,
+ path=None,
+ force=False,
+ quiet=False):
+ """ client wrapper for kernels_output, with same arguments. Extra
+ arguments are described below, and see kernels_output for others.
+ Parameters
+ ==========
+ kernel_opt: option from client instead of kernel, if not defined
+ """
+ kernel = kernel or kernel_opt
+ self.kernels_output(kernel, path, force, quiet)
+
+ def kernels_status(self, kernel):
+ """ call to the api to get the status of a kernel.
+ Parameters
+ ==========
+ kernel: the kernel to get the status for
+ """
+ if kernel is None:
+ raise ValueError('A kernel must be specified')
+ if '/' in kernel:
+ self.validate_kernel_string(kernel)
+ kernel_url_list = kernel.split('/')
+ owner_slug = kernel_url_list[0]
+ kernel_slug = kernel_url_list[1]
+ else:
+ owner_slug = self.get_config_value(self.CONFIG_NAME_USER)
+ kernel_slug = kernel
+ response = self.process_response(
+ self.kernel_status_with_http_info(owner_slug, kernel_slug))
+ return response
+
+ def kernels_status_cli(self, kernel, kernel_opt=None):
+ """ client wrapper for kernel_status
+ Parameters
+ ==========
+ kernel_opt: additional option from the client, if kernel not defined
+ """
+ kernel = kernel or kernel_opt
+ response = self.kernels_status(kernel)
+ status = response['status']
+ message = response['failureMessage']
+ if message:
+ print('%s has status "%s"' % (kernel, status))
+ print('Failure message: "%s"' % message)
+ else:
+ print('%s has status "%s"' % (kernel, status))
+
+ def model_get(self, model):
+ """ call to get a model from the API
+ Parameters
+ ==========
+ model: the string identified of the model
+ should be in format [owner]/[model-name]
+ """
+ owner_slug, model_slug = self.split_model_string(model)
+
+ model_get_result = self.process_response(
+ self.get_model_with_http_info(owner_slug, model_slug))
+ return model_get_result
+
+ def model_get_cli(self, model, folder=None):
+ """ wrapper for client for model_get, with additional
+ model_opt to get a model from the API
+ Parameters
+ ==========
+ model: the string identified of the model
+ should be in format [owner]/[model-name]
+ folder: the folder to download the model metadata file
+ """
+ model = self.model_get(model)
+ if folder is None:
+ self.print_obj(model)
+ else:
+ meta_file = os.path.join(folder, self.MODEL_METADATA_FILE)
+
+ data = {}
+ data['id'] = model['id']
+ model_ref_split = model['ref'].split('/')
+ data['ownerSlug'] = model_ref_split[0]
+ data['slug'] = model_ref_split[1]
+ data['title'] = model['title']
+ data['subtitle'] = model['subtitle']
+ data['isPrivate'] = model['isPrivate']
+ data['description'] = model['description']
+ data['publishTime'] = model['publishTime']
+
+ with open(meta_file, 'w') as f:
+ json.dump(data, f, indent=2)
+ print('Metadata file written to {}'.format(meta_file))
+
+ def model_list(self,
+ sort_by=None,
+ search=None,
+ owner=None,
+ page_size=20,
+ page_token=None):
+ """ return a list of models!
+
+ Parameters
+ ==========
+ sort_by: how to sort the result, see valid_model_sort_bys for options
+ search: a search term to use (default is empty string)
+ owner: username or organization slug to filter the search to
+ page_size: the page size to return (default is 20)
+ page_token: the page token for pagination
+ """
+ if sort_by and sort_by not in self.valid_model_sort_bys:
+ raise ValueError('Invalid sort by specified. Valid options are ' +
+ str(self.valid_model_sort_bys))
+
+ if int(page_size) <= 0:
+ raise ValueError('Page size must be >= 1')
+
+ models_list_result = self.process_response(
+ self.models_list_with_http_info(sort_by=sort_by or 'hotness',
+ search=search or '',
+ owner=owner or '',
+ page_size=page_size,
+ page_token=page_token))
+
+ next_page_token = models_list_result['nextPageToken']
+ if next_page_token != '':
+ print('Next Page Token = {}'.format(next_page_token))
+
+ return [Model(m) for m in models_list_result['models']]
+
+ def model_list_cli(self,
+ sort_by=None,
+ search=None,
+ owner=None,
+ page_size=20,
+ page_token=None,
+ csv_display=False):
+ """ a wrapper to model_list for the client. Additional parameters
+ are described here, see model_list for others.
+
+ Parameters
+ ==========
+ sort_by: how to sort the result, see valid_model_sort_bys for options
+ search: a search term to use (default is empty string)
+ owner: username or organization slug to filter the search to
+ page_size: the page size to return (default is 20)
+ page_token: the page token for pagination
+ csv_display: if True, print comma separated values instead of table
+ """
+ models = self.model_list(sort_by, search, owner, page_size, page_token)
+ fields = ['id', 'ref', 'title', 'subtitle', 'author']
+ if models:
+ if csv_display:
+ self.print_csv(models, fields)
+ else:
+ self.print_table(models, fields)
+ else:
+ print('No models found')
+
+ def model_initialize(self, folder):
+ """ initialize a folder with a model configuration (metadata) file
+ Parameters
+ ==========
+ folder: the folder to initialize the metadata file in
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_data = {
+ 'ownerSlug': 'INSERT_OWNER_SLUG_HERE',
+ 'title': 'INSERT_TITLE_HERE',
+ 'slug': 'INSERT_SLUG_HERE',
+ 'subtitle': '',
+ 'isPrivate': True,
+ 'description': '''# Model Summary
+
+# Model Characteristics
+
+# Data Overview
+
+# Evaluation Results
+''',
+ 'publishTime': '',
+ 'provenanceSources': ''
+ }
+ meta_file = os.path.join(folder, self.MODEL_METADATA_FILE)
+ with open(meta_file, 'w') as f:
+ json.dump(meta_data, f, indent=2)
+
+ print('Model template written to: ' + meta_file)
+ return meta_file
+
+ def model_initialize_cli(self, folder=None):
+ folder = folder or os.getcwd()
+ self.model_initialize(folder)
+
+ def model_create_new(self, folder):
+ """ create a new model.
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_model_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ owner_slug = self.get_or_fail(meta_data, 'ownerSlug')
+ slug = self.get_or_fail(meta_data, 'slug')
+ title = self.get_or_fail(meta_data, 'title')
+ subtitle = meta_data.get('subtitle')
+ is_private = self.get_or_fail(meta_data, 'isPrivate')
+ description = self.sanitize_markdown(
+ self.get_or_fail(meta_data, 'description'))
+ publish_time = meta_data.get('publishTime')
+ provenance_sources = meta_data.get('provenanceSources')
+
+ # validations
+ if owner_slug == 'INSERT_OWNER_SLUG_HERE':
+ raise ValueError(
+ 'Default ownerSlug detected, please change values before uploading'
+ )
+ if title == 'INSERT_TITLE_HERE':
+ raise ValueError(
+ 'Default title detected, please change values before uploading'
+ )
+ if slug == 'INSERT_SLUG_HERE':
+ raise ValueError(
+ 'Default slug detected, please change values before uploading')
+ if not isinstance(is_private, bool):
+ raise ValueError('model.isPrivate must be a boolean')
+ if publish_time:
+ self.validate_date(publish_time)
+
+ request = ModelNewRequest(owner_slug=owner_slug,
+ slug=slug,
+ title=title,
+ subtitle=subtitle,
+ is_private=is_private,
+ description=description,
+ publish_time=publish_time,
+ provenance_sources=provenance_sources)
+ result = ModelNewResponse(
+ self.process_response(
+ self.models_create_new_with_http_info(request)))
+
+ return result
+
+ def model_create_new_cli(self, folder=None):
+ """ client wrapper for creating a new model
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ folder = folder or os.getcwd()
+ result = self.model_create_new(folder)
+
+ if result.hasId:
+ print('Your model was created. Id={}. Url={}'.format(
+ result.id, result.url))
+ else:
+ print('Model creation error: ' + result.error)
+
+ def model_delete(self, model, yes):
+ """ call to delete a model from the API
+ Parameters
+ ==========
+ model: the string identified of the model
+ should be in format [owner]/[model-name]
+ yes: automatic confirmation
+ """
+ owner_slug, model_slug = self.split_model_string(model)
+
+ if not yes:
+ if not self.confirmation():
+ print('Deletion cancelled')
+ exit(0)
+
+ res = ModelDeleteResponse(
+ self.process_response(
+ self.delete_model_with_http_info(owner_slug, model_slug)))
+ return res
+
+ def model_delete_cli(self, model, yes):
+ """ wrapper for client for model_delete
+ Parameters
+ ==========
+ model: the string identified of the model
+ should be in format [owner]/[model-name]
+ yes: automatic confirmation
+ """
+ result = self.model_delete(model, yes)
+
+ if result.hasError:
+ print('Model deletion error: ' + result.error)
+ else:
+ print('The model was deleted.')
+
+ def model_update(self, folder):
+ """ update a model.
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_model_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ owner_slug = self.get_or_fail(meta_data, 'ownerSlug')
+ slug = self.get_or_fail(meta_data, 'slug')
+ title = self.get_or_default(meta_data, 'title', None)
+ subtitle = self.get_or_default(meta_data, 'subtitle', None)
+ is_private = self.get_or_default(meta_data, 'isPrivate', None)
+ description = self.get_or_default(meta_data, 'description', None)
+ publish_time = self.get_or_default(meta_data, 'publishTime', None)
+ provenance_sources = self.get_or_default(meta_data,
+ 'provenanceSources', None)
+
+ # validations
+ if owner_slug == 'INSERT_OWNER_SLUG_HERE':
+ raise ValueError(
+ 'Default ownerSlug detected, please change values before uploading'
+ )
+ if slug == 'INSERT_SLUG_HERE':
+ raise ValueError(
+ 'Default slug detected, please change values before uploading')
+ if is_private != None and not isinstance(is_private, bool):
+ raise ValueError('model.isPrivate must be a boolean')
+ if publish_time:
+ self.validate_date(publish_time)
+
+ # mask
+ update_mask = {'paths': []}
+ if title != None:
+ update_mask['paths'].append('title')
+ if subtitle != None:
+ update_mask['paths'].append('subtitle')
+ if is_private != None:
+ update_mask['paths'].append('is_private')
+ else:
+ is_private = True # default value, not updated
+ if description != None:
+ description = self.sanitize_markdown(description)
+ update_mask['paths'].append('description')
+ if publish_time != None:
+ update_mask['paths'].append('publish_time')
+ if provenance_sources != None:
+ update_mask['paths'].append('provenance_sources')
+
+ request = ModelUpdateRequest(title=title,
+ subtitle=subtitle,
+ is_private=is_private,
+ description=description,
+ publish_time=publish_time,
+ provenance_sources=provenance_sources,
+ update_mask=update_mask)
+ result = ModelNewResponse(
+ self.process_response(
+ self.update_model_with_http_info(owner_slug, slug, request)))
+
+ return result
+
+ def model_update_cli(self, folder=None):
+ """ client wrapper for updating a model
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ folder = folder or os.getcwd()
+ result = self.model_update(folder)
+
+ if result.hasId:
+ print('Your model was updated. Id={}. Url={}'.format(
+ result.id, result.url))
+ else:
+ print('Model update error: ' + result.error)
+
+ def model_instance_get(self, model_instance):
+ """ call to get a model instance from the API
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ """
+ if model_instance is None:
+ raise ValueError('A model instance must be specified')
+ owner_slug, model_slug, framework, instance_slug = self.split_model_instance_string(
+ model_instance)
+
+ mi = self.process_response(
+ self.get_model_instance_with_http_info(owner_slug, model_slug,
+ framework, instance_slug))
+ return mi
+
+ def model_instance_get_cli(self, model_instance, folder=None):
+ """ wrapper for client for model_instance_get
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ folder: the folder to download the model metadata file
+ """
+ mi = self.model_instance_get(model_instance)
+ if folder is None:
+ self.print_obj(mi)
+ else:
+ meta_file = os.path.join(folder, self.MODEL_INSTANCE_METADATA_FILE)
+
+ owner_slug, model_slug, framework, instance_slug = self.split_model_instance_string(
+ model_instance)
+
+ data = {}
+ data['id'] = mi['id']
+ data['ownerSlug'] = owner_slug
+ data['modelSlug'] = model_slug
+ data['instanceSlug'] = mi['slug']
+ data['framework'] = mi['framework']
+ data['overview'] = mi['overview']
+ data['usage'] = mi['usage']
+ data['licenseName'] = mi['licenseName']
+ data['fineTunable'] = mi['fineTunable']
+ data['trainingData'] = mi['trainingData']
+ data['versionId'] = mi['versionId']
+ data['versionNumber'] = mi['versionNumber']
+ data['modelInstanceType'] = mi['modelInstanceType']
+ if mi['baseModelInstanceInformation'] is not None:
+ data['baseModelInstance'] = '{}/{}/{}/{}'.format(
+ mi['baseModelInstanceInformation']['owner']['slug'],
+ mi['baseModelInstanceInformation']['modelSlug'],
+ mi['baseModelInstanceInformation']['framework'],
+ mi['baseModelInstanceInformation']['instanceSlug'])
+ data['externalBaseModelUrl'] = mi['externalBaseModelUrl']
+
+ with open(meta_file, 'w') as f:
+ json.dump(data, f, indent=2)
+ print('Metadata file written to {}'.format(meta_file))
+
+ def model_instance_initialize(self, folder):
+ """ initialize a folder with a model instance configuration (metadata) file
+ Parameters
+ ==========
+ folder: the folder to initialize the metadata file in
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_data = {
+ 'ownerSlug': 'INSERT_OWNER_SLUG_HERE',
+ 'modelSlug': 'INSERT_EXISTING_MODEL_SLUG_HERE',
+ 'instanceSlug': 'INSERT_INSTANCE_SLUG_HERE',
+ 'framework': 'INSERT_FRAMEWORK_HERE',
+ 'overview': '',
+ 'usage': '''# Model Format
+
+# Training Data
+
+# Model Inputs
+
+# Model Outputs
+
+# Model Usage
+
+# Fine-tuning
+
+# Changelog
+''',
+ 'licenseName': 'Apache 2.0',
+ 'fineTunable': False,
+ 'trainingData': [],
+ 'modelInstanceType': 'Unspecified',
+ 'baseModelInstanceId': 0,
+ 'externalBaseModelUrl': ''
+ }
+ meta_file = os.path.join(folder, self.MODEL_INSTANCE_METADATA_FILE)
+ with open(meta_file, 'w') as f:
+ json.dump(meta_data, f, indent=2)
+
+ print('Model Instance template written to: ' + meta_file)
+ return meta_file
+
+ def model_instance_initialize_cli(self, folder):
+ folder = folder or os.getcwd()
+ self.model_instance_initialize(folder)
+
+ def model_instance_create(self, folder, quiet=False, dir_mode='skip'):
+ """ create a new model instance.
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ quiet: suppress verbose output (default is False)
+ dir_mode: what to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_model_instance_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ owner_slug = self.get_or_fail(meta_data, 'ownerSlug')
+ model_slug = self.get_or_fail(meta_data, 'modelSlug')
+ instance_slug = self.get_or_fail(meta_data, 'instanceSlug')
+ framework = self.get_or_fail(meta_data, 'framework')
+ overview = self.sanitize_markdown(
+ self.get_or_default(meta_data, 'overview', ''))
+ usage = self.sanitize_markdown(
+ self.get_or_default(meta_data, 'usage', ''))
+ license_name = self.get_or_fail(meta_data, 'licenseName')
+ fine_tunable = self.get_or_default(meta_data, 'fineTunable', False)
+ training_data = self.get_or_default(meta_data, 'trainingData', [])
+ model_instance_type = self.get_or_default(meta_data,
+ 'modelInstanceType',
+ 'Unspecified')
+ base_model_instance = self.get_or_default(meta_data,
+ 'baseModelInstance', '')
+ external_base_model_url = self.get_or_default(meta_data,
+ 'externalBaseModelUrl',
+ '')
+
+ # validations
+ if owner_slug == 'INSERT_OWNER_SLUG_HERE':
+ raise ValueError(
+ 'Default ownerSlug detected, please change values before uploading'
+ )
+ if model_slug == 'INSERT_EXISTING_MODEL_SLUG_HERE':
+ raise ValueError(
+ 'Default modelSlug detected, please change values before uploading'
+ )
+ if instance_slug == 'INSERT_INSTANCE_SLUG_HERE':
+ raise ValueError(
+ 'Default instanceSlug detected, please change values before uploading'
+ )
+ if framework == 'INSERT_FRAMEWORK_HERE':
+ raise ValueError(
+ 'Default framework detected, please change values before uploading'
+ )
+ if license_name == '':
+ raise ValueError('Please specify a license')
+ if not isinstance(fine_tunable, bool):
+ raise ValueError('modelInstance.fineTunable must be a boolean')
+ if not isinstance(training_data, list):
+ raise ValueError('modelInstance.trainingData must be a list')
+
+ request = ModelNewInstanceRequest(
+ instance_slug=instance_slug,
+ framework=framework,
+ overview=overview,
+ usage=usage,
+ license_name=license_name,
+ fine_tunable=fine_tunable,
+ training_data=training_data,
+ model_instance_type=model_instance_type,
+ base_model_instance=base_model_instance,
+ external_base_model_url=external_base_model_url,
+ files=[])
+
+ with ResumableUploadContext() as upload_context:
+ self.upload_files(request, None, folder, ApiBlobType.MODEL,
+ upload_context, quiet, dir_mode)
+ result = ModelNewResponse(
+ self.process_response(
+ self.with_retry(
+ self.models_create_instance_with_http_info)(owner_slug,
+ model_slug,
+ request)))
+
+ return result
+
+ def model_instance_create_cli(self, folder, quiet=False, dir_mode='skip'):
+ """ client wrapper for creating a new model instance
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ quiet: suppress verbose output (default is False)
+ dir_mode: what to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ folder = folder or os.getcwd()
+ result = self.model_instance_create(folder, quiet, dir_mode)
+
+ if result.hasId:
+ print('Your model instance was created. Id={}. Url={}'.format(
+ result.id, result.url))
+ else:
+ print('Model instance creation error: ' + result.error)
+
+ def model_instance_delete(self, model_instance, yes):
+ """ call to delete a model instance from the API
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ yes: automatic confirmation
+ """
+ if model_instance is None:
+ raise ValueError('A model instance must be specified')
+ owner_slug, model_slug, framework, instance_slug = self.split_model_instance_string(
+ model_instance)
+
+ if not yes:
+ if not self.confirmation():
+ print('Deletion cancelled')
+ exit(0)
+
+ res = ModelDeleteResponse(
+ self.process_response(
+ self.delete_model_instance_with_http_info(
+ owner_slug, model_slug, framework, instance_slug)))
+ return res
+
+ def model_instance_delete_cli(self, model_instance, yes):
+ """ wrapper for client for model_instance_delete
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ yes: automatic confirmation
+ """
+ result = self.model_instance_delete(model_instance, yes)
+
+ if result.hasError:
+ print('Model instance deletion error: ' + result.error)
+ else:
+ print('The model instance was deleted.')
+
+ def model_instance_update(self, folder):
+ """ update a model instance.
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ if not os.path.isdir(folder):
+ raise ValueError('Invalid folder: ' + folder)
+
+ meta_file = self.get_model_instance_metadata_file(folder)
+
+ # read json
+ with open(meta_file) as f:
+ meta_data = json.load(f)
+ owner_slug = self.get_or_fail(meta_data, 'ownerSlug')
+ model_slug = self.get_or_fail(meta_data, 'modelSlug')
+ framework = self.get_or_fail(meta_data, 'framework')
+ instance_slug = self.get_or_fail(meta_data, 'instanceSlug')
+ overview = self.get_or_default(meta_data, 'overview', None)
+ usage = self.get_or_default(meta_data, 'usage', None)
+ license_name = self.get_or_default(meta_data, 'licenseName', None)
+ fine_tunable = self.get_or_default(meta_data, 'fineTunable', None)
+ training_data = self.get_or_default(meta_data, 'trainingData', None)
+ model_instance_type = self.get_or_default(meta_data,
+ 'modelInstanceType', None)
+ base_model_instance = self.get_or_default(meta_data,
+ 'baseModelInstance', None)
+ external_base_model_url = self.get_or_default(meta_data,
+ 'externalBaseModelUrl',
+ None)
+
+ # validations
+ if owner_slug == 'INSERT_OWNER_SLUG_HERE':
+ raise ValueError(
+ 'Default ownerSlug detected, please change values before uploading'
+ )
+ if model_slug == 'INSERT_SLUG_HERE':
+ raise ValueError(
+ 'Default model slug detected, please change values before uploading'
+ )
+ if instance_slug == 'INSERT_INSTANCE_SLUG_HERE':
+ raise ValueError(
+ 'Default instance slug detected, please change values before uploading'
+ )
+ if framework == 'INSERT_FRAMEWORK_HERE':
+ raise ValueError(
+ 'Default framework detected, please change values before uploading'
+ )
+ if fine_tunable != None and not isinstance(fine_tunable, bool):
+ raise ValueError('modelInstance.fineTunable must be a boolean')
+ if training_data != None and not isinstance(training_data, list):
+ raise ValueError('modelInstance.trainingData must be a list')
+
+ # mask
+ update_mask = {'paths': []}
+ if overview != None:
+ overview = self.sanitize_markdown(overview)
+ update_mask['paths'].append('overview')
+ if usage != None:
+ usage = self.sanitize_markdown(usage)
+ update_mask['paths'].append('usage')
+ if license_name != None:
+ update_mask['paths'].append('license_name')
+ else:
+ license_name = "Apache 2.0" # default value even if not updated
+ if fine_tunable != None:
+ update_mask['paths'].append('fine_tunable')
+ if training_data != None:
+ update_mask['paths'].append('training_data')
+ if model_instance_type != None:
+ update_mask['paths'].append('model_instance_type')
+ if base_model_instance != None:
+ update_mask['paths'].append('base_model_instance')
+ if external_base_model_url != None:
+ update_mask['paths'].append('external_base_model_url')
+
+ request = ModelInstanceUpdateRequest(
+ overview=overview,
+ usage=usage,
+ license_name=license_name,
+ fine_tunable=fine_tunable,
+ training_data=training_data,
+ model_instance_type=model_instance_type,
+ base_model_instance=base_model_instance,
+ external_base_model_url=external_base_model_url,
+ update_mask=update_mask)
+ result = ModelNewResponse(
+ self.process_response(
+ self.update_model_instance_with_http_info(
+ owner_slug, model_slug, framework, instance_slug,
+ request)))
+
+ return result
+
+ def model_instance_update_cli(self, folder=None):
+ """ client wrapper for updating a model instance
+ Parameters
+ ==========
+ folder: the folder to get the metadata file from
+ """
+ folder = folder or os.getcwd()
+ result = self.model_instance_update(folder)
+
+ if result.hasId:
+ print('Your model instance was updated. Id={}. Url={}'.format(
+ result.id, result.url))
+ else:
+ print('Model update error: ' + result.error)
+
+ def model_instance_version_create(self,
+ model_instance,
+ folder,
+ version_notes='',
+ quiet=False,
+ dir_mode='skip'):
+ """ create a new model instance version.
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ folder: the folder to get the metadata file from
+ version_notes: the version notes to record for this new version
+ quiet: suppress verbose output (default is False)
+ dir_mode: what to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ owner_slug, model_slug, framework, instance_slug = self.split_model_instance_string(
+ model_instance)
+
+ request = ModelInstanceNewVersionRequest(version_notes=version_notes,
+ files=[])
+
+ with ResumableUploadContext() as upload_context:
+ self.upload_files(request, None, folder, ApiBlobType.MODEL,
+ upload_context, quiet, dir_mode)
+ result = ModelNewResponse(
+ self.process_response(
+ self.with_retry(
+ self.models_create_instance_version_with_http_info)(
+ owner_slug, model_slug, framework, instance_slug,
+ request)))
+
+ return result
+
+ def model_instance_version_create_cli(self,
+ model_instance,
+ folder,
+ version_notes='',
+ quiet=False,
+ dir_mode='skip'):
+ """ client wrapper for creating a new model instance version
+ Parameters
+ ==========
+ model_instance: the string identified of the model instance
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]
+ folder: the folder to get the metadata file from
+ version_notes: the version notes to record for this new version
+ quiet: suppress verbose output (default is False)
+ dir_mode: what to do with directories: "skip" - ignore; "zip" - compress and upload
+ """
+ result = self.model_instance_version_create(model_instance, folder,
+ version_notes, quiet,
+ dir_mode)
+
+ if result.hasId:
+ print('Your model instance version was created. Url={}'.format(
+ result.url))
+ else:
+ print('Model instance version creation error: ' + result.error)
+
+ def model_instance_version_download(self,
+ model_instance_version,
+ path=None,
+ force=False,
+ quiet=True,
+ untar=False):
+ """ download all files for a model instance version
+
+ Parameters
+ ==========
+ model_instance_version: the string identified of the model instance version
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]/[version-number]
+ path: the path to download the model instance version to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is True)
+ untar: if True, untar files upon download (default is False)
+ """
+ if model_instance_version is None:
+ raise ValueError('A model_instance_version must be specified')
+
+ self.validate_model_instance_version_string(model_instance_version)
+ urls = model_instance_version.split('/')
+ owner_slug = urls[0]
+ model_slug = urls[1]
+ framework = urls[2]
+ instance_slug = urls[3]
+ version_number = urls[4]
+
+ if path is None:
+ effective_path = self.get_default_download_dir(
+ 'models', owner_slug, model_slug, framework, instance_slug,
+ version_number)
+ else:
+ effective_path = path
+
+ response = self.process_response(
+ self.model_instance_versions_download_with_http_info(
+ owner_slug=owner_slug,
+ model_slug=model_slug,
+ framework=framework,
+ instance_slug=instance_slug,
+ version_number=version_number,
+ _preload_content=False))
+
+ outfile = os.path.join(effective_path, model_slug + '.tar.gz')
+ if force or self.download_needed(response, outfile, quiet):
+ self.download_file(response, outfile, quiet, not force)
+ downloaded = True
+ else:
+ downloaded = False
+
+ if downloaded:
+ if untar:
+ try:
+ with tarfile.open(outfile, mode='r:gz') as t:
+ t.extractall(effective_path)
+ except Exception as e:
+ raise ValueError(
+ 'Error extracting the tar.gz file, please report on '
+ 'www.github.com/kaggle/kaggle-api', e)
+
+ try:
+ os.remove(outfile)
+ except OSError as e:
+ print('Could not delete tar file, got %s' % e)
+ return outfile
+
+ def model_instance_version_download_cli(self,
+ model_instance_version,
+ path=None,
+ untar=False,
+ force=False,
+ quiet=False):
+ """ client wrapper for model_instance_version_download.
+
+ Parameters
+ ==========
+ model_instance_version: the string identified of the model instance version
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]/[version-number]
+ path: the path to download the model instance version to
+ force: force the download if the file already exists (default False)
+ quiet: suppress verbose output (default is False)
+ untar: if True, untar files upon download (default is False)
+ """
+ return self.model_instance_version_download(model_instance_version,
+ path=path,
+ untar=untar,
+ force=force,
+ quiet=quiet)
+
+ def model_instance_version_delete(self, model_instance_version, yes):
+ """ call to delete a model instance version from the API
+ Parameters
+ ==========
+ model_instance_version: the string identified of the model instance version
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]/[version-number]
+ yes: automatic confirmation
+ """
+ if model_instance_version is None:
+ raise ValueError('A model instance version must be specified')
+
+ self.validate_model_instance_version_string(model_instance_version)
+ urls = model_instance_version.split('/')
+ owner_slug = urls[0]
+ model_slug = urls[1]
+ framework = urls[2]
+ instance_slug = urls[3]
+ version_number = urls[4]
+
+ if not yes:
+ if not self.confirmation():
+ print('Deletion cancelled')
+ exit(0)
+
+ res = ModelDeleteResponse(
+ self.process_response(
+ self.delete_model_instance_version_with_http_info(
+ owner_slug, model_slug, framework, instance_slug,
+ version_number)))
+ return res
+
+ def model_instance_version_delete_cli(self, model_instance_version, yes):
+ """ wrapper for client for model_instance_version_delete
+ Parameters
+ ==========
+ model_instance_version: the string identified of the model instance version
+ should be in format [owner]/[model-name]/[framework]/[instance-slug]/[version-number]
+ yes: automatic confirmation
+ """
+ result = self.model_instance_version_delete(model_instance_version,
+ yes)
+
+ if result.hasError:
+ print('Model instance version deletion error: ' + result.error)
+ else:
+ print('The model instance version was deleted.')
+
+ def files_upload_cli(self, local_paths, inbox_path, no_resume,
+ no_compress):
+ if len(local_paths) > self.MAX_NUM_INBOX_FILES_TO_UPLOAD:
+ print('Cannot upload more than %d files!' %
+ self.MAX_NUM_INBOX_FILES_TO_UPLOAD)
+ return
+
+ files_to_create = []
+ with ResumableUploadContext(no_resume) as upload_context:
+ for local_path in local_paths:
+ (upload_file,
+ file_name) = self.file_upload_cli(local_path, inbox_path,
+ no_compress, upload_context)
+ if upload_file is None:
+ continue
+
+ create_inbox_file_request = CreateInboxFileRequest(
+ virtual_directory=inbox_path,
+ blob_file_token=upload_file.token)
+ files_to_create.append((create_inbox_file_request, file_name))
+
+ for (create_inbox_file_request, file_name) in files_to_create:
+ self.process_response(
+ self.with_retry(
+ self.create_inbox_file)(create_inbox_file_request))
+ print('Inbox file created:', file_name)
+
+ def file_upload_cli(self, local_path, inbox_path, no_compress,
+ upload_context):
+ full_path = os.path.abspath(local_path)
+ parent_path = os.path.dirname(full_path)
+ file_or_folder_name = os.path.basename(full_path)
+ dir_mode = 'tar' if no_compress else 'zip'
+
+ upload_file = self._upload_file_or_folder(parent_path,
+ file_or_folder_name,
+ ApiBlobType.INBOX,
+ upload_context, dir_mode)
+ return (upload_file, file_or_folder_name)
+
+ def print_obj(self, obj, indent=2):
+ pretty = json.dumps(obj, indent=indent)
+ print(pretty)
+
+ def download_needed(self, response, outfile, quiet=True):
+ """ determine if a download is needed based on timestamp. Return True
+ if needed (remote is newer) or False if local is newest.
+ Parameters
+ ==========
+ response: the response from the API
+ outfile: the output file to write to
+ quiet: suppress verbose output (default is True)
+ """
+ try:
+ remote_date = datetime.strptime(response.headers['Last-Modified'],
+ '%a, %d %b %Y %H:%M:%S %Z')
+ file_exists = os.path.isfile(outfile)
+ if file_exists:
+ local_date = datetime.fromtimestamp(os.path.getmtime(outfile))
+ remote_size = int(response.headers['Content-Length'])
+ local_size = os.path.getsize(outfile)
+ if local_size < remote_size:
+ return True
+ if remote_date <= local_date:
+ if not quiet:
+ print(
+ os.path.basename(outfile) +
+ ': Skipping, found more recently modified local '
+ 'copy (use --force to force download)')
+ return False
+ except:
+ pass
+ return True
+
+ def print_table(self, items, fields):
+ """ print a table of items, for a set of fields defined
+
+ Parameters
+ ==========
+ items: a list of items to print
+ fields: a list of fields to select from items
+ """
+ formats = []
+ borders = []
+ for f in fields:
+ length = max(len(f),
+ max([len(self.string(getattr(i, f))) for i in items]))
+ justify = '>' if isinstance(getattr(
+ items[0], f), int) or f == 'size' or f == 'reward' else '<'
+ formats.append('{:' + justify + self.string(length + 2) + '}')
+ borders.append('-' * length + ' ')
+ row_format = u''.join(formats)
+ headers = [f + ' ' for f in fields]
+ print(row_format.format(*headers))
+ print(row_format.format(*borders))
+ for i in items:
+ i_fields = [self.string(getattr(i, f)) + ' ' for f in fields]
+ try:
+ print(row_format.format(*i_fields))
+ except UnicodeEncodeError:
+ print(row_format.format(*i_fields).encode('utf-8'))
+
+ def print_csv(self, items, fields):
+ """ print a set of fields in a set of items using a csv.writer
+
+ Parameters
+ ==========
+ items: a list of items to print
+ fields: a list of fields to select from items
+ """
+ writer = csv.writer(sys.stdout)
+ writer.writerow(fields)
+ for i in items:
+ i_fields = [self.string(getattr(i, f)) for f in fields]
+ writer.writerow(i_fields)
+
+ def string(self, item):
+ return item if isinstance(item, unicode) else str(item)
+
+ def get_or_fail(self, data, key):
+ if key in data:
+ return data[key]
+ raise ValueError('Key ' + key + ' not found in data')
+
+ def get_or_default(self, data, key, default):
+ if key in data:
+ return data[key]
+ return default
+
+ def set_if_present(self, data, key, output, output_key):
+ if key in data:
+ output[output_key] = data[key]
+
+ def get_dataset_metadata_file(self, folder):
+ meta_file = os.path.join(folder, self.DATASET_METADATA_FILE)
+ if not os.path.isfile(meta_file):
+ meta_file = os.path.join(folder, self.OLD_DATASET_METADATA_FILE)
+ if not os.path.isfile(meta_file):
+ raise ValueError('Metadata file not found: ' +
+ self.DATASET_METADATA_FILE)
+ return meta_file
+
+ def get_model_metadata_file(self, folder):
+ meta_file = os.path.join(folder, self.MODEL_METADATA_FILE)
+ if not os.path.isfile(meta_file):
+ raise ValueError('Metadata file not found: ' +
+ self.MODEL_METADATA_FILE)
+ return meta_file
+
+ def get_model_instance_metadata_file(self, folder):
+ meta_file = os.path.join(folder, self.MODEL_INSTANCE_METADATA_FILE)
+ if not os.path.isfile(meta_file):
+ raise ValueError('Metadata file not found: ' +
+ self.MODEL_INSTANCE_METADATA_FILE)
+ return meta_file
+
+ def process_response(self, result):
+ """ process a response from the API. We check the API version against
+ the client's to see if it's old, and give them a warning (once)
+
+ Parameters
+ ==========
+ result: the result from the API
+ """
+ if len(result) == 3:
+ data = result[0]
+ headers = result[2]
+ if self.HEADER_API_VERSION in headers:
+ api_version = headers[self.HEADER_API_VERSION]
+ if (not self.already_printed_version_warning
+ and not self.is_up_to_date(api_version)):
+ print('Warning: Looks like you\'re using an outdated API '
+ 'Version, please consider updating (server ' +
+ api_version + ' / client ' + self.__version__ + ')')
+ self.already_printed_version_warning = True
+ if isinstance(data,
+ dict) and 'code' in data and data['code'] != 200:
+ raise Exception(data['message'])
+ return data
+ return result
+
+ def is_up_to_date(self, server_version):
+ """ determine if a client (on the local user's machine) is up to date
+ with the version provided on the server. Return a boolean with True
+ or False
+ Parameters
+ ==========
+ server_version: the server version string to compare to the host
+ """
+ client_split = self.__version__.split('.')
+ client_len = len(client_split)
+ server_split = server_version.split('.')
+ server_len = len(server_split)
+
+ # Make both lists the same length
+ for i in range(client_len, server_len):
+ client_split.append('0')
+ for i in range(server_len, client_len):
+ server_split.append('0')
+
+ for i in range(0, client_len):
+ if 'a' in client_split[i] or 'b' in client_split[i]:
+ # Using a alpha/beta version, don't check
+ return True
+ client = int(client_split[i])
+ server = int(server_split[i])
+ if client < server:
+ return False
+ elif server < client:
+ return True
+
+ return True
+
+ def upload_files(self,
+ request,
+ resources,
+ folder,
+ blob_type,
+ upload_context,
+ quiet=False,
+ dir_mode='skip'):
+ """ upload files in a folder
+ Parameters
+ ==========
+ request: the prepared request
+ resources: the files to upload
+ folder: the folder to upload from
+ blob_type (ApiBlobType): To which entity the file/blob refers
+ upload_context (ResumableUploadContext): Context for resumable uploads
+ quiet: suppress verbose output (default is False)
+ """
+ for file_name in os.listdir(folder):
+ if (file_name in [
+ self.DATASET_METADATA_FILE, self.OLD_DATASET_METADATA_FILE,
+ self.KERNEL_METADATA_FILE, self.MODEL_METADATA_FILE,
+ self.MODEL_INSTANCE_METADATA_FILE
+ ]):
+ continue
+ upload_file = self._upload_file_or_folder(folder, file_name,
+ blob_type,
+ upload_context, dir_mode,
+ quiet, resources)
+ if upload_file is not None:
+ request.files.append(upload_file)
+
+ def _upload_file_or_folder(self,
+ parent_path,
+ file_or_folder_name,
+ blob_type,
+ upload_context,
+ dir_mode,
+ quiet=False,
+ resources=None):
+ full_path = os.path.join(parent_path, file_or_folder_name)
+ if os.path.isfile(full_path):
+ return self._upload_file(file_or_folder_name, full_path, blob_type,
+ upload_context, quiet, resources)
+
+ elif os.path.isdir(full_path):
+ if dir_mode in ['zip', 'tar']:
+ with DirectoryArchive(full_path, dir_mode) as archive:
+ return self._upload_file(archive.name, archive.path,
+ blob_type, upload_context, quiet,
+ resources)
+ elif not quiet:
+ print("Skipping folder: " + file_or_folder_name +
+ "; use '--dir-mode' to upload folders")
+ else:
+ if not quiet:
+ print('Skipping: ' + file_or_folder_name)
+ return None
+
+ def _upload_file(self, file_name, full_path, blob_type, upload_context,
+ quiet, resources):
+ """ Helper function to upload a single file
+ Parameters
+ ==========
+ file_name: name of the file to upload
+ full_path: path to the file to upload
+ blob_type (ApiBlobType): To which entity the file/blob refers
+ upload_context (ResumableUploadContext): Context for resumable uploads
+ quiet: suppress verbose output
+ resources: optional file metadata
+ :return: None - upload unsuccessful; instance of UploadFile - upload successful
+ """
+
+ if not quiet:
+ print('Starting upload for file ' + file_name)
+
+ content_length = os.path.getsize(full_path)
+ token = self._upload_blob(full_path, quiet, blob_type, upload_context)
+ if token is None:
+ if not quiet:
+ print('Upload unsuccessful: ' + file_name)
+ return None
+ if not quiet:
+ print('Upload successful: ' + file_name + ' (' +
+ File.get_size(content_length) + ')')
+ upload_file = UploadFile()
+ upload_file.token = token
+ if resources:
+ for item in resources:
+ if file_name == item.get('path'):
+ upload_file.description = item.get('description')
+ if 'schema' in item:
+ fields = self.get_or_default(item['schema'], 'fields',
+ [])
+ processed = []
+ count = 0
+ for field in fields:
+ processed.append(self.process_column(field))
+ processed[count].order = count
+ count += 1
+ upload_file.columns = processed
+ return upload_file
+
+ def process_column(self, column):
+ """ process a column, check for the type, and return the processed
+ column
+ Parameters
+ ==========
+ column: a list of values in a column to be processed
+ """
+ processed_column = DatasetColumn(name=self.get_or_fail(column, 'name'),
+ description=self.get_or_default(
+ column, 'description', ''))
+ if 'type' in column:
+ original_type = column['type'].lower()
+ processed_column.original_type = original_type
+ if (original_type == 'string' or original_type == 'date'
+ or original_type == 'time' or original_type == 'yearmonth'
+ or original_type == 'duration'
+ or original_type == 'geopoint'
+ or original_type == 'geojson'):
+ processed_column.type = 'string'
+ elif (original_type == 'numeric' or original_type == 'number'
+ or original_type == 'year'):
+ processed_column.type = 'numeric'
+ elif original_type == 'boolean':
+ processed_column.type = 'boolean'
+ elif original_type == 'datetime':
+ processed_column.type = 'datetime'
+ else:
+ # Possibly extended data type - not going to try to track those
+ # here. Will set the type and let the server handle it.
+ processed_column.type = original_type
+ return processed_column
+
+ def upload_complete(self, path, url, quiet, resume=False):
+ """ function to complete an upload to retrieve a path from a url
+ Parameters
+ ==========
+ path: the path for the upload that is read in
+ url: the url to send the POST to
+ quiet: suppress verbose output (default is False)
+ """
+ file_size = os.path.getsize(path)
+ resumable_upload_result = ResumableUploadResult.Incomplete()
+
+ try:
+ if resume:
+ resumable_upload_result = self._resume_upload(
+ url, file_size, quiet)
+ if resumable_upload_result.result != ResumableUploadResult.INCOMPLETE:
+ return resumable_upload_result.result
+
+ start_at = resumable_upload_result.start_at
+ upload_size = file_size - start_at
+
+ with tqdm(total=upload_size,
+ unit='B',
+ unit_scale=True,
+ unit_divisor=1024,
+ disable=quiet) as progress_bar:
+ with io.open(path, 'rb', buffering=0) as fp:
+ session = requests.Session()
+ if start_at > 0:
+ fp.seek(start_at)
+ session.headers.update({
+ 'Content-Length':
+ '%d' % upload_size,
+ 'Content-Range':
+ 'bytes %d-%d/%d' %
+ (start_at, file_size - 1, file_size)
+ })
+ reader = TqdmBufferedReader(fp, progress_bar)
+ retries = Retry(total=10, backoff_factor=0.5)
+ adapter = HTTPAdapter(max_retries=retries)
+ session.mount('http://', adapter)
+ session.mount('https://', adapter)
+ response = session.put(url, data=reader)
+ if self._is_upload_successful(response):
+ return ResumableUploadResult.COMPLETE
+ if response.status_code == 503:
+ return ResumableUploadResult.INCOMPLETE
+ # Server returned a non-resumable error so give up.
+ return ResumableUploadResult.FAILED
+ except Exception as error:
+ print(error)
+ # There is probably some weird bug in our code so try to resume the upload
+ # in case it works on the next try.
+ return ResumableUploadResult.INCOMPLETE
+
+ def _resume_upload(self, url, content_length, quiet):
+ # Documentation: https://developers.google.com/drive/api/guides/manage-uploads#resume-upload
+ session = requests.Session()
+ session.headers.update({
+ 'Content-Length': '0',
+ 'Content-Range': 'bytes */%d' % content_length,
+ })
+
+ response = session.put(url)
+
+ if self._is_upload_successful(response):
+ return ResumableUploadResult.Complete()
+ if response.status_code == 404:
+ # Upload expired so need to start from scratch.
+ if not query:
+ print('Upload of %s expired. Please try again.' % path)
+ return ResumableUploadResult.Failed()
+ if response.status_code == 308: # Resume Incomplete
+ bytes_uploaded = self._get_bytes_already_uploaded(response, quiet)
+ if bytes_uploaded is None:
+ # There is an error with the Range header so need to start from scratch.
+ return ResumableUploadResult.Failed()
+ result = ResumableUploadResult.Incomplete(bytes_uploaded)
+ if not quiet:
+ print('Already uploaded %d bytes. Will resume upload at %d.' %
+ (result.bytes_uploaded, result.start_at))
+ return result
+ else:
+ if not quiet:
+ print('Server returned %d. Please try again.' %
+ response.status_code)
+ return ResumableUploadResult.Failed()
+
+ def _is_upload_successful(self, response):
+ return response.status_code == 200 or response.status_code == 201
+
+ def _get_bytes_already_uploaded(self, response, quiet):
+ range_val = response.headers.get('Range')
+ if range_val is None:
+ return 0 # This means server hasn't received anything before.
+ items = range_val.split('-') # Example: bytes=0-1000 => ['0', '1000']
+ if len(items) != 2:
+ if not quiet:
+ print('Invalid Range header format: %s. Will try again.' %
+ range_val)
+ return None # Shouldn't happen, something's wrong with Range header format.
+ bytes_uploaded_str = items[-1] # Example: ['0', '1000'] => '1000'
+ try:
+ return int(bytes_uploaded_str) # Example: '1000' => 1000
+ except ValueError:
+ if not quiet:
+ print('Invalid Range header format: %s. Will try again.' %
+ range_val)
+ return None # Shouldn't happen, something's wrong with Range header format.
+
+ def validate_dataset_string(self, dataset):
+ """ determine if a dataset string is valid, meaning it is in the format
+ of {username}/{dataset-slug} or {username}/{dataset-slug}/{version-number}.
+ Parameters
+ ==========
+ dataset: the dataset name to validate
+ """
+ if dataset:
+ if '/' not in dataset:
+ raise ValueError('Dataset must be specified in the form of '
+ '\'{username}/{dataset-slug}\'')
+
+ split = dataset.split('/')
+ if not split[0] or not split[1] or len(split) > 3:
+ raise ValueError('Invalid dataset specification ' + dataset)
+
+ def split_dataset_string(self, dataset):
+ """ split a dataset string into owner_slug, dataset_slug,
+ and optional version_number
+ Parameters
+ ==========
+ dataset: the dataset name to split
+ """
+ if '/' in dataset:
+ self.validate_dataset_string(dataset)
+ urls = dataset.split('/')
+ if len(urls) == 3:
+ return urls[0], urls[1], urls[2]
+ else:
+ return urls[0], urls[1], None
+ else:
+ return self.get_config_value(self.CONFIG_NAME_USER), dataset
+
+ def validate_model_string(self, model):
+ """ determine if a model string is valid, meaning it is in the format
+ of {owner}/{model-slug}.
+ Parameters
+ ==========
+ model: the model name to validate
+ """
+ if model:
+ if model.count('/') != 1:
+ raise ValueError('Model must be specified in the form of '
+ '\'{owner}/{model-slug}\'')
+
+ split = model.split('/')
+ if not split[0] or not split[1]:
+ raise ValueError('Invalid model specification ' + model)
+
+ def split_model_string(self, model):
+ """ split a model string into owner_slug, model_slug
+ Parameters
+ ==========
+ model: the model name to split
+ """
+ if '/' in model:
+ self.validate_model_string(model)
+ model_urls = model.split('/')
+ return model_urls[0], model_urls[1]
+ else:
+ return self.get_config_value(self.CONFIG_NAME_USER), model
+
+ def validate_model_instance_string(self, model_instance):
+ """ determine if a model instance string is valid, meaning it is in the format
+ of {owner}/{model-slug}/{framework}/{instance-slug}.
+ Parameters
+ ==========
+ model_instance: the model instance name to validate
+ """
+ if model_instance:
+ if model_instance.count('/') != 3:
+ raise ValueError(
+ 'Model instance must be specified in the form of '
+ '\'{owner}/{model-slug}/{framework}/{instance-slug}\'')
+
+ split = model_instance.split('/')
+ if not split[0] or not split[1] or not split[2] or not split[3]:
+ raise ValueError('Invalid model instance specification ' +
+ model_instance)
+
+ def split_model_instance_string(self, model_instance):
+ """ split a model instance string into owner_slug, model_slug,
+ framework, instance_slug
+ Parameters
+ ==========
+ model_instance: the model instance name to validate
+ """
+ self.validate_model_instance_string(model_instance)
+ urls = model_instance.split('/')
+ return urls[0], urls[1], urls[2], urls[3]
+
+ def validate_model_instance_version_string(self, model_instance_version):
+ """ determine if a model instance version string is valid, meaning it is in the format
+ of {owner}/{model-slug}/{framework}/{instance-slug}/{version-number}.
+ Parameters
+ ==========
+ model_instance_version: the model instance version name to validate
+ """
+ if model_instance_version:
+ if model_instance_version.count('/') != 4:
+ raise ValueError(
+ 'Model instance version must be specified in the form of '
+ '\'{owner}/{model-slug}/{framework}/{instance-slug}/{version-number}\''
+ )
+
+ split = model_instance_version.split('/')
+ if not split[0] or not split[1] or not split[2] or not split[
+ 3] or not split[4]:
+ raise ValueError(
+ 'Invalid model instance version specification ' +
+ model_instance_version)
+
+ try:
+ version_number = int(split[4])
+ except:
+ raise ValueError(
+ 'Model instance version\'s version-number must be an integer'
+ )
+
+ def validate_kernel_string(self, kernel):
+ """ determine if a kernel string is valid, meaning it is in the format
+ of {username}/{kernel-slug}.
+ Parameters
+ ==========
+ kernel: the kernel name to validate
+ """
+ if kernel:
+ if '/' not in kernel:
+ raise ValueError('Kernel must be specified in the form of '
+ '\'{username}/{kernel-slug}\'')
+
+ split = kernel.split('/')
+ if not split[0] or not split[1]:
+ raise ValueError('Kernel must be specified in the form of '
+ '\'{username}/{kernel-slug}\'')
+
+ if len(split[1]) < 5:
+ raise ValueError(
+ 'Kernel slug must be at least five characters')
+
+ def validate_model_string(self, model):
+ """ determine if a model string is valid, meaning it is in the format
+ of {username}/{model-slug}/{framework}/{variation-slug}/{version-number}.
+ Parameters
+ ==========
+ model: the model name to validate
+ """
+ if model:
+ if '/' not in model:
+ raise ValueError(
+ 'Model must be specified in the form of '
+ '\'{username}/{model-slug}/{framework}/{variation-slug}/{version-number}\''
+ )
+
+ split = model.split('/')
+ if not split[0] or not split[1]:
+ raise ValueError('Invalid model specification ' + model)
+
+ def validate_resources(self, folder, resources):
+ """ validate resources is a wrapper to validate the existence of files
+ and that there are no duplicates for a folder and set of resources.
+
+ Parameters
+ ==========
+ folder: the folder to validate
+ resources: one or more resources to validate within the folder
+ """
+ self.validate_files_exist(folder, resources)
+ self.validate_no_duplicate_paths(resources)
+
+ def validate_files_exist(self, folder, resources):
+ """ ensure that one or more resource files exist in a folder
+
+ Parameters
+ ==========
+ folder: the folder to validate
+ resources: one or more resources to validate within the folder
+ """
+ for item in resources:
+ file_name = item.get('path')
+ full_path = os.path.join(folder, file_name)
+ if not os.path.isfile(full_path):
+ raise ValueError('%s does not exist' % full_path)
+
+ def validate_no_duplicate_paths(self, resources):
+ """ ensure that the user has not provided duplicate paths in
+ a list of resources.
+
+ Parameters
+ ==========
+ resources: one or more resources to validate not duplicated
+ """
+ paths = set()
+ for item in resources:
+ file_name = item.get('path')
+ if file_name in paths:
+ raise ValueError(
+ '%s path was specified more than once in the metadata' %
+ file_name)
+ paths.add(file_name)
+
+ def convert_to_dataset_file_metadata(self, file_data, path):
+ """ convert a set of file_data to a metadata file at path
+
+ Parameters
+ ==========
+ file_data: a dictionary of file data to write to file
+ path: the path to write the metadata to
+ """
+ as_metadata = {
+ 'path': os.path.join(path, file_data['name']),
+ 'description': file_data['description']
+ }
+
+ schema = {}
+ fields = []
+ for column in file_data['columns']:
+ field = {
+ 'name': column['name'],
+ 'title': column['description'],
+ 'type': column['type']
+ }
+ fields.append(field)
+ schema['fields'] = fields
+ as_metadata['schema'] = schema
+
+ return as_metadata
+
+ def validate_date(self, date):
+ datetime.strptime(date, "%Y-%m-%d")
+
+ def sanitize_markdown(self, markdown):
+ return bleach.clean(markdown)
+
+ def confirmation(self):
+ question = "Are you sure?"
+ prompt = "[yes/no]"
+ options = {"yes": True, "no": False}
+ while True:
+ sys.stdout.write('{} {} '.format(question, prompt))
+ choice = input().lower()
+ if choice in options:
+ return options[choice]
+ else:
+ sys.stdout.write("Please respond with 'yes' or 'no'.\n")
+ return False
+
+
+class TqdmBufferedReader(io.BufferedReader):
+
+ def __init__(self, raw, progress_bar):
+ """ helper class to implement an io.BufferedReader
+ Parameters
+ ==========
+ raw: bytes data to pass to the buffered reader
+ progress_bar: a progress bar to initialize the reader
+ """
+ io.BufferedReader.__init__(self, raw)
+ self.progress_bar = progress_bar
+
+ def read(self, *args, **kwargs):
+ """ read the buffer, passing named and non named arguments to the
+ io.BufferedReader function.
+ """
+ buf = io.BufferedReader.read(self, *args, **kwargs)
+ self.increment(len(buf))
+ return buf
+
+ def increment(self, length):
+ """ increment the reader by some length
+
+ Parameters
+ ==========
+ length: bytes to increment the reader by
+ """
+ self.progress_bar.update(length)
diff --git a/.venv/lib/python3.10/site-packages/kaggle/api_client.py b/.venv/lib/python3.10/site-packages/kaggle/api_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a4ac36add86fde1523c88c1beab9679cd2d6866
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/api_client.py
@@ -0,0 +1,617 @@
+# coding: utf-8
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+from __future__ import absolute_import
+
+import datetime
+import json
+import mimetypes
+from multiprocessing.pool import ThreadPool
+import os
+import re
+import tempfile
+
+# python 2 and python 3 compatibility library
+import six
+from six.moves.urllib.parse import quote
+
+from kaggle.configuration import Configuration
+import kaggle.models
+from kaggle import rest
+
+
+class ApiClient(object):
+ """Generic API client for Swagger client library builds.
+
+ Swagger generic API client. This client handles the client-
+ server communication, and is invariant across implementations. Specifics of
+ the methods and models for each application are generated from the Swagger
+ templates.
+
+ NOTE: This class is auto generated by the swagger code generator program.
+ Ref: https://github.com/swagger-api/swagger-codegen
+ Do not edit the class manually.
+
+ :param configuration: .Configuration object for this client
+ :param header_name: a header to pass when making calls to the API.
+ :param header_value: a header value to pass when making calls to
+ the API.
+ :param cookie: a cookie to include in the header when making calls
+ to the API
+ """
+
+ PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
+ NATIVE_TYPES_MAPPING = {
+ 'int': int,
+ 'long': int if six.PY3 else long, # noqa: F821
+ 'float': float,
+ 'str': str,
+ 'bool': bool,
+ 'date': datetime.date,
+ 'datetime': datetime.datetime,
+ 'object': object,
+ }
+
+ def __init__(self, configuration=None, header_name=None, header_value=None,
+ cookie=None):
+ if configuration is None:
+ configuration = Configuration()
+ self.configuration = configuration
+
+ self.pool = ThreadPool()
+ self.rest_client = rest.RESTClientObject(configuration)
+ self.default_headers = {}
+ if header_name is not None:
+ self.default_headers[header_name] = header_value
+ self.cookie = cookie
+ # Set default User-Agent.
+ self.user_agent = 'Swagger-Codegen/1/python'
+
+ @property
+ def user_agent(self):
+ """User agent for this API client"""
+ return self.default_headers['User-Agent']
+
+ @user_agent.setter
+ def user_agent(self, value):
+ self.default_headers['User-Agent'] = value
+
+ def set_default_header(self, header_name, header_value):
+ self.default_headers[header_name] = header_value
+
+ def __call_api(
+ self, resource_path, method, path_params=None,
+ query_params=None, header_params=None, body=None, post_params=None,
+ files=None, response_type=None, auth_settings=None,
+ _return_http_data_only=None, collection_formats=None,
+ _preload_content=True, _request_timeout=None):
+
+ config = self.configuration
+
+ # header parameters
+ header_params = header_params or {}
+ header_params.update(self.default_headers)
+ if self.cookie:
+ header_params['Cookie'] = self.cookie
+ if header_params:
+ header_params = self.sanitize_for_serialization(header_params)
+ header_params = dict(self.parameters_to_tuples(header_params,
+ collection_formats))
+
+ # path parameters
+ if path_params:
+ path_params = self.sanitize_for_serialization(path_params)
+ path_params = self.parameters_to_tuples(path_params,
+ collection_formats)
+ for k, v in path_params:
+ # specified safe chars, encode everything
+ resource_path = resource_path.replace(
+ '{%s}' % k,
+ quote(str(v), safe=config.safe_chars_for_path_param)
+ )
+
+ # query parameters
+ if query_params:
+ query_params = self.sanitize_for_serialization(query_params)
+ query_params = self.parameters_to_tuples(query_params,
+ collection_formats)
+
+ # post parameters
+ if post_params or files:
+ post_params = self.prepare_post_parameters(post_params, files)
+ post_params = self.sanitize_for_serialization(post_params)
+ post_params = self.parameters_to_tuples(post_params,
+ collection_formats)
+
+ # auth setting
+ self.update_params_for_auth(header_params, query_params, auth_settings)
+
+ # body
+ if body:
+ body = self.sanitize_for_serialization(body)
+
+ # request url
+ url = self.configuration.host + resource_path
+
+ # perform request and return response
+ response_data = self.request(
+ method, url, query_params=query_params, headers=header_params,
+ post_params=post_params, body=body,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout)
+
+ self.last_response = response_data
+
+ return_data = response_data
+ if _preload_content:
+ # deserialize response data
+ if response_type:
+ return_data = self.deserialize(response_data, response_type)
+ else:
+ return_data = None
+
+ if _return_http_data_only:
+ return (return_data)
+ else:
+ return (return_data, response_data.status,
+ response_data.getheaders())
+
+ def sanitize_for_serialization(self, obj):
+ """Builds a JSON POST object.
+
+ If obj is None, return None.
+ If obj is str, int, long, float, bool, return directly.
+ If obj is datetime.datetime, datetime.date
+ convert to string in iso8601 format.
+ If obj is list, sanitize each element in the list.
+ If obj is dict, return the dict.
+ If obj is swagger model, return the properties dict.
+
+ :param obj: The data to serialize.
+ :return: The serialized form of data.
+ """
+ if obj is None:
+ return None
+ elif isinstance(obj, self.PRIMITIVE_TYPES):
+ return obj
+ elif isinstance(obj, list):
+ return [self.sanitize_for_serialization(sub_obj)
+ for sub_obj in obj]
+ elif isinstance(obj, tuple):
+ return tuple(self.sanitize_for_serialization(sub_obj)
+ for sub_obj in obj)
+ elif isinstance(obj, (datetime.datetime, datetime.date)):
+ return obj.isoformat()
+
+ if isinstance(obj, dict):
+ obj_dict = obj
+ else:
+ # Convert model obj to dict except
+ # attributes `swagger_types`, `attribute_map`
+ # and attributes which value is not None.
+ # Convert attribute name to json key in
+ # model definition for request.
+ obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
+ for attr, _ in six.iteritems(obj.swagger_types)
+ if getattr(obj, attr) is not None}
+
+ return {key: self.sanitize_for_serialization(val)
+ for key, val in six.iteritems(obj_dict)}
+
+ def deserialize(self, response, response_type):
+ """Deserializes response into an object.
+
+ :param response: RESTResponse object to be deserialized.
+ :param response_type: class literal for
+ deserialized object, or string of class name.
+
+ :return: deserialized object.
+ """
+ # handle file downloading
+ # save response body into a tmp file and return the instance
+ if response_type == "file":
+ return self.__deserialize_file(response)
+
+ # fetch data from response object
+ try:
+ data = json.loads(response.data)
+ except ValueError:
+ data = response.data
+
+ return self.__deserialize(data, response_type)
+
+ def __deserialize(self, data, klass):
+ """Deserializes dict, list, str into an object.
+
+ :param data: dict, list or str.
+ :param klass: class literal, or string of class name.
+
+ :return: object.
+ """
+ if data is None:
+ return None
+
+ if type(klass) == str:
+ if klass.startswith('list['):
+ sub_kls = re.match('list\[(.*)\]', klass).group(1)
+ return [self.__deserialize(sub_data, sub_kls)
+ for sub_data in data]
+
+ if klass.startswith('dict('):
+ sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2)
+ return {k: self.__deserialize(v, sub_kls)
+ for k, v in six.iteritems(data)}
+
+ # convert str to class
+ if klass in self.NATIVE_TYPES_MAPPING:
+ klass = self.NATIVE_TYPES_MAPPING[klass]
+ else:
+ klass = getattr(kaggle.models, klass)
+
+ if klass in self.PRIMITIVE_TYPES:
+ return self.__deserialize_primitive(data, klass)
+ elif klass == object:
+ return self.__deserialize_object(data)
+ elif klass == datetime.date:
+ return self.__deserialize_date(data)
+ elif klass == datetime.datetime:
+ return self.__deserialize_datatime(data)
+ else:
+ return self.__deserialize_model(data, klass)
+
+ def call_api(self, resource_path, method,
+ path_params=None, query_params=None, header_params=None,
+ body=None, post_params=None, files=None,
+ response_type=None, auth_settings=None, async_req=None,
+ _return_http_data_only=None, collection_formats=None,
+ _preload_content=True, _request_timeout=None):
+ """Makes the HTTP request (synchronous) and returns deserialized data.
+
+ To make an async request, set the async_req parameter.
+
+ :param resource_path: Path to method endpoint.
+ :param method: Method to call.
+ :param path_params: Path parameters in the url.
+ :param query_params: Query parameters in the url.
+ :param header_params: Header parameters to be
+ placed in the request header.
+ :param body: Request body.
+ :param post_params dict: Request post form parameters,
+ for `application/x-www-form-urlencoded`, `multipart/form-data`.
+ :param auth_settings list: Auth Settings names for the request.
+ :param response: Response data type.
+ :param files dict: key -> filename, value -> filepath,
+ for `multipart/form-data`.
+ :param async_req bool: execute request asynchronously
+ :param _return_http_data_only: response data without head status code
+ and headers
+ :param collection_formats: dict of collection formats for path, query,
+ header, and post parameters.
+ :param _preload_content: if False, the urllib3.HTTPResponse object will
+ be returned without reading/decoding response
+ data. Default is True.
+ :param _request_timeout: timeout setting for this request. If one
+ number provided, it will be total request
+ timeout. It can also be a pair (tuple) of
+ (connection, read) timeouts.
+ :return:
+ If async_req parameter is True,
+ the request will be called asynchronously.
+ The method will return the request thread.
+ If parameter async_req is False or missing,
+ then the method will return the response directly.
+ """
+ if not async_req:
+ return self.__call_api(resource_path, method,
+ path_params, query_params, header_params,
+ body, post_params, files,
+ response_type, auth_settings,
+ _return_http_data_only, collection_formats,
+ _preload_content, _request_timeout)
+ else:
+ thread = self.pool.apply_async(self.__call_api, (resource_path,
+ method, path_params, query_params,
+ header_params, body,
+ post_params, files,
+ response_type, auth_settings,
+ _return_http_data_only,
+ collection_formats,
+ _preload_content, _request_timeout))
+ return thread
+
+ def request(self, method, url, query_params=None, headers=None,
+ post_params=None, body=None, _preload_content=True,
+ _request_timeout=None):
+ """Makes the HTTP request using RESTClient."""
+ if method == "GET":
+ return self.rest_client.GET(url,
+ query_params=query_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ headers=headers)
+ elif method == "HEAD":
+ return self.rest_client.HEAD(url,
+ query_params=query_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ headers=headers)
+ elif method == "OPTIONS":
+ return self.rest_client.OPTIONS(url,
+ query_params=query_params,
+ headers=headers,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+ elif method == "POST":
+ return self.rest_client.POST(url,
+ query_params=query_params,
+ headers=headers,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+ elif method == "PUT":
+ return self.rest_client.PUT(url,
+ query_params=query_params,
+ headers=headers,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+ elif method == "PATCH":
+ return self.rest_client.PATCH(url,
+ query_params=query_params,
+ headers=headers,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+ elif method == "DELETE":
+ return self.rest_client.DELETE(url,
+ query_params=query_params,
+ headers=headers,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+ else:
+ raise ValueError(
+ "http method must be `GET`, `HEAD`, `OPTIONS`,"
+ " `POST`, `PATCH`, `PUT` or `DELETE`."
+ )
+
+ def parameters_to_tuples(self, params, collection_formats):
+ """Get parameters as list of tuples, formatting collections.
+
+ :param params: Parameters as dict or list of two-tuples
+ :param dict collection_formats: Parameter collection formats
+ :return: Parameters as list of tuples, collections formatted
+ """
+ new_params = []
+ if collection_formats is None:
+ collection_formats = {}
+ for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501
+ if k in collection_formats:
+ collection_format = collection_formats[k]
+ if collection_format == 'multi':
+ new_params.extend((k, value) for value in v)
+ else:
+ if collection_format == 'ssv':
+ delimiter = ' '
+ elif collection_format == 'tsv':
+ delimiter = '\t'
+ elif collection_format == 'pipes':
+ delimiter = '|'
+ else: # csv is the default
+ delimiter = ','
+ new_params.append(
+ (k, delimiter.join(str(value) for value in v)))
+ else:
+ new_params.append((k, v))
+ return new_params
+
+ def prepare_post_parameters(self, post_params=None, files=None):
+ """Builds form parameters.
+
+ :param post_params: Normal form parameters.
+ :param files: File parameters.
+ :return: Form parameters with files.
+ """
+ params = []
+
+ if post_params:
+ params = post_params
+
+ if files:
+ for k, v in six.iteritems(files):
+ if not v:
+ continue
+ file_names = v if type(v) is list else [v]
+ for n in file_names:
+ with open(n, 'rb') as f:
+ filename = os.path.basename(f.name)
+ filedata = f.read()
+ mimetype = (mimetypes.guess_type(filename)[0] or
+ 'application/octet-stream')
+ params.append(
+ tuple([k, tuple([filename, filedata, mimetype])]))
+
+ return params
+
+ def select_header_accept(self, accepts):
+ """Returns `Accept` based on an array of accepts provided.
+
+ :param accepts: List of headers.
+ :return: Accept (e.g. application/json).
+ """
+ if not accepts:
+ return
+
+ accepts = [x.lower() for x in accepts]
+
+ if 'application/json' in accepts:
+ return 'application/json'
+ else:
+ return ', '.join(accepts)
+
+ def select_header_content_type(self, content_types):
+ """Returns `Content-Type` based on an array of content_types provided.
+
+ :param content_types: List of content-types.
+ :return: Content-Type (e.g. application/json).
+ """
+ if not content_types:
+ return 'application/json'
+
+ content_types = [x.lower() for x in content_types]
+
+ if 'application/json' in content_types or '*/*' in content_types:
+ return 'application/json'
+ else:
+ return content_types[0]
+
+ def update_params_for_auth(self, headers, querys, auth_settings):
+ """Updates header and query params based on authentication setting.
+
+ :param headers: Header parameters dict to be updated.
+ :param querys: Query parameters tuple list to be updated.
+ :param auth_settings: Authentication setting identifiers list.
+ """
+ if not auth_settings:
+ return
+
+ for auth in auth_settings:
+ auth_setting = self.configuration.auth_settings().get(auth)
+ if auth_setting:
+ if not auth_setting['value']:
+ continue
+ elif auth_setting['in'] == 'header':
+ headers[auth_setting['key']] = auth_setting['value']
+ elif auth_setting['in'] == 'query':
+ querys.append((auth_setting['key'], auth_setting['value']))
+ else:
+ raise ValueError(
+ 'Authentication token must be in `query` or `header`'
+ )
+
+ def __deserialize_file(self, response):
+ """Deserializes body to file
+
+ Saves response body into a file in a temporary folder,
+ using the filename from the `Content-Disposition` header if provided.
+
+ :param response: RESTResponse.
+ :return: file path.
+ """
+ fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
+ os.close(fd)
+ os.remove(path)
+
+ content_disposition = response.getheader("Content-Disposition")
+ if content_disposition:
+ filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
+ content_disposition).group(1)
+ path = os.path.join(os.path.dirname(path), filename)
+
+ with open(path, "wb") as f:
+ f.write(response.data)
+
+ return path
+
+ def __deserialize_primitive(self, data, klass):
+ """Deserializes string to primitive type.
+
+ :param data: str.
+ :param klass: class literal.
+
+ :return: int, long, float, str, bool.
+ """
+ try:
+ return klass(data)
+ except UnicodeEncodeError:
+ return six.text_type(data)
+ except TypeError:
+ return data
+
+ def __deserialize_object(self, value):
+ """Return a original value.
+
+ :return: object.
+ """
+ return value
+
+ def __deserialize_date(self, string):
+ """Deserializes string to date.
+
+ :param string: str.
+ :return: date.
+ """
+ try:
+ from dateutil.parser import parse
+ return parse(string).date()
+ except ImportError:
+ return string
+ except ValueError:
+ raise rest.ApiException(
+ status=0,
+ reason="Failed to parse `{0}` as date object".format(string)
+ )
+
+ def __deserialize_datatime(self, string):
+ """Deserializes string to datetime.
+
+ The string should be in iso8601 datetime format.
+
+ :param string: str.
+ :return: datetime.
+ """
+ try:
+ from dateutil.parser import parse
+ return parse(string)
+ except ImportError:
+ return string
+ except ValueError:
+ raise rest.ApiException(
+ status=0,
+ reason=(
+ "Failed to parse `{0}` as datetime object"
+ .format(string)
+ )
+ )
+
+ def __deserialize_model(self, data, klass):
+ """Deserializes list or dict to model.
+
+ :param data: dict, list.
+ :param klass: class literal.
+ :return: model object.
+ """
+
+ if not klass.swagger_types and not hasattr(klass,
+ 'get_real_child_model'):
+ return data
+
+ kwargs = {}
+ if klass.swagger_types is not None:
+ for attr, attr_type in six.iteritems(klass.swagger_types):
+ if (data is not None and
+ klass.attribute_map[attr] in data and
+ isinstance(data, (list, dict))):
+ value = data[klass.attribute_map[attr]]
+ kwargs[attr] = self.__deserialize(value, attr_type)
+
+ instance = klass(**kwargs)
+
+ if hasattr(instance, 'get_real_child_model'):
+ klass_name = instance.get_real_child_model(data)
+ if klass_name:
+ instance = self.__deserialize(data, klass_name)
+ return instance
diff --git a/.venv/lib/python3.10/site-packages/kaggle/cli.py b/.venv/lib/python3.10/site-packages/kaggle/cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..e03ea727eeadc47484b4e59854a730f782362e43
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/cli.py
@@ -0,0 +1,1583 @@
+#!/usr/bin/python
+#
+# Copyright 2019 Kaggle Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding=utf-8
+from __future__ import print_function
+import argparse
+import json
+from kaggle import api
+from kaggle import KaggleApi
+from .rest import ApiException
+import six
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter)
+
+ parser.add_argument('-v',
+ '--version',
+ action='version',
+ version='Kaggle API ' + KaggleApi.__version__)
+
+ subparsers = parser.add_subparsers(title='commands',
+ help=Help.kaggle,
+ dest='command')
+ subparsers.required = True
+ subparsers.choices = Help.kaggle_choices
+ parse_competitions(subparsers)
+ parse_datasets(subparsers)
+ parse_kernels(subparsers)
+ parse_models(subparsers)
+ parse_files(subparsers)
+ parse_config(subparsers)
+ args = parser.parse_args()
+ command_args = {}
+ command_args.update(vars(args))
+ del command_args['func']
+ del command_args['command']
+ error = False
+ try:
+ out = args.func(**command_args)
+ except ApiException as e:
+ msg = '{} - {}'.format(str(e.status), e.reason)
+ body = __parse_body(e.body)
+ if body and 'message' in body:
+ msg += ' - {}'.format(body['message'])
+ print(msg)
+ out = None
+ error = True
+ except ValueError as e:
+ print(e)
+ out = None
+ error = True
+ except KeyboardInterrupt:
+ print('User cancelled operation')
+ out = None
+ if out is not None:
+ print(out, end='')
+
+ # This is so that scripts that pick up on error codes can tell when there was a failure
+ if error:
+ exit(1)
+
+
+def __parse_body(body):
+ try:
+ return json.loads(body)
+ except Exception as e:
+ return {}
+
+
+def parse_competitions(subparsers):
+ if six.PY2:
+ parser_competitions = subparsers.add_parser(
+ 'competitions',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_competitions)
+ else:
+ parser_competitions = subparsers.add_parser(
+ 'competitions',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_competitions,
+ aliases=['c'])
+ subparsers_competitions = parser_competitions.add_subparsers(
+ title='commands', dest='command')
+ subparsers_competitions.required = True
+ subparsers_competitions.choices = Help.competitions_choices
+
+ # Competitions list
+ parser_competitions_list = subparsers_competitions.add_parser(
+ 'list',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_list)
+ parser_competitions_list_optional = parser_competitions_list._action_groups.pop(
+ )
+ parser_competitions_list_optional.add_argument(
+ '--group',
+ dest='group',
+ required=False,
+ help=Help.param_competition_group)
+ parser_competitions_list_optional.add_argument(
+ '--category',
+ dest='category',
+ required=False,
+ help=Help.param_competition_category)
+ parser_competitions_list_optional.add_argument(
+ '--sort-by',
+ dest='sort_by',
+ required=False,
+ help=Help.param_competition_sort_by)
+ parser_competitions_list_optional.add_argument('-p',
+ '--page',
+ dest='page',
+ default=1,
+ required=False,
+ help=Help.param_page)
+ parser_competitions_list_optional.add_argument('-s',
+ '--search',
+ dest='search',
+ required=False,
+ help=Help.param_search)
+ parser_competitions_list_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_competitions_list._action_groups.append(
+ parser_competitions_list_optional)
+ parser_competitions_list.set_defaults(func=api.competitions_list_cli)
+
+ # Competitions list files
+ parser_competitions_files = subparsers_competitions.add_parser(
+ 'files',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_files)
+ parser_competitions_files_optional = parser_competitions_files._action_groups.pop(
+ )
+ parser_competitions_files_optional.add_argument(
+ 'competition', nargs='?', default=None, help=Help.param_competition)
+ parser_competitions_files_optional.add_argument('-c',
+ '--competition',
+ dest='competition_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_competitions_files_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_competitions_files_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_competitions_files._action_groups.append(
+ parser_competitions_files_optional)
+ parser_competitions_files.set_defaults(func=api.competition_list_files_cli)
+
+ # Competitions download
+ parser_competitions_download = subparsers_competitions.add_parser(
+ 'download',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_download)
+ parser_competitions_download_optional = parser_competitions_download._action_groups.pop(
+ )
+ parser_competitions_download_optional.add_argument(
+ 'competition', nargs='?', default=None, help=Help.param_competition)
+ parser_competitions_download_optional.add_argument('-c',
+ '--competition',
+ dest='competition_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_competitions_download_optional.add_argument(
+ '-f',
+ '--file',
+ dest='file_name',
+ required=False,
+ help=Help.param_competition_file)
+ parser_competitions_download_optional.add_argument(
+ '-p',
+ '--path',
+ dest='path',
+ required=False,
+ help=Help.param_downfolder)
+ parser_competitions_download_optional.add_argument('-w',
+ '--wp',
+ dest='path',
+ action='store_const',
+ const='.',
+ required=False,
+ help=Help.param_wp)
+ parser_competitions_download_optional.add_argument('-o',
+ '--force',
+ dest='force',
+ action='store_true',
+ help=Help.param_force)
+ parser_competitions_download_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_competitions_download._action_groups.append(
+ parser_competitions_download_optional)
+ parser_competitions_download.set_defaults(
+ func=api.competition_download_cli)
+
+ # Competitions submit
+ parser_competitions_submit = subparsers_competitions.add_parser(
+ 'submit',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_submit)
+ parser_competitions_submit_optional = parser_competitions_submit._action_groups.pop(
+ )
+ parser_competitions_submit_required = parser_competitions_submit.add_argument_group(
+ 'required arguments')
+ parser_competitions_submit_optional.add_argument(
+ 'competition', nargs='?', default=None, help=Help.param_competition)
+ parser_competitions_submit_optional.add_argument('-c',
+ '--competition',
+ dest='competition_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_competitions_submit_required.add_argument('-f',
+ '--file',
+ dest='file_name',
+ required=True,
+ help=Help.param_upfile)
+ parser_competitions_submit_required.add_argument(
+ '-m',
+ '--message',
+ dest='message',
+ required=True,
+ help=Help.param_competition_message)
+ parser_competitions_submit_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_competitions_submit._action_groups.append(
+ parser_competitions_submit_optional)
+ parser_competitions_submit.set_defaults(func=api.competition_submit_cli)
+
+ # Competitions list submissions
+ parser_competitions_submissions = subparsers_competitions.add_parser(
+ 'submissions',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_submissions)
+ parser_competitions_submissions_optional = parser_competitions_submissions._action_groups.pop(
+ )
+ parser_competitions_submissions_optional.add_argument(
+ 'competition', nargs='?', default=None, help=Help.param_competition)
+ parser_competitions_submissions_optional.add_argument(
+ '-c',
+ '--competition',
+ dest='competition_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_competitions_submissions_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_competitions_submissions_optional.add_argument(
+ '-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_competitions_submissions._action_groups.append(
+ parser_competitions_submissions_optional)
+ parser_competitions_submissions.set_defaults(
+ func=api.competition_submissions_cli)
+
+ # Competitions leaderboard
+ parser_competitions_leaderboard = subparsers_competitions.add_parser(
+ 'leaderboard',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_competitions_leaderboard)
+ parser_competitions_leaderboard_optional = parser_competitions_leaderboard._action_groups.pop(
+ )
+ parser_competitions_leaderboard_optional.add_argument(
+ 'competition', nargs='?', default=None, help=Help.param_competition)
+ parser_competitions_leaderboard_optional.add_argument(
+ '-c',
+ '--competition',
+ dest='competition_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_competitions_leaderboard_optional.add_argument(
+ '-s',
+ '--show',
+ dest='view',
+ action='store_true',
+ help=Help.param_competition_leaderboard_view)
+ parser_competitions_leaderboard_optional.add_argument(
+ '-d',
+ '--download',
+ dest='download',
+ action='store_true',
+ help=Help.param_competition_leaderboard_download)
+ parser_competitions_leaderboard_optional.add_argument(
+ '-p', '--path', dest='path', help=Help.param_downfolder)
+ parser_competitions_leaderboard_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_competitions_leaderboard_optional.add_argument(
+ '-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_competitions_leaderboard._action_groups.append(
+ parser_competitions_leaderboard_optional)
+ parser_competitions_leaderboard.set_defaults(
+ func=api.competition_leaderboard_cli)
+
+
+def parse_datasets(subparsers):
+ if six.PY2:
+ parser_datasets = subparsers.add_parser(
+ 'datasets',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_datasets)
+ else:
+ parser_datasets = subparsers.add_parser(
+ 'datasets',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_datasets,
+ aliases=['d'])
+ subparsers_datasets = parser_datasets.add_subparsers(title='commands',
+ dest='command')
+ subparsers_datasets.required = True
+ subparsers_datasets.choices = Help.datasets_choices
+
+ # Datasets list
+ parser_datasets_list = subparsers_datasets.add_parser(
+ 'list',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_list)
+ parser_datasets_list_optional = parser_datasets_list._action_groups.pop()
+ parser_datasets_list.add_argument('--sort-by',
+ dest='sort_by',
+ required=False,
+ help=Help.param_dataset_sort_by)
+ parser_datasets_list.add_argument('--size',
+ dest='size',
+ required=False,
+ help=Help.param_dataset_size)
+ parser_datasets_list.add_argument('--file-type',
+ dest='file_type',
+ required=False,
+ help=Help.param_dataset_file_type)
+ parser_datasets_list.add_argument('--license',
+ dest='license_name',
+ required=False,
+ help=Help.param_dataset_license)
+ parser_datasets_list.add_argument('--tags',
+ dest='tag_ids',
+ required=False,
+ help=Help.param_dataset_tags)
+ parser_datasets_list.add_argument('-s',
+ '--search',
+ dest='search',
+ required=False,
+ help=Help.param_search)
+ parser_datasets_list.add_argument('-m',
+ '--mine',
+ dest='mine',
+ action='store_true',
+ help=Help.param_mine)
+ parser_datasets_list.add_argument('--user',
+ dest='user',
+ required=False,
+ help=Help.param_dataset_user)
+ parser_datasets_list.add_argument('-p',
+ '--page',
+ dest='page',
+ default=1,
+ required=False,
+ help=Help.param_page)
+ parser_datasets_list.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_datasets_list.add_argument('--max-size',
+ dest='max_size',
+ required=False,
+ help=Help.param_dataset_maxsize)
+ parser_datasets_list.add_argument('--min-size',
+ dest='min_size',
+ required=False,
+ help=Help.param_dataset_minsize)
+ parser_datasets_list._action_groups.append(parser_datasets_list_optional)
+ parser_datasets_list.set_defaults(func=api.dataset_list_cli)
+
+ # Datasets file list
+ parser_datasets_files = subparsers_datasets.add_parser(
+ 'files',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_files)
+ parser_datasets_files_optional = parser_datasets_files._action_groups.pop()
+ parser_datasets_files_optional.add_argument('dataset',
+ nargs='?',
+ default=None,
+ help=Help.param_dataset)
+ parser_datasets_files_optional.add_argument('-d',
+ '--dataset',
+ dest='dataset_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_datasets_files_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_datasets_files._action_groups.append(parser_datasets_files_optional)
+ parser_datasets_files.set_defaults(func=api.dataset_list_files_cli)
+
+ # Datasets download
+ parser_datasets_download = subparsers_datasets.add_parser(
+ 'download',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_download)
+ parser_datasets_download_optional = parser_datasets_download._action_groups.pop(
+ )
+ parser_datasets_download_optional.add_argument('dataset',
+ nargs='?',
+ default=None,
+ help=Help.param_dataset)
+ parser_datasets_download_optional.add_argument('-d',
+ '--dataset',
+ dest='dataset_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_datasets_download_optional.add_argument(
+ '-f',
+ '--file',
+ dest='file_name',
+ required=False,
+ help=Help.param_dataset_file)
+ parser_datasets_download_optional.add_argument('-p',
+ '--path',
+ dest='path',
+ required=False,
+ help=Help.param_downfolder)
+ parser_datasets_download_optional.add_argument('-w',
+ '--wp',
+ dest='path',
+ action='store_const',
+ const='.',
+ required=False,
+ help=Help.param_wp)
+ parser_datasets_download_optional.add_argument('--unzip',
+ dest='unzip',
+ action='store_true',
+ help=Help.param_unzip)
+ parser_datasets_download_optional.add_argument('-o',
+ '--force',
+ dest='force',
+ action='store_true',
+ help=Help.param_force)
+ parser_datasets_download_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_datasets_download._action_groups.append(
+ parser_datasets_download_optional)
+ parser_datasets_download.set_defaults(func=api.dataset_download_cli)
+
+ # Datasets create
+ parser_datasets_create = subparsers_datasets.add_parser(
+ 'create',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_new)
+ parser_datasets_create_optional = parser_datasets_create._action_groups.pop(
+ )
+ parser_datasets_create_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_dataset_upfile)
+ parser_datasets_create_optional.add_argument('-u',
+ '--public',
+ dest='public',
+ action='store_true',
+ help=Help.param_public)
+ parser_datasets_create_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_datasets_create_optional.add_argument('-t',
+ '--keep-tabular',
+ dest='convert_to_csv',
+ action='store_false',
+ help=Help.param_keep_tabular)
+ parser_datasets_create_optional.add_argument(
+ '-r',
+ '--dir-mode',
+ dest='dir_mode',
+ choices=['skip', 'zip', 'tar'],
+ default='skip',
+ help=Help.param_dir_mode)
+ parser_datasets_create._action_groups.append(
+ parser_datasets_create_optional)
+ parser_datasets_create.set_defaults(func=api.dataset_create_new_cli)
+
+ # Datasets update
+ parser_datasets_version = subparsers_datasets.add_parser(
+ 'version',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_new_version)
+ parser_datasets_version_optional = parser_datasets_version._action_groups.pop(
+ )
+ parser_datasets_version_required = parser_datasets_version.add_argument_group(
+ 'required arguments')
+ parser_datasets_version_required.add_argument(
+ '-m',
+ '--message',
+ dest='version_notes',
+ required=True,
+ help=Help.param_dataset_version_notes)
+ parser_datasets_version_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_dataset_upfile)
+ parser_datasets_version_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_datasets_version_optional.add_argument('-t',
+ '--keep-tabular',
+ dest='convert_to_csv',
+ action='store_false',
+ help=Help.param_keep_tabular)
+ parser_datasets_version_optional.add_argument(
+ '-r',
+ '--dir-mode',
+ dest='dir_mode',
+ choices=['skip', 'zip', 'tar'],
+ default='skip',
+ help=Help.param_dir_mode)
+ parser_datasets_version_optional.add_argument(
+ '-d',
+ '--delete-old-versions',
+ dest='delete_old_versions',
+ action='store_true',
+ help=Help.param_delete_old_version)
+ parser_datasets_version._action_groups.append(
+ parser_datasets_version_optional)
+ parser_datasets_version.set_defaults(func=api.dataset_create_version_cli)
+
+ # Datasets init
+ parser_datasets_init = subparsers_datasets.add_parser(
+ 'init',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_init)
+ parser_datasets_init_optional = parser_datasets_init._action_groups.pop()
+ parser_datasets_init_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_dataset_upfile)
+ parser_datasets_init._action_groups.append(parser_datasets_init_optional)
+ parser_datasets_init.set_defaults(func=api.dataset_initialize_cli)
+
+ # Datasets metadata
+ parser_datasets_metadata = subparsers_datasets.add_parser(
+ 'metadata',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_metadata)
+ parser_datasets_metadata_optional = parser_datasets_metadata._action_groups.pop(
+ )
+ parser_datasets_metadata_optional.add_argument('dataset',
+ nargs='?',
+ default=None,
+ help=Help.param_dataset)
+ parser_datasets_metadata_optional.add_argument('-d',
+ '--dataset',
+ dest='dataset_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_datasets_metadata_optional.add_argument(
+ '--update',
+ dest='update',
+ action='store_true',
+ help=Help.param_dataset_metadata_update)
+ parser_datasets_metadata_optional.add_argument(
+ '-p', '--path', dest='path', help=Help.param_dataset_metadata_dir)
+ parser_datasets_metadata._action_groups.append(
+ parser_datasets_metadata_optional)
+ parser_datasets_metadata.set_defaults(func=api.dataset_metadata_cli)
+
+ # Datasets status
+ parser_datasets_status = subparsers_datasets.add_parser(
+ 'status',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_datasets_status)
+ parser_datasets_status_optional = parser_datasets_status._action_groups.pop(
+ )
+ parser_datasets_status_optional.add_argument('dataset',
+ nargs='?',
+ default=None,
+ help=Help.param_dataset)
+ parser_datasets_status_optional.add_argument('-d',
+ '--dataset',
+ dest='dataset_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_datasets_status._action_groups.append(
+ parser_datasets_status_optional)
+ parser_datasets_status.set_defaults(func=api.dataset_status_cli)
+
+
+def parse_kernels(subparsers):
+ if six.PY2:
+ parser_kernels = subparsers.add_parser(
+ 'kernels',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_kernels)
+ else:
+ parser_kernels = subparsers.add_parser(
+ 'kernels',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_kernels,
+ aliases=['k'])
+ subparsers_kernels = parser_kernels.add_subparsers(title='commands',
+ dest='command')
+ subparsers_kernels.required = True
+ subparsers_kernels.choices = Help.kernels_choices
+
+ # Kernels list/search
+ parser_kernels_list = subparsers_kernels.add_parser(
+ 'list',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_list)
+ parser_kernels_list_optional = parser_kernels_list._action_groups.pop()
+ parser_kernels_list_optional.add_argument('-m',
+ '--mine',
+ dest='mine',
+ action='store_true',
+ help=Help.param_mine)
+ parser_kernels_list_optional.add_argument('-p',
+ '--page',
+ dest='page',
+ default=1,
+ help=Help.param_page)
+ parser_kernels_list_optional.add_argument('--page-size',
+ dest='page_size',
+ default=20,
+ help=Help.param_page_size)
+ parser_kernels_list_optional.add_argument('-s',
+ '--search',
+ dest='search',
+ help=Help.param_search)
+ parser_kernels_list_optional.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_kernels_list_optional.add_argument('--parent',
+ dest='parent',
+ required=False,
+ help=Help.param_kernel_parent)
+ parser_kernels_list_optional.add_argument(
+ '--competition',
+ dest='competition',
+ required=False,
+ help=Help.param_kernel_competition)
+ parser_kernels_list_optional.add_argument('--dataset',
+ dest='dataset',
+ required=False,
+ help=Help.param_kernel_dataset)
+ parser_kernels_list_optional.add_argument('--user',
+ dest='user',
+ required=False,
+ help=Help.param_kernel_user)
+ parser_kernels_list_optional.add_argument('--language',
+ dest='language',
+ required=False,
+ help=Help.param_kernel_language)
+ parser_kernels_list_optional.add_argument('--kernel-type',
+ dest='kernel_type',
+ required=False,
+ help=Help.param_kernel_type)
+ parser_kernels_list_optional.add_argument(
+ '--output-type',
+ dest='output_type',
+ required=False,
+ help=Help.param_kernel_output_type)
+ parser_kernels_list_optional.add_argument('--sort-by',
+ dest='sort_by',
+ required=False,
+ help=Help.param_kernel_sort_by)
+ parser_kernels_list._action_groups.append(parser_kernels_list_optional)
+ parser_kernels_list.set_defaults(func=api.kernels_list_cli)
+
+ # Kernels init
+ parser_kernels_init = subparsers_kernels.add_parser(
+ 'init',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_init)
+ parser_kernels_init_optional = parser_kernels_init._action_groups.pop()
+ parser_kernels_init_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_kernel_upfile)
+ parser_kernels_init._action_groups.append(parser_kernels_init_optional)
+ parser_kernels_init.set_defaults(func=api.kernels_initialize_cli)
+
+ # Kernels push
+ parser_kernels_push = subparsers_kernels.add_parser(
+ 'push',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_push)
+ parser_kernels_push_optional = parser_kernels_push._action_groups.pop()
+ parser_kernels_push_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_kernel_upfile)
+ parser_kernels_push._action_groups.append(parser_kernels_push_optional)
+ parser_kernels_push.set_defaults(func=api.kernels_push_cli)
+
+ # Kernels pull
+ parser_kernels_pull = subparsers_kernels.add_parser(
+ 'pull',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_pull)
+ parser_kernels_pull_optional = parser_kernels_pull._action_groups.pop()
+ parser_kernels_pull_optional.add_argument('kernel',
+ nargs='?',
+ default=None,
+ help=Help.param_kernel)
+ parser_kernels_pull_optional.add_argument('-k',
+ '--kernel',
+ dest='kernel',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_kernels_pull_optional.add_argument('-p',
+ '--path',
+ dest='path',
+ required=False,
+ help=Help.param_downfolder)
+ parser_kernels_pull_optional.add_argument('-w',
+ '--wp',
+ dest='path',
+ action='store_const',
+ const='.',
+ required=False,
+ help=Help.param_wp)
+ parser_kernels_pull_optional.add_argument(
+ '-m',
+ '--metadata',
+ dest='metadata',
+ action='store_true',
+ help=Help.param_kernel_pull_metadata)
+ parser_kernels_pull._action_groups.append(parser_kernels_pull_optional)
+ parser_kernels_pull.set_defaults(func=api.kernels_pull_cli)
+
+ # Kernels output
+ parser_kernels_output = subparsers_kernels.add_parser(
+ 'output',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_output)
+ parser_kernels_output_optional = parser_kernels_output._action_groups.pop()
+ parser_kernels_output_optional.add_argument('kernel',
+ nargs='?',
+ default=None,
+ help=Help.param_kernel)
+ parser_kernels_output_optional.add_argument('-k',
+ '--kernel',
+ dest='kernel_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_kernels_output_optional.add_argument('-p',
+ '--path',
+ dest='path',
+ required=False,
+ help=Help.param_downfolder)
+ parser_kernels_output_optional.add_argument('-w',
+ '--wp',
+ dest='path',
+ action='store_const',
+ const='.',
+ required=False,
+ help=Help.param_wp)
+ parser_kernels_output_optional.add_argument('-o',
+ '--force',
+ dest='force',
+ action='store_true',
+ required=False,
+ help=Help.param_force)
+ parser_kernels_output_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ required=False,
+ help=Help.param_quiet)
+ parser_kernels_output._action_groups.append(parser_kernels_output_optional)
+ parser_kernels_output.set_defaults(func=api.kernels_output_cli)
+
+ # Kernels status
+ parser_kernels_status = subparsers_kernels.add_parser(
+ 'status',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_kernels_status)
+ parser_kernels_status_optional = parser_kernels_status._action_groups.pop()
+ parser_kernels_status_optional.add_argument('kernel',
+ nargs='?',
+ default=None,
+ help=Help.param_kernel)
+ parser_kernels_status_optional.add_argument('-k',
+ '--kernel',
+ dest='kernel_opt',
+ required=False,
+ help=argparse.SUPPRESS)
+ parser_kernels_status._action_groups.append(parser_kernels_status_optional)
+ parser_kernels_status.set_defaults(func=api.kernels_status_cli)
+
+
+def parse_models(subparsers):
+ parser_models = subparsers.add_parser(
+ 'models',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_models,
+ aliases=['m'])
+
+ subparsers_models = parser_models.add_subparsers(title='commands',
+ dest='command')
+ subparsers_models.required = True
+ subparsers_models.choices = Help.models_choices
+
+ # Models Instances.
+ parse_model_instances(subparsers_models)
+
+ # Models get
+ parser_models_get = subparsers_models.add_parser(
+ 'get',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_get)
+ parser_models_get_optional = parser_models_get._action_groups.pop()
+ parser_models_get_optional.add_argument('model', help=Help.param_model)
+ parser_models_get_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_downfile)
+ parser_models_get._action_groups.append(parser_models_get_optional)
+ parser_models_get.set_defaults(func=api.model_get_cli)
+
+ # Models list
+ parser_models_list = subparsers_models.add_parser(
+ 'list',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_list)
+ parser_models_list_optional = parser_models_list._action_groups.pop()
+ parser_models_list.add_argument('--sort-by',
+ dest='sort_by',
+ required=False,
+ help=Help.param_model_sort_by)
+ parser_models_list.add_argument('-s',
+ '--search',
+ dest='search',
+ required=False,
+ help=Help.param_search)
+ parser_models_list.add_argument('--owner',
+ dest='owner',
+ required=False,
+ help=Help.param_model_owner)
+ parser_models_list.add_argument('--page-size',
+ dest='page_size',
+ default=20,
+ help=Help.param_page_size)
+ parser_models_list.add_argument('--page-token',
+ dest='page_token',
+ required=False,
+ help=Help.param_page_token)
+ parser_models_list.add_argument('-v',
+ '--csv',
+ dest='csv_display',
+ action='store_true',
+ help=Help.param_csv)
+ parser_models_list._action_groups.append(parser_models_list_optional)
+ parser_models_list.set_defaults(func=api.model_list_cli)
+
+ # Models init
+ parser_models_init = subparsers_models.add_parser(
+ 'init',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_init)
+ parser_models_init_optional = parser_models_init._action_groups.pop()
+ parser_models_init_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_upfile)
+ parser_models_init._action_groups.append(parser_models_init_optional)
+ parser_models_init.set_defaults(func=api.model_initialize_cli)
+
+ # Models create
+ parser_models_create = subparsers_models.add_parser(
+ 'create',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_new)
+ parser_models_create_optional = parser_models_create._action_groups.pop()
+ parser_models_create_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_upfile)
+ parser_models_create._action_groups.append(parser_models_create_optional)
+ parser_models_create.set_defaults(func=api.model_create_new_cli)
+
+ # Models delete
+ parser_models_delete = subparsers_models.add_parser(
+ 'delete',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_delete)
+ parser_models_delete_optional = parser_models_delete._action_groups.pop()
+ parser_models_delete_optional.add_argument('model', help=Help.param_model)
+ parser_models_delete_optional.add_argument('-y',
+ '--yes',
+ dest='yes',
+ action='store_true',
+ help=Help.param_yes)
+ parser_models_delete._action_groups.append(parser_models_delete_optional)
+ parser_models_delete.set_defaults(func=api.model_delete_cli)
+
+ # Models update
+ parser_models_update = subparsers_models.add_parser(
+ 'update',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_models_update)
+ parser_models_update_optional = parser_models_update._action_groups.pop()
+ parser_models_update_optional.add_argument('-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_upfile)
+ parser_models_update._action_groups.append(parser_models_update_optional)
+ parser_models_update.set_defaults(func=api.model_update_cli)
+
+
+def parse_model_instances(subparsers):
+ parser_model_instances = subparsers.add_parser(
+ 'instances',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_model_instances,
+ aliases=['mi'])
+
+ subparsers_model_instances = parser_model_instances.add_subparsers(
+ title='commands', dest='command')
+ subparsers_model_instances.required = True
+ subparsers_model_instances.choices = Help.model_instances_choices
+
+ # Models Instances Versions.
+ parse_model_instance_versions(subparsers_model_instances)
+
+ # Models Instances get
+ parser_model_instance_get = subparsers_model_instances.add_parser(
+ 'get',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instances_get)
+ parser_model_instance_get_optional = parser_model_instance_get._action_groups.pop(
+ )
+ parser_model_instance_get_optional.add_argument(
+ 'model_instance', help=Help.param_model_instance)
+ parser_model_instance_get_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_instance_downfile)
+ parser_model_instance_get._action_groups.append(
+ parser_model_instance_get_optional)
+ parser_model_instance_get.set_defaults(func=api.model_instance_get_cli)
+
+ # Model Instances init
+ parser_model_instances_init = subparsers_model_instances.add_parser(
+ 'init',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instances_init)
+ parser_model_instances_init_optional = parser_model_instances_init._action_groups.pop(
+ )
+ parser_model_instances_init_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_instance_upfile)
+ parser_model_instances_init._action_groups.append(
+ parser_model_instances_init_optional)
+ parser_model_instances_init.set_defaults(
+ func=api.model_instance_initialize_cli)
+
+ # Model Instances create
+ parser_model_instances_create = subparsers_model_instances.add_parser(
+ 'create',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instances_new)
+ parser_model_instances_create_optional = parser_model_instances_create._action_groups.pop(
+ )
+ parser_model_instances_create_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_instance_upfile)
+ parser_model_instances_create_optional.add_argument('-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_model_instances_create_optional.add_argument(
+ '-r',
+ '--dir-mode',
+ dest='dir_mode',
+ choices=['skip', 'zip', 'tar'],
+ default='skip',
+ help=Help.param_dir_mode)
+ parser_model_instances_create._action_groups.append(
+ parser_model_instances_create_optional)
+ parser_model_instances_create.set_defaults(
+ func=api.model_instance_create_cli)
+
+ # Models Instances delete
+ parser_model_instances_delete = subparsers_model_instances.add_parser(
+ 'delete',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instances_delete)
+ parser_model_instances_delete_optional = parser_model_instances_delete._action_groups.pop(
+ )
+ parser_model_instances_delete_optional.add_argument(
+ 'model_instance', help=Help.param_model_instance)
+ parser_model_instances_delete_optional.add_argument('-y',
+ '--yes',
+ dest='yes',
+ action='store_true',
+ help=Help.param_yes)
+ parser_model_instances_delete._action_groups.append(
+ parser_model_instances_delete_optional)
+ parser_model_instances_delete.set_defaults(
+ func=api.model_instance_delete_cli)
+
+ # Models Instances update
+ parser_model_instances_update = subparsers_model_instances.add_parser(
+ 'update',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instances_update)
+ parser_model_instances_update_optional = parser_model_instances_update._action_groups.pop(
+ )
+ parser_model_instances_update_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_instance_upfile)
+ parser_model_instances_update._action_groups.append(
+ parser_model_instances_update_optional)
+ parser_model_instances_update.set_defaults(
+ func=api.model_instance_update_cli)
+
+
+def parse_model_instance_versions(subparsers):
+ parser_model_instance_versions = subparsers.add_parser(
+ 'versions',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_model_instance_versions,
+ aliases=['miv'])
+
+ subparsers_model_intance_versions = parser_model_instance_versions.add_subparsers(
+ title='commands', dest='command')
+ subparsers_model_intance_versions.required = True
+ subparsers_model_intance_versions.choices = Help.model_instance_versions_choices
+
+ # Model Instance Versions create
+ parser_model_instance_versions_create = subparsers_model_intance_versions.add_parser(
+ 'create',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instance_versions_new)
+ parser_model_instance_versions_create_optional = parser_model_instance_versions_create._action_groups.pop(
+ )
+ parser_model_instance_versions_create_optional.add_argument(
+ 'model_instance', help=Help.param_model_instance)
+ parser_model_instance_versions_create_optional.add_argument(
+ '-p',
+ '--path',
+ dest='folder',
+ required=False,
+ help=Help.param_model_instance_version_upfile)
+ parser_model_instance_versions_create_optional.add_argument(
+ '-n',
+ '--version-notes',
+ dest='version_notes',
+ required=False,
+ help=Help.param_model_instance_version_upfile)
+ parser_model_instance_versions_create_optional.add_argument(
+ '-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_model_instance_versions_create_optional.add_argument(
+ '-r',
+ '--dir-mode',
+ dest='dir_mode',
+ choices=['skip', 'zip', 'tar'],
+ default='skip',
+ help=Help.param_dir_mode)
+ parser_model_instance_versions_create._action_groups.append(
+ parser_model_instance_versions_create_optional)
+ parser_model_instance_versions_create.set_defaults(
+ func=api.model_instance_version_create_cli)
+
+ # Models Instance Versions download
+ parser_model_instance_versions_download = subparsers_model_intance_versions.add_parser(
+ 'download',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instance_versions_download)
+ parser_model_instance_versions_download_optional = parser_model_instance_versions_download._action_groups.pop(
+ )
+ parser_model_instance_versions_download_optional.add_argument(
+ 'model_instance_version', help=Help.param_model_instance_version)
+ parser_model_instance_versions_download_optional.add_argument(
+ '-p',
+ '--path',
+ dest='path',
+ required=False,
+ help=Help.param_downfolder)
+ parser_model_instance_versions_download_optional.add_argument(
+ '--untar', dest='untar', action='store_true', help=Help.param_untar)
+ parser_model_instance_versions_download_optional.add_argument(
+ '-f',
+ '--force',
+ dest='force',
+ action='store_true',
+ help=Help.param_force)
+ parser_model_instance_versions_download_optional.add_argument(
+ '-q',
+ '--quiet',
+ dest='quiet',
+ action='store_true',
+ help=Help.param_quiet)
+ parser_model_instance_versions_download._action_groups.append(
+ parser_model_instance_versions_download_optional)
+ parser_model_instance_versions_download.set_defaults(
+ func=api.model_instance_version_download_cli)
+
+ # Models Instance Versions delete
+ parser_model_instance_versions_delete = subparsers_model_intance_versions.add_parser(
+ 'delete',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_model_instance_versions_delete)
+ parser_model_instance_versions_delete_optional = parser_model_instance_versions_delete._action_groups.pop(
+ )
+ parser_model_instance_versions_delete_optional.add_argument(
+ 'model_instance_version', help=Help.param_model_instance_version)
+ parser_model_instance_versions_delete_optional.add_argument(
+ '-y', '--yes', dest='yes', action='store_true', help=Help.param_yes)
+ parser_model_instance_versions_delete._action_groups.append(
+ parser_model_instance_versions_delete_optional)
+ parser_model_instance_versions_delete.set_defaults(
+ func=api.model_instance_version_delete_cli)
+
+
+def parse_files(subparsers):
+ parser_files = subparsers.add_parser(
+ 'files',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_files,
+ aliases=['f'])
+
+ subparsers_files = parser_files.add_subparsers(title='commands',
+ dest='command')
+ subparsers_files.required = True
+ subparsers_files.choices = Help.files_choices
+
+ # Files upload
+ parser_files_upload = subparsers_files.add_parser(
+ 'upload',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_files_upload,
+ aliases=['u'])
+ parser_files_upload_optional = parser_files_upload._action_groups.pop()
+ parser_files_upload_optional.add_argument(
+ '-i',
+ '--inbox-path',
+ dest='inbox_path',
+ required=False,
+ default='',
+ help=Help.param_files_upload_inbox_path)
+ parser_files_upload_optional.add_argument(
+ 'local_paths',
+ metavar='local-path',
+ nargs='+',
+ help=Help.param_files_upload_local_paths)
+ parser_files_upload_optional.add_argument(
+ '--no-resume',
+ dest='no_resume',
+ action='store_true',
+ required=False,
+ default=False,
+ help=Help.param_files_upload_no_resume)
+ parser_files_upload_optional.add_argument(
+ '--no-compress',
+ dest='no_compress',
+ action='store_true',
+ required=False,
+ default=False,
+ help=Help.param_files_upload_no_compress)
+ parser_files_upload._action_groups.append(parser_files_upload_optional)
+ parser_files_upload.set_defaults(func=api.files_upload_cli)
+
+
+def parse_config(subparsers):
+ parser_config = subparsers.add_parser(
+ 'config',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.group_config)
+ subparsers_config = parser_config.add_subparsers(title='commands',
+ dest='command')
+ subparsers_config.required = True
+ subparsers_config.choices = Help.config_choices
+
+ parser_config_view = subparsers_config.add_parser(
+ 'view',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_config_view)
+ parser_config_view.set_defaults(func=api.print_config_values)
+
+ parser_config_set = subparsers_config.add_parser(
+ 'set',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_config_set)
+ parser_config_set._action_groups.pop()
+ parser_config_set_required = parser_config_set.add_argument_group(
+ 'required arguments')
+ parser_config_set_required.add_argument('-n',
+ '--name',
+ dest='name',
+ required=True,
+ help=Help.param_config_name)
+ parser_config_set_required.add_argument('-v',
+ '--value',
+ dest='value',
+ required=True,
+ help=Help.param_config_value)
+ parser_config_set.set_defaults(func=api.set_config_value)
+
+ parser_config_unset = subparsers_config.add_parser(
+ 'unset',
+ formatter_class=argparse.RawTextHelpFormatter,
+ help=Help.command_config_unset)
+ parser_config_unset._action_groups.pop()
+ parser_config_unset_required = parser_config_unset.add_argument_group(
+ 'required arguments')
+ parser_config_unset_required.add_argument('-n',
+ '--name',
+ dest='name',
+ required=True,
+ help=Help.param_config_name)
+ parser_config_unset.set_defaults(func=api.unset_config_value)
+
+
+class Help(object):
+ kaggle_choices = [
+ 'competitions', 'c', 'datasets', 'd', 'kernels', 'k', 'models', 'm',
+ 'files', 'f', 'config'
+ ]
+ competitions_choices = [
+ 'list', 'files', 'download', 'submit', 'submissions', 'leaderboard'
+ ]
+ datasets_choices = [
+ 'list', 'files', 'download', 'create', 'version', 'init', 'metadata',
+ 'status'
+ ]
+ kernels_choices = ['list', 'init', 'push', 'pull', 'output', 'status']
+ models_choices = [
+ 'instances', 'get', 'list', 'init', 'create', 'delete', 'update'
+ ]
+ model_instances_choices = [
+ 'versions', 'get', 'init', 'create', 'delete', 'update'
+ ]
+ model_instance_versions_choices = ['init', 'create', 'download', 'delete']
+ files_choices = ['upload']
+ config_choices = ['view', 'set', 'unset']
+
+ kaggle = 'Use one of:\ncompetitions {' + ', '.join(
+ competitions_choices) + '}\ndatasets {' + ', '.join(
+ datasets_choices) + '}\nkernels {' + ', '.join(
+ kernels_choices) + '}\nmodels {' + ', '.join(
+ models_choices) + '}\nmodels instances {' + ', '.join(
+ model_instances_choices
+ ) + '}\nmodels instances versions {' + ', '.join(
+ model_instance_versions_choices
+ ) + '}\nconfig {' + ', '.join(config_choices) + '}'
+
+ group_competitions = 'Commands related to Kaggle competitions'
+ group_datasets = 'Commands related to Kaggle datasets'
+ group_kernels = 'Commands related to Kaggle kernels'
+ group_models = 'Commands related to Kaggle models'
+ group_model_instances = 'Commands related to Kaggle model instances'
+ group_model_instance_versions = 'Commands related to Kaggle model instance versions'
+ group_files = 'Commands related files'
+ group_config = 'Configuration settings'
+
+ # Competitions commands
+ command_competitions_list = 'List available competitions'
+ command_competitions_files = 'List competition files'
+ command_competitions_download = 'Download competition files'
+ command_competitions_submit = 'Make a new competition submission'
+ command_competitions_submissions = 'Show your competition submissions'
+ command_competitions_leaderboard = 'Get competition leaderboard information'
+
+ # Datasets commands
+ command_datasets_list = 'List available datasets'
+ command_datasets_files = 'List dataset files'
+ command_datasets_download = 'Download dataset files'
+ command_datasets_new = 'Create a new dataset'
+ command_datasets_new_version = 'Create a new dataset version'
+ command_datasets_init = 'Initialize metadata file for dataset creation'
+ command_datasets_metadata = 'Download metadata about a dataset'
+ command_datasets_status = 'Get the creation status for a dataset'
+
+ # Kernels commands
+ command_kernels_list = (
+ 'List available kernels. By default, shows 20 results sorted by '
+ 'hotness')
+ command_kernels_init = 'Initialize metadata file for a kernel'
+ command_kernels_push = 'Push new code to a kernel and run the kernel'
+ command_kernels_pull = 'Pull down code from a kernel'
+ command_kernels_output = 'Get data output from the latest kernel run'
+ command_kernels_status = 'Display the status of the latest kernel run'
+
+ # Models commands
+ command_models_get = 'Get a model'
+ command_models_list = 'List models'
+ command_models_init = 'Initialize metadata file for model creation'
+ command_models_new = 'Create a new model'
+ command_models_delete = 'Delete a model'
+ command_models_update = 'Update a model'
+
+ # Files commands
+ command_files_upload = 'Upload files'
+
+ # Config commands
+ command_config_path = (
+ 'Set folder where competition or dataset files will be '
+ 'downloaded')
+ command_config_proxy = 'Set proxy server'
+ command_config_competition = 'Set default competition'
+ command_config_view = 'View current config values'
+ command_config_set = 'Set a configuration value'
+ command_config_unset = 'Clear a configuration value'
+
+ # General params
+ param_downfolder = (
+ 'Folder where file(s) will be downloaded, defaults to current working '
+ 'directory')
+ param_wp = 'Download files to current working path'
+ param_proxy = 'Proxy for HTTP requests'
+ param_quiet = (
+ 'Suppress printing information about the upload/download progress')
+ param_public = 'Create publicly (default is private)'
+ param_keep_tabular = (
+ 'Do not convert tabular files to CSV (default is to convert)')
+ param_dir_mode = (
+ 'What to do with directories: "skip" - ignore; "zip" - compressed upload; "tar" - '
+ 'uncompressed upload')
+ param_delete_old_version = 'Delete old versions of this dataset'
+ param_force = (
+ 'Skip check whether local version of file is up to date, force'
+ ' file download')
+ param_upfile = 'File for upload (full path)'
+ param_csv = 'Print results in CSV format (if not set print in table format)'
+ param_page = 'Page number for results paging. Page size is 20 by default'
+ param_page_size = (
+ 'Number of items to show on a page. Default size is 20, '
+ 'max is 100')
+ param_page_token = 'Page token for results paging.'
+ param_search = 'Term(s) to search for'
+ param_mine = 'Display only my items'
+ param_unzip = (
+ 'Unzip the downloaded file. Will delete the zip file when completed.')
+ param_untar = (
+ 'Untar the downloaded file. Will delete the tar file when completed.')
+ param_yes = (
+ 'Sets any confirmation values to "yes" automatically. Users will not be asked to confirm.'
+ )
+
+ # Competitions params
+ param_competition = (
+ 'Competition URL suffix (use "kaggle competitions list" '
+ 'to show options)\nIf empty, the default competition '
+ 'will be used (use "kaggle config set competition")"')
+ param_competition_nonempty = (
+ 'Competition URL suffix (use "kaggle competitions list" to show '
+ 'options)')
+ param_competition_leaderboard_view = 'Show the top of the leaderboard'
+ param_competition_leaderboard_download = 'Download entire leaderboard'
+ param_competition_file = (
+ 'File name, all files downloaded if not provided\n(use "kaggle '
+ 'competitions files -c " to show options)')
+ param_competition_message = 'Message describing this submission'
+ param_competition_group = (
+ 'Search for competitions in a specific group. Default is \'general\'. '
+ 'Valid options are \'general\', \'entered\', and \'inClass\'')
+ param_competition_category = (
+ 'Search for competitions of a specific category. Default is \'all\'. '
+ 'Valid options are \'all\', \'featured\', \'research\', '
+ '\'recruitment\', \'gettingStarted\', \'masters\', and \'playground\'')
+ param_competition_sort_by = (
+ 'Sort list results. Default is \'latestDeadline\'. Valid options are '
+ '\'grouped\', \'prize\', \'earliestDeadline\', \'latestDeadline\', '
+ '\'numberOfTeams\', and \'recentlyCreated\'')
+
+ # Datasets params
+ param_dataset = (
+ 'Dataset URL suffix in format / (use '
+ '"kaggle datasets list" to show options)')
+ param_dataset_file = (
+ 'File name, all files downloaded if not provided\n(use '
+ '"kaggle datasets files -d " to show options)')
+ param_dataset_version_notes = 'Message describing the new version'
+ param_dataset_upfile = (
+ 'Folder for upload, containing data files and a '
+ 'special datasets-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/Dataset-Metadata). '
+ 'Defaults to current working directory')
+ param_dataset_sort_by = (
+ 'Sort list results. Default is \'hottest\'. Valid options are '
+ '\'hottest\', \'votes\', \'updated\', and \'active\'')
+ param_dataset_size = (
+ 'DEPRECATED. Please use --max-size and --min-size to filter dataset sizes.'
+ )
+ param_dataset_file_type = (
+ 'Search for datasets with a specific file type. Default is \'all\'. '
+ 'Valid options are \'all\', \'csv\', \'sqlite\', \'json\', and '
+ '\'bigQuery\'. Please note that bigQuery datasets cannot be downloaded'
+ )
+ param_dataset_license = (
+ 'Search for datasets with a specific license. Default is \'all\'. '
+ 'Valid options are \'all\', \'cc\', \'gpl\', \'odb\', and \'other\'')
+ param_dataset_tags = (
+ 'Search for datasets that have specific tags. Tag list should be '
+ 'comma separated')
+ param_dataset_user = (
+ 'Find public datasets owned by a specific user or organization')
+ param_dataset_metadata_dir = (
+ 'Location to download dataset metadata to. Defaults to current working '
+ 'directory')
+ param_dataset_metadata_update = ('A flag to indicate whether the dataset'
+ 'metadata should be updated.')
+ param_dataset_maxsize = 'Specify the maximum size of the dataset to return (bytes)'
+ param_dataset_minsize = 'Specify the minimum size of the dataset to return (bytes)'
+
+ # Kernels params
+ param_kernel = (
+ 'Kernel URL suffix in format / (use "kaggle '
+ 'kernels list" to show options)')
+ param_kernel_init = (
+ 'Create a metadata file for an existing kernel URL suffix in format '
+ '/ (use "kaggle kernels list" to show options)')
+ param_kernel_upfile = (
+ 'Folder for upload, containing data files and a '
+ 'special kernel-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/Kernel-Metadata). '
+ 'Defaults to current working directory')
+ param_kernel_parent = 'Find children of the specified parent kernel'
+ param_kernel_competition = 'Find kernels for a given competition slug'
+ param_kernel_dataset = ('Find kernels for a given dataset slug. Format is '
+ '{username/dataset-slug}')
+ param_kernel_user = 'Find kernels created by a given username'
+ # TODO(b/129357583): Pull these from the same spot as the api impl
+ param_kernel_language = (
+ 'Specify the language the kernel is written in. Default is \'all\'. '
+ 'Valid options are \'all\', \'python\', \'r\', \'sqlite\', and '
+ '\'julia\'')
+ param_kernel_type = (
+ 'Specify the type of kernel. Default is \'all\'. Valid '
+ 'options are \'all\', \'script\', and \'notebook\'')
+ param_kernel_output_type = (
+ 'Search for specific kernel output types. '
+ 'Default is \'all\'. Valid options are \'all\', '
+ '\'visualizations\', and \'data\'')
+ param_kernel_sort_by = (
+ 'Sort list results. Default is \'hotness\'. Valid '
+ 'options are \'hotness\', \'commentCount\', '
+ '\'dateCreated\', \'dateRun\', \'relevance\', '
+ '\'scoreAscending\', \'scoreDescending\', '
+ '\'viewCount\', and \'voteCount\'. \'relevance\' '
+ 'is only applicable if a search term is specified.')
+ param_kernel_pull_metadata = 'Generate metadata when pulling kernel'
+
+ # Models params
+ param_model = ('Model URL suffix in format /')
+ param_model_sort_by = (
+ 'Sort list results. Default is \'hotness\'. Valid options are '
+ '\'hotness\', \'downloadCount\', \'voteCount\', \'notebookCount\' and \'createTime\''
+ )
+ param_model_owner = (
+ 'Find public models owned by a specific user or organization')
+ param_model_downfile = (
+ 'Folder containing the special model-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/Model-Metadata).')
+ param_model_upfile = (
+ 'Folder containing the special model-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/Model-Metadata). '
+ 'Defaults to current working directory')
+
+ # Model Instances params
+ param_model_instance = (
+ 'Model Instance URL suffix in format ///'
+ )
+ command_model_instances_get = 'Get a model instance'
+ command_model_instances_init = 'Initialize metadata file for model instance creation'
+ command_model_instances_new = 'Create a new model instance'
+ param_model_instance_downfile = (
+ 'Folder for downloading the special model-instance-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/ModelInstance-Metadata). ')
+ param_model_instance_upfile = (
+ 'Folder for upload, containing data files and a '
+ 'special model-instance-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/ModelInstance-Metadata). '
+ 'Defaults to current working directory')
+ command_model_instances_delete = 'Delete a model instance'
+ command_model_instances_update = 'Update a model instance'
+
+ # Model Instance Versions params
+ param_model_instance_version = (
+ 'Model Instance Version URL suffix in format ////'
+ )
+
+ # Model Instance Versions params
+ command_model_instance_versions_new = 'Create a new model instance version'
+ param_model_instance_version_upfile = (
+ 'Folder for upload, containing data files and a '
+ 'special model-instance_version-metadata.json file '
+ '(https://github.com/Kaggle/kaggle-api/wiki/ModelInstanceVersion-Metadata). '
+ 'Defaults to current working directory')
+ command_model_instance_versions_delete = 'Delete a model instance version'
+ command_model_instance_versions_download = 'Download model instance version files'
+ param_model_instance_version_notes = 'Version notes to record for the new model instance version'
+
+ # Files params
+ param_files_upload_inbox_path = 'Virtual path on the server where the uploaded files will be stored'
+ param_files_upload_local_paths = (
+ 'List of local filesystem paths. Each path creates a separate file on the server. '
+ 'Directories are uploaded as zip archives by default (e.g., a directory called '
+ '"data" will be uploaded as "data.zip")')
+ param_files_upload_no_compress = 'Whether to compress directories (zip) or not (tar)'
+ param_files_upload_no_resume = 'Whether to skip resumable uploads.'
+
+ # Config params
+ param_config_name = ('Name of the configuration parameter\n(one of '
+ 'competition, path, proxy)')
+ param_config_value = (
+ ('Value of the configuration parameter, valid values '
+ 'depending on name\n- competition: ') + param_competition_nonempty +
+ '\n- path: ' + param_downfolder + '\n- proxy: ' + param_proxy)
diff --git a/.venv/lib/python3.10/site-packages/kaggle/configuration.py b/.venv/lib/python3.10/site-packages/kaggle/configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..9610cad66a3bb6e24dd838816f97c4d714ab298d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/configuration.py
@@ -0,0 +1,258 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+from __future__ import absolute_import
+
+import copy
+import logging
+import multiprocessing
+import sys
+import urllib3
+
+import six
+from six.moves import http_client as httplib
+
+
+class TypeWithDefault(type):
+ def __init__(cls, name, bases, dct):
+ super(TypeWithDefault, cls).__init__(name, bases, dct)
+ cls._default = None
+
+ def __call__(cls):
+ if cls._default is None:
+ cls._default = type.__call__(cls)
+ return copy.copy(cls._default)
+
+ def set_default(cls, default):
+ cls._default = copy.copy(default)
+
+
+class Configuration(six.with_metaclass(TypeWithDefault, object)):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Ref: https://github.com/swagger-api/swagger-codegen
+ Do not edit the class manually.
+ """
+
+ def __init__(self):
+ """Constructor"""
+ # Default Base url
+ self.host = _get_endpoint_from_env() or "https://www.kaggle.com/api/v1"
+ # Temp file folder for downloading files
+ self.temp_folder_path = None
+
+ # Authentication Settings
+ # dict to store API key(s)
+ self.api_key = {}
+ # dict to store API prefix (e.g. Bearer)
+ self.api_key_prefix = {}
+ # Username for HTTP basic authentication
+ self.username = ""
+ # Password for HTTP basic authentication
+ self.password = ""
+
+ # Logging Settings
+ self.logger = {}
+ self.logger["package_logger"] = logging.getLogger("kaggle")
+ self.logger["urllib3_logger"] = logging.getLogger("urllib3")
+ # Log format
+ self.logger_format = '%(asctime)s %(levelname)s %(message)s'
+ # Log stream handler
+ self.logger_stream_handler = None
+ # Log file handler
+ self.logger_file_handler = None
+ # Debug file location
+ self.logger_file = None
+ # Debug switch
+ self.debug = False
+
+ # SSL/TLS verification
+ # Set this to false to skip verifying SSL certificate when calling API
+ # from https server.
+ self.verify_ssl = True
+ # Set this to customize the certificate file to verify the peer.
+ self.ssl_ca_cert = None
+ # client certificate file
+ self.cert_file = None
+ # client key file
+ self.key_file = None
+ # Set this to True/False to enable/disable SSL hostname verification.
+ self.assert_hostname = None
+
+ # urllib3 connection pool's maximum number of connections saved
+ # per pool. urllib3 uses 1 connection as default value, but this is
+ # not the best value when you are making a lot of possibly parallel
+ # requests to the same host, which is often the case here.
+ # cpu_count * 5 is used as default value to increase performance.
+ self.connection_pool_maxsize = multiprocessing.cpu_count() * 5
+
+ # Proxy URL
+ self.proxy = None
+ # Safe chars for path_param
+ self.safe_chars_for_path_param = ''
+
+ @property
+ def logger_file(self):
+ """The logger file.
+
+ If the logger_file is None, then add stream handler and remove file
+ handler. Otherwise, add file handler and remove stream handler.
+
+ :param value: The logger_file path.
+ :type: str
+ """
+ return self.__logger_file
+
+ @logger_file.setter
+ def logger_file(self, value):
+ """The logger file.
+
+ If the logger_file is None, then add stream handler and remove file
+ handler. Otherwise, add file handler and remove stream handler.
+
+ :param value: The logger_file path.
+ :type: str
+ """
+ self.__logger_file = value
+ if self.__logger_file:
+ # If set logging file,
+ # then add file handler and remove stream handler.
+ self.logger_file_handler = logging.FileHandler(self.__logger_file)
+ self.logger_file_handler.setFormatter(self.logger_formatter)
+ for _, logger in six.iteritems(self.logger):
+ logger.addHandler(self.logger_file_handler)
+ if self.logger_stream_handler:
+ logger.removeHandler(self.logger_stream_handler)
+ else:
+ # If not set logging file,
+ # then add stream handler and remove file handler.
+ self.logger_stream_handler = logging.StreamHandler()
+ self.logger_stream_handler.setFormatter(self.logger_formatter)
+ for _, logger in six.iteritems(self.logger):
+ logger.addHandler(self.logger_stream_handler)
+ if self.logger_file_handler:
+ logger.removeHandler(self.logger_file_handler)
+
+ @property
+ def debug(self):
+ """Debug status
+
+ :param value: The debug status, True or False.
+ :type: bool
+ """
+ return self.__debug
+
+ @debug.setter
+ def debug(self, value):
+ """Debug status
+
+ :param value: The debug status, True or False.
+ :type: bool
+ """
+ self.__debug = value
+ if self.__debug:
+ # if debug status is True, turn on debug logging
+ for _, logger in six.iteritems(self.logger):
+ logger.setLevel(logging.DEBUG)
+ # turn on httplib debug
+ httplib.HTTPConnection.debuglevel = 1
+ else:
+ # if debug status is False, turn off debug logging,
+ # setting log level to default `logging.WARNING`
+ for _, logger in six.iteritems(self.logger):
+ logger.setLevel(logging.WARNING)
+ # turn off httplib debug
+ httplib.HTTPConnection.debuglevel = 0
+
+ @property
+ def logger_format(self):
+ """The logger format.
+
+ The logger_formatter will be updated when sets logger_format.
+
+ :param value: The format string.
+ :type: str
+ """
+ return self.__logger_format
+
+ @logger_format.setter
+ def logger_format(self, value):
+ """The logger format.
+
+ The logger_formatter will be updated when sets logger_format.
+
+ :param value: The format string.
+ :type: str
+ """
+ self.__logger_format = value
+ self.logger_formatter = logging.Formatter(self.__logger_format)
+
+ def get_api_key_with_prefix(self, identifier):
+ """Gets API key (with prefix if set).
+
+ :param identifier: The identifier of apiKey.
+ :return: The token for api key authentication.
+ """
+ if (self.api_key.get(identifier) and
+ self.api_key_prefix.get(identifier)):
+ return self.api_key_prefix[identifier] + ' ' + self.api_key[identifier] # noqa: E501
+ elif self.api_key.get(identifier):
+ return self.api_key[identifier]
+
+ def get_basic_auth_token(self):
+ """Gets HTTP basic authentication header (string).
+
+ :return: The token for basic HTTP authentication.
+ """
+ return urllib3.util.make_headers(
+ basic_auth=self.username + ':' + self.password
+ ).get('authorization')
+
+ def auth_settings(self):
+ """Gets Auth Settings dict for api client.
+
+ :return: The Auth Settings information dict.
+ """
+ return {
+ 'basicAuth':
+ {
+ 'type': 'basic',
+ 'in': 'header',
+ 'key': 'Authorization',
+ 'value': self.get_basic_auth_token()
+ },
+
+ }
+
+ def to_debug_report(self):
+ """Gets the essential information for debugging.
+
+ :return: The report for debugging.
+ """
+ return "Python SDK Debug Report:\n"\
+ "OS: {env}\n"\
+ "Python Version: {pyversion}\n"\
+ "Version of the API: 1\n"\
+ "SDK Package Version: 1".\
+ format(env=sys.platform, pyversion=sys.version)
+
+
+def _get_endpoint_from_env():
+ import os
+ endpoint = os.environ.get("KAGGLE_API_ENDPOINT")
+ if endpoint is None:
+ return None
+ endpoint = endpoint.rstrip("/")
+ if endpoint.endswith("/api/v1"):
+ return endpoint
+ return endpoint + "/api/v1"
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__init__.py b/.venv/lib/python3.10/site-packages/kaggle/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1faa097b3cecb54e9277dcfacd72f4d0dca2499
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/__init__.py
@@ -0,0 +1,36 @@
+# coding: utf-8
+
+# flake8: noqa
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+from __future__ import absolute_import
+
+# import models into model package
+from kaggle.models.api_blob_type import ApiBlobType
+from kaggle.models.collaborator import Collaborator
+from kaggle.models.create_inbox_file_request import CreateInboxFileRequest
+from kaggle.models.dataset_column import DatasetColumn
+from kaggle.models.dataset_new_request import DatasetNewRequest
+from kaggle.models.dataset_new_version_request import DatasetNewVersionRequest
+from kaggle.models.dataset_update_settings_request import DatasetUpdateSettingsRequest
+from kaggle.models.error import Error
+from kaggle.models.kernel_push_request import KernelPushRequest
+from kaggle.models.license import License
+from kaggle.models.model_instance_new_version_request import ModelInstanceNewVersionRequest
+from kaggle.models.model_instance_update_request import ModelInstanceUpdateRequest
+from kaggle.models.model_new_instance_request import ModelNewInstanceRequest
+from kaggle.models.model_new_request import ModelNewRequest
+from kaggle.models.model_update_request import ModelUpdateRequest
+from kaggle.models.result import Result
+from kaggle.models.start_blob_upload_request import StartBlobUploadRequest
+from kaggle.models.start_blob_upload_response import StartBlobUploadResponse
+from kaggle.models.upload_file import UploadFile
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/__init__.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9bd5e18330312ac2e52d205739887fa09d2f150b
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/__init__.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/api_blob_type.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/api_blob_type.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cc034e7f4e9fdaa5241abb8333865ff75b00b938
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/api_blob_type.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/collaborator.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/collaborator.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c1ba3279b699ef66b0d1d52398f1111cd690634a
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/collaborator.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/create_inbox_file_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/create_inbox_file_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0d0f717964b10df33315ebc441e3e858b01464e1
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/create_inbox_file_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_column.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_column.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59996dbe22bbdddd0010b58cbf71fa8915fa6186
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_column.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e7c9cf2c4e7b4eece6822fc052924bd4e78d5723
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_version_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_version_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..36434075fcd69ddc0e6a06b5984fdbbbadfb74d0
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_new_version_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_update_settings_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_update_settings_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0d3743946615d4977e7a58d957a37c14419c1142
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/dataset_update_settings_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/error.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/error.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1e00380540ab1b8bc6434c457e91c6b9b290c529
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/error.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kaggle_models_extended.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kaggle_models_extended.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..843e726545d842b055eaaf9f31ac383fe916e1e1
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kaggle_models_extended.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kernel_push_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kernel_push_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..91d99626a48af7638b0769c3a767c622e98b1c31
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/kernel_push_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/license.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/license.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..26983477a705c13ea9dd6ea2da914101f54e7bc9
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/license.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_new_version_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_new_version_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..05010af03bd7166bb302470ea62d513f99a3b0de
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_new_version_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_update_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_update_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..52a6e99a7b411090ef6a8f63ea1d32616b839da2
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_instance_update_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_instance_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_instance_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1e24d65810e49c305c689ca269ccc21bd53e322d
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_instance_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59ecd4e7612d149ca31629cda06aa3f74589d258
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_new_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_update_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_update_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a5a923800684705a1959a70adf45e8b2dd45719f
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/model_update_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/result.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/result.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a9176ae44c64dbee25c950aa1d93b7c9dde8cc5c
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/result.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_request.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_request.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8b13dd1753973acb9b6a222870451166e3e89fbe
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_request.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_response.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_response.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f26dea686537bc2cef7fb08da5bdce5c1926b3cf
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/start_blob_upload_response.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/upload_file.cpython-310.pyc b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/upload_file.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0bae9cb55f28b9bbbfc38bab1c62d42170de829e
Binary files /dev/null and b/.venv/lib/python3.10/site-packages/kaggle/models/__pycache__/upload_file.cpython-310.pyc differ
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/api_blob_type.py b/.venv/lib/python3.10/site-packages/kaggle/models/api_blob_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..73b876642306a9998fbdfe6ecf72848c72da0bd8
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/api_blob_type.py
@@ -0,0 +1,91 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class ApiBlobType(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ allowed enum values
+ """
+ DATASET = "dataset"
+ MODEL = "model"
+ INBOX = "inbox"
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ }
+
+ attribute_map = {
+ }
+
+ def __init__(self): # noqa: E501
+ """ApiBlobType - a model defined in Swagger""" # noqa: E501
+ self.discriminator = None
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ApiBlobType):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/collaborator.py b/.venv/lib/python3.10/site-packages/kaggle/models/collaborator.py
new file mode 100644
index 0000000000000000000000000000000000000000..d134ef149001781d4a8f48e66b17157a85d8ae1a
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/collaborator.py
@@ -0,0 +1,150 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class Collaborator(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'username': 'str',
+ 'role': 'str'
+ }
+
+ attribute_map = {
+ 'username': 'username',
+ 'role': 'role'
+ }
+
+ def __init__(self, username=None, role=None): # noqa: E501
+ """Collaborator - a model defined in Swagger""" # noqa: E501
+
+ self._username = None
+ self._role = None
+ self.discriminator = None
+
+ self.username = username
+ self.role = role
+
+ @property
+ def username(self):
+ """Gets the username of this Collaborator. # noqa: E501
+
+ Username of the collaborator # noqa: E501
+
+ :return: The username of this Collaborator. # noqa: E501
+ :rtype: str
+ """
+ return self._username
+
+ @username.setter
+ def username(self, username):
+ """Sets the username of this Collaborator.
+
+ Username of the collaborator # noqa: E501
+
+ :param username: The username of this Collaborator. # noqa: E501
+ :type: str
+ """
+ if username is None:
+ raise ValueError("Invalid value for `username`, must not be `None`") # noqa: E501
+
+ self._username = username
+
+ @property
+ def role(self):
+ """Gets the role of this Collaborator. # noqa: E501
+
+ Role of the collaborator # noqa: E501
+
+ :return: The role of this Collaborator. # noqa: E501
+ :rtype: str
+ """
+ return self._role
+
+ @role.setter
+ def role(self, role):
+ """Sets the role of this Collaborator.
+
+ Role of the collaborator # noqa: E501
+
+ :param role: The role of this Collaborator. # noqa: E501
+ :type: str
+ """
+ if role is None:
+ raise ValueError("Invalid value for `role`, must not be `None`") # noqa: E501
+ allowed_values = ["reader", "writer"] # noqa: E501
+ if role not in allowed_values:
+ raise ValueError(
+ "Invalid value for `role` ({0}), must be one of {1}" # noqa: E501
+ .format(role, allowed_values)
+ )
+
+ self._role = role
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, Collaborator):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/create_inbox_file_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/create_inbox_file_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..f40be733992679d714abc6d1f34a8f4888fc93c5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/create_inbox_file_request.py
@@ -0,0 +1,144 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class CreateInboxFileRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'virtual_directory': 'str',
+ 'blob_file_token': 'str'
+ }
+
+ attribute_map = {
+ 'virtual_directory': 'virtualDirectory',
+ 'blob_file_token': 'blobFileToken'
+ }
+
+ def __init__(self, virtual_directory=None, blob_file_token=None): # noqa: E501
+ """CreateInboxFileRequest - a model defined in Swagger""" # noqa: E501
+
+ self._virtual_directory = None
+ self._blob_file_token = None
+ self.discriminator = None
+
+ self.virtual_directory = virtual_directory
+ self.blob_file_token = blob_file_token
+
+ @property
+ def virtual_directory(self):
+ """Gets the virtual_directory of this CreateInboxFileRequest. # noqa: E501
+
+ Directory name used for tagging the uploaded file # noqa: E501
+
+ :return: The virtual_directory of this CreateInboxFileRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._virtual_directory
+
+ @virtual_directory.setter
+ def virtual_directory(self, virtual_directory):
+ """Sets the virtual_directory of this CreateInboxFileRequest.
+
+ Directory name used for tagging the uploaded file # noqa: E501
+
+ :param virtual_directory: The virtual_directory of this CreateInboxFileRequest. # noqa: E501
+ :type: str
+ """
+ if virtual_directory is None:
+ raise ValueError("Invalid value for `virtual_directory`, must not be `None`") # noqa: E501
+
+ self._virtual_directory = virtual_directory
+
+ @property
+ def blob_file_token(self):
+ """Gets the blob_file_token of this CreateInboxFileRequest. # noqa: E501
+
+ Token representing the uploaded file # noqa: E501
+
+ :return: The blob_file_token of this CreateInboxFileRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._blob_file_token
+
+ @blob_file_token.setter
+ def blob_file_token(self, blob_file_token):
+ """Sets the blob_file_token of this CreateInboxFileRequest.
+
+ Token representing the uploaded file # noqa: E501
+
+ :param blob_file_token: The blob_file_token of this CreateInboxFileRequest. # noqa: E501
+ :type: str
+ """
+ if blob_file_token is None:
+ raise ValueError("Invalid value for `blob_file_token`, must not be `None`") # noqa: E501
+
+ self._blob_file_token = blob_file_token
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, CreateInboxFileRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/dataset_column.py b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_column.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2b4ff9e6bc4decd81c83232943db3ec2dabc76c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_column.py
@@ -0,0 +1,226 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class DatasetColumn(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'order': 'float',
+ 'name': 'str',
+ 'type': 'str',
+ 'original_type': 'str',
+ 'description': 'str'
+ }
+
+ attribute_map = {
+ 'order': 'order',
+ 'name': 'name',
+ 'type': 'type',
+ 'original_type': 'originalType',
+ 'description': 'description'
+ }
+
+ def __init__(self, order=None, name=None, type=None, original_type=None, description=None): # noqa: E501
+ """DatasetColumn - a model defined in Swagger""" # noqa: E501
+
+ self._order = None
+ self._name = None
+ self._type = None
+ self._original_type = None
+ self._description = None
+ self.discriminator = None
+
+ if order is not None:
+ self.order = order
+ if name is not None:
+ self.name = name
+ if type is not None:
+ self.type = type
+ if original_type is not None:
+ self.original_type = original_type
+ if description is not None:
+ self.description = description
+
+ @property
+ def order(self):
+ """Gets the order of this DatasetColumn. # noqa: E501
+
+ The order that the column comes in, 0-based. (The first column is 0, second is 1, etc.) # noqa: E501
+
+ :return: The order of this DatasetColumn. # noqa: E501
+ :rtype: float
+ """
+ return self._order
+
+ @order.setter
+ def order(self, order):
+ """Sets the order of this DatasetColumn.
+
+ The order that the column comes in, 0-based. (The first column is 0, second is 1, etc.) # noqa: E501
+
+ :param order: The order of this DatasetColumn. # noqa: E501
+ :type: float
+ """
+
+ self._order = order
+
+ @property
+ def name(self):
+ """Gets the name of this DatasetColumn. # noqa: E501
+
+ The column name # noqa: E501
+
+ :return: The name of this DatasetColumn. # noqa: E501
+ :rtype: str
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Sets the name of this DatasetColumn.
+
+ The column name # noqa: E501
+
+ :param name: The name of this DatasetColumn. # noqa: E501
+ :type: str
+ """
+
+ self._name = name
+
+ @property
+ def type(self):
+ """Gets the type of this DatasetColumn. # noqa: E501
+
+ The type of all of the fields in the column. Please see the data types on https://github.com/Kaggle/kaggle-api/wiki/Dataset-Metadata # noqa: E501
+
+ :return: The type of this DatasetColumn. # noqa: E501
+ :rtype: str
+ """
+ return self._type
+
+ @type.setter
+ def type(self, type):
+ """Sets the type of this DatasetColumn.
+
+ The type of all of the fields in the column. Please see the data types on https://github.com/Kaggle/kaggle-api/wiki/Dataset-Metadata # noqa: E501
+
+ :param type: The type of this DatasetColumn. # noqa: E501
+ :type: str
+ """
+
+ self._type = type
+
+ @property
+ def original_type(self):
+ """Gets the original_type of this DatasetColumn. # noqa: E501
+
+ Used to store the original type of the column, which will be converted to Kaggle's types. For example, an `originalType` of `\"integer\"` would convert to a `type` of `\"numeric\"` # noqa: E501
+
+ :return: The original_type of this DatasetColumn. # noqa: E501
+ :rtype: str
+ """
+ return self._original_type
+
+ @original_type.setter
+ def original_type(self, original_type):
+ """Sets the original_type of this DatasetColumn.
+
+ Used to store the original type of the column, which will be converted to Kaggle's types. For example, an `originalType` of `\"integer\"` would convert to a `type` of `\"numeric\"` # noqa: E501
+
+ :param original_type: The original_type of this DatasetColumn. # noqa: E501
+ :type: str
+ """
+
+ self._original_type = original_type
+
+ @property
+ def description(self):
+ """Gets the description of this DatasetColumn. # noqa: E501
+
+ The description of the column # noqa: E501
+
+ :return: The description of this DatasetColumn. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this DatasetColumn.
+
+ The description of the column # noqa: E501
+
+ :param description: The description of this DatasetColumn. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, DatasetColumn):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..58f2c8b7d038dd7ee976a8a8b4463f081d180030
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_request.py
@@ -0,0 +1,376 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+from kaggle.models.upload_file import UploadFile # noqa: F401,E501
+
+
+class DatasetNewRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'title': 'str',
+ 'slug': 'str',
+ 'owner_slug': 'str',
+ 'license_name': 'str',
+ 'subtitle': 'str',
+ 'description': 'str',
+ 'files': 'list[UploadFile]',
+ 'is_private': 'bool',
+ 'convert_to_csv': 'bool',
+ 'category_ids': 'list[str]'
+ }
+
+ attribute_map = {
+ 'title': 'title',
+ 'slug': 'slug',
+ 'owner_slug': 'ownerSlug',
+ 'license_name': 'licenseName',
+ 'subtitle': 'subtitle',
+ 'description': 'description',
+ 'files': 'files',
+ 'is_private': 'isPrivate',
+ 'convert_to_csv': 'convertToCsv',
+ 'category_ids': 'categoryIds'
+ }
+
+ def __init__(self, title=None, slug=None, owner_slug=None, license_name='unknown', subtitle=None, description='', files=None, is_private=True, convert_to_csv=True, category_ids=None): # noqa: E501
+ """DatasetNewRequest - a model defined in Swagger""" # noqa: E501
+
+ self._title = None
+ self._slug = None
+ self._owner_slug = None
+ self._license_name = None
+ self._subtitle = None
+ self._description = None
+ self._files = None
+ self._is_private = None
+ self._convert_to_csv = None
+ self._category_ids = None
+ self.discriminator = None
+
+ self.title = title
+ if slug is not None:
+ self.slug = slug
+ if owner_slug is not None:
+ self.owner_slug = owner_slug
+ if license_name is not None:
+ self.license_name = license_name
+ if subtitle is not None:
+ self.subtitle = subtitle
+ if description is not None:
+ self.description = description
+ self.files = files
+ if is_private is not None:
+ self.is_private = is_private
+ if convert_to_csv is not None:
+ self.convert_to_csv = convert_to_csv
+ if category_ids is not None:
+ self.category_ids = category_ids
+
+ @property
+ def title(self):
+ """Gets the title of this DatasetNewRequest. # noqa: E501
+
+ The title of the new dataset # noqa: E501
+
+ :return: The title of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._title
+
+ @title.setter
+ def title(self, title):
+ """Sets the title of this DatasetNewRequest.
+
+ The title of the new dataset # noqa: E501
+
+ :param title: The title of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+ if title is None:
+ raise ValueError("Invalid value for `title`, must not be `None`") # noqa: E501
+
+ self._title = title
+
+ @property
+ def slug(self):
+ """Gets the slug of this DatasetNewRequest. # noqa: E501
+
+ The slug that the dataset should be created with # noqa: E501
+
+ :return: The slug of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._slug
+
+ @slug.setter
+ def slug(self, slug):
+ """Sets the slug of this DatasetNewRequest.
+
+ The slug that the dataset should be created with # noqa: E501
+
+ :param slug: The slug of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._slug = slug
+
+ @property
+ def owner_slug(self):
+ """Gets the owner_slug of this DatasetNewRequest. # noqa: E501
+
+ The owner's username # noqa: E501
+
+ :return: The owner_slug of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._owner_slug
+
+ @owner_slug.setter
+ def owner_slug(self, owner_slug):
+ """Sets the owner_slug of this DatasetNewRequest.
+
+ The owner's username # noqa: E501
+
+ :param owner_slug: The owner_slug of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._owner_slug = owner_slug
+
+ @property
+ def license_name(self):
+ """Gets the license_name of this DatasetNewRequest. # noqa: E501
+
+ The license that should be associated with the dataset # noqa: E501
+
+ :return: The license_name of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._license_name
+
+ @license_name.setter
+ def license_name(self, license_name):
+ """Sets the license_name of this DatasetNewRequest.
+
+ The license that should be associated with the dataset # noqa: E501
+
+ :param license_name: The license_name of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+ allowed_values = ["CC0-1.0", "CC-BY-SA-4.0", "GPL-2.0", "ODbL-1.0", "CC-BY-NC-SA-4.0", "unknown", "DbCL-1.0", "CC-BY-SA-3.0", "copyright-authors", "other", "reddit-api", "world-bank", "CC-BY-4.0", "CC-BY-NC-4.0", "PDDL", "CC-BY-3.0", "CC-BY-3.0-IGO", "US-Government-Works", "CC-BY-NC-SA-3.0-IGO", "CDLA-Permissive-1.0", "CDLA-Sharing-1.0", "CC-BY-ND-4.0", "CC-BY-NC-ND-4.0", "ODC-BY-1.0", "LGPL-3.0", "AGPL-3.0", "FDL-1.3", "EU-ODP-Legal-Notice", "apache-2.0", "GPL-3.0"] # noqa: E501
+ if license_name not in allowed_values:
+ raise ValueError(
+ "Invalid value for `license_name` ({0}), must be one of {1}" # noqa: E501
+ .format(license_name, allowed_values)
+ )
+
+ self._license_name = license_name
+
+ @property
+ def subtitle(self):
+ """Gets the subtitle of this DatasetNewRequest. # noqa: E501
+
+ The subtitle to be set on the dataset # noqa: E501
+
+ :return: The subtitle of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._subtitle
+
+ @subtitle.setter
+ def subtitle(self, subtitle):
+ """Sets the subtitle of this DatasetNewRequest.
+
+ The subtitle to be set on the dataset # noqa: E501
+
+ :param subtitle: The subtitle of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._subtitle = subtitle
+
+ @property
+ def description(self):
+ """Gets the description of this DatasetNewRequest. # noqa: E501
+
+ The description to be set on the dataset # noqa: E501
+
+ :return: The description of this DatasetNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this DatasetNewRequest.
+
+ The description to be set on the dataset # noqa: E501
+
+ :param description: The description of this DatasetNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def files(self):
+ """Gets the files of this DatasetNewRequest. # noqa: E501
+
+ A list of files that should be associated with the dataset # noqa: E501
+
+ :return: The files of this DatasetNewRequest. # noqa: E501
+ :rtype: list[UploadFile]
+ """
+ return self._files
+
+ @files.setter
+ def files(self, files):
+ """Sets the files of this DatasetNewRequest.
+
+ A list of files that should be associated with the dataset # noqa: E501
+
+ :param files: The files of this DatasetNewRequest. # noqa: E501
+ :type: list[UploadFile]
+ """
+ if files is None:
+ raise ValueError("Invalid value for `files`, must not be `None`") # noqa: E501
+
+ self._files = files
+
+ @property
+ def is_private(self):
+ """Gets the is_private of this DatasetNewRequest. # noqa: E501
+
+ Whether or not the dataset should be private # noqa: E501
+
+ :return: The is_private of this DatasetNewRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._is_private
+
+ @is_private.setter
+ def is_private(self, is_private):
+ """Sets the is_private of this DatasetNewRequest.
+
+ Whether or not the dataset should be private # noqa: E501
+
+ :param is_private: The is_private of this DatasetNewRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._is_private = is_private
+
+ @property
+ def convert_to_csv(self):
+ """Gets the convert_to_csv of this DatasetNewRequest. # noqa: E501
+
+ Whether or not a tabular dataset should be converted to csv # noqa: E501
+
+ :return: The convert_to_csv of this DatasetNewRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._convert_to_csv
+
+ @convert_to_csv.setter
+ def convert_to_csv(self, convert_to_csv):
+ """Sets the convert_to_csv of this DatasetNewRequest.
+
+ Whether or not a tabular dataset should be converted to csv # noqa: E501
+
+ :param convert_to_csv: The convert_to_csv of this DatasetNewRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._convert_to_csv = convert_to_csv
+
+ @property
+ def category_ids(self):
+ """Gets the category_ids of this DatasetNewRequest. # noqa: E501
+
+ A list of tag IDs to associated with the dataset # noqa: E501
+
+ :return: The category_ids of this DatasetNewRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._category_ids
+
+ @category_ids.setter
+ def category_ids(self, category_ids):
+ """Sets the category_ids of this DatasetNewRequest.
+
+ A list of tag IDs to associated with the dataset # noqa: E501
+
+ :param category_ids: The category_ids of this DatasetNewRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._category_ids = category_ids
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, DatasetNewRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_version_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_version_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab7140cb3f23f8bb5ff400964f432682dd86f3e8
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_new_version_request.py
@@ -0,0 +1,286 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+from kaggle.models.upload_file import UploadFile # noqa: F401,E501
+
+
+class DatasetNewVersionRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'version_notes': 'str',
+ 'subtitle': 'str',
+ 'description': 'str',
+ 'files': 'list[UploadFile]',
+ 'convert_to_csv': 'bool',
+ 'category_ids': 'list[str]',
+ 'delete_old_versions': 'bool'
+ }
+
+ attribute_map = {
+ 'version_notes': 'versionNotes',
+ 'subtitle': 'subtitle',
+ 'description': 'description',
+ 'files': 'files',
+ 'convert_to_csv': 'convertToCsv',
+ 'category_ids': 'categoryIds',
+ 'delete_old_versions': 'deleteOldVersions'
+ }
+
+ def __init__(self, version_notes=None, subtitle=None, description=None, files=None, convert_to_csv=True, category_ids=None, delete_old_versions=False): # noqa: E501
+ """DatasetNewVersionRequest - a model defined in Swagger""" # noqa: E501
+
+ self._version_notes = None
+ self._subtitle = None
+ self._description = None
+ self._files = None
+ self._convert_to_csv = None
+ self._category_ids = None
+ self._delete_old_versions = None
+ self.discriminator = None
+
+ self.version_notes = version_notes
+ if subtitle is not None:
+ self.subtitle = subtitle
+ if description is not None:
+ self.description = description
+ self.files = files
+ if convert_to_csv is not None:
+ self.convert_to_csv = convert_to_csv
+ if category_ids is not None:
+ self.category_ids = category_ids
+ if delete_old_versions is not None:
+ self.delete_old_versions = delete_old_versions
+
+ @property
+ def version_notes(self):
+ """Gets the version_notes of this DatasetNewVersionRequest. # noqa: E501
+
+ The version notes for the new dataset version # noqa: E501
+
+ :return: The version_notes of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._version_notes
+
+ @version_notes.setter
+ def version_notes(self, version_notes):
+ """Sets the version_notes of this DatasetNewVersionRequest.
+
+ The version notes for the new dataset version # noqa: E501
+
+ :param version_notes: The version_notes of this DatasetNewVersionRequest. # noqa: E501
+ :type: str
+ """
+ if version_notes is None:
+ raise ValueError("Invalid value for `version_notes`, must not be `None`") # noqa: E501
+
+ self._version_notes = version_notes
+
+ @property
+ def subtitle(self):
+ """Gets the subtitle of this DatasetNewVersionRequest. # noqa: E501
+
+ The subtitle to set on the dataset # noqa: E501
+
+ :return: The subtitle of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._subtitle
+
+ @subtitle.setter
+ def subtitle(self, subtitle):
+ """Sets the subtitle of this DatasetNewVersionRequest.
+
+ The subtitle to set on the dataset # noqa: E501
+
+ :param subtitle: The subtitle of this DatasetNewVersionRequest. # noqa: E501
+ :type: str
+ """
+
+ self._subtitle = subtitle
+
+ @property
+ def description(self):
+ """Gets the description of this DatasetNewVersionRequest. # noqa: E501
+
+ The description to set on the dataset # noqa: E501
+
+ :return: The description of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this DatasetNewVersionRequest.
+
+ The description to set on the dataset # noqa: E501
+
+ :param description: The description of this DatasetNewVersionRequest. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def files(self):
+ """Gets the files of this DatasetNewVersionRequest. # noqa: E501
+
+ A list of files that should be associated with the dataset # noqa: E501
+
+ :return: The files of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: list[UploadFile]
+ """
+ return self._files
+
+ @files.setter
+ def files(self, files):
+ """Sets the files of this DatasetNewVersionRequest.
+
+ A list of files that should be associated with the dataset # noqa: E501
+
+ :param files: The files of this DatasetNewVersionRequest. # noqa: E501
+ :type: list[UploadFile]
+ """
+ if files is None:
+ raise ValueError("Invalid value for `files`, must not be `None`") # noqa: E501
+
+ self._files = files
+
+ @property
+ def convert_to_csv(self):
+ """Gets the convert_to_csv of this DatasetNewVersionRequest. # noqa: E501
+
+ Whether or not a tabular dataset should be converted to csv # noqa: E501
+
+ :return: The convert_to_csv of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._convert_to_csv
+
+ @convert_to_csv.setter
+ def convert_to_csv(self, convert_to_csv):
+ """Sets the convert_to_csv of this DatasetNewVersionRequest.
+
+ Whether or not a tabular dataset should be converted to csv # noqa: E501
+
+ :param convert_to_csv: The convert_to_csv of this DatasetNewVersionRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._convert_to_csv = convert_to_csv
+
+ @property
+ def category_ids(self):
+ """Gets the category_ids of this DatasetNewVersionRequest. # noqa: E501
+
+ A list of tag IDs to associated with the dataset # noqa: E501
+
+ :return: The category_ids of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._category_ids
+
+ @category_ids.setter
+ def category_ids(self, category_ids):
+ """Sets the category_ids of this DatasetNewVersionRequest.
+
+ A list of tag IDs to associated with the dataset # noqa: E501
+
+ :param category_ids: The category_ids of this DatasetNewVersionRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._category_ids = category_ids
+
+ @property
+ def delete_old_versions(self):
+ """Gets the delete_old_versions of this DatasetNewVersionRequest. # noqa: E501
+
+ Whether or not all previous versions of the dataset should be deleted upon creating the new version # noqa: E501
+
+ :return: The delete_old_versions of this DatasetNewVersionRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._delete_old_versions
+
+ @delete_old_versions.setter
+ def delete_old_versions(self, delete_old_versions):
+ """Sets the delete_old_versions of this DatasetNewVersionRequest.
+
+ Whether or not all previous versions of the dataset should be deleted upon creating the new version # noqa: E501
+
+ :param delete_old_versions: The delete_old_versions of this DatasetNewVersionRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._delete_old_versions = delete_old_versions
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, DatasetNewVersionRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/dataset_update_settings_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_update_settings_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..36469f9e0e76c3735fb8879185a7aacc93737652
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/dataset_update_settings_request.py
@@ -0,0 +1,310 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class DatasetUpdateSettingsRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'title': 'str',
+ 'subtitle': 'str',
+ 'description': 'str',
+ 'is_private': 'bool',
+ 'licenses': 'list[object]',
+ 'keywords': 'list[str]',
+ 'collaborators': 'list[object]',
+ 'data': 'list[object]'
+ }
+
+ attribute_map = {
+ 'title': 'title',
+ 'subtitle': 'subtitle',
+ 'description': 'description',
+ 'is_private': 'isPrivate',
+ 'licenses': 'licenses',
+ 'keywords': 'keywords',
+ 'collaborators': 'collaborators',
+ 'data': 'data'
+ }
+
+ def __init__(self, title=None, subtitle=None, description=None, is_private=None, licenses=None, keywords=None, collaborators=None, data=None): # noqa: E501
+ """DatasetUpdateSettingsRequest - a model defined in Swagger""" # noqa: E501
+
+ self._title = None
+ self._subtitle = None
+ self._description = None
+ self._is_private = None
+ self._licenses = None
+ self._keywords = None
+ self._collaborators = None
+ self._data = None
+ self.discriminator = None
+
+ if title is not None:
+ self.title = title
+ if subtitle is not None:
+ self.subtitle = subtitle
+ if description is not None:
+ self.description = description
+ if is_private is not None:
+ self.is_private = is_private
+ if licenses is not None:
+ self.licenses = licenses
+ if keywords is not None:
+ self.keywords = keywords
+ if collaborators is not None:
+ self.collaborators = collaborators
+ if data is not None:
+ self.data = data
+
+ @property
+ def title(self):
+ """Gets the title of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ Title of the dataset # noqa: E501
+
+ :return: The title of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._title
+
+ @title.setter
+ def title(self, title):
+ """Sets the title of this DatasetUpdateSettingsRequest.
+
+ Title of the dataset # noqa: E501
+
+ :param title: The title of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: str
+ """
+
+ self._title = title
+
+ @property
+ def subtitle(self):
+ """Gets the subtitle of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ Subtitle of the dataset # noqa: E501
+
+ :return: The subtitle of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._subtitle
+
+ @subtitle.setter
+ def subtitle(self, subtitle):
+ """Sets the subtitle of this DatasetUpdateSettingsRequest.
+
+ Subtitle of the dataset # noqa: E501
+
+ :param subtitle: The subtitle of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: str
+ """
+
+ self._subtitle = subtitle
+
+ @property
+ def description(self):
+ """Gets the description of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ Decription of the dataset # noqa: E501
+
+ :return: The description of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this DatasetUpdateSettingsRequest.
+
+ Decription of the dataset # noqa: E501
+
+ :param description: The description of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def is_private(self):
+ """Gets the is_private of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ Whether or not the dataset should be private # noqa: E501
+
+ :return: The is_private of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._is_private
+
+ @is_private.setter
+ def is_private(self, is_private):
+ """Sets the is_private of this DatasetUpdateSettingsRequest.
+
+ Whether or not the dataset should be private # noqa: E501
+
+ :param is_private: The is_private of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._is_private = is_private
+
+ @property
+ def licenses(self):
+ """Gets the licenses of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ A list of licenses that apply to this dataset # noqa: E501
+
+ :return: The licenses of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: list[object]
+ """
+ return self._licenses
+
+ @licenses.setter
+ def licenses(self, licenses):
+ """Sets the licenses of this DatasetUpdateSettingsRequest.
+
+ A list of licenses that apply to this dataset # noqa: E501
+
+ :param licenses: The licenses of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: list[object]
+ """
+
+ self._licenses = licenses
+
+ @property
+ def keywords(self):
+ """Gets the keywords of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ A list of keywords that apply to this dataset # noqa: E501
+
+ :return: The keywords of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._keywords
+
+ @keywords.setter
+ def keywords(self, keywords):
+ """Sets the keywords of this DatasetUpdateSettingsRequest.
+
+ A list of keywords that apply to this dataset # noqa: E501
+
+ :param keywords: The keywords of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._keywords = keywords
+
+ @property
+ def collaborators(self):
+ """Gets the collaborators of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ A list of collaborators that may read or edit this dataset # noqa: E501
+
+ :return: The collaborators of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: list[object]
+ """
+ return self._collaborators
+
+ @collaborators.setter
+ def collaborators(self, collaborators):
+ """Sets the collaborators of this DatasetUpdateSettingsRequest.
+
+ A list of collaborators that may read or edit this dataset # noqa: E501
+
+ :param collaborators: The collaborators of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: list[object]
+ """
+
+ self._collaborators = collaborators
+
+ @property
+ def data(self):
+ """Gets the data of this DatasetUpdateSettingsRequest. # noqa: E501
+
+ A list containing metadata for each file in the dataset # noqa: E501
+
+ :return: The data of this DatasetUpdateSettingsRequest. # noqa: E501
+ :rtype: list[object]
+ """
+ return self._data
+
+ @data.setter
+ def data(self, data):
+ """Sets the data of this DatasetUpdateSettingsRequest.
+
+ A list containing metadata for each file in the dataset # noqa: E501
+
+ :param data: The data of this DatasetUpdateSettingsRequest. # noqa: E501
+ :type: list[object]
+ """
+
+ self._data = data
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, DatasetUpdateSettingsRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/error.py b/.venv/lib/python3.10/site-packages/kaggle/models/error.py
new file mode 100644
index 0000000000000000000000000000000000000000..7abab1f4cb7ccde4b655f6653e9623cebe2eaa0f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/error.py
@@ -0,0 +1,142 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class Error(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'code': 'int',
+ 'message': 'str'
+ }
+
+ attribute_map = {
+ 'code': 'code',
+ 'message': 'message'
+ }
+
+ def __init__(self, code=None, message=None): # noqa: E501
+ """Error - a model defined in Swagger""" # noqa: E501
+
+ self._code = None
+ self._message = None
+ self.discriminator = None
+
+ if code is not None:
+ self.code = code
+ if message is not None:
+ self.message = message
+
+ @property
+ def code(self):
+ """Gets the code of this Error. # noqa: E501
+
+ The server error code returned # noqa: E501
+
+ :return: The code of this Error. # noqa: E501
+ :rtype: int
+ """
+ return self._code
+
+ @code.setter
+ def code(self, code):
+ """Sets the code of this Error.
+
+ The server error code returned # noqa: E501
+
+ :param code: The code of this Error. # noqa: E501
+ :type: int
+ """
+
+ self._code = code
+
+ @property
+ def message(self):
+ """Gets the message of this Error. # noqa: E501
+
+ The error message generated by the server # noqa: E501
+
+ :return: The message of this Error. # noqa: E501
+ :rtype: str
+ """
+ return self._message
+
+ @message.setter
+ def message(self, message):
+ """Sets the message of this Error.
+
+ The error message generated by the server # noqa: E501
+
+ :param message: The message of this Error. # noqa: E501
+ :type: str
+ """
+
+ self._message = message
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, Error):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/kaggle_models_extended.py b/.venv/lib/python3.10/site-packages/kaggle/models/kaggle_models_extended.py
new file mode 100644
index 0000000000000000000000000000000000000000..79c9750aa991572938f9f8bd7426bd46ef15712d
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/kaggle_models_extended.py
@@ -0,0 +1,260 @@
+#!/usr/bin/python
+#
+# Copyright 2019 Kaggle Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# coding=utf-8
+import os
+import time
+from datetime import datetime
+
+
+class Competition(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+ self.tags = [Tag(t) for t in self.tags]
+
+ def __repr__(self):
+ return self.ref
+
+
+class SubmitResult(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.message
+
+
+class Submission(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+ if self.totalBytes is None:
+ self.size = None
+ else:
+ self.size = File.get_size(self.totalBytes)
+
+ def __repr__(self):
+ return str(self.ref)
+
+
+class LeaderboardEntry(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.teamId
+
+
+class Dataset(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+ self.tags = [Tag(t) for t in self.tags]
+ self.files = [File(f) for f in self.files]
+ self.versions = [DatasetVersion(v) for v in self.versions]
+ self.size = File.get_size(self.totalBytes)
+
+ def __repr__(self):
+ return self.ref
+
+
+class Model(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.ref
+
+
+class Metadata(object):
+
+ def __init__(self, init_info):
+ parsed_info = {k: parse(v) for k, v in init_info.items()}
+ # backwards compatibility
+ self.id = parsed_info["ownerUser"] + "/" + parsed_info['datasetSlug']
+ self.id_no = parsed_info['datasetId']
+ self.__dict__.update(parsed_info)
+
+ def __repr__(self):
+ return str(self.datasetId)
+
+
+class DatasetVersion(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return str(self.versionNumber)
+
+
+class File(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+ self.size = File.get_size(self.totalBytes)
+
+ def __repr__(self):
+ return self.ref
+
+ @staticmethod
+ def get_size(size, precision=0):
+ suffixes = ['B', 'KB', 'MB', 'GB', 'TB']
+ suffix_index = 0
+ while size >= 1024 and suffix_index < 4:
+ suffix_index += 1
+ size /= 1024.0
+ return '%.*f%s' % (precision, size, suffixes[suffix_index])
+
+
+class Tag(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.ref
+
+
+class DatasetNewVersionResponse(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.url
+
+
+class DatasetNewResponse(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.url
+
+
+class ListFilesResult(object):
+
+ def __init__(self, init_dict):
+ self.error_message = init_dict['errorMessage']
+ files = init_dict['datasetFiles']
+ if files:
+ self.files = [File(f) for f in files]
+ else:
+ self.files = {}
+
+ def __repr__(self):
+ return self.error_message
+
+
+class Kernel:
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.title
+
+
+class KernelPushResponse(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.newUrl
+
+
+class ModelNewResponse(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.url
+
+
+class ModelDeleteResponse(object):
+
+ def __init__(self, init_dict):
+ parsed_dict = {k: parse(v) for k, v in init_dict.items()}
+ self.__dict__.update(parsed_dict)
+
+ def __repr__(self):
+ return self.error
+
+
+def parse(string):
+ time_formats = [
+ '%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%f',
+ '%Y-%m-%dT%H:%M:%S.%fZ'
+ ]
+ for t in time_formats:
+ try:
+ result = datetime.strptime(string[:26], t).replace(microsecond=0)
+ return result
+ except:
+ pass
+ return string
+
+
+class ResumableUploadResult(object):
+ # Upload was complete, i.e., all bytes were received by the server.
+ COMPLETE = 1
+
+ # There was a non-transient error during the upload or the upload expired.
+ # The upload cannot be resumed so it should be restarted from scratch
+ # (i.e., call /api/v1/files/upload to initiate the upload and get the
+ # create/upload url and token).
+ FAILED = 2
+
+ # Upload was interrupted due to some (transient) failure but it can be
+ # safely resumed.
+ INCOMPLETE = 3
+
+ def __init__(self, result, bytes_uploaded=None):
+ self.result = result
+ self.bytes_uploaded = bytes_uploaded
+ self.start_at = 0 if bytes_uploaded is None else bytes_uploaded + 1
+
+ def Complete():
+ return ResumableUploadResult(ResumableUploadResult.COMPLETE)
+
+ def Failed():
+ return ResumableUploadResult(ResumableUploadResult.FAILED)
+
+ def Incomplete(bytes_uploaded=None):
+ return ResumableUploadResult(ResumableUploadResult.INCOMPLETE,
+ bytes_uploaded)
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/kernel_push_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/kernel_push_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4022aa1debd5d34e588d52c79b21a5f1bbaceb5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/kernel_push_request.py
@@ -0,0 +1,555 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class KernelPushRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'id': 'int',
+ 'slug': 'str',
+ 'new_title': 'str',
+ 'text': 'str',
+ 'language': 'str',
+ 'kernel_type': 'str',
+ 'is_private': 'bool',
+ 'enable_gpu': 'bool',
+ 'enable_tpu': 'bool',
+ 'enable_internet': 'bool',
+ 'dataset_data_sources': 'list[str]',
+ 'competition_data_sources': 'list[str]',
+ 'kernel_data_sources': 'list[str]',
+ 'model_data_sources': 'list[str]',
+ 'category_ids': 'list[str]',
+ 'docker_image_pinning_type': 'str'
+ }
+
+ attribute_map = {
+ 'id': 'id',
+ 'slug': 'slug',
+ 'new_title': 'newTitle',
+ 'text': 'text',
+ 'language': 'language',
+ 'kernel_type': 'kernelType',
+ 'is_private': 'isPrivate',
+ 'enable_gpu': 'enableGpu',
+ 'enable_tpu': 'enableTpu',
+ 'enable_internet': 'enableInternet',
+ 'dataset_data_sources': 'datasetDataSources',
+ 'competition_data_sources': 'competitionDataSources',
+ 'kernel_data_sources': 'kernelDataSources',
+ 'model_data_sources': 'modelDataSources',
+ 'category_ids': 'categoryIds',
+ 'docker_image_pinning_type': 'dockerImagePinningType'
+ }
+
+ def __init__(self, id=None, slug=None, new_title=None, text=None, language=None, kernel_type=None, is_private=None, enable_gpu=None, enable_tpu=None, enable_internet=None, dataset_data_sources=None, competition_data_sources=None, kernel_data_sources=None, model_data_sources=None, category_ids=None, docker_image_pinning_type=None): # noqa: E501
+ """KernelPushRequest - a model defined in Swagger""" # noqa: E501
+
+ self._id = None
+ self._slug = None
+ self._new_title = None
+ self._text = None
+ self._language = None
+ self._kernel_type = None
+ self._is_private = None
+ self._enable_gpu = None
+ self._enable_tpu = None
+ self._enable_internet = None
+ self._dataset_data_sources = None
+ self._competition_data_sources = None
+ self._kernel_data_sources = None
+ self._model_data_sources = None
+ self._category_ids = None
+ self._docker_image_pinning_type = None
+ self.discriminator = None
+
+ if id is not None:
+ self.id = id
+ if slug is not None:
+ self.slug = slug
+ if new_title is not None:
+ self.new_title = new_title
+ self.text = text
+ self.language = language
+ self.kernel_type = kernel_type
+ if is_private is not None:
+ self.is_private = is_private
+ if enable_gpu is not None:
+ self.enable_gpu = enable_gpu
+ if enable_tpu is not None:
+ self.enable_tpu = enable_tpu
+ if enable_internet is not None:
+ self.enable_internet = enable_internet
+ if dataset_data_sources is not None:
+ self.dataset_data_sources = dataset_data_sources
+ if competition_data_sources is not None:
+ self.competition_data_sources = competition_data_sources
+ if kernel_data_sources is not None:
+ self.kernel_data_sources = kernel_data_sources
+ if model_data_sources is not None:
+ self.model_data_sources = model_data_sources
+ if category_ids is not None:
+ self.category_ids = category_ids
+ if docker_image_pinning_type is not None:
+ self.docker_image_pinning_type = docker_image_pinning_type
+
+ @property
+ def id(self):
+ """Gets the id of this KernelPushRequest. # noqa: E501
+
+ The kernel's ID number. One of `id` and `slug` are required. If both are specified, `id` will be preferred # noqa: E501
+
+ :return: The id of this KernelPushRequest. # noqa: E501
+ :rtype: int
+ """
+ return self._id
+
+ @id.setter
+ def id(self, id):
+ """Sets the id of this KernelPushRequest.
+
+ The kernel's ID number. One of `id` and `slug` are required. If both are specified, `id` will be preferred # noqa: E501
+
+ :param id: The id of this KernelPushRequest. # noqa: E501
+ :type: int
+ """
+
+ self._id = id
+
+ @property
+ def slug(self):
+ """Gets the slug of this KernelPushRequest. # noqa: E501
+
+ The full slug of the kernel to push to, in the format `USERNAME/KERNEL-SLUG`. The kernel slug must be the title lowercased with dashes (`-`) replacing spaces. One of `id` and `slug` are required. If both are specified, `id` will be preferred # noqa: E501
+
+ :return: The slug of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._slug
+
+ @slug.setter
+ def slug(self, slug):
+ """Sets the slug of this KernelPushRequest.
+
+ The full slug of the kernel to push to, in the format `USERNAME/KERNEL-SLUG`. The kernel slug must be the title lowercased with dashes (`-`) replacing spaces. One of `id` and `slug` are required. If both are specified, `id` will be preferred # noqa: E501
+
+ :param slug: The slug of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+
+ self._slug = slug
+
+ @property
+ def new_title(self):
+ """Gets the new_title of this KernelPushRequest. # noqa: E501
+
+ The title to be set on the kernel # noqa: E501
+
+ :return: The new_title of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._new_title
+
+ @new_title.setter
+ def new_title(self, new_title):
+ """Sets the new_title of this KernelPushRequest.
+
+ The title to be set on the kernel # noqa: E501
+
+ :param new_title: The new_title of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+
+ self._new_title = new_title
+
+ @property
+ def text(self):
+ """Gets the text of this KernelPushRequest. # noqa: E501
+
+ The kernel's source code # noqa: E501
+
+ :return: The text of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._text
+
+ @text.setter
+ def text(self, text):
+ """Sets the text of this KernelPushRequest.
+
+ The kernel's source code # noqa: E501
+
+ :param text: The text of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+ if text is None:
+ raise ValueError("Invalid value for `text`, must not be `None`") # noqa: E501
+
+ self._text = text
+
+ @property
+ def language(self):
+ """Gets the language of this KernelPushRequest. # noqa: E501
+
+ The language that the kernel is written in # noqa: E501
+
+ :return: The language of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._language
+
+ @language.setter
+ def language(self, language):
+ """Sets the language of this KernelPushRequest.
+
+ The language that the kernel is written in # noqa: E501
+
+ :param language: The language of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+ if language is None:
+ raise ValueError("Invalid value for `language`, must not be `None`") # noqa: E501
+ allowed_values = ["python", "r", "rmarkdown"] # noqa: E501
+ if language not in allowed_values:
+ raise ValueError(
+ "Invalid value for `language` ({0}), must be one of {1}" # noqa: E501
+ .format(language, allowed_values)
+ )
+
+ self._language = language
+
+ @property
+ def kernel_type(self):
+ """Gets the kernel_type of this KernelPushRequest. # noqa: E501
+
+ The type of kernel. Cannot be changed once the kernel has been created # noqa: E501
+
+ :return: The kernel_type of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._kernel_type
+
+ @kernel_type.setter
+ def kernel_type(self, kernel_type):
+ """Sets the kernel_type of this KernelPushRequest.
+
+ The type of kernel. Cannot be changed once the kernel has been created # noqa: E501
+
+ :param kernel_type: The kernel_type of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+ if kernel_type is None:
+ raise ValueError("Invalid value for `kernel_type`, must not be `None`") # noqa: E501
+ allowed_values = ["script", "notebook"] # noqa: E501
+ if kernel_type not in allowed_values:
+ raise ValueError(
+ "Invalid value for `kernel_type` ({0}), must be one of {1}" # noqa: E501
+ .format(kernel_type, allowed_values)
+ )
+
+ self._kernel_type = kernel_type
+
+ @property
+ def is_private(self):
+ """Gets the is_private of this KernelPushRequest. # noqa: E501
+
+ Whether or not the kernel should be private # noqa: E501
+
+ :return: The is_private of this KernelPushRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._is_private
+
+ @is_private.setter
+ def is_private(self, is_private):
+ """Sets the is_private of this KernelPushRequest.
+
+ Whether or not the kernel should be private # noqa: E501
+
+ :param is_private: The is_private of this KernelPushRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._is_private = is_private
+
+ @property
+ def enable_gpu(self):
+ """Gets the enable_gpu of this KernelPushRequest. # noqa: E501
+
+ Whether or not the kernel should run on a GPU # noqa: E501
+
+ :return: The enable_gpu of this KernelPushRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._enable_gpu
+
+ @enable_gpu.setter
+ def enable_gpu(self, enable_gpu):
+ """Sets the enable_gpu of this KernelPushRequest.
+
+ Whether or not the kernel should run on a GPU # noqa: E501
+
+ :param enable_gpu: The enable_gpu of this KernelPushRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._enable_gpu = enable_gpu
+
+ @property
+ def enable_tpu(self):
+ """Gets the enable_tpu of this KernelPushRequest. # noqa: E501
+
+ Whether or not the kernel should run on a TPU # noqa: E501
+
+ :return: The enable_tpu of this KernelPushRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._enable_tpu
+
+ @enable_tpu.setter
+ def enable_tpu(self, enable_tpu):
+ """Sets the enable_tpu of this KernelPushRequest.
+
+ Whether or not the kernel should run on a TPU # noqa: E501
+
+ :param enable_tpu: The enable_tpu of this KernelPushRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._enable_tpu = enable_tpu
+
+ @property
+ def enable_internet(self):
+ """Gets the enable_internet of this KernelPushRequest. # noqa: E501
+
+ Whether or not the kernel should be able to access the internet # noqa: E501
+
+ :return: The enable_internet of this KernelPushRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._enable_internet
+
+ @enable_internet.setter
+ def enable_internet(self, enable_internet):
+ """Sets the enable_internet of this KernelPushRequest.
+
+ Whether or not the kernel should be able to access the internet # noqa: E501
+
+ :param enable_internet: The enable_internet of this KernelPushRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._enable_internet = enable_internet
+
+ @property
+ def dataset_data_sources(self):
+ """Gets the dataset_data_sources of this KernelPushRequest. # noqa: E501
+
+ A list of dataset data sources that the kernel should use. Each dataset is specified as `USERNAME/DATASET-SLUG` # noqa: E501
+
+ :return: The dataset_data_sources of this KernelPushRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._dataset_data_sources
+
+ @dataset_data_sources.setter
+ def dataset_data_sources(self, dataset_data_sources):
+ """Sets the dataset_data_sources of this KernelPushRequest.
+
+ A list of dataset data sources that the kernel should use. Each dataset is specified as `USERNAME/DATASET-SLUG` # noqa: E501
+
+ :param dataset_data_sources: The dataset_data_sources of this KernelPushRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._dataset_data_sources = dataset_data_sources
+
+ @property
+ def competition_data_sources(self):
+ """Gets the competition_data_sources of this KernelPushRequest. # noqa: E501
+
+ A list of competition data sources that the kernel should use # noqa: E501
+
+ :return: The competition_data_sources of this KernelPushRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._competition_data_sources
+
+ @competition_data_sources.setter
+ def competition_data_sources(self, competition_data_sources):
+ """Sets the competition_data_sources of this KernelPushRequest.
+
+ A list of competition data sources that the kernel should use # noqa: E501
+
+ :param competition_data_sources: The competition_data_sources of this KernelPushRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._competition_data_sources = competition_data_sources
+
+ @property
+ def kernel_data_sources(self):
+ """Gets the kernel_data_sources of this KernelPushRequest. # noqa: E501
+
+ A list of kernel data sources that the kernel should use. Each dataset is specified as `USERNAME/KERNEL-SLUG` # noqa: E501
+
+ :return: The kernel_data_sources of this KernelPushRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._kernel_data_sources
+
+ @kernel_data_sources.setter
+ def kernel_data_sources(self, kernel_data_sources):
+ """Sets the kernel_data_sources of this KernelPushRequest.
+
+ A list of kernel data sources that the kernel should use. Each dataset is specified as `USERNAME/KERNEL-SLUG` # noqa: E501
+
+ :param kernel_data_sources: The kernel_data_sources of this KernelPushRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._kernel_data_sources = kernel_data_sources
+
+ @property
+ def model_data_sources(self):
+ """Gets the model_data_sources of this KernelPushRequest. # noqa: E501
+
+ A list of model data sources that the kernel should use. Each model is specified as `USERNAME/MODEL-SLUG/FRAMEWORK/VARIATION-SLUG/VERSION-NUMBER` # noqa: E501
+
+ :return: The model_data_sources of this KernelPushRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._model_data_sources
+
+ @model_data_sources.setter
+ def model_data_sources(self, model_data_sources):
+ """Sets the model_data_sources of this KernelPushRequest.
+
+ A list of model data sources that the kernel should use. Each model is specified as `USERNAME/MODEL-SLUG/FRAMEWORK/VARIATION-SLUG/VERSION-NUMBER` # noqa: E501
+
+ :param model_data_sources: The model_data_sources of this KernelPushRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._model_data_sources = model_data_sources
+
+ @property
+ def category_ids(self):
+ """Gets the category_ids of this KernelPushRequest. # noqa: E501
+
+ A list of tag IDs to associated with the kernel # noqa: E501
+
+ :return: The category_ids of this KernelPushRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._category_ids
+
+ @category_ids.setter
+ def category_ids(self, category_ids):
+ """Sets the category_ids of this KernelPushRequest.
+
+ A list of tag IDs to associated with the kernel # noqa: E501
+
+ :param category_ids: The category_ids of this KernelPushRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._category_ids = category_ids
+
+ @property
+ def docker_image_pinning_type(self):
+ """Gets the docker_image_pinning_type of this KernelPushRequest. # noqa: E501
+
+ Which docker image to use for executing new versions going forward. # noqa: E501
+
+ :return: The docker_image_pinning_type of this KernelPushRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._docker_image_pinning_type
+
+ @docker_image_pinning_type.setter
+ def docker_image_pinning_type(self, docker_image_pinning_type):
+ """Sets the docker_image_pinning_type of this KernelPushRequest.
+
+ Which docker image to use for executing new versions going forward. # noqa: E501
+
+ :param docker_image_pinning_type: The docker_image_pinning_type of this KernelPushRequest. # noqa: E501
+ :type: str
+ """
+ allowed_values = ["original", "latest"] # noqa: E501
+ if docker_image_pinning_type not in allowed_values:
+ raise ValueError(
+ "Invalid value for `docker_image_pinning_type` ({0}), must be one of {1}" # noqa: E501
+ .format(docker_image_pinning_type, allowed_values)
+ )
+
+ self._docker_image_pinning_type = docker_image_pinning_type
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, KernelPushRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/license.py b/.venv/lib/python3.10/site-packages/kaggle/models/license.py
new file mode 100644
index 0000000000000000000000000000000000000000..48d8164fff219f0d2652de66770aaadbd3d16b70
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/license.py
@@ -0,0 +1,121 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class License(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'name': 'str'
+ }
+
+ attribute_map = {
+ 'name': 'name'
+ }
+
+ def __init__(self, name=None): # noqa: E501
+ """License - a model defined in Swagger""" # noqa: E501
+
+ self._name = None
+ self.discriminator = None
+
+ self.name = name
+
+ @property
+ def name(self):
+ """Gets the name of this License. # noqa: E501
+
+ Name of the license # noqa: E501
+
+ :return: The name of this License. # noqa: E501
+ :rtype: str
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Sets the name of this License.
+
+ Name of the license # noqa: E501
+
+ :param name: The name of this License. # noqa: E501
+ :type: str
+ """
+ if name is None:
+ raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501
+ allowed_values = ["CC0-1.0", "CC-BY-SA-4.0", "GPL-2.0", "ODbL-1.0", "CC-BY-NC-SA-4.0", "unknown", "DbCL-1.0", "CC-BY-SA-3.0", "copyright-authors", "other", "reddit-api", "world-bank", "CC-BY-4.0", "CC-BY-NC-4.0", "PDDL", "CC-BY-3.0", "CC-BY-3.0-IGO", "US-Government-Works", "CC-BY-NC-SA-3.0-IGO", "CDLA-Permissive-1.0", "CDLA-Sharing-1.0", "CC-BY-ND-4.0", "CC-BY-NC-ND-4.0", "ODC-BY-1.0", "LGPL-3.0", "AGPL-3.0", "FDL-1.3", "EU-ODP-Legal-Notice", "apache-2.0", "GPL-3.0"] # noqa: E501
+ if name not in allowed_values:
+ raise ValueError(
+ "Invalid value for `name` ({0}), must be one of {1}" # noqa: E501
+ .format(name, allowed_values)
+ )
+
+ self._name = name
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, License):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_new_version_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_new_version_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c9e6e9a7ad805c0d35daf8ea3aa4c7688b433bb
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_new_version_request.py
@@ -0,0 +1,145 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+from kaggle.models.upload_file import UploadFile # noqa: F401,E501
+
+
+class ModelInstanceNewVersionRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'version_notes': 'str',
+ 'files': 'list[UploadFile]'
+ }
+
+ attribute_map = {
+ 'version_notes': 'versionNotes',
+ 'files': 'files'
+ }
+
+ def __init__(self, version_notes=None, files=None): # noqa: E501
+ """ModelInstanceNewVersionRequest - a model defined in Swagger""" # noqa: E501
+
+ self._version_notes = None
+ self._files = None
+ self.discriminator = None
+
+ if version_notes is not None:
+ self.version_notes = version_notes
+ self.files = files
+
+ @property
+ def version_notes(self):
+ """Gets the version_notes of this ModelInstanceNewVersionRequest. # noqa: E501
+
+ The version notes for the model instance version # noqa: E501
+
+ :return: The version_notes of this ModelInstanceNewVersionRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._version_notes
+
+ @version_notes.setter
+ def version_notes(self, version_notes):
+ """Sets the version_notes of this ModelInstanceNewVersionRequest.
+
+ The version notes for the model instance version # noqa: E501
+
+ :param version_notes: The version_notes of this ModelInstanceNewVersionRequest. # noqa: E501
+ :type: str
+ """
+
+ self._version_notes = version_notes
+
+ @property
+ def files(self):
+ """Gets the files of this ModelInstanceNewVersionRequest. # noqa: E501
+
+ A list of files that should be associated with the model instance version # noqa: E501
+
+ :return: The files of this ModelInstanceNewVersionRequest. # noqa: E501
+ :rtype: list[UploadFile]
+ """
+ return self._files
+
+ @files.setter
+ def files(self, files):
+ """Sets the files of this ModelInstanceNewVersionRequest.
+
+ A list of files that should be associated with the model instance version # noqa: E501
+
+ :param files: The files of this ModelInstanceNewVersionRequest. # noqa: E501
+ :type: list[UploadFile]
+ """
+ if files is None:
+ raise ValueError("Invalid value for `files`, must not be `None`") # noqa: E501
+
+ self._files = files
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ModelInstanceNewVersionRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_update_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_update_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..05ce871a78fb20485c9322c5aa0f8e46d7ef3ab5
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/model_instance_update_request.py
@@ -0,0 +1,351 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class ModelInstanceUpdateRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'overview': 'str',
+ 'usage': 'str',
+ 'license_name': 'str',
+ 'fine_tunable': 'bool',
+ 'training_data': 'list[str]',
+ 'model_instance_type': 'str',
+ 'base_model_instance': 'str',
+ 'external_base_model_url': 'int',
+ 'update_mask': 'str'
+ }
+
+ attribute_map = {
+ 'overview': 'overview',
+ 'usage': 'usage',
+ 'license_name': 'licenseName',
+ 'fine_tunable': 'fineTunable',
+ 'training_data': 'trainingData',
+ 'model_instance_type': 'modelInstanceType',
+ 'base_model_instance': 'baseModelInstance',
+ 'external_base_model_url': 'externalBaseModelUrl',
+ 'update_mask': 'updateMask'
+ }
+
+ def __init__(self, overview=None, usage=None, license_name='Apache 2.0', fine_tunable=True, training_data=None, model_instance_type=None, base_model_instance=None, external_base_model_url=None, update_mask=None): # noqa: E501
+ """ModelInstanceUpdateRequest - a model defined in Swagger""" # noqa: E501
+
+ self._overview = None
+ self._usage = None
+ self._license_name = None
+ self._fine_tunable = None
+ self._training_data = None
+ self._model_instance_type = None
+ self._base_model_instance = None
+ self._external_base_model_url = None
+ self._update_mask = None
+ self.discriminator = None
+
+ if overview is not None:
+ self.overview = overview
+ if usage is not None:
+ self.usage = usage
+ if license_name is not None:
+ self.license_name = license_name
+ if fine_tunable is not None:
+ self.fine_tunable = fine_tunable
+ if training_data is not None:
+ self.training_data = training_data
+ if model_instance_type is not None:
+ self.model_instance_type = model_instance_type
+ if base_model_instance is not None:
+ self.base_model_instance = base_model_instance
+ if external_base_model_url is not None:
+ self.external_base_model_url = external_base_model_url
+ self.update_mask = update_mask
+
+ @property
+ def overview(self):
+ """Gets the overview of this ModelInstanceUpdateRequest. # noqa: E501
+
+ The overview of the model instance (markdown) # noqa: E501
+
+ :return: The overview of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._overview
+
+ @overview.setter
+ def overview(self, overview):
+ """Sets the overview of this ModelInstanceUpdateRequest.
+
+ The overview of the model instance (markdown) # noqa: E501
+
+ :param overview: The overview of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._overview = overview
+
+ @property
+ def usage(self):
+ """Gets the usage of this ModelInstanceUpdateRequest. # noqa: E501
+
+ The description of how to use the model instance (markdown) # noqa: E501
+
+ :return: The usage of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._usage
+
+ @usage.setter
+ def usage(self, usage):
+ """Sets the usage of this ModelInstanceUpdateRequest.
+
+ The description of how to use the model instance (markdown) # noqa: E501
+
+ :param usage: The usage of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._usage = usage
+
+ @property
+ def license_name(self):
+ """Gets the license_name of this ModelInstanceUpdateRequest. # noqa: E501
+
+ The license that should be associated with the model instance # noqa: E501
+
+ :return: The license_name of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._license_name
+
+ @license_name.setter
+ def license_name(self, license_name):
+ """Sets the license_name of this ModelInstanceUpdateRequest.
+
+ The license that should be associated with the model instance # noqa: E501
+
+ :param license_name: The license_name of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+ allowed_values = ["CC BY-NC-SA 4.0", "CC BY-SA 4.0", "GPL 2", "CC BY-SA 3.0", "Attribution 4.0 International (CC BY 4.0)", "Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)", "ODC Public Domain Dedication and Licence (PDDL)", "Attribution 3.0 Unported (CC BY 3.0)", "Attribution 3.0 IGO (CC BY 3.0 IGO)", "Attribution-NonCommercial-ShareAlike 3.0 IGO (CC BY-NC-SA 3.0 IGO)", "Community Data License Agreement - Permissive - Version 1.0", "Community Data License Agreement - Sharing - Version 1.0", "Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)", "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)", "ODC Attribution License (ODC-By)", "GNU Lesser General Public License 3.0", "GNU Affero General Public License 3.0", "GNU Free Documentation License 1.3", "Apache 2.0", "MIT", "BSD-3-Clause", "GPL 3"] # noqa: E501
+ if license_name not in allowed_values:
+ raise ValueError(
+ "Invalid value for `license_name` ({0}), must be one of {1}" # noqa: E501
+ .format(license_name, allowed_values)
+ )
+
+ self._license_name = license_name
+
+ @property
+ def fine_tunable(self):
+ """Gets the fine_tunable of this ModelInstanceUpdateRequest. # noqa: E501
+
+ Whether the model instance is fine tunable # noqa: E501
+
+ :return: The fine_tunable of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._fine_tunable
+
+ @fine_tunable.setter
+ def fine_tunable(self, fine_tunable):
+ """Sets the fine_tunable of this ModelInstanceUpdateRequest.
+
+ Whether the model instance is fine tunable # noqa: E501
+
+ :param fine_tunable: The fine_tunable of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._fine_tunable = fine_tunable
+
+ @property
+ def training_data(self):
+ """Gets the training_data of this ModelInstanceUpdateRequest. # noqa: E501
+
+ A list of training data (urls or names) # noqa: E501
+
+ :return: The training_data of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._training_data
+
+ @training_data.setter
+ def training_data(self, training_data):
+ """Sets the training_data of this ModelInstanceUpdateRequest.
+
+ A list of training data (urls or names) # noqa: E501
+
+ :param training_data: The training_data of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._training_data = training_data
+
+ @property
+ def model_instance_type(self):
+ """Gets the model_instance_type of this ModelInstanceUpdateRequest. # noqa: E501
+
+ Whether the model instance is a base model, external variant, internal variant, or unspecified # noqa: E501
+
+ :return: The model_instance_type of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._model_instance_type
+
+ @model_instance_type.setter
+ def model_instance_type(self, model_instance_type):
+ """Sets the model_instance_type of this ModelInstanceUpdateRequest.
+
+ Whether the model instance is a base model, external variant, internal variant, or unspecified # noqa: E501
+
+ :param model_instance_type: The model_instance_type of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+ allowed_values = ["Unspecified", "BaseModel", "KaggleVariant", "ExternalVariant"] # noqa: E501
+ if model_instance_type not in allowed_values:
+ raise ValueError(
+ "Invalid value for `model_instance_type` ({0}), must be one of {1}" # noqa: E501
+ .format(model_instance_type, allowed_values)
+ )
+
+ self._model_instance_type = model_instance_type
+
+ @property
+ def base_model_instance(self):
+ """Gets the base_model_instance of this ModelInstanceUpdateRequest. # noqa: E501
+
+ If this is an internal variant, the `{owner-slug}/{model-slug}/{framework}/{instance-slug}` of the base model instance # noqa: E501
+
+ :return: The base_model_instance of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._base_model_instance
+
+ @base_model_instance.setter
+ def base_model_instance(self, base_model_instance):
+ """Sets the base_model_instance of this ModelInstanceUpdateRequest.
+
+ If this is an internal variant, the `{owner-slug}/{model-slug}/{framework}/{instance-slug}` of the base model instance # noqa: E501
+
+ :param base_model_instance: The base_model_instance of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._base_model_instance = base_model_instance
+
+ @property
+ def external_base_model_url(self):
+ """Gets the external_base_model_url of this ModelInstanceUpdateRequest. # noqa: E501
+
+ If this is an external variant, a URL to the base model # noqa: E501
+
+ :return: The external_base_model_url of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: int
+ """
+ return self._external_base_model_url
+
+ @external_base_model_url.setter
+ def external_base_model_url(self, external_base_model_url):
+ """Sets the external_base_model_url of this ModelInstanceUpdateRequest.
+
+ If this is an external variant, a URL to the base model # noqa: E501
+
+ :param external_base_model_url: The external_base_model_url of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: int
+ """
+
+ self._external_base_model_url = external_base_model_url
+
+ @property
+ def update_mask(self):
+ """Gets the update_mask of this ModelInstanceUpdateRequest. # noqa: E501
+
+ Describes which fields to update # noqa: E501
+
+ :return: The update_mask of this ModelInstanceUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._update_mask
+
+ @update_mask.setter
+ def update_mask(self, update_mask):
+ """Sets the update_mask of this ModelInstanceUpdateRequest.
+
+ Describes which fields to update # noqa: E501
+
+ :param update_mask: The update_mask of this ModelInstanceUpdateRequest. # noqa: E501
+ :type: str
+ """
+ if update_mask is None:
+ raise ValueError("Invalid value for `update_mask`, must not be `None`") # noqa: E501
+
+ self._update_mask = update_mask
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ModelInstanceUpdateRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/model_new_instance_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/model_new_instance_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..346ad1b055633b2154284b74d95a574b629989d1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/model_new_instance_request.py
@@ -0,0 +1,417 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+from kaggle.models.upload_file import UploadFile # noqa: F401,E501
+
+
+class ModelNewInstanceRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'instance_slug': 'str',
+ 'framework': 'str',
+ 'overview': 'str',
+ 'usage': 'str',
+ 'license_name': 'str',
+ 'fine_tunable': 'bool',
+ 'training_data': 'list[str]',
+ 'model_instance_type': 'str',
+ 'base_model_instance': 'str',
+ 'external_base_model_url': 'int',
+ 'files': 'list[UploadFile]'
+ }
+
+ attribute_map = {
+ 'instance_slug': 'instanceSlug',
+ 'framework': 'framework',
+ 'overview': 'overview',
+ 'usage': 'usage',
+ 'license_name': 'licenseName',
+ 'fine_tunable': 'fineTunable',
+ 'training_data': 'trainingData',
+ 'model_instance_type': 'modelInstanceType',
+ 'base_model_instance': 'baseModelInstance',
+ 'external_base_model_url': 'externalBaseModelUrl',
+ 'files': 'files'
+ }
+
+ def __init__(self, instance_slug=None, framework=None, overview=None, usage=None, license_name='Apache 2.0', fine_tunable=True, training_data=None, model_instance_type=None, base_model_instance=None, external_base_model_url=None, files=None): # noqa: E501
+ """ModelNewInstanceRequest - a model defined in Swagger""" # noqa: E501
+
+ self._instance_slug = None
+ self._framework = None
+ self._overview = None
+ self._usage = None
+ self._license_name = None
+ self._fine_tunable = None
+ self._training_data = None
+ self._model_instance_type = None
+ self._base_model_instance = None
+ self._external_base_model_url = None
+ self._files = None
+ self.discriminator = None
+
+ self.instance_slug = instance_slug
+ self.framework = framework
+ if overview is not None:
+ self.overview = overview
+ if usage is not None:
+ self.usage = usage
+ self.license_name = license_name
+ if fine_tunable is not None:
+ self.fine_tunable = fine_tunable
+ if training_data is not None:
+ self.training_data = training_data
+ if model_instance_type is not None:
+ self.model_instance_type = model_instance_type
+ if base_model_instance is not None:
+ self.base_model_instance = base_model_instance
+ if external_base_model_url is not None:
+ self.external_base_model_url = external_base_model_url
+ if files is not None:
+ self.files = files
+
+ @property
+ def instance_slug(self):
+ """Gets the instance_slug of this ModelNewInstanceRequest. # noqa: E501
+
+ The slug that the model instance should be created with # noqa: E501
+
+ :return: The instance_slug of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._instance_slug
+
+ @instance_slug.setter
+ def instance_slug(self, instance_slug):
+ """Sets the instance_slug of this ModelNewInstanceRequest.
+
+ The slug that the model instance should be created with # noqa: E501
+
+ :param instance_slug: The instance_slug of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+ if instance_slug is None:
+ raise ValueError("Invalid value for `instance_slug`, must not be `None`") # noqa: E501
+
+ self._instance_slug = instance_slug
+
+ @property
+ def framework(self):
+ """Gets the framework of this ModelNewInstanceRequest. # noqa: E501
+
+ The framework of the model instance # noqa: E501
+
+ :return: The framework of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._framework
+
+ @framework.setter
+ def framework(self, framework):
+ """Sets the framework of this ModelNewInstanceRequest.
+
+ The framework of the model instance # noqa: E501
+
+ :param framework: The framework of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+ if framework is None:
+ raise ValueError("Invalid value for `framework`, must not be `None`") # noqa: E501
+ allowed_values = ["tensorFlow1", "tensorFlow2", "tfLite", "tfJs", "pyTorch", "jax", "flax", "pax", "maxText", "gemmaCpp", "tensorRtLlm", "ggml", "gguf", "coral", "scikitLearn", "mxnet", "onnx", "keras", "transformers", "other"] # noqa: E501
+ if framework not in allowed_values:
+ raise ValueError(
+ "Invalid value for `framework` ({0}), must be one of {1}" # noqa: E501
+ .format(framework, allowed_values)
+ )
+
+ self._framework = framework
+
+ @property
+ def overview(self):
+ """Gets the overview of this ModelNewInstanceRequest. # noqa: E501
+
+ The overview of the model instance (markdown) # noqa: E501
+
+ :return: The overview of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._overview
+
+ @overview.setter
+ def overview(self, overview):
+ """Sets the overview of this ModelNewInstanceRequest.
+
+ The overview of the model instance (markdown) # noqa: E501
+
+ :param overview: The overview of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+
+ self._overview = overview
+
+ @property
+ def usage(self):
+ """Gets the usage of this ModelNewInstanceRequest. # noqa: E501
+
+ The description of how to use the model instance (markdown) # noqa: E501
+
+ :return: The usage of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._usage
+
+ @usage.setter
+ def usage(self, usage):
+ """Sets the usage of this ModelNewInstanceRequest.
+
+ The description of how to use the model instance (markdown) # noqa: E501
+
+ :param usage: The usage of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+
+ self._usage = usage
+
+ @property
+ def license_name(self):
+ """Gets the license_name of this ModelNewInstanceRequest. # noqa: E501
+
+ The license that should be associated with the model instance # noqa: E501
+
+ :return: The license_name of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._license_name
+
+ @license_name.setter
+ def license_name(self, license_name):
+ """Sets the license_name of this ModelNewInstanceRequest.
+
+ The license that should be associated with the model instance # noqa: E501
+
+ :param license_name: The license_name of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+ if license_name is None:
+ raise ValueError("Invalid value for `license_name`, must not be `None`") # noqa: E501
+ allowed_values = ["CC BY-NC-SA 4.0", "CC BY-SA 4.0", "GPL 2", "CC BY-SA 3.0", "Attribution 4.0 International (CC BY 4.0)", "Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)", "ODC Public Domain Dedication and Licence (PDDL)", "Attribution 3.0 Unported (CC BY 3.0)", "Attribution 3.0 IGO (CC BY 3.0 IGO)", "Attribution-NonCommercial-ShareAlike 3.0 IGO (CC BY-NC-SA 3.0 IGO)", "Community Data License Agreement - Permissive - Version 1.0", "Community Data License Agreement - Sharing - Version 1.0", "Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)", "Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)", "ODC Attribution License (ODC-By)", "GNU Lesser General Public License 3.0", "GNU Affero General Public License 3.0", "GNU Free Documentation License 1.3", "Apache 2.0", "MIT", "BSD-3-Clause", "GPL 3"] # noqa: E501
+ if license_name not in allowed_values:
+ raise ValueError(
+ "Invalid value for `license_name` ({0}), must be one of {1}" # noqa: E501
+ .format(license_name, allowed_values)
+ )
+
+ self._license_name = license_name
+
+ @property
+ def fine_tunable(self):
+ """Gets the fine_tunable of this ModelNewInstanceRequest. # noqa: E501
+
+ Whether the model instance is fine tunable # noqa: E501
+
+ :return: The fine_tunable of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._fine_tunable
+
+ @fine_tunable.setter
+ def fine_tunable(self, fine_tunable):
+ """Sets the fine_tunable of this ModelNewInstanceRequest.
+
+ Whether the model instance is fine tunable # noqa: E501
+
+ :param fine_tunable: The fine_tunable of this ModelNewInstanceRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._fine_tunable = fine_tunable
+
+ @property
+ def training_data(self):
+ """Gets the training_data of this ModelNewInstanceRequest. # noqa: E501
+
+ A list of training data (urls or names) # noqa: E501
+
+ :return: The training_data of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: list[str]
+ """
+ return self._training_data
+
+ @training_data.setter
+ def training_data(self, training_data):
+ """Sets the training_data of this ModelNewInstanceRequest.
+
+ A list of training data (urls or names) # noqa: E501
+
+ :param training_data: The training_data of this ModelNewInstanceRequest. # noqa: E501
+ :type: list[str]
+ """
+
+ self._training_data = training_data
+
+ @property
+ def model_instance_type(self):
+ """Gets the model_instance_type of this ModelNewInstanceRequest. # noqa: E501
+
+ Whether the model instance is a base model, external variant, internal variant, or unspecified # noqa: E501
+
+ :return: The model_instance_type of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._model_instance_type
+
+ @model_instance_type.setter
+ def model_instance_type(self, model_instance_type):
+ """Sets the model_instance_type of this ModelNewInstanceRequest.
+
+ Whether the model instance is a base model, external variant, internal variant, or unspecified # noqa: E501
+
+ :param model_instance_type: The model_instance_type of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+ allowed_values = ["Unspecified", "BaseModel", "KaggleVariant", "ExternalVariant"] # noqa: E501
+ if model_instance_type not in allowed_values:
+ raise ValueError(
+ "Invalid value for `model_instance_type` ({0}), must be one of {1}" # noqa: E501
+ .format(model_instance_type, allowed_values)
+ )
+
+ self._model_instance_type = model_instance_type
+
+ @property
+ def base_model_instance(self):
+ """Gets the base_model_instance of this ModelNewInstanceRequest. # noqa: E501
+
+ If this is an internal variant, the `{owner-slug}/{model-slug}/{framework}/{instance-slug}` of the base model instance # noqa: E501
+
+ :return: The base_model_instance of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._base_model_instance
+
+ @base_model_instance.setter
+ def base_model_instance(self, base_model_instance):
+ """Sets the base_model_instance of this ModelNewInstanceRequest.
+
+ If this is an internal variant, the `{owner-slug}/{model-slug}/{framework}/{instance-slug}` of the base model instance # noqa: E501
+
+ :param base_model_instance: The base_model_instance of this ModelNewInstanceRequest. # noqa: E501
+ :type: str
+ """
+
+ self._base_model_instance = base_model_instance
+
+ @property
+ def external_base_model_url(self):
+ """Gets the external_base_model_url of this ModelNewInstanceRequest. # noqa: E501
+
+ If this is an external variant, a URL to the base model # noqa: E501
+
+ :return: The external_base_model_url of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: int
+ """
+ return self._external_base_model_url
+
+ @external_base_model_url.setter
+ def external_base_model_url(self, external_base_model_url):
+ """Sets the external_base_model_url of this ModelNewInstanceRequest.
+
+ If this is an external variant, a URL to the base model # noqa: E501
+
+ :param external_base_model_url: The external_base_model_url of this ModelNewInstanceRequest. # noqa: E501
+ :type: int
+ """
+
+ self._external_base_model_url = external_base_model_url
+
+ @property
+ def files(self):
+ """Gets the files of this ModelNewInstanceRequest. # noqa: E501
+
+ A list of files that should be associated with the model instance version # noqa: E501
+
+ :return: The files of this ModelNewInstanceRequest. # noqa: E501
+ :rtype: list[UploadFile]
+ """
+ return self._files
+
+ @files.setter
+ def files(self, files):
+ """Sets the files of this ModelNewInstanceRequest.
+
+ A list of files that should be associated with the model instance version # noqa: E501
+
+ :param files: The files of this ModelNewInstanceRequest. # noqa: E501
+ :type: list[UploadFile]
+ """
+
+ self._files = files
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ModelNewInstanceRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/model_new_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/model_new_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9f2d18f973bd7ca9b45b9bb89b7bbf75a2759cf
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/model_new_request.py
@@ -0,0 +1,314 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class ModelNewRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'owner_slug': 'str',
+ 'slug': 'str',
+ 'title': 'str',
+ 'subtitle': 'str',
+ 'is_private': 'bool',
+ 'description': 'str',
+ 'publish_time': 'date',
+ 'provenance_sources': 'str'
+ }
+
+ attribute_map = {
+ 'owner_slug': 'ownerSlug',
+ 'slug': 'slug',
+ 'title': 'title',
+ 'subtitle': 'subtitle',
+ 'is_private': 'isPrivate',
+ 'description': 'description',
+ 'publish_time': 'publishTime',
+ 'provenance_sources': 'provenanceSources'
+ }
+
+ def __init__(self, owner_slug=None, slug=None, title=None, subtitle=None, is_private=True, description='', publish_time=None, provenance_sources=''): # noqa: E501
+ """ModelNewRequest - a model defined in Swagger""" # noqa: E501
+
+ self._owner_slug = None
+ self._slug = None
+ self._title = None
+ self._subtitle = None
+ self._is_private = None
+ self._description = None
+ self._publish_time = None
+ self._provenance_sources = None
+ self.discriminator = None
+
+ self.owner_slug = owner_slug
+ self.slug = slug
+ self.title = title
+ if subtitle is not None:
+ self.subtitle = subtitle
+ self.is_private = is_private
+ if description is not None:
+ self.description = description
+ if publish_time is not None:
+ self.publish_time = publish_time
+ if provenance_sources is not None:
+ self.provenance_sources = provenance_sources
+
+ @property
+ def owner_slug(self):
+ """Gets the owner_slug of this ModelNewRequest. # noqa: E501
+
+ The owner's slug # noqa: E501
+
+ :return: The owner_slug of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._owner_slug
+
+ @owner_slug.setter
+ def owner_slug(self, owner_slug):
+ """Sets the owner_slug of this ModelNewRequest.
+
+ The owner's slug # noqa: E501
+
+ :param owner_slug: The owner_slug of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+ if owner_slug is None:
+ raise ValueError("Invalid value for `owner_slug`, must not be `None`") # noqa: E501
+
+ self._owner_slug = owner_slug
+
+ @property
+ def slug(self):
+ """Gets the slug of this ModelNewRequest. # noqa: E501
+
+ The slug that the model should be created with # noqa: E501
+
+ :return: The slug of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._slug
+
+ @slug.setter
+ def slug(self, slug):
+ """Sets the slug of this ModelNewRequest.
+
+ The slug that the model should be created with # noqa: E501
+
+ :param slug: The slug of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+ if slug is None:
+ raise ValueError("Invalid value for `slug`, must not be `None`") # noqa: E501
+
+ self._slug = slug
+
+ @property
+ def title(self):
+ """Gets the title of this ModelNewRequest. # noqa: E501
+
+ The title of the new model # noqa: E501
+
+ :return: The title of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._title
+
+ @title.setter
+ def title(self, title):
+ """Sets the title of this ModelNewRequest.
+
+ The title of the new model # noqa: E501
+
+ :param title: The title of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+ if title is None:
+ raise ValueError("Invalid value for `title`, must not be `None`") # noqa: E501
+
+ self._title = title
+
+ @property
+ def subtitle(self):
+ """Gets the subtitle of this ModelNewRequest. # noqa: E501
+
+ The subtitle of the new model # noqa: E501
+
+ :return: The subtitle of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._subtitle
+
+ @subtitle.setter
+ def subtitle(self, subtitle):
+ """Sets the subtitle of this ModelNewRequest.
+
+ The subtitle of the new model # noqa: E501
+
+ :param subtitle: The subtitle of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._subtitle = subtitle
+
+ @property
+ def is_private(self):
+ """Gets the is_private of this ModelNewRequest. # noqa: E501
+
+ Whether or not the model should be private # noqa: E501
+
+ :return: The is_private of this ModelNewRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._is_private
+
+ @is_private.setter
+ def is_private(self, is_private):
+ """Sets the is_private of this ModelNewRequest.
+
+ Whether or not the model should be private # noqa: E501
+
+ :param is_private: The is_private of this ModelNewRequest. # noqa: E501
+ :type: bool
+ """
+ if is_private is None:
+ raise ValueError("Invalid value for `is_private`, must not be `None`") # noqa: E501
+
+ self._is_private = is_private
+
+ @property
+ def description(self):
+ """Gets the description of this ModelNewRequest. # noqa: E501
+
+ The description to be set on the model # noqa: E501
+
+ :return: The description of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this ModelNewRequest.
+
+ The description to be set on the model # noqa: E501
+
+ :param description: The description of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def publish_time(self):
+ """Gets the publish_time of this ModelNewRequest. # noqa: E501
+
+ When the model was initially published # noqa: E501
+
+ :return: The publish_time of this ModelNewRequest. # noqa: E501
+ :rtype: date
+ """
+ return self._publish_time
+
+ @publish_time.setter
+ def publish_time(self, publish_time):
+ """Sets the publish_time of this ModelNewRequest.
+
+ When the model was initially published # noqa: E501
+
+ :param publish_time: The publish_time of this ModelNewRequest. # noqa: E501
+ :type: date
+ """
+
+ self._publish_time = publish_time
+
+ @property
+ def provenance_sources(self):
+ """Gets the provenance_sources of this ModelNewRequest. # noqa: E501
+
+ The provenance sources to be set on the model # noqa: E501
+
+ :return: The provenance_sources of this ModelNewRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._provenance_sources
+
+ @provenance_sources.setter
+ def provenance_sources(self, provenance_sources):
+ """Sets the provenance_sources of this ModelNewRequest.
+
+ The provenance sources to be set on the model # noqa: E501
+
+ :param provenance_sources: The provenance_sources of this ModelNewRequest. # noqa: E501
+ :type: str
+ """
+
+ self._provenance_sources = provenance_sources
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ModelNewRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/model_update_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/model_update_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..a15f573b3d01dce6cfda8c76a3cdab6f06c2708c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/model_update_request.py
@@ -0,0 +1,282 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class ModelUpdateRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'title': 'str',
+ 'subtitle': 'str',
+ 'is_private': 'bool',
+ 'description': 'str',
+ 'publish_time': 'date',
+ 'provenance_sources': 'str',
+ 'update_mask': 'str'
+ }
+
+ attribute_map = {
+ 'title': 'title',
+ 'subtitle': 'subtitle',
+ 'is_private': 'isPrivate',
+ 'description': 'description',
+ 'publish_time': 'publishTime',
+ 'provenance_sources': 'provenanceSources',
+ 'update_mask': 'updateMask'
+ }
+
+ def __init__(self, title=None, subtitle=None, is_private=True, description='', publish_time=None, provenance_sources='', update_mask=None): # noqa: E501
+ """ModelUpdateRequest - a model defined in Swagger""" # noqa: E501
+
+ self._title = None
+ self._subtitle = None
+ self._is_private = None
+ self._description = None
+ self._publish_time = None
+ self._provenance_sources = None
+ self._update_mask = None
+ self.discriminator = None
+
+ if title is not None:
+ self.title = title
+ if subtitle is not None:
+ self.subtitle = subtitle
+ if is_private is not None:
+ self.is_private = is_private
+ if description is not None:
+ self.description = description
+ if publish_time is not None:
+ self.publish_time = publish_time
+ if provenance_sources is not None:
+ self.provenance_sources = provenance_sources
+ if update_mask is not None:
+ self.update_mask = update_mask
+
+ @property
+ def title(self):
+ """Gets the title of this ModelUpdateRequest. # noqa: E501
+
+ The title of the new model # noqa: E501
+
+ :return: The title of this ModelUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._title
+
+ @title.setter
+ def title(self, title):
+ """Sets the title of this ModelUpdateRequest.
+
+ The title of the new model # noqa: E501
+
+ :param title: The title of this ModelUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._title = title
+
+ @property
+ def subtitle(self):
+ """Gets the subtitle of this ModelUpdateRequest. # noqa: E501
+
+ The subtitle of the new model # noqa: E501
+
+ :return: The subtitle of this ModelUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._subtitle
+
+ @subtitle.setter
+ def subtitle(self, subtitle):
+ """Sets the subtitle of this ModelUpdateRequest.
+
+ The subtitle of the new model # noqa: E501
+
+ :param subtitle: The subtitle of this ModelUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._subtitle = subtitle
+
+ @property
+ def is_private(self):
+ """Gets the is_private of this ModelUpdateRequest. # noqa: E501
+
+ Whether or not the model should be private # noqa: E501
+
+ :return: The is_private of this ModelUpdateRequest. # noqa: E501
+ :rtype: bool
+ """
+ return self._is_private
+
+ @is_private.setter
+ def is_private(self, is_private):
+ """Sets the is_private of this ModelUpdateRequest.
+
+ Whether or not the model should be private # noqa: E501
+
+ :param is_private: The is_private of this ModelUpdateRequest. # noqa: E501
+ :type: bool
+ """
+
+ self._is_private = is_private
+
+ @property
+ def description(self):
+ """Gets the description of this ModelUpdateRequest. # noqa: E501
+
+ The description to be set on the model # noqa: E501
+
+ :return: The description of this ModelUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this ModelUpdateRequest.
+
+ The description to be set on the model # noqa: E501
+
+ :param description: The description of this ModelUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def publish_time(self):
+ """Gets the publish_time of this ModelUpdateRequest. # noqa: E501
+
+ When the model was initially published # noqa: E501
+
+ :return: The publish_time of this ModelUpdateRequest. # noqa: E501
+ :rtype: date
+ """
+ return self._publish_time
+
+ @publish_time.setter
+ def publish_time(self, publish_time):
+ """Sets the publish_time of this ModelUpdateRequest.
+
+ When the model was initially published # noqa: E501
+
+ :param publish_time: The publish_time of this ModelUpdateRequest. # noqa: E501
+ :type: date
+ """
+
+ self._publish_time = publish_time
+
+ @property
+ def provenance_sources(self):
+ """Gets the provenance_sources of this ModelUpdateRequest. # noqa: E501
+
+ The provenance sources to be set on the model # noqa: E501
+
+ :return: The provenance_sources of this ModelUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._provenance_sources
+
+ @provenance_sources.setter
+ def provenance_sources(self, provenance_sources):
+ """Sets the provenance_sources of this ModelUpdateRequest.
+
+ The provenance sources to be set on the model # noqa: E501
+
+ :param provenance_sources: The provenance_sources of this ModelUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._provenance_sources = provenance_sources
+
+ @property
+ def update_mask(self):
+ """Gets the update_mask of this ModelUpdateRequest. # noqa: E501
+
+ Describes which fields to update # noqa: E501
+
+ :return: The update_mask of this ModelUpdateRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._update_mask
+
+ @update_mask.setter
+ def update_mask(self, update_mask):
+ """Sets the update_mask of this ModelUpdateRequest.
+
+ Describes which fields to update # noqa: E501
+
+ :param update_mask: The update_mask of this ModelUpdateRequest. # noqa: E501
+ :type: str
+ """
+
+ self._update_mask = update_mask
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, ModelUpdateRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/result.py b/.venv/lib/python3.10/site-packages/kaggle/models/result.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4ce2decea1c87df8fe0522aaa9887f6826411ad
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/result.py
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class Result(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ }
+
+ attribute_map = {
+ }
+
+ def __init__(self): # noqa: E501
+ """Result - a model defined in Swagger""" # noqa: E501
+ self.discriminator = None
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, Result):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_request.py b/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..19443a9224f2c8601b6c2c0ba89ebfe2409fce3b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_request.py
@@ -0,0 +1,228 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class StartBlobUploadRequest(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'type': 'object',
+ 'name': 'str',
+ 'content_length': 'int',
+ 'content_type': 'str',
+ 'last_modified_epoch_seconds': 'int'
+ }
+
+ attribute_map = {
+ 'type': 'type',
+ 'name': 'name',
+ 'content_length': 'contentLength',
+ 'content_type': 'contentType',
+ 'last_modified_epoch_seconds': 'lastModifiedEpochSeconds'
+ }
+
+ def __init__(self, type=None, name=None, content_length=None, content_type=None, last_modified_epoch_seconds=None): # noqa: E501
+ """StartBlobUploadRequest - a model defined in Swagger""" # noqa: E501
+
+ self._type = None
+ self._name = None
+ self._content_length = None
+ self._content_type = None
+ self._last_modified_epoch_seconds = None
+ self.discriminator = None
+
+ if type is not None:
+ self.type = type
+ self.name = name
+ self.content_length = content_length
+ if content_type is not None:
+ self.content_type = content_type
+ if last_modified_epoch_seconds is not None:
+ self.last_modified_epoch_seconds = last_modified_epoch_seconds
+
+ @property
+ def type(self):
+ """Gets the type of this StartBlobUploadRequest. # noqa: E501
+
+ The type of the blob (one of \"dataset\", \"model\", \"inbox\") # noqa: E501
+
+ :return: The type of this StartBlobUploadRequest. # noqa: E501
+ :rtype: object
+ """
+ return self._type
+
+ @type.setter
+ def type(self, type):
+ """Sets the type of this StartBlobUploadRequest.
+
+ The type of the blob (one of \"dataset\", \"model\", \"inbox\") # noqa: E501
+
+ :param type: The type of this StartBlobUploadRequest. # noqa: E501
+ :type: object
+ """
+
+ self._type = type
+
+ @property
+ def name(self):
+ """Gets the name of this StartBlobUploadRequest. # noqa: E501
+
+ Name of the file # noqa: E501
+
+ :return: The name of this StartBlobUploadRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Sets the name of this StartBlobUploadRequest.
+
+ Name of the file # noqa: E501
+
+ :param name: The name of this StartBlobUploadRequest. # noqa: E501
+ :type: str
+ """
+ if name is None:
+ raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501
+
+ self._name = name
+
+ @property
+ def content_length(self):
+ """Gets the content_length of this StartBlobUploadRequest. # noqa: E501
+
+ Content length of the file in bytes # noqa: E501
+
+ :return: The content_length of this StartBlobUploadRequest. # noqa: E501
+ :rtype: int
+ """
+ return self._content_length
+
+ @content_length.setter
+ def content_length(self, content_length):
+ """Sets the content_length of this StartBlobUploadRequest.
+
+ Content length of the file in bytes # noqa: E501
+
+ :param content_length: The content_length of this StartBlobUploadRequest. # noqa: E501
+ :type: int
+ """
+ if content_length is None:
+ raise ValueError("Invalid value for `content_length`, must not be `None`") # noqa: E501
+
+ self._content_length = content_length
+
+ @property
+ def content_type(self):
+ """Gets the content_type of this StartBlobUploadRequest. # noqa: E501
+
+ Content/MIME type (e.g. \"text/plain\") of the file # noqa: E501
+
+ :return: The content_type of this StartBlobUploadRequest. # noqa: E501
+ :rtype: str
+ """
+ return self._content_type
+
+ @content_type.setter
+ def content_type(self, content_type):
+ """Sets the content_type of this StartBlobUploadRequest.
+
+ Content/MIME type (e.g. \"text/plain\") of the file # noqa: E501
+
+ :param content_type: The content_type of this StartBlobUploadRequest. # noqa: E501
+ :type: str
+ """
+
+ self._content_type = content_type
+
+ @property
+ def last_modified_epoch_seconds(self):
+ """Gets the last_modified_epoch_seconds of this StartBlobUploadRequest. # noqa: E501
+
+ Last modified date of file in seconds since epoch in UTC # noqa: E501
+
+ :return: The last_modified_epoch_seconds of this StartBlobUploadRequest. # noqa: E501
+ :rtype: int
+ """
+ return self._last_modified_epoch_seconds
+
+ @last_modified_epoch_seconds.setter
+ def last_modified_epoch_seconds(self, last_modified_epoch_seconds):
+ """Sets the last_modified_epoch_seconds of this StartBlobUploadRequest.
+
+ Last modified date of file in seconds since epoch in UTC # noqa: E501
+
+ :param last_modified_epoch_seconds: The last_modified_epoch_seconds of this StartBlobUploadRequest. # noqa: E501
+ :type: int
+ """
+
+ self._last_modified_epoch_seconds = last_modified_epoch_seconds
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, StartBlobUploadRequest):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_response.py b/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_response.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fabaf700e3f9ff219d9dc209f5f2d783a6029c3
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/start_blob_upload_response.py
@@ -0,0 +1,144 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+
+class StartBlobUploadResponse(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'token': 'str',
+ 'create_url': 'str'
+ }
+
+ attribute_map = {
+ 'token': 'token',
+ 'create_url': 'createUrl'
+ }
+
+ def __init__(self, token=None, create_url=None): # noqa: E501
+ """StartBlobUploadResponse - a model defined in Swagger""" # noqa: E501
+
+ self._token = None
+ self._create_url = None
+ self.discriminator = None
+
+ self.token = token
+ self.create_url = create_url
+
+ @property
+ def token(self):
+ """Gets the token of this StartBlobUploadResponse. # noqa: E501
+
+ Opaque string token used to reference the new blob/file. # noqa: E501
+
+ :return: The token of this StartBlobUploadResponse. # noqa: E501
+ :rtype: str
+ """
+ return self._token
+
+ @token.setter
+ def token(self, token):
+ """Sets the token of this StartBlobUploadResponse.
+
+ Opaque string token used to reference the new blob/file. # noqa: E501
+
+ :param token: The token of this StartBlobUploadResponse. # noqa: E501
+ :type: str
+ """
+ if token is None:
+ raise ValueError("Invalid value for `token`, must not be `None`") # noqa: E501
+
+ self._token = token
+
+ @property
+ def create_url(self):
+ """Gets the create_url of this StartBlobUploadResponse. # noqa: E501
+
+ URL to use to start the upload. # noqa: E501
+
+ :return: The create_url of this StartBlobUploadResponse. # noqa: E501
+ :rtype: str
+ """
+ return self._create_url
+
+ @create_url.setter
+ def create_url(self, create_url):
+ """Sets the create_url of this StartBlobUploadResponse.
+
+ URL to use to start the upload. # noqa: E501
+
+ :param create_url: The create_url of this StartBlobUploadResponse. # noqa: E501
+ :type: str
+ """
+ if create_url is None:
+ raise ValueError("Invalid value for `create_url`, must not be `None`") # noqa: E501
+
+ self._create_url = create_url
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, StartBlobUploadResponse):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/models/upload_file.py b/.venv/lib/python3.10/site-packages/kaggle/models/upload_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..77059ba835a154a168342dfe6a6df901a40795a7
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/models/upload_file.py
@@ -0,0 +1,172 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+import pprint
+import re # noqa: F401
+
+import six
+
+from kaggle.models.dataset_column import DatasetColumn # noqa: F401,E501
+
+
+class UploadFile(object):
+ """NOTE: This class is auto generated by the swagger code generator program.
+
+ Do not edit the class manually.
+ """
+
+ """
+ Attributes:
+ swagger_types (dict): The key is attribute name
+ and the value is attribute type.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ """
+ swagger_types = {
+ 'token': 'str',
+ 'description': 'str',
+ 'columns': 'list[DatasetColumn]'
+ }
+
+ attribute_map = {
+ 'token': 'token',
+ 'description': 'description',
+ 'columns': 'columns'
+ }
+
+ def __init__(self, token=None, description=None, columns=None): # noqa: E501
+ """UploadFile - a model defined in Swagger""" # noqa: E501
+
+ self._token = None
+ self._description = None
+ self._columns = None
+ self.discriminator = None
+
+ if token is not None:
+ self.token = token
+ if description is not None:
+ self.description = description
+ if columns is not None:
+ self.columns = columns
+
+ @property
+ def token(self):
+ """Gets the token of this UploadFile. # noqa: E501
+
+ A token referencing a specific file upload that can be used across requests # noqa: E501
+
+ :return: The token of this UploadFile. # noqa: E501
+ :rtype: str
+ """
+ return self._token
+
+ @token.setter
+ def token(self, token):
+ """Sets the token of this UploadFile.
+
+ A token referencing a specific file upload that can be used across requests # noqa: E501
+
+ :param token: The token of this UploadFile. # noqa: E501
+ :type: str
+ """
+
+ self._token = token
+
+ @property
+ def description(self):
+ """Gets the description of this UploadFile. # noqa: E501
+
+ The file description # noqa: E501
+
+ :return: The description of this UploadFile. # noqa: E501
+ :rtype: str
+ """
+ return self._description
+
+ @description.setter
+ def description(self, description):
+ """Sets the description of this UploadFile.
+
+ The file description # noqa: E501
+
+ :param description: The description of this UploadFile. # noqa: E501
+ :type: str
+ """
+
+ self._description = description
+
+ @property
+ def columns(self):
+ """Gets the columns of this UploadFile. # noqa: E501
+
+ A list of dataset column metadata # noqa: E501
+
+ :return: The columns of this UploadFile. # noqa: E501
+ :rtype: list[DatasetColumn]
+ """
+ return self._columns
+
+ @columns.setter
+ def columns(self, columns):
+ """Sets the columns of this UploadFile.
+
+ A list of dataset column metadata # noqa: E501
+
+ :param columns: The columns of this UploadFile. # noqa: E501
+ :type: list[DatasetColumn]
+ """
+
+ self._columns = columns
+
+ def to_dict(self):
+ """Returns the model properties as a dict"""
+ result = {}
+
+ for attr, _ in six.iteritems(self.swagger_types):
+ value = getattr(self, attr)
+ if isinstance(value, list):
+ result[attr] = list(map(
+ lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
+ value
+ ))
+ elif hasattr(value, "to_dict"):
+ result[attr] = value.to_dict()
+ elif isinstance(value, dict):
+ result[attr] = dict(map(
+ lambda item: (item[0], item[1].to_dict())
+ if hasattr(item[1], "to_dict") else item,
+ value.items()
+ ))
+ else:
+ result[attr] = value
+
+ return result
+
+ def to_str(self):
+ """Returns the string representation of the model"""
+ return pprint.pformat(self.to_dict())
+
+ def __repr__(self):
+ """For `print` and `pprint`"""
+ return self.to_str()
+
+ def __eq__(self, other):
+ """Returns true if both objects are equal"""
+ if not isinstance(other, UploadFile):
+ return False
+
+ return self.__dict__ == other.__dict__
+
+ def __ne__(self, other):
+ """Returns true if both objects are not equal"""
+ return not self == other
diff --git a/.venv/lib/python3.10/site-packages/kaggle/rest.py b/.venv/lib/python3.10/site-packages/kaggle/rest.py
new file mode 100644
index 0000000000000000000000000000000000000000..94c68a95aed5b0ab4c83298f2117675338f3999c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/rest.py
@@ -0,0 +1,320 @@
+# coding: utf-8
+
+"""
+ Kaggle API
+
+ API for kaggle.com # noqa: E501
+
+ OpenAPI spec version: 1
+
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+
+from __future__ import absolute_import
+
+import io
+import json
+import logging
+import re
+import ssl
+
+import certifi
+# python 2 and python 3 compatibility library
+import six
+from six.moves.urllib.parse import urlencode
+
+try:
+ import urllib3
+except ImportError:
+ raise ImportError('Swagger python client requires urllib3.')
+
+
+logger = logging.getLogger(__name__)
+
+
+class RESTResponse(io.IOBase):
+
+ def __init__(self, resp):
+ self.urllib3_response = resp
+ self.status = resp.status
+ self.reason = resp.reason
+ self.data = resp.data
+
+ def getheaders(self):
+ """Returns a dictionary of the response headers."""
+ return self.urllib3_response.getheaders()
+
+ def getheader(self, name, default=None):
+ """Returns a given response header."""
+ return self.urllib3_response.getheader(name, default)
+
+
+class RESTClientObject(object):
+
+ def __init__(self, configuration, pools_size=4, maxsize=None):
+ # urllib3.PoolManager will pass all kw parameters to connectionpool
+ # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501
+ # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501
+ # maxsize is the number of requests to host that are allowed in parallel # noqa: E501
+ # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501
+
+ # cert_reqs
+ if configuration.verify_ssl:
+ cert_reqs = ssl.CERT_REQUIRED
+ else:
+ cert_reqs = ssl.CERT_NONE
+
+ # ca_certs
+ if configuration.ssl_ca_cert:
+ ca_certs = configuration.ssl_ca_cert
+ else:
+ # if not set certificate file, use Mozilla's root certificates.
+ ca_certs = certifi.where()
+
+ addition_pool_args = {}
+ if configuration.assert_hostname is not None:
+ addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501
+
+ if maxsize is None:
+ if configuration.connection_pool_maxsize is not None:
+ maxsize = configuration.connection_pool_maxsize
+ else:
+ maxsize = 4
+
+ # https pool manager
+ if configuration.proxy:
+ self.pool_manager = urllib3.ProxyManager(
+ num_pools=pools_size,
+ maxsize=maxsize,
+ cert_reqs=cert_reqs,
+ ca_certs=ca_certs,
+ cert_file=configuration.cert_file,
+ key_file=configuration.key_file,
+ proxy_url=configuration.proxy,
+ **addition_pool_args
+ )
+ else:
+ self.pool_manager = urllib3.PoolManager(
+ num_pools=pools_size,
+ maxsize=maxsize,
+ cert_reqs=cert_reqs,
+ ca_certs=ca_certs,
+ cert_file=configuration.cert_file,
+ key_file=configuration.key_file,
+ **addition_pool_args
+ )
+
+ def request(self, method, url, query_params=None, headers=None,
+ body=None, post_params=None, _preload_content=True,
+ _request_timeout=None):
+ """Perform requests.
+
+ :param method: http request method
+ :param url: http request url
+ :param query_params: query parameters in the url
+ :param headers: http request headers
+ :param body: request json body, for `application/json`
+ :param post_params: request post parameters,
+ `application/x-www-form-urlencoded`
+ and `multipart/form-data`
+ :param _preload_content: if False, the urllib3.HTTPResponse object will
+ be returned without reading/decoding response
+ data. Default is True.
+ :param _request_timeout: timeout setting for this request. If one
+ number provided, it will be total request
+ timeout. It can also be a pair (tuple) of
+ (connection, read) timeouts.
+ """
+ method = method.upper()
+ assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
+ 'PATCH', 'OPTIONS']
+
+ if post_params and body:
+ raise ValueError(
+ "body parameter cannot be used with post_params parameter."
+ )
+
+ post_params = post_params or {}
+ headers = headers or {}
+
+ timeout = None
+ if _request_timeout:
+ if isinstance(_request_timeout, (int, ) if six.PY3 else (int, long)): # noqa: E501,F821
+ timeout = urllib3.Timeout(total=_request_timeout)
+ elif (isinstance(_request_timeout, tuple) and
+ len(_request_timeout) == 2):
+ timeout = urllib3.Timeout(
+ connect=_request_timeout[0], read=_request_timeout[1])
+
+ try:
+ # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
+ if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
+ if query_params:
+ url += '?' + urlencode(query_params)
+ if re.search('json', headers['Content-Type'], re.IGNORECASE):
+ request_body = None
+ if body is not None:
+ request_body = json.dumps(body)
+ r = self.pool_manager.request(
+ method, url,
+ body=request_body,
+ preload_content=_preload_content,
+ timeout=timeout,
+ headers=headers)
+ elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501
+ r = self.pool_manager.request(
+ method, url,
+ fields=post_params,
+ encode_multipart=False,
+ preload_content=_preload_content,
+ timeout=timeout,
+ headers=headers)
+ elif headers['Content-Type'] == 'multipart/form-data':
+ # must del headers['Content-Type'], or the correct
+ # Content-Type which generated by urllib3 will be
+ # overwritten.
+ del headers['Content-Type']
+ r = self.pool_manager.request(
+ method, url,
+ fields=post_params,
+ encode_multipart=True,
+ preload_content=_preload_content,
+ timeout=timeout,
+ headers=headers)
+ # Pass a `string` parameter directly in the body to support
+ # other content types than Json when `body` argument is
+ # provided in serialized form
+ elif isinstance(body, str):
+ request_body = body
+ r = self.pool_manager.request(
+ method, url,
+ body=request_body,
+ preload_content=_preload_content,
+ timeout=timeout,
+ headers=headers)
+ else:
+ # Cannot generate the request from given parameters
+ msg = """Cannot prepare a request message for provided
+ arguments. Please check that your arguments match
+ declared content type."""
+ raise ApiException(status=0, reason=msg)
+ # For `GET`, `HEAD`
+ else:
+ r = self.pool_manager.request(method, url,
+ fields=query_params,
+ preload_content=_preload_content,
+ timeout=timeout,
+ headers=headers)
+ except urllib3.exceptions.SSLError as e:
+ msg = "{0}\n{1}".format(type(e).__name__, str(e))
+ raise ApiException(status=0, reason=msg)
+
+ if _preload_content:
+ r = RESTResponse(r)
+
+ # In the python 3, the response.data is bytes.
+ # we need to decode it to string.
+ if six.PY3:
+ r.data = r.data.decode('utf8')
+
+ # log response body
+ logger.debug("response body: %s", r.data)
+
+ if not 200 <= r.status <= 299:
+ raise ApiException(http_resp=r)
+
+ return r
+
+ def GET(self, url, headers=None, query_params=None, _preload_content=True,
+ _request_timeout=None):
+ return self.request("GET", url,
+ headers=headers,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ query_params=query_params)
+
+ def HEAD(self, url, headers=None, query_params=None, _preload_content=True,
+ _request_timeout=None):
+ return self.request("HEAD", url,
+ headers=headers,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ query_params=query_params)
+
+ def OPTIONS(self, url, headers=None, query_params=None, post_params=None,
+ body=None, _preload_content=True, _request_timeout=None):
+ return self.request("OPTIONS", url,
+ headers=headers,
+ query_params=query_params,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+
+ def DELETE(self, url, headers=None, query_params=None, body=None,
+ _preload_content=True, _request_timeout=None):
+ return self.request("DELETE", url,
+ headers=headers,
+ query_params=query_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+
+ def POST(self, url, headers=None, query_params=None, post_params=None,
+ body=None, _preload_content=True, _request_timeout=None):
+ return self.request("POST", url,
+ headers=headers,
+ query_params=query_params,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+
+ def PUT(self, url, headers=None, query_params=None, post_params=None,
+ body=None, _preload_content=True, _request_timeout=None):
+ return self.request("PUT", url,
+ headers=headers,
+ query_params=query_params,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+
+ def PATCH(self, url, headers=None, query_params=None, post_params=None,
+ body=None, _preload_content=True, _request_timeout=None):
+ return self.request("PATCH", url,
+ headers=headers,
+ query_params=query_params,
+ post_params=post_params,
+ _preload_content=_preload_content,
+ _request_timeout=_request_timeout,
+ body=body)
+
+
+class ApiException(Exception):
+
+ def __init__(self, status=None, reason=None, http_resp=None):
+ if http_resp:
+ self.status = http_resp.status
+ self.reason = http_resp.reason
+ self.body = http_resp.data
+ self.headers = http_resp.getheaders()
+ else:
+ self.status = status
+ self.reason = reason
+ self.body = None
+ self.headers = None
+
+ def __str__(self):
+ """Custom error messages for exception"""
+ error_message = "({0})\n"\
+ "Reason: {1}\n".format(self.status, self.reason)
+ if self.headers:
+ error_message += "HTTP response headers: {0}\n".format(
+ self.headers)
+
+ if self.body:
+ error_message += "HTTP response body: {0}\n".format(self.body)
+
+ return error_message
diff --git a/.venv/lib/python3.10/site-packages/kaggle/test/__init__.py b/.venv/lib/python3.10/site-packages/kaggle/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.venv/lib/python3.10/site-packages/kaggle/test/test_authenticate.py b/.venv/lib/python3.10/site-packages/kaggle/test/test_authenticate.py
new file mode 100644
index 0000000000000000000000000000000000000000..4aea6458a7915044bb447fe0af354843f341ae1f
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/kaggle/test/test_authenticate.py
@@ -0,0 +1,43 @@
+from kaggle.api.kaggle_api_extended import KaggleApi
+
+# python -m unittest tests.test_authenticate
+
+import os
+import unittest
+
+
+class TestAuthenticate(unittest.TestCase):
+
+ def setUp(self):
+ print("setup class:%s" % self)
+
+ def tearDown(self):
+ print("teardown class:TestStuff")
+
+ # Environment
+
+ def test_environment_variables(self):
+ os.environ['KAGGLE_USERNAME'] = 'dinosaur'
+ os.environ['KAGGLE_KEY'] = 'xxxxxxxxxxxx'
+ api = KaggleApi()
+
+ # We haven't authenticated yet
+ self.assertTrue("key" not in api.config_values)
+ self.assertTrue("username" not in api.config_values)
+ api.authenticate()
+
+ # Should be set from the environment
+ self.assertEqual(api.config_values['key'], 'xxxxxxxxxxxx')
+ self.assertEqual(api.config_values['username'], 'dinosaur')
+
+ # Configuration Actions
+
+ def test_config_actions(self):
+ api = KaggleApi()
+
+ self.assertTrue(api.config_dir.endswith('.kaggle'))
+ self.assertEqual(api.get_config_value('doesntexist'), None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6f62d44e4ef733c0e713afcd2371fed7f2b3de67
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE
@@ -0,0 +1,3 @@
+This software is made available under the terms of *either* of the licenses
+found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
+under the terms of *both* these licenses.
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.APACHE b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.APACHE
new file mode 100644
index 0000000000000000000000000000000000000000..f433b1a53f5b830a205fd2df78e2b34974656c7b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.APACHE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.BSD b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.BSD
new file mode 100644
index 0000000000000000000000000000000000000000..42ce7b75c92fb01a3f6ed17eea363f756b7da582
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/LICENSE.BSD
@@ -0,0 +1,23 @@
+Copyright (c) Donald Stufft and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/METADATA b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..10ab4390a96923dea26efffa4c42eaacb502536b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/METADATA
@@ -0,0 +1,102 @@
+Metadata-Version: 2.1
+Name: packaging
+Version: 24.0
+Summary: Core utilities for Python packages
+Author-email: Donald Stufft
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Project-URL: Documentation, https://packaging.pypa.io/
+Project-URL: Source, https://github.com/pypa/packaging
+
+packaging
+=========
+
+.. start-intro
+
+Reusable core utilities for various Python Packaging
+`interoperability specifications `_.
+
+This library provides utilities that implement the interoperability
+specifications which have clearly one correct behaviour (eg: :pep:`440`)
+or benefit greatly from having a single shared implementation (eg: :pep:`425`).
+
+.. end-intro
+
+The ``packaging`` project includes the following: version handling, specifiers,
+markers, requirements, tags, utilities.
+
+Documentation
+-------------
+
+The `documentation`_ provides information and the API for the following:
+
+- Version Handling
+- Specifiers
+- Markers
+- Requirements
+- Tags
+- Utilities
+
+Installation
+------------
+
+Use ``pip`` to install these utilities::
+
+ pip install packaging
+
+The ``packaging`` library uses calendar-based versioning (``YY.N``).
+
+Discussion
+----------
+
+If you run into bugs, you can file them in our `issue tracker`_.
+
+You can also join ``#pypa`` on Freenode to ask questions or get involved.
+
+
+.. _`documentation`: https://packaging.pypa.io/
+.. _`issue tracker`: https://github.com/pypa/packaging/issues
+
+
+Code of Conduct
+---------------
+
+Everyone interacting in the packaging project's codebases, issue trackers, chat
+rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
+
+.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+
+Contributing
+------------
+
+The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
+well as how to report a potential security issue. The documentation for this
+project also covers information about `project development`_ and `security`_.
+
+.. _`project development`: https://packaging.pypa.io/en/latest/development/
+.. _`security`: https://packaging.pypa.io/en/latest/security/
+
+Project History
+---------------
+
+Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
+recent changes and project history.
+
+.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
+
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/RECORD b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..85cd1fd20f24a80cbe7338e8558ad0bb63cba665
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/RECORD
@@ -0,0 +1,22 @@
+packaging/__init__.py,sha256=UzotcV07p8vcJzd80S-W0srhgY8NMVD_XvJcZ7JN-tA,496
+packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
+packaging/_manylinux.py,sha256=1ng_TqyH49hY6s3W_zVHyoJIaogbJqbIF1jJ0fAehc4,9590
+packaging/_musllinux.py,sha256=kgmBGLFybpy8609-KTvzmt2zChCPWYvhp5BWP4JX7dE,2676
+packaging/_parser.py,sha256=zlsFB1FpMRjkUdQb6WLq7xON52ruQadxFpYsDXWhLb4,10347
+packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
+packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
+packaging/markers.py,sha256=eH-txS2zq1HdNpTd9LcZUcVIwewAiNU0grmq5wjKnOk,8208
+packaging/metadata.py,sha256=w7jPEg6mDf1FTZMn79aFxFuk4SKtynUJtxr2InTxlV4,33036
+packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+packaging/requirements.py,sha256=dgoBeVprPu2YE6Q8nGfwOPTjATHbRa_ZGLyXhFEln6Q,2933
+packaging/specifiers.py,sha256=dB2DwbmvSbEuVilEyiIQ382YfW5JfwzXTfRRPVtaENY,39784
+packaging/tags.py,sha256=fedHXiOHkBxNZTXotXv8uXPmMFU9ae-TKBujgYHigcA,18950
+packaging/utils.py,sha256=XgdmP3yx9-wQEFjO7OvMj9RjEf5JlR5HFFR69v7SQ9E,5268
+packaging/version.py,sha256=XjRBLNK17UMDgLeP8UHnqwiY3TdSi03xFQURtec211A,16236
+packaging-24.0.dist-info/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
+packaging-24.0.dist-info/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
+packaging-24.0.dist-info/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
+packaging-24.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+packaging-24.0.dist-info/METADATA,sha256=0dESdhY_wHValuOrbgdebiEw04EbX4dkujlxPdEsFus,3203
+packaging-24.0.dist-info/INSTALLER,sha256=4EobgVZEtoZym__e-MIhNYRUXcWFMMbrrt6xRpKyZoQ,12
+packaging-24.0.dist-info/RECORD,,
diff --git a/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/WHEEL b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..3b5e64b5e6c4a210201d1676a891fd57b15cda99
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging-24.0.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/.venv/lib/python3.10/site-packages/packaging/__init__.py b/.venv/lib/python3.10/site-packages/packaging/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7c0aa12ca950f230c8092436a985b2305702642
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/__init__.py
@@ -0,0 +1,15 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+__title__ = "packaging"
+__summary__ = "Core utilities for Python packages"
+__uri__ = "https://github.com/pypa/packaging"
+
+__version__ = "24.0"
+
+__author__ = "Donald Stufft and individual contributors"
+__email__ = "donald@stufft.io"
+
+__license__ = "BSD-2-Clause or Apache-2.0"
+__copyright__ = "2014 %s" % __author__
diff --git a/.venv/lib/python3.10/site-packages/packaging/_elffile.py b/.venv/lib/python3.10/site-packages/packaging/_elffile.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fb19b30bb53c18f38a9ef02dd7c4478670fb962
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_elffile.py
@@ -0,0 +1,108 @@
+"""
+ELF file parser.
+
+This provides a class ``ELFFile`` that parses an ELF executable in a similar
+interface to ``ZipFile``. Only the read interface is implemented.
+
+Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
+ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
+"""
+
+import enum
+import os
+import struct
+from typing import IO, Optional, Tuple
+
+
+class ELFInvalid(ValueError):
+ pass
+
+
+class EIClass(enum.IntEnum):
+ C32 = 1
+ C64 = 2
+
+
+class EIData(enum.IntEnum):
+ Lsb = 1
+ Msb = 2
+
+
+class EMachine(enum.IntEnum):
+ I386 = 3
+ S390 = 22
+ Arm = 40
+ X8664 = 62
+ AArc64 = 183
+
+
+class ELFFile:
+ """
+ Representation of an ELF executable.
+ """
+
+ def __init__(self, f: IO[bytes]) -> None:
+ self._f = f
+
+ try:
+ ident = self._read("16B")
+ except struct.error:
+ raise ELFInvalid("unable to parse identification")
+ magic = bytes(ident[:4])
+ if magic != b"\x7fELF":
+ raise ELFInvalid(f"invalid magic: {magic!r}")
+
+ self.capacity = ident[4] # Format for program header (bitness).
+ self.encoding = ident[5] # Data structure encoding (endianness).
+
+ try:
+ # e_fmt: Format for program header.
+ # p_fmt: Format for section header.
+ # p_idx: Indexes to find p_type, p_offset, and p_filesz.
+ e_fmt, self._p_fmt, self._p_idx = {
+ (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
+ (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
+ }[(self.capacity, self.encoding)]
+ except KeyError:
+ raise ELFInvalid(
+ f"unrecognized capacity ({self.capacity}) or "
+ f"encoding ({self.encoding})"
+ )
+
+ try:
+ (
+ _,
+ self.machine, # Architecture type.
+ _,
+ _,
+ self._e_phoff, # Offset of program header.
+ _,
+ self.flags, # Processor-specific flags.
+ _,
+ self._e_phentsize, # Size of section.
+ self._e_phnum, # Number of sections.
+ ) = self._read(e_fmt)
+ except struct.error as e:
+ raise ELFInvalid("unable to parse machine and section information") from e
+
+ def _read(self, fmt: str) -> Tuple[int, ...]:
+ return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
+
+ @property
+ def interpreter(self) -> Optional[str]:
+ """
+ The path recorded in the ``PT_INTERP`` section header.
+ """
+ for index in range(self._e_phnum):
+ self._f.seek(self._e_phoff + self._e_phentsize * index)
+ try:
+ data = self._read(self._p_fmt)
+ except struct.error:
+ continue
+ if data[self._p_idx[0]] != 3: # Not PT_INTERP.
+ continue
+ self._f.seek(data[self._p_idx[1]])
+ return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
+ return None
diff --git a/.venv/lib/python3.10/site-packages/packaging/_manylinux.py b/.venv/lib/python3.10/site-packages/packaging/_manylinux.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad62505f3ff66c3d4da07ce1f2a50d9f10bc1bdd
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_manylinux.py
@@ -0,0 +1,260 @@
+import collections
+import contextlib
+import functools
+import os
+import re
+import sys
+import warnings
+from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple
+
+from ._elffile import EIClass, EIData, ELFFile, EMachine
+
+EF_ARM_ABIMASK = 0xFF000000
+EF_ARM_ABI_VER5 = 0x05000000
+EF_ARM_ABI_FLOAT_HARD = 0x00000400
+
+
+# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
+# as the type for `path` until then.
+@contextlib.contextmanager
+def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
+ try:
+ with open(path, "rb") as f:
+ yield ELFFile(f)
+ except (OSError, TypeError, ValueError):
+ yield None
+
+
+def _is_linux_armhf(executable: str) -> bool:
+ # hard-float ABI can be detected from the ELF header of the running
+ # process
+ # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
+ with _parse_elf(executable) as f:
+ return (
+ f is not None
+ and f.capacity == EIClass.C32
+ and f.encoding == EIData.Lsb
+ and f.machine == EMachine.Arm
+ and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
+ and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
+ )
+
+
+def _is_linux_i686(executable: str) -> bool:
+ with _parse_elf(executable) as f:
+ return (
+ f is not None
+ and f.capacity == EIClass.C32
+ and f.encoding == EIData.Lsb
+ and f.machine == EMachine.I386
+ )
+
+
+def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
+ if "armv7l" in archs:
+ return _is_linux_armhf(executable)
+ if "i686" in archs:
+ return _is_linux_i686(executable)
+ allowed_archs = {
+ "x86_64",
+ "aarch64",
+ "ppc64",
+ "ppc64le",
+ "s390x",
+ "loongarch64",
+ "riscv64",
+ }
+ return any(arch in allowed_archs for arch in archs)
+
+
+# If glibc ever changes its major version, we need to know what the last
+# minor version was, so we can build the complete list of all versions.
+# For now, guess what the highest minor version might be, assume it will
+# be 50 for testing. Once this actually happens, update the dictionary
+# with the actual value.
+_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50)
+
+
+class _GLibCVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _glibc_version_string_confstr() -> Optional[str]:
+ """
+ Primary implementation of glibc_version_string using os.confstr.
+ """
+ # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
+ # to be broken or missing. This strategy is used in the standard library
+ # platform module.
+ # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
+ try:
+ # Should be a string like "glibc 2.17".
+ version_string: Optional[str] = os.confstr("CS_GNU_LIBC_VERSION")
+ assert version_string is not None
+ _, version = version_string.rsplit()
+ except (AssertionError, AttributeError, OSError, ValueError):
+ # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
+ return None
+ return version
+
+
+def _glibc_version_string_ctypes() -> Optional[str]:
+ """
+ Fallback implementation of glibc_version_string using ctypes.
+ """
+ try:
+ import ctypes
+ except ImportError:
+ return None
+
+ # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
+ # manpage says, "If filename is NULL, then the returned handle is for the
+ # main program". This way we can let the linker do the work to figure out
+ # which libc our process is actually using.
+ #
+ # We must also handle the special case where the executable is not a
+ # dynamically linked executable. This can occur when using musl libc,
+ # for example. In this situation, dlopen() will error, leading to an
+ # OSError. Interestingly, at least in the case of musl, there is no
+ # errno set on the OSError. The single string argument used to construct
+ # OSError comes from libc itself and is therefore not portable to
+ # hard code here. In any case, failure to call dlopen() means we
+ # can proceed, so we bail on our attempt.
+ try:
+ process_namespace = ctypes.CDLL(None)
+ except OSError:
+ return None
+
+ try:
+ gnu_get_libc_version = process_namespace.gnu_get_libc_version
+ except AttributeError:
+ # Symbol doesn't exist -> therefore, we are not linked to
+ # glibc.
+ return None
+
+ # Call gnu_get_libc_version, which returns a string like "2.5"
+ gnu_get_libc_version.restype = ctypes.c_char_p
+ version_str: str = gnu_get_libc_version()
+ # py2 / py3 compatibility:
+ if not isinstance(version_str, str):
+ version_str = version_str.decode("ascii")
+
+ return version_str
+
+
+def _glibc_version_string() -> Optional[str]:
+ """Returns glibc version string, or None if not using glibc."""
+ return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
+
+
+def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
+ """Parse glibc version.
+
+ We use a regexp instead of str.split because we want to discard any
+ random junk that might come after the minor version -- this might happen
+ in patched/forked versions of glibc (e.g. Linaro's version of glibc
+ uses version strings like "2.20-2014.11"). See gh-3588.
+ """
+ m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str)
+ if not m:
+ warnings.warn(
+ f"Expected glibc version with 2 components major.minor,"
+ f" got: {version_str}",
+ RuntimeWarning,
+ )
+ return -1, -1
+ return int(m.group("major")), int(m.group("minor"))
+
+
+@functools.lru_cache()
+def _get_glibc_version() -> Tuple[int, int]:
+ version_str = _glibc_version_string()
+ if version_str is None:
+ return (-1, -1)
+ return _parse_glibc_version(version_str)
+
+
+# From PEP 513, PEP 600
+def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
+ sys_glibc = _get_glibc_version()
+ if sys_glibc < version:
+ return False
+ # Check for presence of _manylinux module.
+ try:
+ import _manylinux
+ except ImportError:
+ return True
+ if hasattr(_manylinux, "manylinux_compatible"):
+ result = _manylinux.manylinux_compatible(version[0], version[1], arch)
+ if result is not None:
+ return bool(result)
+ return True
+ if version == _GLibCVersion(2, 5):
+ if hasattr(_manylinux, "manylinux1_compatible"):
+ return bool(_manylinux.manylinux1_compatible)
+ if version == _GLibCVersion(2, 12):
+ if hasattr(_manylinux, "manylinux2010_compatible"):
+ return bool(_manylinux.manylinux2010_compatible)
+ if version == _GLibCVersion(2, 17):
+ if hasattr(_manylinux, "manylinux2014_compatible"):
+ return bool(_manylinux.manylinux2014_compatible)
+ return True
+
+
+_LEGACY_MANYLINUX_MAP = {
+ # CentOS 7 w/ glibc 2.17 (PEP 599)
+ (2, 17): "manylinux2014",
+ # CentOS 6 w/ glibc 2.12 (PEP 571)
+ (2, 12): "manylinux2010",
+ # CentOS 5 w/ glibc 2.5 (PEP 513)
+ (2, 5): "manylinux1",
+}
+
+
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
+ """Generate manylinux tags compatible to the current platform.
+
+ :param archs: Sequence of compatible architectures.
+ The first one shall be the closest to the actual architecture and be the part of
+ platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+ The ``linux_`` prefix is assumed as a prerequisite for the current platform to
+ be manylinux-compatible.
+
+ :returns: An iterator of compatible manylinux tags.
+ """
+ if not _have_compatible_abi(sys.executable, archs):
+ return
+ # Oldest glibc to be supported regardless of architecture is (2, 17).
+ too_old_glibc2 = _GLibCVersion(2, 16)
+ if set(archs) & {"x86_64", "i686"}:
+ # On x86/i686 also oldest glibc to be supported is (2, 5).
+ too_old_glibc2 = _GLibCVersion(2, 4)
+ current_glibc = _GLibCVersion(*_get_glibc_version())
+ glibc_max_list = [current_glibc]
+ # We can assume compatibility across glibc major versions.
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
+ #
+ # Build a list of maximum glibc versions so that we can
+ # output the canonical list of all glibc from current_glibc
+ # down to too_old_glibc2, including all intermediary versions.
+ for glibc_major in range(current_glibc.major - 1, 1, -1):
+ glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
+ glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
+ for arch in archs:
+ for glibc_max in glibc_max_list:
+ if glibc_max.major == too_old_glibc2.major:
+ min_minor = too_old_glibc2.minor
+ else:
+ # For other glibc major versions oldest supported is (x, 0).
+ min_minor = -1
+ for glibc_minor in range(glibc_max.minor, min_minor, -1):
+ glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
+ tag = "manylinux_{}_{}".format(*glibc_version)
+ if _is_compatible(arch, glibc_version):
+ yield f"{tag}_{arch}"
+ # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
+ if glibc_version in _LEGACY_MANYLINUX_MAP:
+ legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
+ if _is_compatible(arch, glibc_version):
+ yield f"{legacy_tag}_{arch}"
diff --git a/.venv/lib/python3.10/site-packages/packaging/_musllinux.py b/.venv/lib/python3.10/site-packages/packaging/_musllinux.py
new file mode 100644
index 0000000000000000000000000000000000000000..86419df9d7087f3f8b6d0096f32a52c24b05e7c1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_musllinux.py
@@ -0,0 +1,83 @@
+"""PEP 656 support.
+
+This module implements logic to detect if the currently running Python is
+linked against musl, and what musl version is used.
+"""
+
+import functools
+import re
+import subprocess
+import sys
+from typing import Iterator, NamedTuple, Optional, Sequence
+
+from ._elffile import ELFFile
+
+
+class _MuslVersion(NamedTuple):
+ major: int
+ minor: int
+
+
+def _parse_musl_version(output: str) -> Optional[_MuslVersion]:
+ lines = [n for n in (n.strip() for n in output.splitlines()) if n]
+ if len(lines) < 2 or lines[0][:4] != "musl":
+ return None
+ m = re.match(r"Version (\d+)\.(\d+)", lines[1])
+ if not m:
+ return None
+ return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
+
+
+@functools.lru_cache()
+def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
+ """Detect currently-running musl runtime version.
+
+ This is done by checking the specified executable's dynamic linking
+ information, and invoking the loader to parse its output for a version
+ string. If the loader is musl, the output would be something like::
+
+ musl libc (x86_64)
+ Version 1.2.2
+ Dynamic Program Loader
+ """
+ try:
+ with open(executable, "rb") as f:
+ ld = ELFFile(f).interpreter
+ except (OSError, TypeError, ValueError):
+ return None
+ if ld is None or "musl" not in ld:
+ return None
+ proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
+ return _parse_musl_version(proc.stderr)
+
+
+def platform_tags(archs: Sequence[str]) -> Iterator[str]:
+ """Generate musllinux tags compatible to the current platform.
+
+ :param archs: Sequence of compatible architectures.
+ The first one shall be the closest to the actual architecture and be the part of
+ platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
+ The ``linux_`` prefix is assumed as a prerequisite for the current platform to
+ be musllinux-compatible.
+
+ :returns: An iterator of compatible musllinux tags.
+ """
+ sys_musl = _get_musl_version(sys.executable)
+ if sys_musl is None: # Python not dynamically linked against musl.
+ return
+ for arch in archs:
+ for minor in range(sys_musl.minor, -1, -1):
+ yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
+
+
+if __name__ == "__main__": # pragma: no cover
+ import sysconfig
+
+ plat = sysconfig.get_platform()
+ assert plat.startswith("linux-"), "not linux"
+
+ print("plat:", plat)
+ print("musl:", _get_musl_version(sys.executable))
+ print("tags:", end=" ")
+ for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
+ print(t, end="\n ")
diff --git a/.venv/lib/python3.10/site-packages/packaging/_parser.py b/.venv/lib/python3.10/site-packages/packaging/_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..684df75457cb82d3683dc99ff52c5bf911f3341b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_parser.py
@@ -0,0 +1,356 @@
+"""Handwritten parser of dependency specifiers.
+
+The docstring for each __parse_* function contains ENBF-inspired grammar representing
+the implementation.
+"""
+
+import ast
+from typing import Any, List, NamedTuple, Optional, Tuple, Union
+
+from ._tokenizer import DEFAULT_RULES, Tokenizer
+
+
+class Node:
+ def __init__(self, value: str) -> None:
+ self.value = value
+
+ def __str__(self) -> str:
+ return self.value
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__}('{self}')>"
+
+ def serialize(self) -> str:
+ raise NotImplementedError
+
+
+class Variable(Node):
+ def serialize(self) -> str:
+ return str(self)
+
+
+class Value(Node):
+ def serialize(self) -> str:
+ return f'"{self}"'
+
+
+class Op(Node):
+ def serialize(self) -> str:
+ return str(self)
+
+
+MarkerVar = Union[Variable, Value]
+MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
+# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]]
+# MarkerList = List[Union["MarkerList", MarkerAtom, str]]
+# mypy does not support recursive type definition
+# https://github.com/python/mypy/issues/731
+MarkerAtom = Any
+MarkerList = List[Any]
+
+
+class ParsedRequirement(NamedTuple):
+ name: str
+ url: str
+ extras: List[str]
+ specifier: str
+ marker: Optional[MarkerList]
+
+
+# --------------------------------------------------------------------------------------
+# Recursive descent parser for dependency specifier
+# --------------------------------------------------------------------------------------
+def parse_requirement(source: str) -> ParsedRequirement:
+ return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
+
+
+def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
+ """
+ requirement = WS? IDENTIFIER WS? extras WS? requirement_details
+ """
+ tokenizer.consume("WS")
+
+ name_token = tokenizer.expect(
+ "IDENTIFIER", expected="package name at the start of dependency specifier"
+ )
+ name = name_token.text
+ tokenizer.consume("WS")
+
+ extras = _parse_extras(tokenizer)
+ tokenizer.consume("WS")
+
+ url, specifier, marker = _parse_requirement_details(tokenizer)
+ tokenizer.expect("END", expected="end of dependency specifier")
+
+ return ParsedRequirement(name, url, extras, specifier, marker)
+
+
+def _parse_requirement_details(
+ tokenizer: Tokenizer,
+) -> Tuple[str, str, Optional[MarkerList]]:
+ """
+ requirement_details = AT URL (WS requirement_marker?)?
+ | specifier WS? (requirement_marker)?
+ """
+
+ specifier = ""
+ url = ""
+ marker = None
+
+ if tokenizer.check("AT"):
+ tokenizer.read()
+ tokenizer.consume("WS")
+
+ url_start = tokenizer.position
+ url = tokenizer.expect("URL", expected="URL after @").text
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ tokenizer.expect("WS", expected="whitespace after URL")
+
+ # The input might end after whitespace.
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ marker = _parse_requirement_marker(
+ tokenizer, span_start=url_start, after="URL and whitespace"
+ )
+ else:
+ specifier_start = tokenizer.position
+ specifier = _parse_specifier(tokenizer)
+ tokenizer.consume("WS")
+
+ if tokenizer.check("END", peek=True):
+ return (url, specifier, marker)
+
+ marker = _parse_requirement_marker(
+ tokenizer,
+ span_start=specifier_start,
+ after=(
+ "version specifier"
+ if specifier
+ else "name and no valid version specifier"
+ ),
+ )
+
+ return (url, specifier, marker)
+
+
+def _parse_requirement_marker(
+ tokenizer: Tokenizer, *, span_start: int, after: str
+) -> MarkerList:
+ """
+ requirement_marker = SEMICOLON marker WS?
+ """
+
+ if not tokenizer.check("SEMICOLON"):
+ tokenizer.raise_syntax_error(
+ f"Expected end or semicolon (after {after})",
+ span_start=span_start,
+ )
+ tokenizer.read()
+
+ marker = _parse_marker(tokenizer)
+ tokenizer.consume("WS")
+
+ return marker
+
+
+def _parse_extras(tokenizer: Tokenizer) -> List[str]:
+ """
+ extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
+ """
+ if not tokenizer.check("LEFT_BRACKET", peek=True):
+ return []
+
+ with tokenizer.enclosing_tokens(
+ "LEFT_BRACKET",
+ "RIGHT_BRACKET",
+ around="extras",
+ ):
+ tokenizer.consume("WS")
+ extras = _parse_extras_list(tokenizer)
+ tokenizer.consume("WS")
+
+ return extras
+
+
+def _parse_extras_list(tokenizer: Tokenizer) -> List[str]:
+ """
+ extras_list = identifier (wsp* ',' wsp* identifier)*
+ """
+ extras: List[str] = []
+
+ if not tokenizer.check("IDENTIFIER"):
+ return extras
+
+ extras.append(tokenizer.read().text)
+
+ while True:
+ tokenizer.consume("WS")
+ if tokenizer.check("IDENTIFIER", peek=True):
+ tokenizer.raise_syntax_error("Expected comma between extra names")
+ elif not tokenizer.check("COMMA"):
+ break
+
+ tokenizer.read()
+ tokenizer.consume("WS")
+
+ extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
+ extras.append(extra_token.text)
+
+ return extras
+
+
+def _parse_specifier(tokenizer: Tokenizer) -> str:
+ """
+ specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
+ | WS? version_many WS?
+ """
+ with tokenizer.enclosing_tokens(
+ "LEFT_PARENTHESIS",
+ "RIGHT_PARENTHESIS",
+ around="version specifier",
+ ):
+ tokenizer.consume("WS")
+ parsed_specifiers = _parse_version_many(tokenizer)
+ tokenizer.consume("WS")
+
+ return parsed_specifiers
+
+
+def _parse_version_many(tokenizer: Tokenizer) -> str:
+ """
+ version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
+ """
+ parsed_specifiers = ""
+ while tokenizer.check("SPECIFIER"):
+ span_start = tokenizer.position
+ parsed_specifiers += tokenizer.read().text
+ if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
+ tokenizer.raise_syntax_error(
+ ".* suffix can only be used with `==` or `!=` operators",
+ span_start=span_start,
+ span_end=tokenizer.position + 1,
+ )
+ if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
+ tokenizer.raise_syntax_error(
+ "Local version label can only be used with `==` or `!=` operators",
+ span_start=span_start,
+ span_end=tokenizer.position,
+ )
+ tokenizer.consume("WS")
+ if not tokenizer.check("COMMA"):
+ break
+ parsed_specifiers += tokenizer.read().text
+ tokenizer.consume("WS")
+
+ return parsed_specifiers
+
+
+# --------------------------------------------------------------------------------------
+# Recursive descent parser for marker expression
+# --------------------------------------------------------------------------------------
+def parse_marker(source: str) -> MarkerList:
+ return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
+
+
+def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
+ retval = _parse_marker(tokenizer)
+ tokenizer.expect("END", expected="end of marker expression")
+ return retval
+
+
+def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
+ """
+ marker = marker_atom (BOOLOP marker_atom)+
+ """
+ expression = [_parse_marker_atom(tokenizer)]
+ while tokenizer.check("BOOLOP"):
+ token = tokenizer.read()
+ expr_right = _parse_marker_atom(tokenizer)
+ expression.extend((token.text, expr_right))
+ return expression
+
+
+def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
+ """
+ marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
+ | WS? marker_item WS?
+ """
+
+ tokenizer.consume("WS")
+ if tokenizer.check("LEFT_PARENTHESIS", peek=True):
+ with tokenizer.enclosing_tokens(
+ "LEFT_PARENTHESIS",
+ "RIGHT_PARENTHESIS",
+ around="marker expression",
+ ):
+ tokenizer.consume("WS")
+ marker: MarkerAtom = _parse_marker(tokenizer)
+ tokenizer.consume("WS")
+ else:
+ marker = _parse_marker_item(tokenizer)
+ tokenizer.consume("WS")
+ return marker
+
+
+def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
+ """
+ marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
+ """
+ tokenizer.consume("WS")
+ marker_var_left = _parse_marker_var(tokenizer)
+ tokenizer.consume("WS")
+ marker_op = _parse_marker_op(tokenizer)
+ tokenizer.consume("WS")
+ marker_var_right = _parse_marker_var(tokenizer)
+ tokenizer.consume("WS")
+ return (marker_var_left, marker_op, marker_var_right)
+
+
+def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
+ """
+ marker_var = VARIABLE | QUOTED_STRING
+ """
+ if tokenizer.check("VARIABLE"):
+ return process_env_var(tokenizer.read().text.replace(".", "_"))
+ elif tokenizer.check("QUOTED_STRING"):
+ return process_python_str(tokenizer.read().text)
+ else:
+ tokenizer.raise_syntax_error(
+ message="Expected a marker variable or quoted string"
+ )
+
+
+def process_env_var(env_var: str) -> Variable:
+ if env_var in ("platform_python_implementation", "python_implementation"):
+ return Variable("platform_python_implementation")
+ else:
+ return Variable(env_var)
+
+
+def process_python_str(python_str: str) -> Value:
+ value = ast.literal_eval(python_str)
+ return Value(str(value))
+
+
+def _parse_marker_op(tokenizer: Tokenizer) -> Op:
+ """
+ marker_op = IN | NOT IN | OP
+ """
+ if tokenizer.check("IN"):
+ tokenizer.read()
+ return Op("in")
+ elif tokenizer.check("NOT"):
+ tokenizer.read()
+ tokenizer.expect("WS", expected="whitespace after 'not'")
+ tokenizer.expect("IN", expected="'in' after 'not'")
+ return Op("not in")
+ elif tokenizer.check("OP"):
+ return Op(tokenizer.read().text)
+ else:
+ return tokenizer.raise_syntax_error(
+ "Expected marker operator, one of "
+ "<=, <, !=, ==, >=, >, ~=, ===, in, not in"
+ )
diff --git a/.venv/lib/python3.10/site-packages/packaging/_structures.py b/.venv/lib/python3.10/site-packages/packaging/_structures.py
new file mode 100644
index 0000000000000000000000000000000000000000..90a6465f9682c886363eea5327dac64bf623a6ff
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_structures.py
@@ -0,0 +1,61 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+
+class InfinityType:
+ def __repr__(self) -> str:
+ return "Infinity"
+
+ def __hash__(self) -> int:
+ return hash(repr(self))
+
+ def __lt__(self, other: object) -> bool:
+ return False
+
+ def __le__(self, other: object) -> bool:
+ return False
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, self.__class__)
+
+ def __gt__(self, other: object) -> bool:
+ return True
+
+ def __ge__(self, other: object) -> bool:
+ return True
+
+ def __neg__(self: object) -> "NegativeInfinityType":
+ return NegativeInfinity
+
+
+Infinity = InfinityType()
+
+
+class NegativeInfinityType:
+ def __repr__(self) -> str:
+ return "-Infinity"
+
+ def __hash__(self) -> int:
+ return hash(repr(self))
+
+ def __lt__(self, other: object) -> bool:
+ return True
+
+ def __le__(self, other: object) -> bool:
+ return True
+
+ def __eq__(self, other: object) -> bool:
+ return isinstance(other, self.__class__)
+
+ def __gt__(self, other: object) -> bool:
+ return False
+
+ def __ge__(self, other: object) -> bool:
+ return False
+
+ def __neg__(self: object) -> InfinityType:
+ return Infinity
+
+
+NegativeInfinity = NegativeInfinityType()
diff --git a/.venv/lib/python3.10/site-packages/packaging/_tokenizer.py b/.venv/lib/python3.10/site-packages/packaging/_tokenizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd0d648d49a7c1a62d25ce5c9107aa448a8a22d1
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/_tokenizer.py
@@ -0,0 +1,192 @@
+import contextlib
+import re
+from dataclasses import dataclass
+from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
+
+from .specifiers import Specifier
+
+
+@dataclass
+class Token:
+ name: str
+ text: str
+ position: int
+
+
+class ParserSyntaxError(Exception):
+ """The provided source text could not be parsed correctly."""
+
+ def __init__(
+ self,
+ message: str,
+ *,
+ source: str,
+ span: Tuple[int, int],
+ ) -> None:
+ self.span = span
+ self.message = message
+ self.source = source
+
+ super().__init__()
+
+ def __str__(self) -> str:
+ marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
+ return "\n ".join([self.message, self.source, marker])
+
+
+DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
+ "LEFT_PARENTHESIS": r"\(",
+ "RIGHT_PARENTHESIS": r"\)",
+ "LEFT_BRACKET": r"\[",
+ "RIGHT_BRACKET": r"\]",
+ "SEMICOLON": r";",
+ "COMMA": r",",
+ "QUOTED_STRING": re.compile(
+ r"""
+ (
+ ('[^']*')
+ |
+ ("[^"]*")
+ )
+ """,
+ re.VERBOSE,
+ ),
+ "OP": r"(===|==|~=|!=|<=|>=|<|>)",
+ "BOOLOP": r"\b(or|and)\b",
+ "IN": r"\bin\b",
+ "NOT": r"\bnot\b",
+ "VARIABLE": re.compile(
+ r"""
+ \b(
+ python_version
+ |python_full_version
+ |os[._]name
+ |sys[._]platform
+ |platform_(release|system)
+ |platform[._](version|machine|python_implementation)
+ |python_implementation
+ |implementation_(name|version)
+ |extra
+ )\b
+ """,
+ re.VERBOSE,
+ ),
+ "SPECIFIER": re.compile(
+ Specifier._operator_regex_str + Specifier._version_regex_str,
+ re.VERBOSE | re.IGNORECASE,
+ ),
+ "AT": r"\@",
+ "URL": r"[^ \t]+",
+ "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
+ "VERSION_PREFIX_TRAIL": r"\.\*",
+ "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
+ "WS": r"[ \t]+",
+ "END": r"$",
+}
+
+
+class Tokenizer:
+ """Context-sensitive token parsing.
+
+ Provides methods to examine the input stream to check whether the next token
+ matches.
+ """
+
+ def __init__(
+ self,
+ source: str,
+ *,
+ rules: "Dict[str, Union[str, re.Pattern[str]]]",
+ ) -> None:
+ self.source = source
+ self.rules: Dict[str, re.Pattern[str]] = {
+ name: re.compile(pattern) for name, pattern in rules.items()
+ }
+ self.next_token: Optional[Token] = None
+ self.position = 0
+
+ def consume(self, name: str) -> None:
+ """Move beyond provided token name, if at current position."""
+ if self.check(name):
+ self.read()
+
+ def check(self, name: str, *, peek: bool = False) -> bool:
+ """Check whether the next token has the provided name.
+
+ By default, if the check succeeds, the token *must* be read before
+ another check. If `peek` is set to `True`, the token is not loaded and
+ would need to be checked again.
+ """
+ assert (
+ self.next_token is None
+ ), f"Cannot check for {name!r}, already have {self.next_token!r}"
+ assert name in self.rules, f"Unknown token name: {name!r}"
+
+ expression = self.rules[name]
+
+ match = expression.match(self.source, self.position)
+ if match is None:
+ return False
+ if not peek:
+ self.next_token = Token(name, match[0], self.position)
+ return True
+
+ def expect(self, name: str, *, expected: str) -> Token:
+ """Expect a certain token name next, failing with a syntax error otherwise.
+
+ The token is *not* read.
+ """
+ if not self.check(name):
+ raise self.raise_syntax_error(f"Expected {expected}")
+ return self.read()
+
+ def read(self) -> Token:
+ """Consume the next token and return it."""
+ token = self.next_token
+ assert token is not None
+
+ self.position += len(token.text)
+ self.next_token = None
+
+ return token
+
+ def raise_syntax_error(
+ self,
+ message: str,
+ *,
+ span_start: Optional[int] = None,
+ span_end: Optional[int] = None,
+ ) -> NoReturn:
+ """Raise ParserSyntaxError at the given position."""
+ span = (
+ self.position if span_start is None else span_start,
+ self.position if span_end is None else span_end,
+ )
+ raise ParserSyntaxError(
+ message,
+ source=self.source,
+ span=span,
+ )
+
+ @contextlib.contextmanager
+ def enclosing_tokens(
+ self, open_token: str, close_token: str, *, around: str
+ ) -> Iterator[None]:
+ if self.check(open_token):
+ open_position = self.position
+ self.read()
+ else:
+ open_position = None
+
+ yield
+
+ if open_position is None:
+ return
+
+ if not self.check(close_token):
+ self.raise_syntax_error(
+ f"Expected matching {close_token} for {open_token}, after {around}",
+ span_start=open_position,
+ )
+
+ self.read()
diff --git a/.venv/lib/python3.10/site-packages/packaging/markers.py b/.venv/lib/python3.10/site-packages/packaging/markers.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b98fca7233be6dd9324cd2b6d71b6a8ac91a6cb
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/markers.py
@@ -0,0 +1,252 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+import operator
+import os
+import platform
+import sys
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+
+from ._parser import (
+ MarkerAtom,
+ MarkerList,
+ Op,
+ Value,
+ Variable,
+ parse_marker as _parse_marker,
+)
+from ._tokenizer import ParserSyntaxError
+from .specifiers import InvalidSpecifier, Specifier
+from .utils import canonicalize_name
+
+__all__ = [
+ "InvalidMarker",
+ "UndefinedComparison",
+ "UndefinedEnvironmentName",
+ "Marker",
+ "default_environment",
+]
+
+Operator = Callable[[str, str], bool]
+
+
+class InvalidMarker(ValueError):
+ """
+ An invalid marker was found, users should refer to PEP 508.
+ """
+
+
+class UndefinedComparison(ValueError):
+ """
+ An invalid operation was attempted on a value that doesn't support it.
+ """
+
+
+class UndefinedEnvironmentName(ValueError):
+ """
+ A name was attempted to be used that does not exist inside of the
+ environment.
+ """
+
+
+def _normalize_extra_values(results: Any) -> Any:
+ """
+ Normalize extra values.
+ """
+ if isinstance(results[0], tuple):
+ lhs, op, rhs = results[0]
+ if isinstance(lhs, Variable) and lhs.value == "extra":
+ normalized_extra = canonicalize_name(rhs.value)
+ rhs = Value(normalized_extra)
+ elif isinstance(rhs, Variable) and rhs.value == "extra":
+ normalized_extra = canonicalize_name(lhs.value)
+ lhs = Value(normalized_extra)
+ results[0] = lhs, op, rhs
+ return results
+
+
+def _format_marker(
+ marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
+) -> str:
+
+ assert isinstance(marker, (list, tuple, str))
+
+ # Sometimes we have a structure like [[...]] which is a single item list
+ # where the single item is itself it's own list. In that case we want skip
+ # the rest of this function so that we don't get extraneous () on the
+ # outside.
+ if (
+ isinstance(marker, list)
+ and len(marker) == 1
+ and isinstance(marker[0], (list, tuple))
+ ):
+ return _format_marker(marker[0])
+
+ if isinstance(marker, list):
+ inner = (_format_marker(m, first=False) for m in marker)
+ if first:
+ return " ".join(inner)
+ else:
+ return "(" + " ".join(inner) + ")"
+ elif isinstance(marker, tuple):
+ return " ".join([m.serialize() for m in marker])
+ else:
+ return marker
+
+
+_operators: Dict[str, Operator] = {
+ "in": lambda lhs, rhs: lhs in rhs,
+ "not in": lambda lhs, rhs: lhs not in rhs,
+ "<": operator.lt,
+ "<=": operator.le,
+ "==": operator.eq,
+ "!=": operator.ne,
+ ">=": operator.ge,
+ ">": operator.gt,
+}
+
+
+def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
+ try:
+ spec = Specifier("".join([op.serialize(), rhs]))
+ except InvalidSpecifier:
+ pass
+ else:
+ return spec.contains(lhs, prereleases=True)
+
+ oper: Optional[Operator] = _operators.get(op.serialize())
+ if oper is None:
+ raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
+
+ return oper(lhs, rhs)
+
+
+def _normalize(*values: str, key: str) -> Tuple[str, ...]:
+ # PEP 685 – Comparison of extra names for optional distribution dependencies
+ # https://peps.python.org/pep-0685/
+ # > When comparing extra names, tools MUST normalize the names being
+ # > compared using the semantics outlined in PEP 503 for names
+ if key == "extra":
+ return tuple(canonicalize_name(v) for v in values)
+
+ # other environment markers don't have such standards
+ return values
+
+
+def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool:
+ groups: List[List[bool]] = [[]]
+
+ for marker in markers:
+ assert isinstance(marker, (list, tuple, str))
+
+ if isinstance(marker, list):
+ groups[-1].append(_evaluate_markers(marker, environment))
+ elif isinstance(marker, tuple):
+ lhs, op, rhs = marker
+
+ if isinstance(lhs, Variable):
+ environment_key = lhs.value
+ lhs_value = environment[environment_key]
+ rhs_value = rhs.value
+ else:
+ lhs_value = lhs.value
+ environment_key = rhs.value
+ rhs_value = environment[environment_key]
+
+ lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
+ groups[-1].append(_eval_op(lhs_value, op, rhs_value))
+ else:
+ assert marker in ["and", "or"]
+ if marker == "or":
+ groups.append([])
+
+ return any(all(item) for item in groups)
+
+
+def format_full_version(info: "sys._version_info") -> str:
+ version = "{0.major}.{0.minor}.{0.micro}".format(info)
+ kind = info.releaselevel
+ if kind != "final":
+ version += kind[0] + str(info.serial)
+ return version
+
+
+def default_environment() -> Dict[str, str]:
+ iver = format_full_version(sys.implementation.version)
+ implementation_name = sys.implementation.name
+ return {
+ "implementation_name": implementation_name,
+ "implementation_version": iver,
+ "os_name": os.name,
+ "platform_machine": platform.machine(),
+ "platform_release": platform.release(),
+ "platform_system": platform.system(),
+ "platform_version": platform.version(),
+ "python_full_version": platform.python_version(),
+ "platform_python_implementation": platform.python_implementation(),
+ "python_version": ".".join(platform.python_version_tuple()[:2]),
+ "sys_platform": sys.platform,
+ }
+
+
+class Marker:
+ def __init__(self, marker: str) -> None:
+ # Note: We create a Marker object without calling this constructor in
+ # packaging.requirements.Requirement. If any additional logic is
+ # added here, make sure to mirror/adapt Requirement.
+ try:
+ self._markers = _normalize_extra_values(_parse_marker(marker))
+ # The attribute `_markers` can be described in terms of a recursive type:
+ # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
+ #
+ # For example, the following expression:
+ # python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
+ #
+ # is parsed into:
+ # [
+ # (, ')>, ),
+ # 'and',
+ # [
+ # (, , ),
+ # 'or',
+ # (, , )
+ # ]
+ # ]
+ except ParserSyntaxError as e:
+ raise InvalidMarker(str(e)) from e
+
+ def __str__(self) -> str:
+ return _format_marker(self._markers)
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __hash__(self) -> int:
+ return hash((self.__class__.__name__, str(self)))
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Marker):
+ return NotImplemented
+
+ return str(self) == str(other)
+
+ def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
+ """Evaluate a marker.
+
+ Return the boolean from evaluating the given marker against the
+ environment. environment is an optional argument to override all or
+ part of the determined environment.
+
+ The environment is determined from the current Python process.
+ """
+ current_environment = default_environment()
+ current_environment["extra"] = ""
+ if environment is not None:
+ current_environment.update(environment)
+ # The API used to allow setting extra to None. We need to handle this
+ # case for backwards compatibility.
+ if current_environment["extra"] is None:
+ current_environment["extra"] = ""
+
+ return _evaluate_markers(self._markers, current_environment)
diff --git a/.venv/lib/python3.10/site-packages/packaging/metadata.py b/.venv/lib/python3.10/site-packages/packaging/metadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb274930799da0f8ee17566b5b587b4047282c7b
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/metadata.py
@@ -0,0 +1,825 @@
+import email.feedparser
+import email.header
+import email.message
+import email.parser
+import email.policy
+import sys
+import typing
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Generic,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ Union,
+ cast,
+)
+
+from . import requirements, specifiers, utils, version as version_module
+
+T = typing.TypeVar("T")
+if sys.version_info[:2] >= (3, 8): # pragma: no cover
+ from typing import Literal, TypedDict
+else: # pragma: no cover
+ if typing.TYPE_CHECKING:
+ from typing_extensions import Literal, TypedDict
+ else:
+ try:
+ from typing_extensions import Literal, TypedDict
+ except ImportError:
+
+ class Literal:
+ def __init_subclass__(*_args, **_kwargs):
+ pass
+
+ class TypedDict:
+ def __init_subclass__(*_args, **_kwargs):
+ pass
+
+
+try:
+ ExceptionGroup
+except NameError: # pragma: no cover
+
+ class ExceptionGroup(Exception): # noqa: N818
+ """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
+
+ If :external:exc:`ExceptionGroup` is already defined by Python itself,
+ that version is used instead.
+ """
+
+ message: str
+ exceptions: List[Exception]
+
+ def __init__(self, message: str, exceptions: List[Exception]) -> None:
+ self.message = message
+ self.exceptions = exceptions
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
+
+else: # pragma: no cover
+ ExceptionGroup = ExceptionGroup
+
+
+class InvalidMetadata(ValueError):
+ """A metadata field contains invalid data."""
+
+ field: str
+ """The name of the field that contains invalid data."""
+
+ def __init__(self, field: str, message: str) -> None:
+ self.field = field
+ super().__init__(message)
+
+
+# The RawMetadata class attempts to make as few assumptions about the underlying
+# serialization formats as possible. The idea is that as long as a serialization
+# formats offer some very basic primitives in *some* way then we can support
+# serializing to and from that format.
+class RawMetadata(TypedDict, total=False):
+ """A dictionary of raw core metadata.
+
+ Each field in core metadata maps to a key of this dictionary (when data is
+ provided). The key is lower-case and underscores are used instead of dashes
+ compared to the equivalent core metadata field. Any core metadata field that
+ can be specified multiple times or can hold multiple values in a single
+ field have a key with a plural name. See :class:`Metadata` whose attributes
+ match the keys of this dictionary.
+
+ Core metadata fields that can be specified multiple times are stored as a
+ list or dict depending on which is appropriate for the field. Any fields
+ which hold multiple values in a single field are stored as a list.
+
+ """
+
+ # Metadata 1.0 - PEP 241
+ metadata_version: str
+ name: str
+ version: str
+ platforms: List[str]
+ summary: str
+ description: str
+ keywords: List[str]
+ home_page: str
+ author: str
+ author_email: str
+ license: str
+
+ # Metadata 1.1 - PEP 314
+ supported_platforms: List[str]
+ download_url: str
+ classifiers: List[str]
+ requires: List[str]
+ provides: List[str]
+ obsoletes: List[str]
+
+ # Metadata 1.2 - PEP 345
+ maintainer: str
+ maintainer_email: str
+ requires_dist: List[str]
+ provides_dist: List[str]
+ obsoletes_dist: List[str]
+ requires_python: str
+ requires_external: List[str]
+ project_urls: Dict[str, str]
+
+ # Metadata 2.0
+ # PEP 426 attempted to completely revamp the metadata format
+ # but got stuck without ever being able to build consensus on
+ # it and ultimately ended up withdrawn.
+ #
+ # However, a number of tools had started emitting METADATA with
+ # `2.0` Metadata-Version, so for historical reasons, this version
+ # was skipped.
+
+ # Metadata 2.1 - PEP 566
+ description_content_type: str
+ provides_extra: List[str]
+
+ # Metadata 2.2 - PEP 643
+ dynamic: List[str]
+
+ # Metadata 2.3 - PEP 685
+ # No new fields were added in PEP 685, just some edge case were
+ # tightened up to provide better interoptability.
+
+
+_STRING_FIELDS = {
+ "author",
+ "author_email",
+ "description",
+ "description_content_type",
+ "download_url",
+ "home_page",
+ "license",
+ "maintainer",
+ "maintainer_email",
+ "metadata_version",
+ "name",
+ "requires_python",
+ "summary",
+ "version",
+}
+
+_LIST_FIELDS = {
+ "classifiers",
+ "dynamic",
+ "obsoletes",
+ "obsoletes_dist",
+ "platforms",
+ "provides",
+ "provides_dist",
+ "provides_extra",
+ "requires",
+ "requires_dist",
+ "requires_external",
+ "supported_platforms",
+}
+
+_DICT_FIELDS = {
+ "project_urls",
+}
+
+
+def _parse_keywords(data: str) -> List[str]:
+ """Split a string of comma-separate keyboards into a list of keywords."""
+ return [k.strip() for k in data.split(",")]
+
+
+def _parse_project_urls(data: List[str]) -> Dict[str, str]:
+ """Parse a list of label/URL string pairings separated by a comma."""
+ urls = {}
+ for pair in data:
+ # Our logic is slightly tricky here as we want to try and do
+ # *something* reasonable with malformed data.
+ #
+ # The main thing that we have to worry about, is data that does
+ # not have a ',' at all to split the label from the Value. There
+ # isn't a singular right answer here, and we will fail validation
+ # later on (if the caller is validating) so it doesn't *really*
+ # matter, but since the missing value has to be an empty str
+ # and our return value is dict[str, str], if we let the key
+ # be the missing value, then they'd have multiple '' values that
+ # overwrite each other in a accumulating dict.
+ #
+ # The other potentional issue is that it's possible to have the
+ # same label multiple times in the metadata, with no solid "right"
+ # answer with what to do in that case. As such, we'll do the only
+ # thing we can, which is treat the field as unparseable and add it
+ # to our list of unparsed fields.
+ parts = [p.strip() for p in pair.split(",", 1)]
+ parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
+
+ # TODO: The spec doesn't say anything about if the keys should be
+ # considered case sensitive or not... logically they should
+ # be case-preserving and case-insensitive, but doing that
+ # would open up more cases where we might have duplicate
+ # entries.
+ label, url = parts
+ if label in urls:
+ # The label already exists in our set of urls, so this field
+ # is unparseable, and we can just add the whole thing to our
+ # unparseable data and stop processing it.
+ raise KeyError("duplicate labels in project urls")
+ urls[label] = url
+
+ return urls
+
+
+def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str:
+ """Get the body of the message."""
+ # If our source is a str, then our caller has managed encodings for us,
+ # and we don't need to deal with it.
+ if isinstance(source, str):
+ payload: str = msg.get_payload()
+ return payload
+ # If our source is a bytes, then we're managing the encoding and we need
+ # to deal with it.
+ else:
+ bpayload: bytes = msg.get_payload(decode=True)
+ try:
+ return bpayload.decode("utf8", "strict")
+ except UnicodeDecodeError:
+ raise ValueError("payload in an invalid encoding")
+
+
+# The various parse_FORMAT functions here are intended to be as lenient as
+# possible in their parsing, while still returning a correctly typed
+# RawMetadata.
+#
+# To aid in this, we also generally want to do as little touching of the
+# data as possible, except where there are possibly some historic holdovers
+# that make valid data awkward to work with.
+#
+# While this is a lower level, intermediate format than our ``Metadata``
+# class, some light touch ups can make a massive difference in usability.
+
+# Map METADATA fields to RawMetadata.
+_EMAIL_TO_RAW_MAPPING = {
+ "author": "author",
+ "author-email": "author_email",
+ "classifier": "classifiers",
+ "description": "description",
+ "description-content-type": "description_content_type",
+ "download-url": "download_url",
+ "dynamic": "dynamic",
+ "home-page": "home_page",
+ "keywords": "keywords",
+ "license": "license",
+ "maintainer": "maintainer",
+ "maintainer-email": "maintainer_email",
+ "metadata-version": "metadata_version",
+ "name": "name",
+ "obsoletes": "obsoletes",
+ "obsoletes-dist": "obsoletes_dist",
+ "platform": "platforms",
+ "project-url": "project_urls",
+ "provides": "provides",
+ "provides-dist": "provides_dist",
+ "provides-extra": "provides_extra",
+ "requires": "requires",
+ "requires-dist": "requires_dist",
+ "requires-external": "requires_external",
+ "requires-python": "requires_python",
+ "summary": "summary",
+ "supported-platform": "supported_platforms",
+ "version": "version",
+}
+_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
+
+
+def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[str]]]:
+ """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
+
+ This function returns a two-item tuple of dicts. The first dict is of
+ recognized fields from the core metadata specification. Fields that can be
+ parsed and translated into Python's built-in types are converted
+ appropriately. All other fields are left as-is. Fields that are allowed to
+ appear multiple times are stored as lists.
+
+ The second dict contains all other fields from the metadata. This includes
+ any unrecognized fields. It also includes any fields which are expected to
+ be parsed into a built-in type but were not formatted appropriately. Finally,
+ any fields that are expected to appear only once but are repeated are
+ included in this dict.
+
+ """
+ raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
+ unparsed: Dict[str, List[str]] = {}
+
+ if isinstance(data, str):
+ parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
+ else:
+ parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
+
+ # We have to wrap parsed.keys() in a set, because in the case of multiple
+ # values for a key (a list), the key will appear multiple times in the
+ # list of keys, but we're avoiding that by using get_all().
+ for name in frozenset(parsed.keys()):
+ # Header names in RFC are case insensitive, so we'll normalize to all
+ # lower case to make comparisons easier.
+ name = name.lower()
+
+ # We use get_all() here, even for fields that aren't multiple use,
+ # because otherwise someone could have e.g. two Name fields, and we
+ # would just silently ignore it rather than doing something about it.
+ headers = parsed.get_all(name) or []
+
+ # The way the email module works when parsing bytes is that it
+ # unconditionally decodes the bytes as ascii using the surrogateescape
+ # handler. When you pull that data back out (such as with get_all() ),
+ # it looks to see if the str has any surrogate escapes, and if it does
+ # it wraps it in a Header object instead of returning the string.
+ #
+ # As such, we'll look for those Header objects, and fix up the encoding.
+ value = []
+ # Flag if we have run into any issues processing the headers, thus
+ # signalling that the data belongs in 'unparsed'.
+ valid_encoding = True
+ for h in headers:
+ # It's unclear if this can return more types than just a Header or
+ # a str, so we'll just assert here to make sure.
+ assert isinstance(h, (email.header.Header, str))
+
+ # If it's a header object, we need to do our little dance to get
+ # the real data out of it. In cases where there is invalid data
+ # we're going to end up with mojibake, but there's no obvious, good
+ # way around that without reimplementing parts of the Header object
+ # ourselves.
+ #
+ # That should be fine since, if mojibacked happens, this key is
+ # going into the unparsed dict anyways.
+ if isinstance(h, email.header.Header):
+ # The Header object stores it's data as chunks, and each chunk
+ # can be independently encoded, so we'll need to check each
+ # of them.
+ chunks: List[Tuple[bytes, Optional[str]]] = []
+ for bin, encoding in email.header.decode_header(h):
+ try:
+ bin.decode("utf8", "strict")
+ except UnicodeDecodeError:
+ # Enable mojibake.
+ encoding = "latin1"
+ valid_encoding = False
+ else:
+ encoding = "utf8"
+ chunks.append((bin, encoding))
+
+ # Turn our chunks back into a Header object, then let that
+ # Header object do the right thing to turn them into a
+ # string for us.
+ value.append(str(email.header.make_header(chunks)))
+ # This is already a string, so just add it.
+ else:
+ value.append(h)
+
+ # We've processed all of our values to get them into a list of str,
+ # but we may have mojibake data, in which case this is an unparsed
+ # field.
+ if not valid_encoding:
+ unparsed[name] = value
+ continue
+
+ raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
+ if raw_name is None:
+ # This is a bit of a weird situation, we've encountered a key that
+ # we don't know what it means, so we don't know whether it's meant
+ # to be a list or not.
+ #
+ # Since we can't really tell one way or another, we'll just leave it
+ # as a list, even though it may be a single item list, because that's
+ # what makes the most sense for email headers.
+ unparsed[name] = value
+ continue
+
+ # If this is one of our string fields, then we'll check to see if our
+ # value is a list of a single item. If it is then we'll assume that
+ # it was emitted as a single string, and unwrap the str from inside
+ # the list.
+ #
+ # If it's any other kind of data, then we haven't the faintest clue
+ # what we should parse it as, and we have to just add it to our list
+ # of unparsed stuff.
+ if raw_name in _STRING_FIELDS and len(value) == 1:
+ raw[raw_name] = value[0]
+ # If this is one of our list of string fields, then we can just assign
+ # the value, since email *only* has strings, and our get_all() call
+ # above ensures that this is a list.
+ elif raw_name in _LIST_FIELDS:
+ raw[raw_name] = value
+ # Special Case: Keywords
+ # The keywords field is implemented in the metadata spec as a str,
+ # but it conceptually is a list of strings, and is serialized using
+ # ", ".join(keywords), so we'll do some light data massaging to turn
+ # this into what it logically is.
+ elif raw_name == "keywords" and len(value) == 1:
+ raw[raw_name] = _parse_keywords(value[0])
+ # Special Case: Project-URL
+ # The project urls is implemented in the metadata spec as a list of
+ # specially-formatted strings that represent a key and a value, which
+ # is fundamentally a mapping, however the email format doesn't support
+ # mappings in a sane way, so it was crammed into a list of strings
+ # instead.
+ #
+ # We will do a little light data massaging to turn this into a map as
+ # it logically should be.
+ elif raw_name == "project_urls":
+ try:
+ raw[raw_name] = _parse_project_urls(value)
+ except KeyError:
+ unparsed[name] = value
+ # Nothing that we've done has managed to parse this, so it'll just
+ # throw it in our unparseable data and move on.
+ else:
+ unparsed[name] = value
+
+ # We need to support getting the Description from the message payload in
+ # addition to getting it from the the headers. This does mean, though, there
+ # is the possibility of it being set both ways, in which case we put both
+ # in 'unparsed' since we don't know which is right.
+ try:
+ payload = _get_payload(parsed, data)
+ except ValueError:
+ unparsed.setdefault("description", []).append(
+ parsed.get_payload(decode=isinstance(data, bytes))
+ )
+ else:
+ if payload:
+ # Check to see if we've already got a description, if so then both
+ # it, and this body move to unparseable.
+ if "description" in raw:
+ description_header = cast(str, raw.pop("description"))
+ unparsed.setdefault("description", []).extend(
+ [description_header, payload]
+ )
+ elif "description" in unparsed:
+ unparsed["description"].append(payload)
+ else:
+ raw["description"] = payload
+
+ # We need to cast our `raw` to a metadata, because a TypedDict only support
+ # literal key names, but we're computing our key names on purpose, but the
+ # way this function is implemented, our `TypedDict` can only have valid key
+ # names.
+ return cast(RawMetadata, raw), unparsed
+
+
+_NOT_FOUND = object()
+
+
+# Keep the two values in sync.
+_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"]
+_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"]
+
+_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
+
+
+class _Validator(Generic[T]):
+ """Validate a metadata field.
+
+ All _process_*() methods correspond to a core metadata field. The method is
+ called with the field's raw value. If the raw value is valid it is returned
+ in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
+ If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
+ as appropriate).
+ """
+
+ name: str
+ raw_name: str
+ added: _MetadataVersion
+
+ def __init__(
+ self,
+ *,
+ added: _MetadataVersion = "1.0",
+ ) -> None:
+ self.added = added
+
+ def __set_name__(self, _owner: "Metadata", name: str) -> None:
+ self.name = name
+ self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
+
+ def __get__(self, instance: "Metadata", _owner: Type["Metadata"]) -> T:
+ # With Python 3.8, the caching can be replaced with functools.cached_property().
+ # No need to check the cache as attribute lookup will resolve into the
+ # instance's __dict__ before __get__ is called.
+ cache = instance.__dict__
+ value = instance._raw.get(self.name)
+
+ # To make the _process_* methods easier, we'll check if the value is None
+ # and if this field is NOT a required attribute, and if both of those
+ # things are true, we'll skip the the converter. This will mean that the
+ # converters never have to deal with the None union.
+ if self.name in _REQUIRED_ATTRS or value is not None:
+ try:
+ converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
+ except AttributeError:
+ pass
+ else:
+ value = converter(value)
+
+ cache[self.name] = value
+ try:
+ del instance._raw[self.name] # type: ignore[misc]
+ except KeyError:
+ pass
+
+ return cast(T, value)
+
+ def _invalid_metadata(
+ self, msg: str, cause: Optional[Exception] = None
+ ) -> InvalidMetadata:
+ exc = InvalidMetadata(
+ self.raw_name, msg.format_map({"field": repr(self.raw_name)})
+ )
+ exc.__cause__ = cause
+ return exc
+
+ def _process_metadata_version(self, value: str) -> _MetadataVersion:
+ # Implicitly makes Metadata-Version required.
+ if value not in _VALID_METADATA_VERSIONS:
+ raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
+ return cast(_MetadataVersion, value)
+
+ def _process_name(self, value: str) -> str:
+ if not value:
+ raise self._invalid_metadata("{field} is a required field")
+ # Validate the name as a side-effect.
+ try:
+ utils.canonicalize_name(value, validate=True)
+ except utils.InvalidName as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ )
+ else:
+ return value
+
+ def _process_version(self, value: str) -> version_module.Version:
+ if not value:
+ raise self._invalid_metadata("{field} is a required field")
+ try:
+ return version_module.parse(value)
+ except version_module.InvalidVersion as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ )
+
+ def _process_summary(self, value: str) -> str:
+ """Check the field contains no newlines."""
+ if "\n" in value:
+ raise self._invalid_metadata("{field} must be a single line")
+ return value
+
+ def _process_description_content_type(self, value: str) -> str:
+ content_types = {"text/plain", "text/x-rst", "text/markdown"}
+ message = email.message.EmailMessage()
+ message["content-type"] = value
+
+ content_type, parameters = (
+ # Defaults to `text/plain` if parsing failed.
+ message.get_content_type().lower(),
+ message["content-type"].params,
+ )
+ # Check if content-type is valid or defaulted to `text/plain` and thus was
+ # not parseable.
+ if content_type not in content_types or content_type not in value.lower():
+ raise self._invalid_metadata(
+ f"{{field}} must be one of {list(content_types)}, not {value!r}"
+ )
+
+ charset = parameters.get("charset", "UTF-8")
+ if charset != "UTF-8":
+ raise self._invalid_metadata(
+ f"{{field}} can only specify the UTF-8 charset, not {list(charset)}"
+ )
+
+ markdown_variants = {"GFM", "CommonMark"}
+ variant = parameters.get("variant", "GFM") # Use an acceptable default.
+ if content_type == "text/markdown" and variant not in markdown_variants:
+ raise self._invalid_metadata(
+ f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
+ f"not {variant!r}",
+ )
+ return value
+
+ def _process_dynamic(self, value: List[str]) -> List[str]:
+ for dynamic_field in map(str.lower, value):
+ if dynamic_field in {"name", "version", "metadata-version"}:
+ raise self._invalid_metadata(
+ f"{value!r} is not allowed as a dynamic field"
+ )
+ elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
+ raise self._invalid_metadata(f"{value!r} is not a valid dynamic field")
+ return list(map(str.lower, value))
+
+ def _process_provides_extra(
+ self,
+ value: List[str],
+ ) -> List[utils.NormalizedName]:
+ normalized_names = []
+ try:
+ for name in value:
+ normalized_names.append(utils.canonicalize_name(name, validate=True))
+ except utils.InvalidName as exc:
+ raise self._invalid_metadata(
+ f"{name!r} is invalid for {{field}}", cause=exc
+ )
+ else:
+ return normalized_names
+
+ def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
+ try:
+ return specifiers.SpecifierSet(value)
+ except specifiers.InvalidSpecifier as exc:
+ raise self._invalid_metadata(
+ f"{value!r} is invalid for {{field}}", cause=exc
+ )
+
+ def _process_requires_dist(
+ self,
+ value: List[str],
+ ) -> List[requirements.Requirement]:
+ reqs = []
+ try:
+ for req in value:
+ reqs.append(requirements.Requirement(req))
+ except requirements.InvalidRequirement as exc:
+ raise self._invalid_metadata(f"{req!r} is invalid for {{field}}", cause=exc)
+ else:
+ return reqs
+
+
+class Metadata:
+ """Representation of distribution metadata.
+
+ Compared to :class:`RawMetadata`, this class provides objects representing
+ metadata fields instead of only using built-in types. Any invalid metadata
+ will cause :exc:`InvalidMetadata` to be raised (with a
+ :py:attr:`~BaseException.__cause__` attribute as appropriate).
+ """
+
+ _raw: RawMetadata
+
+ @classmethod
+ def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> "Metadata":
+ """Create an instance from :class:`RawMetadata`.
+
+ If *validate* is true, all metadata will be validated. All exceptions
+ related to validation will be gathered and raised as an :class:`ExceptionGroup`.
+ """
+ ins = cls()
+ ins._raw = data.copy() # Mutations occur due to caching enriched values.
+
+ if validate:
+ exceptions: List[Exception] = []
+ try:
+ metadata_version = ins.metadata_version
+ metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
+ except InvalidMetadata as metadata_version_exc:
+ exceptions.append(metadata_version_exc)
+ metadata_version = None
+
+ # Make sure to check for the fields that are present, the required
+ # fields (so their absence can be reported).
+ fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
+ # Remove fields that have already been checked.
+ fields_to_check -= {"metadata_version"}
+
+ for key in fields_to_check:
+ try:
+ if metadata_version:
+ # Can't use getattr() as that triggers descriptor protocol which
+ # will fail due to no value for the instance argument.
+ try:
+ field_metadata_version = cls.__dict__[key].added
+ except KeyError:
+ exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
+ exceptions.append(exc)
+ continue
+ field_age = _VALID_METADATA_VERSIONS.index(
+ field_metadata_version
+ )
+ if field_age > metadata_age:
+ field = _RAW_TO_EMAIL_MAPPING[key]
+ exc = InvalidMetadata(
+ field,
+ "{field} introduced in metadata version "
+ "{field_metadata_version}, not {metadata_version}",
+ )
+ exceptions.append(exc)
+ continue
+ getattr(ins, key)
+ except InvalidMetadata as exc:
+ exceptions.append(exc)
+
+ if exceptions:
+ raise ExceptionGroup("invalid metadata", exceptions)
+
+ return ins
+
+ @classmethod
+ def from_email(
+ cls, data: Union[bytes, str], *, validate: bool = True
+ ) -> "Metadata":
+ """Parse metadata from email headers.
+
+ If *validate* is true, the metadata will be validated. All exceptions
+ related to validation will be gathered and raised as an :class:`ExceptionGroup`.
+ """
+ raw, unparsed = parse_email(data)
+
+ if validate:
+ exceptions: list[Exception] = []
+ for unparsed_key in unparsed:
+ if unparsed_key in _EMAIL_TO_RAW_MAPPING:
+ message = f"{unparsed_key!r} has invalid data"
+ else:
+ message = f"unrecognized field: {unparsed_key!r}"
+ exceptions.append(InvalidMetadata(unparsed_key, message))
+
+ if exceptions:
+ raise ExceptionGroup("unparsed", exceptions)
+
+ try:
+ return cls.from_raw(raw, validate=validate)
+ except ExceptionGroup as exc_group:
+ raise ExceptionGroup(
+ "invalid or unparsed metadata", exc_group.exceptions
+ ) from None
+
+ metadata_version: _Validator[_MetadataVersion] = _Validator()
+ """:external:ref:`core-metadata-metadata-version`
+ (required; validated to be a valid metadata version)"""
+ name: _Validator[str] = _Validator()
+ """:external:ref:`core-metadata-name`
+ (required; validated using :func:`~packaging.utils.canonicalize_name` and its
+ *validate* parameter)"""
+ version: _Validator[version_module.Version] = _Validator()
+ """:external:ref:`core-metadata-version` (required)"""
+ dynamic: _Validator[Optional[List[str]]] = _Validator(
+ added="2.2",
+ )
+ """:external:ref:`core-metadata-dynamic`
+ (validated against core metadata field names and lowercased)"""
+ platforms: _Validator[Optional[List[str]]] = _Validator()
+ """:external:ref:`core-metadata-platform`"""
+ supported_platforms: _Validator[Optional[List[str]]] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-supported-platform`"""
+ summary: _Validator[Optional[str]] = _Validator()
+ """:external:ref:`core-metadata-summary` (validated to contain no newlines)"""
+ description: _Validator[Optional[str]] = _Validator() # TODO 2.1: can be in body
+ """:external:ref:`core-metadata-description`"""
+ description_content_type: _Validator[Optional[str]] = _Validator(added="2.1")
+ """:external:ref:`core-metadata-description-content-type` (validated)"""
+ keywords: _Validator[Optional[List[str]]] = _Validator()
+ """:external:ref:`core-metadata-keywords`"""
+ home_page: _Validator[Optional[str]] = _Validator()
+ """:external:ref:`core-metadata-home-page`"""
+ download_url: _Validator[Optional[str]] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-download-url`"""
+ author: _Validator[Optional[str]] = _Validator()
+ """:external:ref:`core-metadata-author`"""
+ author_email: _Validator[Optional[str]] = _Validator()
+ """:external:ref:`core-metadata-author-email`"""
+ maintainer: _Validator[Optional[str]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-maintainer`"""
+ maintainer_email: _Validator[Optional[str]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-maintainer-email`"""
+ license: _Validator[Optional[str]] = _Validator()
+ """:external:ref:`core-metadata-license`"""
+ classifiers: _Validator[Optional[List[str]]] = _Validator(added="1.1")
+ """:external:ref:`core-metadata-classifier`"""
+ requires_dist: _Validator[Optional[List[requirements.Requirement]]] = _Validator(
+ added="1.2"
+ )
+ """:external:ref:`core-metadata-requires-dist`"""
+ requires_python: _Validator[Optional[specifiers.SpecifierSet]] = _Validator(
+ added="1.2"
+ )
+ """:external:ref:`core-metadata-requires-python`"""
+ # Because `Requires-External` allows for non-PEP 440 version specifiers, we
+ # don't do any processing on the values.
+ requires_external: _Validator[Optional[List[str]]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-requires-external`"""
+ project_urls: _Validator[Optional[Dict[str, str]]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-project-url`"""
+ # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
+ # regardless of metadata version.
+ provides_extra: _Validator[Optional[List[utils.NormalizedName]]] = _Validator(
+ added="2.1",
+ )
+ """:external:ref:`core-metadata-provides-extra`"""
+ provides_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-provides-dist`"""
+ obsoletes_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2")
+ """:external:ref:`core-metadata-obsoletes-dist`"""
+ requires: _Validator[Optional[List[str]]] = _Validator(added="1.1")
+ """``Requires`` (deprecated)"""
+ provides: _Validator[Optional[List[str]]] = _Validator(added="1.1")
+ """``Provides`` (deprecated)"""
+ obsoletes: _Validator[Optional[List[str]]] = _Validator(added="1.1")
+ """``Obsoletes`` (deprecated)"""
diff --git a/.venv/lib/python3.10/site-packages/packaging/py.typed b/.venv/lib/python3.10/site-packages/packaging/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.venv/lib/python3.10/site-packages/packaging/requirements.py b/.venv/lib/python3.10/site-packages/packaging/requirements.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdc43a7e98d87dba0c2069bfb4554f71d228cad4
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/requirements.py
@@ -0,0 +1,90 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from typing import Any, Iterator, Optional, Set
+
+from ._parser import parse_requirement as _parse_requirement
+from ._tokenizer import ParserSyntaxError
+from .markers import Marker, _normalize_extra_values
+from .specifiers import SpecifierSet
+from .utils import canonicalize_name
+
+
+class InvalidRequirement(ValueError):
+ """
+ An invalid requirement was found, users should refer to PEP 508.
+ """
+
+
+class Requirement:
+ """Parse a requirement.
+
+ Parse a given requirement string into its parts, such as name, specifier,
+ URL, and extras. Raises InvalidRequirement on a badly-formed requirement
+ string.
+ """
+
+ # TODO: Can we test whether something is contained within a requirement?
+ # If so how do we do that? Do we need to test against the _name_ of
+ # the thing as well as the version? What about the markers?
+ # TODO: Can we normalize the name and extra name?
+
+ def __init__(self, requirement_string: str) -> None:
+ try:
+ parsed = _parse_requirement(requirement_string)
+ except ParserSyntaxError as e:
+ raise InvalidRequirement(str(e)) from e
+
+ self.name: str = parsed.name
+ self.url: Optional[str] = parsed.url or None
+ self.extras: Set[str] = set(parsed.extras or [])
+ self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
+ self.marker: Optional[Marker] = None
+ if parsed.marker is not None:
+ self.marker = Marker.__new__(Marker)
+ self.marker._markers = _normalize_extra_values(parsed.marker)
+
+ def _iter_parts(self, name: str) -> Iterator[str]:
+ yield name
+
+ if self.extras:
+ formatted_extras = ",".join(sorted(self.extras))
+ yield f"[{formatted_extras}]"
+
+ if self.specifier:
+ yield str(self.specifier)
+
+ if self.url:
+ yield f"@ {self.url}"
+ if self.marker:
+ yield " "
+
+ if self.marker:
+ yield f"; {self.marker}"
+
+ def __str__(self) -> str:
+ return "".join(self._iter_parts(self.name))
+
+ def __repr__(self) -> str:
+ return f""
+
+ def __hash__(self) -> int:
+ return hash(
+ (
+ self.__class__.__name__,
+ *self._iter_parts(canonicalize_name(self.name)),
+ )
+ )
+
+ def __eq__(self, other: Any) -> bool:
+ if not isinstance(other, Requirement):
+ return NotImplemented
+
+ return (
+ canonicalize_name(self.name) == canonicalize_name(other.name)
+ and self.extras == other.extras
+ and self.specifier == other.specifier
+ and self.url == other.url
+ and self.marker == other.marker
+ )
diff --git a/.venv/lib/python3.10/site-packages/packaging/specifiers.py b/.venv/lib/python3.10/site-packages/packaging/specifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d015bab5958fd9767cf5c9e449f2fa33292c962
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/specifiers.py
@@ -0,0 +1,1017 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+"""
+.. testsetup::
+
+ from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
+ from packaging.version import Version
+"""
+
+import abc
+import itertools
+import re
+from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union
+
+from .utils import canonicalize_version
+from .version import Version
+
+UnparsedVersion = Union[Version, str]
+UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
+CallableOperator = Callable[[Version, str], bool]
+
+
+def _coerce_version(version: UnparsedVersion) -> Version:
+ if not isinstance(version, Version):
+ version = Version(version)
+ return version
+
+
+class InvalidSpecifier(ValueError):
+ """
+ Raised when attempting to create a :class:`Specifier` with a specifier
+ string that is invalid.
+
+ >>> Specifier("lolwat")
+ Traceback (most recent call last):
+ ...
+ packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
+ """
+
+
+class BaseSpecifier(metaclass=abc.ABCMeta):
+ @abc.abstractmethod
+ def __str__(self) -> str:
+ """
+ Returns the str representation of this Specifier-like object. This
+ should be representative of the Specifier itself.
+ """
+
+ @abc.abstractmethod
+ def __hash__(self) -> int:
+ """
+ Returns a hash value for this Specifier-like object.
+ """
+
+ @abc.abstractmethod
+ def __eq__(self, other: object) -> bool:
+ """
+ Returns a boolean representing whether or not the two Specifier-like
+ objects are equal.
+
+ :param other: The other object to check against.
+ """
+
+ @property
+ @abc.abstractmethod
+ def prereleases(self) -> Optional[bool]:
+ """Whether or not pre-releases as a whole are allowed.
+
+ This can be set to either ``True`` or ``False`` to explicitly enable or disable
+ prereleases or it can be set to ``None`` (the default) to use default semantics.
+ """
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ """Setter for :attr:`prereleases`.
+
+ :param value: The value to set.
+ """
+
+ @abc.abstractmethod
+ def contains(self, item: str, prereleases: Optional[bool] = None) -> bool:
+ """
+ Determines if the given item is contained within this specifier.
+ """
+
+ @abc.abstractmethod
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """
+ Takes an iterable of items and filters them so that only items which
+ are contained within this specifier are allowed in it.
+ """
+
+
+class Specifier(BaseSpecifier):
+ """This class abstracts handling of version specifiers.
+
+ .. tip::
+
+ It is generally not required to instantiate this manually. You should instead
+ prefer to work with :class:`SpecifierSet` instead, which can parse
+ comma-separated version specifiers (which is what package metadata contains).
+ """
+
+ _operator_regex_str = r"""
+ (?P(~=|==|!=|<=|>=|<|>|===))
+ """
+ _version_regex_str = r"""
+ (?P
+ (?:
+ # The identity operators allow for an escape hatch that will
+ # do an exact string match of the version you wish to install.
+ # This will not be parsed by PEP 440 and we cannot determine
+ # any semantic meaning from it. This operator is discouraged
+ # but included entirely as an escape hatch.
+ (?<====) # Only match for the identity operator
+ \s*
+ [^\s;)]* # The arbitrary version can be just about anything,
+ # we match everything except for whitespace, a
+ # semi-colon for marker support, and a closing paren
+ # since versions can be enclosed in them.
+ )
+ |
+ (?:
+ # The (non)equality operators allow for wild card and local
+ # versions to be specified so we have to define these two
+ # operators separately to enable that.
+ (?<===|!=) # Only match for equals and not equals
+
+ \s*
+ v?
+ (?:[0-9]+!)? # epoch
+ [0-9]+(?:\.[0-9]+)* # release
+
+ # You cannot use a wild card and a pre-release, post-release, a dev or
+ # local version together so group them with a | and make them optional.
+ (?:
+ \.\* # Wild card syntax of .*
+ |
+ (?: # pre release
+ [-_\.]?
+ (alpha|beta|preview|pre|a|b|c|rc)
+ [-_\.]?
+ [0-9]*
+ )?
+ (?: # post release
+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
+ )?
+ (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
+ (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
+ )?
+ )
+ |
+ (?:
+ # The compatible operator requires at least two digits in the
+ # release segment.
+ (?<=~=) # Only match for the compatible operator
+
+ \s*
+ v?
+ (?:[0-9]+!)? # epoch
+ [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
+ (?: # pre release
+ [-_\.]?
+ (alpha|beta|preview|pre|a|b|c|rc)
+ [-_\.]?
+ [0-9]*
+ )?
+ (?: # post release
+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
+ )?
+ (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
+ )
+ |
+ (?:
+ # All other operators only allow a sub set of what the
+ # (non)equality operators do. Specifically they do not allow
+ # local versions to be specified nor do they allow the prefix
+ # matching wild cards.
+ (?=": "greater_than_equal",
+ "<": "less_than",
+ ">": "greater_than",
+ "===": "arbitrary",
+ }
+
+ def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
+ """Initialize a Specifier instance.
+
+ :param spec:
+ The string representation of a specifier which will be parsed and
+ normalized before use.
+ :param prereleases:
+ This tells the specifier if it should accept prerelease versions if
+ applicable or not. The default of ``None`` will autodetect it from the
+ given specifiers.
+ :raises InvalidSpecifier:
+ If the given specifier is invalid (i.e. bad syntax).
+ """
+ match = self._regex.search(spec)
+ if not match:
+ raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
+
+ self._spec: Tuple[str, str] = (
+ match.group("operator").strip(),
+ match.group("version").strip(),
+ )
+
+ # Store whether or not this Specifier should accept prereleases
+ self._prereleases = prereleases
+
+ # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
+ @property # type: ignore[override]
+ def prereleases(self) -> bool:
+ # If there is an explicit prereleases set for this, then we'll just
+ # blindly use that.
+ if self._prereleases is not None:
+ return self._prereleases
+
+ # Look at all of our specifiers and determine if they are inclusive
+ # operators, and if they are if they are including an explicit
+ # prerelease.
+ operator, version = self._spec
+ if operator in ["==", ">=", "<=", "~=", "==="]:
+ # The == specifier can include a trailing .*, if it does we
+ # want to remove before parsing.
+ if operator == "==" and version.endswith(".*"):
+ version = version[:-2]
+
+ # Parse the version, and if it is a pre-release than this
+ # specifier allows pre-releases.
+ if Version(version).is_prerelease:
+ return True
+
+ return False
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ self._prereleases = value
+
+ @property
+ def operator(self) -> str:
+ """The operator of this specifier.
+
+ >>> Specifier("==1.2.3").operator
+ '=='
+ """
+ return self._spec[0]
+
+ @property
+ def version(self) -> str:
+ """The version of this specifier.
+
+ >>> Specifier("==1.2.3").version
+ '1.2.3'
+ """
+ return self._spec[1]
+
+ def __repr__(self) -> str:
+ """A representation of the Specifier that shows all internal state.
+
+ >>> Specifier('>=1.0.0')
+ =1.0.0')>
+ >>> Specifier('>=1.0.0', prereleases=False)
+ =1.0.0', prereleases=False)>
+ >>> Specifier('>=1.0.0', prereleases=True)
+ =1.0.0', prereleases=True)>
+ """
+ pre = (
+ f", prereleases={self.prereleases!r}"
+ if self._prereleases is not None
+ else ""
+ )
+
+ return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
+
+ def __str__(self) -> str:
+ """A string representation of the Specifier that can be round-tripped.
+
+ >>> str(Specifier('>=1.0.0'))
+ '>=1.0.0'
+ >>> str(Specifier('>=1.0.0', prereleases=False))
+ '>=1.0.0'
+ """
+ return "{}{}".format(*self._spec)
+
+ @property
+ def _canonical_spec(self) -> Tuple[str, str]:
+ canonical_version = canonicalize_version(
+ self._spec[1],
+ strip_trailing_zero=(self._spec[0] != "~="),
+ )
+ return self._spec[0], canonical_version
+
+ def __hash__(self) -> int:
+ return hash(self._canonical_spec)
+
+ def __eq__(self, other: object) -> bool:
+ """Whether or not the two Specifier-like objects are equal.
+
+ :param other: The other object to check against.
+
+ The value of :attr:`prereleases` is ignored.
+
+ >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
+ True
+ >>> (Specifier("==1.2.3", prereleases=False) ==
+ ... Specifier("==1.2.3", prereleases=True))
+ True
+ >>> Specifier("==1.2.3") == "==1.2.3"
+ True
+ >>> Specifier("==1.2.3") == Specifier("==1.2.4")
+ False
+ >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
+ False
+ """
+ if isinstance(other, str):
+ try:
+ other = self.__class__(str(other))
+ except InvalidSpecifier:
+ return NotImplemented
+ elif not isinstance(other, self.__class__):
+ return NotImplemented
+
+ return self._canonical_spec == other._canonical_spec
+
+ def _get_operator(self, op: str) -> CallableOperator:
+ operator_callable: CallableOperator = getattr(
+ self, f"_compare_{self._operators[op]}"
+ )
+ return operator_callable
+
+ def _compare_compatible(self, prospective: Version, spec: str) -> bool:
+
+ # Compatible releases have an equivalent combination of >= and ==. That
+ # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
+ # implement this in terms of the other specifiers instead of
+ # implementing it ourselves. The only thing we need to do is construct
+ # the other specifiers.
+
+ # We want everything but the last item in the version, but we want to
+ # ignore suffix segments.
+ prefix = _version_join(
+ list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
+ )
+
+ # Add the prefix notation to the end of our string
+ prefix += ".*"
+
+ return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
+ prospective, prefix
+ )
+
+ def _compare_equal(self, prospective: Version, spec: str) -> bool:
+
+ # We need special logic to handle prefix matching
+ if spec.endswith(".*"):
+ # In the case of prefix matching we want to ignore local segment.
+ normalized_prospective = canonicalize_version(
+ prospective.public, strip_trailing_zero=False
+ )
+ # Get the normalized version string ignoring the trailing .*
+ normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
+ # Split the spec out by bangs and dots, and pretend that there is
+ # an implicit dot in between a release segment and a pre-release segment.
+ split_spec = _version_split(normalized_spec)
+
+ # Split the prospective version out by bangs and dots, and pretend
+ # that there is an implicit dot in between a release segment and
+ # a pre-release segment.
+ split_prospective = _version_split(normalized_prospective)
+
+ # 0-pad the prospective version before shortening it to get the correct
+ # shortened version.
+ padded_prospective, _ = _pad_version(split_prospective, split_spec)
+
+ # Shorten the prospective version to be the same length as the spec
+ # so that we can determine if the specifier is a prefix of the
+ # prospective version or not.
+ shortened_prospective = padded_prospective[: len(split_spec)]
+
+ return shortened_prospective == split_spec
+ else:
+ # Convert our spec string into a Version
+ spec_version = Version(spec)
+
+ # If the specifier does not have a local segment, then we want to
+ # act as if the prospective version also does not have a local
+ # segment.
+ if not spec_version.local:
+ prospective = Version(prospective.public)
+
+ return prospective == spec_version
+
+ def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
+ return not self._compare_equal(prospective, spec)
+
+ def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
+
+ # NB: Local version identifiers are NOT permitted in the version
+ # specifier, so local version labels can be universally removed from
+ # the prospective version.
+ return Version(prospective.public) <= Version(spec)
+
+ def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
+
+ # NB: Local version identifiers are NOT permitted in the version
+ # specifier, so local version labels can be universally removed from
+ # the prospective version.
+ return Version(prospective.public) >= Version(spec)
+
+ def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
+
+ # Convert our spec to a Version instance, since we'll want to work with
+ # it as a version.
+ spec = Version(spec_str)
+
+ # Check to see if the prospective version is less than the spec
+ # version. If it's not we can short circuit and just return False now
+ # instead of doing extra unneeded work.
+ if not prospective < spec:
+ return False
+
+ # This special case is here so that, unless the specifier itself
+ # includes is a pre-release version, that we do not accept pre-release
+ # versions for the version mentioned in the specifier (e.g. <3.1 should
+ # not match 3.1.dev0, but should match 3.0.dev0).
+ if not spec.is_prerelease and prospective.is_prerelease:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # If we've gotten to here, it means that prospective version is both
+ # less than the spec version *and* it's not a pre-release of the same
+ # version in the spec.
+ return True
+
+ def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
+
+ # Convert our spec to a Version instance, since we'll want to work with
+ # it as a version.
+ spec = Version(spec_str)
+
+ # Check to see if the prospective version is greater than the spec
+ # version. If it's not we can short circuit and just return False now
+ # instead of doing extra unneeded work.
+ if not prospective > spec:
+ return False
+
+ # This special case is here so that, unless the specifier itself
+ # includes is a post-release version, that we do not accept
+ # post-release versions for the version mentioned in the specifier
+ # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
+ if not spec.is_postrelease and prospective.is_postrelease:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # Ensure that we do not allow a local version of the version mentioned
+ # in the specifier, which is technically greater than, to match.
+ if prospective.local is not None:
+ if Version(prospective.base_version) == Version(spec.base_version):
+ return False
+
+ # If we've gotten to here, it means that prospective version is both
+ # greater than the spec version *and* it's not a pre-release of the
+ # same version in the spec.
+ return True
+
+ def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
+ return str(prospective).lower() == str(spec).lower()
+
+ def __contains__(self, item: Union[str, Version]) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item: The item to check for.
+
+ This is used for the ``in`` operator and behaves the same as
+ :meth:`contains` with no ``prereleases`` argument passed.
+
+ >>> "1.2.3" in Specifier(">=1.2.3")
+ True
+ >>> Version("1.2.3") in Specifier(">=1.2.3")
+ True
+ >>> "1.0.0" in Specifier(">=1.2.3")
+ False
+ >>> "1.3.0a1" in Specifier(">=1.2.3")
+ False
+ >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
+ True
+ """
+ return self.contains(item)
+
+ def contains(
+ self, item: UnparsedVersion, prereleases: Optional[bool] = None
+ ) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item:
+ The item to check for, which can be a version string or a
+ :class:`Version` instance.
+ :param prereleases:
+ Whether or not to match prereleases with this Specifier. If set to
+ ``None`` (the default), it uses :attr:`prereleases` to determine
+ whether or not prereleases are allowed.
+
+ >>> Specifier(">=1.2.3").contains("1.2.3")
+ True
+ >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
+ True
+ >>> Specifier(">=1.2.3").contains("1.0.0")
+ False
+ >>> Specifier(">=1.2.3").contains("1.3.0a1")
+ False
+ >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
+ True
+ >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
+ True
+ """
+
+ # Determine if prereleases are to be allowed or not.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # Normalize item to a Version, this allows us to have a shortcut for
+ # "2.0" in Specifier(">=2")
+ normalized_item = _coerce_version(item)
+
+ # Determine if we should be supporting prereleases in this specifier
+ # or not, if we do not support prereleases than we can short circuit
+ # logic if this version is a prereleases.
+ if normalized_item.is_prerelease and not prereleases:
+ return False
+
+ # Actually do the comparison to determine if this item is contained
+ # within this Specifier or not.
+ operator_callable: CallableOperator = self._get_operator(self.operator)
+ return operator_callable(normalized_item, self.version)
+
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """Filter items in the given iterable, that match the specifier.
+
+ :param iterable:
+ An iterable that can contain version strings and :class:`Version` instances.
+ The items in the iterable will be filtered according to the specifier.
+ :param prereleases:
+ Whether or not to allow prereleases in the returned iterator. If set to
+ ``None`` (the default), it will be intelligently decide whether to allow
+ prereleases or not (based on the :attr:`prereleases` attribute, and
+ whether the only versions matching are prereleases).
+
+ This method is smarter than just ``filter(Specifier().contains, [...])``
+ because it implements the rule from :pep:`440` that a prerelease item
+ SHOULD be accepted if no other versions match the given specifier.
+
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
+ ['1.2.3', '1.3', ]
+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
+ ['1.5a1']
+ >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+ """
+
+ yielded = False
+ found_prereleases = []
+
+ kw = {"prereleases": prereleases if prereleases is not None else True}
+
+ # Attempt to iterate over all the values in the iterable and if any of
+ # them match, yield them.
+ for version in iterable:
+ parsed_version = _coerce_version(version)
+
+ if self.contains(parsed_version, **kw):
+ # If our version is a prerelease, and we were not set to allow
+ # prereleases, then we'll store it for later in case nothing
+ # else matches this specifier.
+ if parsed_version.is_prerelease and not (
+ prereleases or self.prereleases
+ ):
+ found_prereleases.append(version)
+ # Either this is not a prerelease, or we should have been
+ # accepting prereleases from the beginning.
+ else:
+ yielded = True
+ yield version
+
+ # Now that we've iterated over everything, determine if we've yielded
+ # any values, and if we have not and we have any prereleases stored up
+ # then we will go ahead and yield the prereleases.
+ if not yielded and found_prereleases:
+ for version in found_prereleases:
+ yield version
+
+
+_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
+
+
+def _version_split(version: str) -> List[str]:
+ """Split version into components.
+
+ The split components are intended for version comparison. The logic does
+ not attempt to retain the original version string, so joining the
+ components back with :func:`_version_join` may not produce the original
+ version string.
+ """
+ result: List[str] = []
+
+ epoch, _, rest = version.rpartition("!")
+ result.append(epoch or "0")
+
+ for item in rest.split("."):
+ match = _prefix_regex.search(item)
+ if match:
+ result.extend(match.groups())
+ else:
+ result.append(item)
+ return result
+
+
+def _version_join(components: List[str]) -> str:
+ """Join split version components into a version string.
+
+ This function assumes the input came from :func:`_version_split`, where the
+ first component must be the epoch (either empty or numeric), and all other
+ components numeric.
+ """
+ epoch, *rest = components
+ return f"{epoch}!{'.'.join(rest)}"
+
+
+def _is_not_suffix(segment: str) -> bool:
+ return not any(
+ segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
+ )
+
+
+def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
+ left_split, right_split = [], []
+
+ # Get the release segment of our versions
+ left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
+ right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
+
+ # Get the rest of our versions
+ left_split.append(left[len(left_split[0]) :])
+ right_split.append(right[len(right_split[0]) :])
+
+ # Insert our padding
+ left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
+ right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
+
+ return (
+ list(itertools.chain.from_iterable(left_split)),
+ list(itertools.chain.from_iterable(right_split)),
+ )
+
+
+class SpecifierSet(BaseSpecifier):
+ """This class abstracts handling of a set of version specifiers.
+
+ It can be passed a single specifier (``>=3.0``), a comma-separated list of
+ specifiers (``>=3.0,!=3.1``), or no specifier at all.
+ """
+
+ def __init__(
+ self, specifiers: str = "", prereleases: Optional[bool] = None
+ ) -> None:
+ """Initialize a SpecifierSet instance.
+
+ :param specifiers:
+ The string representation of a specifier or a comma-separated list of
+ specifiers which will be parsed and normalized before use.
+ :param prereleases:
+ This tells the SpecifierSet if it should accept prerelease versions if
+ applicable or not. The default of ``None`` will autodetect it from the
+ given specifiers.
+
+ :raises InvalidSpecifier:
+ If the given ``specifiers`` are not parseable than this exception will be
+ raised.
+ """
+
+ # Split on `,` to break each individual specifier into it's own item, and
+ # strip each item to remove leading/trailing whitespace.
+ split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
+
+ # Make each individual specifier a Specifier and save in a frozen set for later.
+ self._specs = frozenset(map(Specifier, split_specifiers))
+
+ # Store our prereleases value so we can use it later to determine if
+ # we accept prereleases or not.
+ self._prereleases = prereleases
+
+ @property
+ def prereleases(self) -> Optional[bool]:
+ # If we have been given an explicit prerelease modifier, then we'll
+ # pass that through here.
+ if self._prereleases is not None:
+ return self._prereleases
+
+ # If we don't have any specifiers, and we don't have a forced value,
+ # then we'll just return None since we don't know if this should have
+ # pre-releases or not.
+ if not self._specs:
+ return None
+
+ # Otherwise we'll see if any of the given specifiers accept
+ # prereleases, if any of them do we'll return True, otherwise False.
+ return any(s.prereleases for s in self._specs)
+
+ @prereleases.setter
+ def prereleases(self, value: bool) -> None:
+ self._prereleases = value
+
+ def __repr__(self) -> str:
+ """A representation of the specifier set that shows all internal state.
+
+ Note that the ordering of the individual specifiers within the set may not
+ match the input string.
+
+ >>> SpecifierSet('>=1.0.0,!=2.0.0')
+ =1.0.0')>
+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
+ =1.0.0', prereleases=False)>
+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
+ =1.0.0', prereleases=True)>
+ """
+ pre = (
+ f", prereleases={self.prereleases!r}"
+ if self._prereleases is not None
+ else ""
+ )
+
+ return f""
+
+ def __str__(self) -> str:
+ """A string representation of the specifier set that can be round-tripped.
+
+ Note that the ordering of the individual specifiers within the set may not
+ match the input string.
+
+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
+ '!=1.0.1,>=1.0.0'
+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
+ '!=1.0.1,>=1.0.0'
+ """
+ return ",".join(sorted(str(s) for s in self._specs))
+
+ def __hash__(self) -> int:
+ return hash(self._specs)
+
+ def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
+ """Return a SpecifierSet which is a combination of the two sets.
+
+ :param other: The other object to combine with.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
+ =1.0.0')>
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
+ =1.0.0')>
+ """
+ if isinstance(other, str):
+ other = SpecifierSet(other)
+ elif not isinstance(other, SpecifierSet):
+ return NotImplemented
+
+ specifier = SpecifierSet()
+ specifier._specs = frozenset(self._specs | other._specs)
+
+ if self._prereleases is None and other._prereleases is not None:
+ specifier._prereleases = other._prereleases
+ elif self._prereleases is not None and other._prereleases is None:
+ specifier._prereleases = self._prereleases
+ elif self._prereleases == other._prereleases:
+ specifier._prereleases = self._prereleases
+ else:
+ raise ValueError(
+ "Cannot combine SpecifierSets with True and False prerelease "
+ "overrides."
+ )
+
+ return specifier
+
+ def __eq__(self, other: object) -> bool:
+ """Whether or not the two SpecifierSet-like objects are equal.
+
+ :param other: The other object to check against.
+
+ The value of :attr:`prereleases` is ignored.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
+ ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
+ False
+ """
+ if isinstance(other, (str, Specifier)):
+ other = SpecifierSet(str(other))
+ elif not isinstance(other, SpecifierSet):
+ return NotImplemented
+
+ return self._specs == other._specs
+
+ def __len__(self) -> int:
+ """Returns the number of specifiers in this specifier set."""
+ return len(self._specs)
+
+ def __iter__(self) -> Iterator[Specifier]:
+ """
+ Returns an iterator over all the underlying :class:`Specifier` instances
+ in this specifier set.
+
+ >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
+ [, =1.0.0')>]
+ """
+ return iter(self._specs)
+
+ def __contains__(self, item: UnparsedVersion) -> bool:
+ """Return whether or not the item is contained in this specifier.
+
+ :param item: The item to check for.
+
+ This is used for the ``in`` operator and behaves the same as
+ :meth:`contains` with no ``prereleases`` argument passed.
+
+ >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
+ True
+ >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
+ False
+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
+ False
+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
+ True
+ """
+ return self.contains(item)
+
+ def contains(
+ self,
+ item: UnparsedVersion,
+ prereleases: Optional[bool] = None,
+ installed: Optional[bool] = None,
+ ) -> bool:
+ """Return whether or not the item is contained in this SpecifierSet.
+
+ :param item:
+ The item to check for, which can be a version string or a
+ :class:`Version` instance.
+ :param prereleases:
+ Whether or not to match prereleases with this SpecifierSet. If set to
+ ``None`` (the default), it uses :attr:`prereleases` to determine
+ whether or not prereleases are allowed.
+
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
+ False
+ >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")
+ True
+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
+ True
+ """
+ # Ensure that our item is a Version instance.
+ if not isinstance(item, Version):
+ item = Version(item)
+
+ # Determine if we're forcing a prerelease or not, if we're not forcing
+ # one for this particular filter call, then we'll use whatever the
+ # SpecifierSet thinks for whether or not we should support prereleases.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # We can determine if we're going to allow pre-releases by looking to
+ # see if any of the underlying items supports them. If none of them do
+ # and this item is a pre-release then we do not allow it and we can
+ # short circuit that here.
+ # Note: This means that 1.0.dev1 would not be contained in something
+ # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
+ if not prereleases and item.is_prerelease:
+ return False
+
+ if installed and item.is_prerelease:
+ item = Version(item.base_version)
+
+ # We simply dispatch to the underlying specs here to make sure that the
+ # given version is contained within all of them.
+ # Note: This use of all() here means that an empty set of specifiers
+ # will always return True, this is an explicit design decision.
+ return all(s.contains(item, prereleases=prereleases) for s in self._specs)
+
+ def filter(
+ self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None
+ ) -> Iterator[UnparsedVersionVar]:
+ """Filter items in the given iterable, that match the specifiers in this set.
+
+ :param iterable:
+ An iterable that can contain version strings and :class:`Version` instances.
+ The items in the iterable will be filtered according to the specifier.
+ :param prereleases:
+ Whether or not to allow prereleases in the returned iterator. If set to
+ ``None`` (the default), it will be intelligently decide whether to allow
+ prereleases or not (based on the :attr:`prereleases` attribute, and
+ whether the only versions matching are prereleases).
+
+ This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``
+ because it implements the rule from :pep:`440` that a prerelease item
+ SHOULD be accepted if no other versions match the given specifier.
+
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
+ ['1.3', ]
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
+ []
+ >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+
+ An "empty" SpecifierSet will filter items based on the presence of prerelease
+ versions in the set.
+
+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
+ ['1.3']
+ >>> list(SpecifierSet("").filter(["1.5a1"]))
+ ['1.5a1']
+ >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
+ ['1.3', '1.5a1']
+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
+ ['1.3', '1.5a1']
+ """
+ # Determine if we're forcing a prerelease or not, if we're not forcing
+ # one for this particular filter call, then we'll use whatever the
+ # SpecifierSet thinks for whether or not we should support prereleases.
+ if prereleases is None:
+ prereleases = self.prereleases
+
+ # If we have any specifiers, then we want to wrap our iterable in the
+ # filter method for each one, this will act as a logical AND amongst
+ # each specifier.
+ if self._specs:
+ for spec in self._specs:
+ iterable = spec.filter(iterable, prereleases=bool(prereleases))
+ return iter(iterable)
+ # If we do not have any specifiers, then we need to have a rough filter
+ # which will filter out any pre-releases, unless there are no final
+ # releases.
+ else:
+ filtered: List[UnparsedVersionVar] = []
+ found_prereleases: List[UnparsedVersionVar] = []
+
+ for item in iterable:
+ parsed_version = _coerce_version(item)
+
+ # Store any item which is a pre-release for later unless we've
+ # already found a final version or we are accepting prereleases
+ if parsed_version.is_prerelease and not prereleases:
+ if not filtered:
+ found_prereleases.append(item)
+ else:
+ filtered.append(item)
+
+ # If we've found no items except for pre-releases, then we'll go
+ # ahead and use the pre-releases
+ if not filtered and found_prereleases and prereleases is None:
+ return iter(found_prereleases)
+
+ return iter(filtered)
diff --git a/.venv/lib/python3.10/site-packages/packaging/tags.py b/.venv/lib/python3.10/site-packages/packaging/tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..89f1926137dd2d2a6bd63616bf5b9f722fc8d584
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/tags.py
@@ -0,0 +1,571 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+import logging
+import platform
+import re
+import struct
+import subprocess
+import sys
+import sysconfig
+from importlib.machinery import EXTENSION_SUFFIXES
+from typing import (
+ Dict,
+ FrozenSet,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+ cast,
+)
+
+from . import _manylinux, _musllinux
+
+logger = logging.getLogger(__name__)
+
+PythonVersion = Sequence[int]
+MacVersion = Tuple[int, int]
+
+INTERPRETER_SHORT_NAMES: Dict[str, str] = {
+ "python": "py", # Generic.
+ "cpython": "cp",
+ "pypy": "pp",
+ "ironpython": "ip",
+ "jython": "jy",
+}
+
+
+_32_BIT_INTERPRETER = struct.calcsize("P") == 4
+
+
+class Tag:
+ """
+ A representation of the tag triple for a wheel.
+
+ Instances are considered immutable and thus are hashable. Equality checking
+ is also supported.
+ """
+
+ __slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
+
+ def __init__(self, interpreter: str, abi: str, platform: str) -> None:
+ self._interpreter = interpreter.lower()
+ self._abi = abi.lower()
+ self._platform = platform.lower()
+ # The __hash__ of every single element in a Set[Tag] will be evaluated each time
+ # that a set calls its `.disjoint()` method, which may be called hundreds of
+ # times when scanning a page of links for packages with tags matching that
+ # Set[Tag]. Pre-computing the value here produces significant speedups for
+ # downstream consumers.
+ self._hash = hash((self._interpreter, self._abi, self._platform))
+
+ @property
+ def interpreter(self) -> str:
+ return self._interpreter
+
+ @property
+ def abi(self) -> str:
+ return self._abi
+
+ @property
+ def platform(self) -> str:
+ return self._platform
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Tag):
+ return NotImplemented
+
+ return (
+ (self._hash == other._hash) # Short-circuit ASAP for perf reasons.
+ and (self._platform == other._platform)
+ and (self._abi == other._abi)
+ and (self._interpreter == other._interpreter)
+ )
+
+ def __hash__(self) -> int:
+ return self._hash
+
+ def __str__(self) -> str:
+ return f"{self._interpreter}-{self._abi}-{self._platform}"
+
+ def __repr__(self) -> str:
+ return f"<{self} @ {id(self)}>"
+
+
+def parse_tag(tag: str) -> FrozenSet[Tag]:
+ """
+ Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
+
+ Returning a set is required due to the possibility that the tag is a
+ compressed tag set.
+ """
+ tags = set()
+ interpreters, abis, platforms = tag.split("-")
+ for interpreter in interpreters.split("."):
+ for abi in abis.split("."):
+ for platform_ in platforms.split("."):
+ tags.add(Tag(interpreter, abi, platform_))
+ return frozenset(tags)
+
+
+def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
+ value: Union[int, str, None] = sysconfig.get_config_var(name)
+ if value is None and warn:
+ logger.debug(
+ "Config variable '%s' is unset, Python ABI tag may be incorrect", name
+ )
+ return value
+
+
+def _normalize_string(string: str) -> str:
+ return string.replace(".", "_").replace("-", "_").replace(" ", "_")
+
+
+def _is_threaded_cpython(abis: List[str]) -> bool:
+ """
+ Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
+
+ The threaded builds are indicated by a "t" in the abiflags.
+ """
+ if len(abis) == 0:
+ return False
+ # expect e.g., cp313
+ m = re.match(r"cp\d+(.*)", abis[0])
+ if not m:
+ return False
+ abiflags = m.group(1)
+ return "t" in abiflags
+
+
+def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
+ """
+ Determine if the Python version supports abi3.
+
+ PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
+ builds do not support abi3.
+ """
+ return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
+
+
+def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
+ py_version = tuple(py_version) # To allow for version comparison.
+ abis = []
+ version = _version_nodot(py_version[:2])
+ threading = debug = pymalloc = ucs4 = ""
+ with_debug = _get_config_var("Py_DEBUG", warn)
+ has_refcount = hasattr(sys, "gettotalrefcount")
+ # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
+ # extension modules is the best option.
+ # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
+ has_ext = "_d.pyd" in EXTENSION_SUFFIXES
+ if with_debug or (with_debug is None and (has_refcount or has_ext)):
+ debug = "d"
+ if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
+ threading = "t"
+ if py_version < (3, 8):
+ with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
+ if with_pymalloc or with_pymalloc is None:
+ pymalloc = "m"
+ if py_version < (3, 3):
+ unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
+ if unicode_size == 4 or (
+ unicode_size is None and sys.maxunicode == 0x10FFFF
+ ):
+ ucs4 = "u"
+ elif debug:
+ # Debug builds can also load "normal" extension modules.
+ # We can also assume no UCS-4 or pymalloc requirement.
+ abis.append(f"cp{version}{threading}")
+ abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
+ return abis
+
+
+def cpython_tags(
+ python_version: Optional[PythonVersion] = None,
+ abis: Optional[Iterable[str]] = None,
+ platforms: Optional[Iterable[str]] = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
+ """
+ Yields the tags for a CPython interpreter.
+
+ The tags consist of:
+ - cp--
+ - cp-abi3-
+ - cp-none-
+ - cp-abi3- # Older Python versions down to 3.2.
+
+ If python_version only specifies a major version then user-provided ABIs and
+ the 'none' ABItag will be used.
+
+ If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
+ their normal position and not at the beginning.
+ """
+ if not python_version:
+ python_version = sys.version_info[:2]
+
+ interpreter = f"cp{_version_nodot(python_version[:2])}"
+
+ if abis is None:
+ if len(python_version) > 1:
+ abis = _cpython_abis(python_version, warn)
+ else:
+ abis = []
+ abis = list(abis)
+ # 'abi3' and 'none' are explicitly handled later.
+ for explicit_abi in ("abi3", "none"):
+ try:
+ abis.remove(explicit_abi)
+ except ValueError:
+ pass
+
+ platforms = list(platforms or platform_tags())
+ for abi in abis:
+ for platform_ in platforms:
+ yield Tag(interpreter, abi, platform_)
+
+ threading = _is_threaded_cpython(abis)
+ use_abi3 = _abi3_applies(python_version, threading)
+ if use_abi3:
+ yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
+ yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
+
+ if use_abi3:
+ for minor_version in range(python_version[1] - 1, 1, -1):
+ for platform_ in platforms:
+ interpreter = "cp{version}".format(
+ version=_version_nodot((python_version[0], minor_version))
+ )
+ yield Tag(interpreter, "abi3", platform_)
+
+
+def _generic_abi() -> List[str]:
+ """
+ Return the ABI tag based on EXT_SUFFIX.
+ """
+ # The following are examples of `EXT_SUFFIX`.
+ # We want to keep the parts which are related to the ABI and remove the
+ # parts which are related to the platform:
+ # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
+ # - mac: '.cpython-310-darwin.so' => cp310
+ # - win: '.cp310-win_amd64.pyd' => cp310
+ # - win: '.pyd' => cp37 (uses _cpython_abis())
+ # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
+ # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
+ # => graalpy_38_native
+
+ ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
+ if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
+ raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
+ parts = ext_suffix.split(".")
+ if len(parts) < 3:
+ # CPython3.7 and earlier uses ".pyd" on Windows.
+ return _cpython_abis(sys.version_info[:2])
+ soabi = parts[1]
+ if soabi.startswith("cpython"):
+ # non-windows
+ abi = "cp" + soabi.split("-")[1]
+ elif soabi.startswith("cp"):
+ # windows
+ abi = soabi.split("-")[0]
+ elif soabi.startswith("pypy"):
+ abi = "-".join(soabi.split("-")[:2])
+ elif soabi.startswith("graalpy"):
+ abi = "-".join(soabi.split("-")[:3])
+ elif soabi:
+ # pyston, ironpython, others?
+ abi = soabi
+ else:
+ return []
+ return [_normalize_string(abi)]
+
+
+def generic_tags(
+ interpreter: Optional[str] = None,
+ abis: Optional[Iterable[str]] = None,
+ platforms: Optional[Iterable[str]] = None,
+ *,
+ warn: bool = False,
+) -> Iterator[Tag]:
+ """
+ Yields the tags for a generic interpreter.
+
+ The tags consist of:
+ - --
+
+ The "none" ABI will be added if it was not explicitly provided.
+ """
+ if not interpreter:
+ interp_name = interpreter_name()
+ interp_version = interpreter_version(warn=warn)
+ interpreter = "".join([interp_name, interp_version])
+ if abis is None:
+ abis = _generic_abi()
+ else:
+ abis = list(abis)
+ platforms = list(platforms or platform_tags())
+ if "none" not in abis:
+ abis.append("none")
+ for abi in abis:
+ for platform_ in platforms:
+ yield Tag(interpreter, abi, platform_)
+
+
+def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
+ """
+ Yields Python versions in descending order.
+
+ After the latest version, the major-only version will be yielded, and then
+ all previous versions of that major version.
+ """
+ if len(py_version) > 1:
+ yield f"py{_version_nodot(py_version[:2])}"
+ yield f"py{py_version[0]}"
+ if len(py_version) > 1:
+ for minor in range(py_version[1] - 1, -1, -1):
+ yield f"py{_version_nodot((py_version[0], minor))}"
+
+
+def compatible_tags(
+ python_version: Optional[PythonVersion] = None,
+ interpreter: Optional[str] = None,
+ platforms: Optional[Iterable[str]] = None,
+) -> Iterator[Tag]:
+ """
+ Yields the sequence of tags that are compatible with a specific version of Python.
+
+ The tags consist of:
+ - py*-none-
+ - -none-any # ... if `interpreter` is provided.
+ - py*-none-any
+ """
+ if not python_version:
+ python_version = sys.version_info[:2]
+ platforms = list(platforms or platform_tags())
+ for version in _py_interpreter_range(python_version):
+ for platform_ in platforms:
+ yield Tag(version, "none", platform_)
+ if interpreter:
+ yield Tag(interpreter, "none", "any")
+ for version in _py_interpreter_range(python_version):
+ yield Tag(version, "none", "any")
+
+
+def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
+ if not is_32bit:
+ return arch
+
+ if arch.startswith("ppc"):
+ return "ppc"
+
+ return "i386"
+
+
+def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
+ formats = [cpu_arch]
+ if cpu_arch == "x86_64":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat64", "fat32"])
+
+ elif cpu_arch == "i386":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat32", "fat"])
+
+ elif cpu_arch == "ppc64":
+ # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
+ if version > (10, 5) or version < (10, 4):
+ return []
+ formats.append("fat64")
+
+ elif cpu_arch == "ppc":
+ if version > (10, 6):
+ return []
+ formats.extend(["fat32", "fat"])
+
+ if cpu_arch in {"arm64", "x86_64"}:
+ formats.append("universal2")
+
+ if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
+ formats.append("universal")
+
+ return formats
+
+
+def mac_platforms(
+ version: Optional[MacVersion] = None, arch: Optional[str] = None
+) -> Iterator[str]:
+ """
+ Yields the platform tags for a macOS system.
+
+ The `version` parameter is a two-item tuple specifying the macOS version to
+ generate platform tags for. The `arch` parameter is the CPU architecture to
+ generate platform tags for. Both parameters default to the appropriate value
+ for the current system.
+ """
+ version_str, _, cpu_arch = platform.mac_ver()
+ if version is None:
+ version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
+ if version == (10, 16):
+ # When built against an older macOS SDK, Python will report macOS 10.16
+ # instead of the real version.
+ version_str = subprocess.run(
+ [
+ sys.executable,
+ "-sS",
+ "-c",
+ "import platform; print(platform.mac_ver()[0])",
+ ],
+ check=True,
+ env={"SYSTEM_VERSION_COMPAT": "0"},
+ stdout=subprocess.PIPE,
+ text=True,
+ ).stdout
+ version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
+ else:
+ version = version
+ if arch is None:
+ arch = _mac_arch(cpu_arch)
+ else:
+ arch = arch
+
+ if (10, 0) <= version and version < (11, 0):
+ # Prior to Mac OS 11, each yearly release of Mac OS bumped the
+ # "minor" version number. The major version was always 10.
+ for minor_version in range(version[1], -1, -1):
+ compat_version = 10, minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield "macosx_{major}_{minor}_{binary_format}".format(
+ major=10, minor=minor_version, binary_format=binary_format
+ )
+
+ if version >= (11, 0):
+ # Starting with Mac OS 11, each yearly release bumps the major version
+ # number. The minor versions are now the midyear updates.
+ for major_version in range(version[0], 10, -1):
+ compat_version = major_version, 0
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield "macosx_{major}_{minor}_{binary_format}".format(
+ major=major_version, minor=0, binary_format=binary_format
+ )
+
+ if version >= (11, 0):
+ # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
+ # Arm64 support was introduced in 11.0, so no Arm binaries from previous
+ # releases exist.
+ #
+ # However, the "universal2" binary format can have a
+ # macOS version earlier than 11.0 when the x86_64 part of the binary supports
+ # that version of macOS.
+ if arch == "x86_64":
+ for minor_version in range(16, 3, -1):
+ compat_version = 10, minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ yield "macosx_{major}_{minor}_{binary_format}".format(
+ major=compat_version[0],
+ minor=compat_version[1],
+ binary_format=binary_format,
+ )
+ else:
+ for minor_version in range(16, 3, -1):
+ compat_version = 10, minor_version
+ binary_format = "universal2"
+ yield "macosx_{major}_{minor}_{binary_format}".format(
+ major=compat_version[0],
+ minor=compat_version[1],
+ binary_format=binary_format,
+ )
+
+
+def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
+ linux = _normalize_string(sysconfig.get_platform())
+ if not linux.startswith("linux_"):
+ # we should never be here, just yield the sysconfig one and return
+ yield linux
+ return
+ if is_32bit:
+ if linux == "linux_x86_64":
+ linux = "linux_i686"
+ elif linux == "linux_aarch64":
+ linux = "linux_armv8l"
+ _, arch = linux.split("_", 1)
+ archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
+ yield from _manylinux.platform_tags(archs)
+ yield from _musllinux.platform_tags(archs)
+ for arch in archs:
+ yield f"linux_{arch}"
+
+
+def _generic_platforms() -> Iterator[str]:
+ yield _normalize_string(sysconfig.get_platform())
+
+
+def platform_tags() -> Iterator[str]:
+ """
+ Provides the platform tags for this installation.
+ """
+ if platform.system() == "Darwin":
+ return mac_platforms()
+ elif platform.system() == "Linux":
+ return _linux_platforms()
+ else:
+ return _generic_platforms()
+
+
+def interpreter_name() -> str:
+ """
+ Returns the name of the running interpreter.
+
+ Some implementations have a reserved, two-letter abbreviation which will
+ be returned when appropriate.
+ """
+ name = sys.implementation.name
+ return INTERPRETER_SHORT_NAMES.get(name) or name
+
+
+def interpreter_version(*, warn: bool = False) -> str:
+ """
+ Returns the version of the running interpreter.
+ """
+ version = _get_config_var("py_version_nodot", warn=warn)
+ if version:
+ version = str(version)
+ else:
+ version = _version_nodot(sys.version_info[:2])
+ return version
+
+
+def _version_nodot(version: PythonVersion) -> str:
+ return "".join(map(str, version))
+
+
+def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
+ """
+ Returns the sequence of tag triples for the running interpreter.
+
+ The order of the sequence corresponds to priority order for the
+ interpreter, from most to least important.
+ """
+
+ interp_name = interpreter_name()
+ if interp_name == "cp":
+ yield from cpython_tags(warn=warn)
+ else:
+ yield from generic_tags()
+
+ if interp_name == "pp":
+ interp = "pp3"
+ elif interp_name == "cp":
+ interp = "cp" + interpreter_version(warn=warn)
+ else:
+ interp = None
+ yield from compatible_tags(interpreter=interp)
diff --git a/.venv/lib/python3.10/site-packages/packaging/utils.py b/.venv/lib/python3.10/site-packages/packaging/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2c2f75aa806282d322c76c2117c0f0fdfb09d25
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/utils.py
@@ -0,0 +1,172 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+import re
+from typing import FrozenSet, NewType, Tuple, Union, cast
+
+from .tags import Tag, parse_tag
+from .version import InvalidVersion, Version
+
+BuildTag = Union[Tuple[()], Tuple[int, str]]
+NormalizedName = NewType("NormalizedName", str)
+
+
+class InvalidName(ValueError):
+ """
+ An invalid distribution name; users should refer to the packaging user guide.
+ """
+
+
+class InvalidWheelFilename(ValueError):
+ """
+ An invalid wheel filename was found, users should refer to PEP 427.
+ """
+
+
+class InvalidSdistFilename(ValueError):
+ """
+ An invalid sdist filename was found, users should refer to the packaging user guide.
+ """
+
+
+# Core metadata spec for `Name`
+_validate_regex = re.compile(
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
+)
+_canonicalize_regex = re.compile(r"[-_.]+")
+_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
+# PEP 427: The build number must start with a digit.
+_build_tag_regex = re.compile(r"(\d+)(.*)")
+
+
+def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
+ if validate and not _validate_regex.match(name):
+ raise InvalidName(f"name is invalid: {name!r}")
+ # This is taken from PEP 503.
+ value = _canonicalize_regex.sub("-", name).lower()
+ return cast(NormalizedName, value)
+
+
+def is_normalized_name(name: str) -> bool:
+ return _normalized_regex.match(name) is not None
+
+
+def canonicalize_version(
+ version: Union[Version, str], *, strip_trailing_zero: bool = True
+) -> str:
+ """
+ This is very similar to Version.__str__, but has one subtle difference
+ with the way it handles the release segment.
+ """
+ if isinstance(version, str):
+ try:
+ parsed = Version(version)
+ except InvalidVersion:
+ # Legacy versions cannot be normalized
+ return version
+ else:
+ parsed = version
+
+ parts = []
+
+ # Epoch
+ if parsed.epoch != 0:
+ parts.append(f"{parsed.epoch}!")
+
+ # Release segment
+ release_segment = ".".join(str(x) for x in parsed.release)
+ if strip_trailing_zero:
+ # NB: This strips trailing '.0's to normalize
+ release_segment = re.sub(r"(\.0)+$", "", release_segment)
+ parts.append(release_segment)
+
+ # Pre-release
+ if parsed.pre is not None:
+ parts.append("".join(str(x) for x in parsed.pre))
+
+ # Post-release
+ if parsed.post is not None:
+ parts.append(f".post{parsed.post}")
+
+ # Development release
+ if parsed.dev is not None:
+ parts.append(f".dev{parsed.dev}")
+
+ # Local version segment
+ if parsed.local is not None:
+ parts.append(f"+{parsed.local}")
+
+ return "".join(parts)
+
+
+def parse_wheel_filename(
+ filename: str,
+) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:
+ if not filename.endswith(".whl"):
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (extension must be '.whl'): {filename}"
+ )
+
+ filename = filename[:-4]
+ dashes = filename.count("-")
+ if dashes not in (4, 5):
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (wrong number of parts): {filename}"
+ )
+
+ parts = filename.split("-", dashes - 2)
+ name_part = parts[0]
+ # See PEP 427 for the rules on escaping the project name.
+ if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
+ raise InvalidWheelFilename(f"Invalid project name: {filename}")
+ name = canonicalize_name(name_part)
+
+ try:
+ version = Version(parts[1])
+ except InvalidVersion as e:
+ raise InvalidWheelFilename(
+ f"Invalid wheel filename (invalid version): {filename}"
+ ) from e
+
+ if dashes == 5:
+ build_part = parts[2]
+ build_match = _build_tag_regex.match(build_part)
+ if build_match is None:
+ raise InvalidWheelFilename(
+ f"Invalid build number: {build_part} in '{filename}'"
+ )
+ build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
+ else:
+ build = ()
+ tags = parse_tag(parts[-1])
+ return (name, version, build, tags)
+
+
+def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:
+ if filename.endswith(".tar.gz"):
+ file_stem = filename[: -len(".tar.gz")]
+ elif filename.endswith(".zip"):
+ file_stem = filename[: -len(".zip")]
+ else:
+ raise InvalidSdistFilename(
+ f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
+ f" {filename}"
+ )
+
+ # We are requiring a PEP 440 version, which cannot contain dashes,
+ # so we split on the last dash.
+ name_part, sep, version_part = file_stem.rpartition("-")
+ if not sep:
+ raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
+
+ name = canonicalize_name(name_part)
+
+ try:
+ version = Version(version_part)
+ except InvalidVersion as e:
+ raise InvalidSdistFilename(
+ f"Invalid sdist filename (invalid version): {filename}"
+ ) from e
+
+ return (name, version)
diff --git a/.venv/lib/python3.10/site-packages/packaging/version.py b/.venv/lib/python3.10/site-packages/packaging/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..5faab9bd0dcf28847960162b2b4f13a8a556ef20
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/packaging/version.py
@@ -0,0 +1,563 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+"""
+.. testsetup::
+
+ from packaging.version import parse, Version
+"""
+
+import itertools
+import re
+from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union
+
+from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
+
+__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
+
+LocalType = Tuple[Union[int, str], ...]
+
+CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]]
+CmpLocalType = Union[
+ NegativeInfinityType,
+ Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...],
+]
+CmpKey = Tuple[
+ int,
+ Tuple[int, ...],
+ CmpPrePostDevType,
+ CmpPrePostDevType,
+ CmpPrePostDevType,
+ CmpLocalType,
+]
+VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
+
+
+class _Version(NamedTuple):
+ epoch: int
+ release: Tuple[int, ...]
+ dev: Optional[Tuple[str, int]]
+ pre: Optional[Tuple[str, int]]
+ post: Optional[Tuple[str, int]]
+ local: Optional[LocalType]
+
+
+def parse(version: str) -> "Version":
+ """Parse the given version string.
+
+ >>> parse('1.0.dev1')
+
+
+ :param version: The version string to parse.
+ :raises InvalidVersion: When the version string is not a valid version.
+ """
+ return Version(version)
+
+
+class InvalidVersion(ValueError):
+ """Raised when a version string is not a valid version.
+
+ >>> Version("invalid")
+ Traceback (most recent call last):
+ ...
+ packaging.version.InvalidVersion: Invalid version: 'invalid'
+ """
+
+
+class _BaseVersion:
+ _key: Tuple[Any, ...]
+
+ def __hash__(self) -> int:
+ return hash(self._key)
+
+ # Please keep the duplicated `isinstance` check
+ # in the six comparisons hereunder
+ # unless you find a way to avoid adding overhead function calls.
+ def __lt__(self, other: "_BaseVersion") -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key < other._key
+
+ def __le__(self, other: "_BaseVersion") -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key <= other._key
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key == other._key
+
+ def __ge__(self, other: "_BaseVersion") -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key >= other._key
+
+ def __gt__(self, other: "_BaseVersion") -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key > other._key
+
+ def __ne__(self, other: object) -> bool:
+ if not isinstance(other, _BaseVersion):
+ return NotImplemented
+
+ return self._key != other._key
+
+
+# Deliberately not anchored to the start and end of the string, to make it
+# easier for 3rd party code to reuse
+_VERSION_PATTERN = r"""
+ v?
+ (?:
+ (?:(?P[0-9]+)!)? # epoch
+ (?P[0-9]+(?:\.[0-9]+)*) # release segment
+ (?P # pre-release
+ [-_\.]?
+ (?Palpha|a|beta|b|preview|pre|c|rc)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ (?P # post release
+ (?:-(?P[0-9]+))
+ |
+ (?:
+ [-_\.]?
+ (?Ppost|rev|r)
+ [-_\.]?
+ (?P[0-9]+)?
+ )
+ )?
+ (?P # dev release
+ [-_\.]?
+ (?Pdev)
+ [-_\.]?
+ (?P[0-9]+)?
+ )?
+ )
+ (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
+"""
+
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
+
+class Version(_BaseVersion):
+ """This class abstracts handling of a project's versions.
+
+ A :class:`Version` instance is comparison aware and can be compared and
+ sorted using the standard Python interfaces.
+
+ >>> v1 = Version("1.0a5")
+ >>> v2 = Version("1.0")
+ >>> v1
+
+ >>> v2
+
+ >>> v1 < v2
+ True
+ >>> v1 == v2
+ False
+ >>> v1 > v2
+ False
+ >>> v1 >= v2
+ False
+ >>> v1 <= v2
+ True
+ """
+
+ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _key: CmpKey
+
+ def __init__(self, version: str) -> None:
+ """Initialize a Version object.
+
+ :param version:
+ The string representation of a version which will be parsed and normalized
+ before use.
+ :raises InvalidVersion:
+ If the ``version`` does not conform to PEP 440 in any way then this
+ exception will be raised.
+ """
+
+ # Validate the version and parse it into pieces
+ match = self._regex.search(version)
+ if not match:
+ raise InvalidVersion(f"Invalid version: '{version}'")
+
+ # Store the parsed out pieces of the version
+ self._version = _Version(
+ epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+ release=tuple(int(i) for i in match.group("release").split(".")),
+ pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+ post=_parse_letter_version(
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+ ),
+ dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+ local=_parse_local_version(match.group("local")),
+ )
+
+ # Generate a key which will be used for sorting
+ self._key = _cmpkey(
+ self._version.epoch,
+ self._version.release,
+ self._version.pre,
+ self._version.post,
+ self._version.dev,
+ self._version.local,
+ )
+
+ def __repr__(self) -> str:
+ """A representation of the Version that shows all internal state.
+
+ >>> Version('1.0.0')
+
+ """
+ return f""
+
+ def __str__(self) -> str:
+ """A string representation of the version that can be rounded-tripped.
+
+ >>> str(Version("1.0a5"))
+ '1.0a5'
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ # Pre-release
+ if self.pre is not None:
+ parts.append("".join(str(x) for x in self.pre))
+
+ # Post-release
+ if self.post is not None:
+ parts.append(f".post{self.post}")
+
+ # Development release
+ if self.dev is not None:
+ parts.append(f".dev{self.dev}")
+
+ # Local version segment
+ if self.local is not None:
+ parts.append(f"+{self.local}")
+
+ return "".join(parts)
+
+ @property
+ def epoch(self) -> int:
+ """The epoch of the version.
+
+ >>> Version("2.0.0").epoch
+ 0
+ >>> Version("1!2.0.0").epoch
+ 1
+ """
+ return self._version.epoch
+
+ @property
+ def release(self) -> Tuple[int, ...]:
+ """The components of the "release" segment of the version.
+
+ >>> Version("1.2.3").release
+ (1, 2, 3)
+ >>> Version("2.0.0").release
+ (2, 0, 0)
+ >>> Version("1!2.0.0.post0").release
+ (2, 0, 0)
+
+ Includes trailing zeroes but not the epoch or any pre-release / development /
+ post-release suffixes.
+ """
+ return self._version.release
+
+ @property
+ def pre(self) -> Optional[Tuple[str, int]]:
+ """The pre-release segment of the version.
+
+ >>> print(Version("1.2.3").pre)
+ None
+ >>> Version("1.2.3a1").pre
+ ('a', 1)
+ >>> Version("1.2.3b1").pre
+ ('b', 1)
+ >>> Version("1.2.3rc1").pre
+ ('rc', 1)
+ """
+ return self._version.pre
+
+ @property
+ def post(self) -> Optional[int]:
+ """The post-release number of the version.
+
+ >>> print(Version("1.2.3").post)
+ None
+ >>> Version("1.2.3.post1").post
+ 1
+ """
+ return self._version.post[1] if self._version.post else None
+
+ @property
+ def dev(self) -> Optional[int]:
+ """The development number of the version.
+
+ >>> print(Version("1.2.3").dev)
+ None
+ >>> Version("1.2.3.dev1").dev
+ 1
+ """
+ return self._version.dev[1] if self._version.dev else None
+
+ @property
+ def local(self) -> Optional[str]:
+ """The local version segment of the version.
+
+ >>> print(Version("1.2.3").local)
+ None
+ >>> Version("1.2.3+abc").local
+ 'abc'
+ """
+ if self._version.local:
+ return ".".join(str(x) for x in self._version.local)
+ else:
+ return None
+
+ @property
+ def public(self) -> str:
+ """The public portion of the version.
+
+ >>> Version("1.2.3").public
+ '1.2.3'
+ >>> Version("1.2.3+abc").public
+ '1.2.3'
+ >>> Version("1.2.3+abc.dev1").public
+ '1.2.3'
+ """
+ return str(self).split("+", 1)[0]
+
+ @property
+ def base_version(self) -> str:
+ """The "base version" of the version.
+
+ >>> Version("1.2.3").base_version
+ '1.2.3'
+ >>> Version("1.2.3+abc").base_version
+ '1.2.3'
+ >>> Version("1!1.2.3+abc.dev1").base_version
+ '1!1.2.3'
+
+ The "base version" is the public version of the project without any pre or post
+ release markers.
+ """
+ parts = []
+
+ # Epoch
+ if self.epoch != 0:
+ parts.append(f"{self.epoch}!")
+
+ # Release segment
+ parts.append(".".join(str(x) for x in self.release))
+
+ return "".join(parts)
+
+ @property
+ def is_prerelease(self) -> bool:
+ """Whether this version is a pre-release.
+
+ >>> Version("1.2.3").is_prerelease
+ False
+ >>> Version("1.2.3a1").is_prerelease
+ True
+ >>> Version("1.2.3b1").is_prerelease
+ True
+ >>> Version("1.2.3rc1").is_prerelease
+ True
+ >>> Version("1.2.3dev1").is_prerelease
+ True
+ """
+ return self.dev is not None or self.pre is not None
+
+ @property
+ def is_postrelease(self) -> bool:
+ """Whether this version is a post-release.
+
+ >>> Version("1.2.3").is_postrelease
+ False
+ >>> Version("1.2.3.post1").is_postrelease
+ True
+ """
+ return self.post is not None
+
+ @property
+ def is_devrelease(self) -> bool:
+ """Whether this version is a development release.
+
+ >>> Version("1.2.3").is_devrelease
+ False
+ >>> Version("1.2.3.dev1").is_devrelease
+ True
+ """
+ return self.dev is not None
+
+ @property
+ def major(self) -> int:
+ """The first item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").major
+ 1
+ """
+ return self.release[0] if len(self.release) >= 1 else 0
+
+ @property
+ def minor(self) -> int:
+ """The second item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").minor
+ 2
+ >>> Version("1").minor
+ 0
+ """
+ return self.release[1] if len(self.release) >= 2 else 0
+
+ @property
+ def micro(self) -> int:
+ """The third item of :attr:`release` or ``0`` if unavailable.
+
+ >>> Version("1.2.3").micro
+ 3
+ >>> Version("1").micro
+ 0
+ """
+ return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+ letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
+) -> Optional[Tuple[str, int]]:
+
+ if letter:
+ # We consider there to be an implicit 0 in a pre-release if there is
+ # not a numeral associated with it.
+ if number is None:
+ number = 0
+
+ # We normalize any letters to their lower case form
+ letter = letter.lower()
+
+ # We consider some words to be alternate spellings of other words and
+ # in those cases we want to normalize the spellings to our preferred
+ # spelling.
+ if letter == "alpha":
+ letter = "a"
+ elif letter == "beta":
+ letter = "b"
+ elif letter in ["c", "pre", "preview"]:
+ letter = "rc"
+ elif letter in ["rev", "r"]:
+ letter = "post"
+
+ return letter, int(number)
+ if not letter and number:
+ # We assume if we are given a number, but we are not given a letter
+ # then this is using the implicit post release syntax (e.g. 1.0-1)
+ letter = "post"
+
+ return letter, int(number)
+
+ return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
+ """
+ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+ """
+ if local is not None:
+ return tuple(
+ part.lower() if not part.isdigit() else int(part)
+ for part in _local_version_separators.split(local)
+ )
+ return None
+
+
+def _cmpkey(
+ epoch: int,
+ release: Tuple[int, ...],
+ pre: Optional[Tuple[str, int]],
+ post: Optional[Tuple[str, int]],
+ dev: Optional[Tuple[str, int]],
+ local: Optional[LocalType],
+) -> CmpKey:
+
+ # When we compare a release version, we want to compare it with all of the
+ # trailing zeros removed. So we'll use a reverse the list, drop all the now
+ # leading zeros until we come to something non zero, then take the rest
+ # re-reverse it back into the correct order and make it a tuple and use
+ # that for our sorting key.
+ _release = tuple(
+ reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+ )
+
+ # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+ # We'll do this by abusing the pre segment, but we _only_ want to do this
+ # if there is not a pre or a post segment. If we have one of those then
+ # the normal sorting rules will handle this case correctly.
+ if pre is None and post is None and dev is not None:
+ _pre: CmpPrePostDevType = NegativeInfinity
+ # Versions without a pre-release (except as noted above) should sort after
+ # those with one.
+ elif pre is None:
+ _pre = Infinity
+ else:
+ _pre = pre
+
+ # Versions without a post segment should sort before those with one.
+ if post is None:
+ _post: CmpPrePostDevType = NegativeInfinity
+
+ else:
+ _post = post
+
+ # Versions without a development segment should sort after those with one.
+ if dev is None:
+ _dev: CmpPrePostDevType = Infinity
+
+ else:
+ _dev = dev
+
+ if local is None:
+ # Versions without a local segment should sort before those with one.
+ _local: CmpLocalType = NegativeInfinity
+ else:
+ # Versions with a local segment need that segment parsed to implement
+ # the sorting rules in PEP440.
+ # - Alpha numeric segments sort before numeric segments
+ # - Alpha numeric segments sort lexicographically
+ # - Numeric segments sort numerically
+ # - Shorter versions sort before longer versions when the prefixes
+ # match exactly
+ _local = tuple(
+ (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+ )
+
+ return epoch, _release, _pre, _post, _dev, _local
diff --git a/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..7571366d2c4207ce9625260a06f42811917f143c
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER
@@ -0,0 +1 @@
+Poetry 1.8.2
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..1e65815cf0b3132689485874a93034ede7206bf4
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE
@@ -0,0 +1,54 @@
+Copyright 2017- Paul Ganssle
+Copyright 2017- dateutil contributors (see AUTHORS file)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+The above license applies to all contributions after 2017-12-01, as well as
+all contributions that have been re-licensed (see AUTHORS file for the list of
+contributors who have re-licensed their code).
+--------------------------------------------------------------------------------
+dateutil - Extensions to the standard Python datetime module.
+
+Copyright (c) 2003-2011 - Gustavo Niemeyer
+Copyright (c) 2012-2014 - Tomi Pieviläinen
+Copyright (c) 2014-2016 - Yaron de Leeuw
+Copyright (c) 2015- - Paul Ganssle
+Copyright (c) 2015- - dateutil contributors (see AUTHORS file)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The above BSD License Applies to all code, even that also covered by Apache 2.0.
\ No newline at end of file
diff --git a/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..577f2bf2b7749e1b123b8225d610b1b257e430cc
--- /dev/null
+++ b/.venv/lib/python3.10/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA
@@ -0,0 +1,204 @@
+Metadata-Version: 2.1
+Name: python-dateutil
+Version: 2.9.0.post0
+Summary: Extensions to the standard Python datetime module
+Home-page: https://github.com/dateutil/dateutil
+Author: Gustavo Niemeyer
+Author-email: gustavo@niemeyer.net
+Maintainer: Paul Ganssle
+Maintainer-email: dateutil@python.org
+License: Dual License
+Project-URL: Documentation, https://dateutil.readthedocs.io/en/stable/
+Project-URL: Source, https://github.com/dateutil/dateutil
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Topic :: Software Development :: Libraries
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,>=2.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Requires-Dist: six >=1.5
+
+dateutil - powerful extensions to datetime
+==========================================
+
+|pypi| |support| |licence|
+
+|gitter| |readthedocs|
+
+|travis| |appveyor| |pipelines| |coverage|
+
+.. |pypi| image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square
+ :target: https://pypi.org/project/python-dateutil/
+ :alt: pypi version
+
+.. |support| image:: https://img.shields.io/pypi/pyversions/python-dateutil.svg?style=flat-square
+ :target: https://pypi.org/project/python-dateutil/
+ :alt: supported Python version
+
+.. |travis| image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square&label=Travis%20Build
+ :target: https://travis-ci.org/dateutil/dateutil
+ :alt: travis build status
+
+.. |appveyor| image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square&logo=appveyor
+ :target: https://ci.appveyor.com/project/dateutil/dateutil
+ :alt: appveyor build status
+
+.. |pipelines| image:: https://dev.azure.com/pythondateutilazure/dateutil/_apis/build/status/dateutil.dateutil?branchName=master
+ :target: https://dev.azure.com/pythondateutilazure/dateutil/_build/latest?definitionId=1&branchName=master
+ :alt: azure pipelines build status
+
+.. |coverage| image:: https://codecov.io/gh/dateutil/dateutil/branch/master/graphs/badge.svg?branch=master
+ :target: https://codecov.io/gh/dateutil/dateutil?branch=master
+ :alt: Code coverage
+
+.. |gitter| image:: https://badges.gitter.im/dateutil/dateutil.svg
+ :alt: Join the chat at https://gitter.im/dateutil/dateutil
+ :target: https://gitter.im/dateutil/dateutil
+
+.. |licence| image:: https://img.shields.io/pypi/l/python-dateutil.svg?style=flat-square
+ :target: https://pypi.org/project/python-dateutil/
+ :alt: licence
+
+.. |readthedocs| image:: https://img.shields.io/readthedocs/dateutil/latest.svg?style=flat-square&label=Read%20the%20Docs
+ :alt: Read the documentation at https://dateutil.readthedocs.io/en/latest/
+ :target: https://dateutil.readthedocs.io/en/latest/
+
+The `dateutil` module provides powerful extensions to
+the standard `datetime` module, available in Python.
+
+Installation
+============
+`dateutil` can be installed from PyPI using `pip` (note that the package name is
+different from the importable name)::
+
+ pip install python-dateutil
+
+Download
+========
+dateutil is available on PyPI
+https://pypi.org/project/python-dateutil/
+
+The documentation is hosted at:
+https://dateutil.readthedocs.io/en/stable/
+
+Code
+====
+The code and issue tracker are hosted on GitHub:
+https://github.com/dateutil/dateutil/
+
+Features
+========
+
+* Computing of relative deltas (next month, next year,
+ next Monday, last week of month, etc);
+* Computing of relative deltas between two given
+ date and/or datetime objects;
+* Computing of dates based on very flexible recurrence rules,
+ using a superset of the `iCalendar `_
+ specification. Parsing of RFC strings is supported as well.
+* Generic parsing of dates in almost any string format;
+* Timezone (tzinfo) implementations for tzfile(5) format
+ files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
+ environment string (in all known formats), iCalendar
+ format files, given ranges (with help from relative deltas),
+ local machine timezone, fixed offset timezone, UTC timezone,
+ and Windows registry-based time zones.
+* Internal up-to-date world timezone information based on
+ Olson's database.
+* Computing of Easter Sunday dates for any given year,
+ using Western, Orthodox or Julian algorithms;
+* A comprehensive test suite.
+
+Quick example
+=============
+Here's a snapshot, just to give an idea about the power of the
+package. For more examples, look at the documentation.
+
+Suppose you want to know how much time is left, in
+years/months/days/etc, before the next easter happening on a
+year with a Friday 13th in August, and you want to get today's
+date out of the "date" unix system command. Here is the code:
+
+.. code-block:: python3
+
+ >>> from dateutil.relativedelta import *
+ >>> from dateutil.easter import *
+ >>> from dateutil.rrule import *
+ >>> from dateutil.parser import *
+ >>> from datetime import *
+ >>> now = parse("Sat Oct 11 17:13:46 UTC 2003")
+ >>> today = now.date()
+ >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year
+ >>> rdelta = relativedelta(easter(year), today)
+ >>> print("Today is: %s" % today)
+ Today is: 2003-10-11
+ >>> print("Year with next Aug 13th on a Friday is: %s" % year)
+ Year with next Aug 13th on a Friday is: 2004
+ >>> print("How far is the Easter of that year: %s" % rdelta)
+ How far is the Easter of that year: relativedelta(months=+6)
+ >>> print("And the Easter of that year is: %s" % (today+rdelta))
+ And the Easter of that year is: 2004-04-11
+
+Being exactly 6 months ahead was **really** a coincidence :)
+
+Contributing
+============
+
+We welcome many types of contributions - bug reports, pull requests (code, infrastructure or documentation fixes). For more information about how to contribute to the project, see the ``CONTRIBUTING.md`` file in the repository.
+
+
+Author
+======
+The dateutil module was written by Gustavo Niemeyer
+in 2003.
+
+It is maintained by:
+
+* Gustavo Niemeyer