file-keeper 0.0.1a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- file_keeper-0.0.1a0/PKG-INFO +55 -0
- file_keeper-0.0.1a0/README.md +3 -0
- file_keeper-0.0.1a0/pyproject.toml +211 -0
- file_keeper-0.0.1a0/setup.cfg +4 -0
- file_keeper-0.0.1a0/setup.py +3 -0
- file_keeper-0.0.1a0/src/file_keeper/__init__.py +48 -0
- file_keeper-0.0.1a0/src/file_keeper/core/__init__.py +0 -0
- file_keeper-0.0.1a0/src/file_keeper/core/data.py +107 -0
- file_keeper-0.0.1a0/src/file_keeper/core/exceptions.py +275 -0
- file_keeper-0.0.1a0/src/file_keeper/core/registry.py +73 -0
- file_keeper-0.0.1a0/src/file_keeper/core/storage.py +620 -0
- file_keeper-0.0.1a0/src/file_keeper/core/types.py +30 -0
- file_keeper-0.0.1a0/src/file_keeper/core/upload.py +146 -0
- file_keeper-0.0.1a0/src/file_keeper/core/utils.py +262 -0
- file_keeper-0.0.1a0/src/file_keeper/default/__init__.py +133 -0
- file_keeper-0.0.1a0/src/file_keeper/default/adapters/__init__.py +5 -0
- file_keeper-0.0.1a0/src/file_keeper/default/adapters/fs.py +447 -0
- file_keeper-0.0.1a0/src/file_keeper/ext/__init__.py +24 -0
- file_keeper-0.0.1a0/src/file_keeper/ext/spec.py +27 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/PKG-INFO +55 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/SOURCES.txt +23 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/dependency_links.txt +1 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/entry_points.txt +2 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/requires.txt +39 -0
- file_keeper-0.0.1a0/src/file_keeper.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: file-keeper
|
|
3
|
+
Version: 0.0.1a0
|
|
4
|
+
Author-email: DataShades <datashades@linkdigital.com.au>, Sergey Motornyuk <sergey.motornyuk@linkdigital.com.au>
|
|
5
|
+
Maintainer-email: DataShades <datashades@linkdigital.com.au>
|
|
6
|
+
License: AGPL
|
|
7
|
+
Project-URL: Homepage, https://github.com/DataShades/file-keeper
|
|
8
|
+
Keywords: file-management,cloud,filesystem,DAL
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: typing_extensions
|
|
21
|
+
Requires-Dist: pluggy
|
|
22
|
+
Requires-Dist: pytz
|
|
23
|
+
Requires-Dist: python-magic
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
26
|
+
Requires-Dist: pytest-faker; extra == "test"
|
|
27
|
+
Requires-Dist: responses; extra == "test"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
30
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
31
|
+
Requires-Dist: pymdown-extensions; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-faker; extra == "dev"
|
|
36
|
+
Requires-Dist: freezegun; extra == "dev"
|
|
37
|
+
Requires-Dist: responses; extra == "dev"
|
|
38
|
+
Requires-Dist: mkdocs; extra == "dev"
|
|
39
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
|
40
|
+
Requires-Dist: pymdown-extensions; extra == "dev"
|
|
41
|
+
Requires-Dist: mkdocstrings[python]; extra == "dev"
|
|
42
|
+
Provides-Extra: s3
|
|
43
|
+
Requires-Dist: boto3; extra == "s3"
|
|
44
|
+
Requires-Dist: boto3-stubs[essential]; extra == "s3"
|
|
45
|
+
Provides-Extra: gcs
|
|
46
|
+
Requires-Dist: google-cloud-storage; extra == "gcs"
|
|
47
|
+
Provides-Extra: opendal
|
|
48
|
+
Requires-Dist: opendal; extra == "opendal"
|
|
49
|
+
Provides-Extra: libcloud
|
|
50
|
+
Requires-Dist: apache-libcloud; extra == "libcloud"
|
|
51
|
+
Requires-Dist: cryptography; extra == "libcloud"
|
|
52
|
+
|
|
53
|
+
# file-keeper
|
|
54
|
+
|
|
55
|
+
Abstraction level for object storages.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [ "setuptools",]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools.dynamic]
|
|
6
|
+
version = {attr = "file_keeper.__version__"}
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "file-keeper"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = ""
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
15
|
+
"Programming Language :: Python :: 3.8",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
]
|
|
23
|
+
keywords = [ "file-management", "cloud", "filesystem", "DAL" ]
|
|
24
|
+
requires-python = ">= 3.8"
|
|
25
|
+
dependencies = [ "typing_extensions", "pluggy", "pytz", "python-magic" ]
|
|
26
|
+
authors = [
|
|
27
|
+
{name = "DataShades", email = "datashades@linkdigital.com.au"},
|
|
28
|
+
{name = "Sergey Motornyuk", email = "sergey.motornyuk@linkdigital.com.au"},
|
|
29
|
+
]
|
|
30
|
+
maintainers = [
|
|
31
|
+
{name = "DataShades", email = "datashades@linkdigital.com.au"},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.readme]
|
|
35
|
+
file = "README.md"
|
|
36
|
+
content-type = "text/markdown"
|
|
37
|
+
|
|
38
|
+
[project.license]
|
|
39
|
+
text = "AGPL"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/DataShades/file-keeper"
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = [ "pytest-cov", "pytest-faker", "responses" ]
|
|
46
|
+
docs = [ "mkdocs", "mkdocs-material", "pymdown-extensions", "mkdocstrings[python]",]
|
|
47
|
+
dev = [ "pytest-cov", "pytest-faker", "freezegun", "responses", "mkdocs", "mkdocs-material", "pymdown-extensions", "mkdocstrings[python]",]
|
|
48
|
+
s3 = [ "boto3", "boto3-stubs[essential]"]
|
|
49
|
+
gcs = [ "google-cloud-storage",]
|
|
50
|
+
opendal = [ "opendal",]
|
|
51
|
+
libcloud = [ "apache-libcloud", "cryptography",]
|
|
52
|
+
|
|
53
|
+
[project.entry-points."file_keeper_ext"]
|
|
54
|
+
default = "file_keeper.default"
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
target-version = "py38"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = [
|
|
61
|
+
"ANN0", # type annotations for function arguments
|
|
62
|
+
"B", # likely bugs and design problems
|
|
63
|
+
"BLE", # do not catch blind exception
|
|
64
|
+
"C4", # better list/set/dict comprehensions
|
|
65
|
+
"C90", # check McCabe complexity
|
|
66
|
+
"DTZ", # enforce timezone in date objects
|
|
67
|
+
"E", # pycodestyle error
|
|
68
|
+
"W", # pycodestyle warning
|
|
69
|
+
"F", # pyflakes
|
|
70
|
+
"FA", # verify annotations from future
|
|
71
|
+
"G", # format strings for logging statements
|
|
72
|
+
"N", # naming conventions
|
|
73
|
+
"I", # isort
|
|
74
|
+
"ICN", # import conventions
|
|
75
|
+
# "D1", # require doc
|
|
76
|
+
"D2", # doc formatting
|
|
77
|
+
"D4", # doc convention
|
|
78
|
+
"PL", # pylint
|
|
79
|
+
"PERF", # performance anti-patterns
|
|
80
|
+
"PT", # pytest style
|
|
81
|
+
"PIE", # misc lints
|
|
82
|
+
"RET", # improvements for return statements
|
|
83
|
+
"RSE", # improvements for rise statements
|
|
84
|
+
"S", # security testing
|
|
85
|
+
"SIM", # simplify code
|
|
86
|
+
"T10", # debugging statements
|
|
87
|
+
"T20", # print statements
|
|
88
|
+
"TID", # tidier imports
|
|
89
|
+
"TRY", # better exceptions
|
|
90
|
+
"UP", # upgrade syntax for newer versions of the language
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
ignore = [
|
|
94
|
+
"RET503", # don't enforce return-None
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint.per-file-ignores]
|
|
98
|
+
"tests/*" = ["S", "PL", "ANN", "D"]
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.pydocstyle]
|
|
101
|
+
convention = "google"
|
|
102
|
+
|
|
103
|
+
[tool.ruff.lint.isort]
|
|
104
|
+
section-order = [
|
|
105
|
+
"future",
|
|
106
|
+
"standard-library",
|
|
107
|
+
"first-party",
|
|
108
|
+
"third-party",
|
|
109
|
+
"self",
|
|
110
|
+
"local-folder",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[tool.ruff.lint.isort.sections]
|
|
114
|
+
self = ["file_keeper"]
|
|
115
|
+
|
|
116
|
+
[tool.pytest.ini_options]
|
|
117
|
+
addopts = "-p no:ckan"
|
|
118
|
+
|
|
119
|
+
[tool.git-changelog]
|
|
120
|
+
output = "CHANGELOG.md"
|
|
121
|
+
convention = "conventional"
|
|
122
|
+
parse-trailers = true
|
|
123
|
+
|
|
124
|
+
[tool.pyright]
|
|
125
|
+
pythonVersion = "3.8"
|
|
126
|
+
include = ["."]
|
|
127
|
+
exclude = [
|
|
128
|
+
# "**/test*",
|
|
129
|
+
]
|
|
130
|
+
strict = []
|
|
131
|
+
|
|
132
|
+
strictParameterNoneValue = true
|
|
133
|
+
# Check the meaning of rules here
|
|
134
|
+
# https://github.com/microsoft/pyright/blob/main/docs/configuration.md
|
|
135
|
+
reportFunctionMemberAccess = true # non-standard member accesses for functions
|
|
136
|
+
reportMissingImports = true
|
|
137
|
+
reportMissingModuleSource = true
|
|
138
|
+
reportMissingTypeStubs = false
|
|
139
|
+
reportImportCycles = false
|
|
140
|
+
reportUnusedImport = true
|
|
141
|
+
reportUnusedClass = true
|
|
142
|
+
reportUnusedFunction = true
|
|
143
|
+
reportUnusedVariable = true
|
|
144
|
+
reportDuplicateImport = true
|
|
145
|
+
reportOptionalSubscript = true
|
|
146
|
+
reportOptionalMemberAccess = true
|
|
147
|
+
reportOptionalCall = true
|
|
148
|
+
reportOptionalIterable = true
|
|
149
|
+
reportOptionalContextManager = true
|
|
150
|
+
reportOptionalOperand = true
|
|
151
|
+
reportTypedDictNotRequiredAccess = true
|
|
152
|
+
reportConstantRedefinition = true
|
|
153
|
+
reportIncompatibleMethodOverride = true
|
|
154
|
+
reportIncompatibleVariableOverride = true
|
|
155
|
+
reportOverlappingOverload = true
|
|
156
|
+
reportUntypedFunctionDecorator = false
|
|
157
|
+
reportUnknownParameterType = true
|
|
158
|
+
reportUnknownArgumentType = false
|
|
159
|
+
reportUnknownLambdaType = false
|
|
160
|
+
reportUnknownMemberType = false
|
|
161
|
+
reportMissingTypeArgument = true
|
|
162
|
+
reportInvalidTypeVarUse = true
|
|
163
|
+
reportCallInDefaultInitializer = true
|
|
164
|
+
reportUnknownVariableType = true
|
|
165
|
+
reportUntypedBaseClass = true
|
|
166
|
+
reportUnnecessaryIsInstance = true
|
|
167
|
+
reportUnnecessaryCast = true
|
|
168
|
+
reportUnnecessaryComparison = true
|
|
169
|
+
reportAssertAlwaysTrue = true
|
|
170
|
+
reportSelfClsParameterName = true
|
|
171
|
+
reportUnusedCallResult = false
|
|
172
|
+
useLibraryCodeForTypes = true
|
|
173
|
+
reportGeneralTypeIssues = true
|
|
174
|
+
reportPropertyTypeMismatch = true
|
|
175
|
+
reportWildcardImportFromLibrary = true
|
|
176
|
+
reportUntypedClassDecorator = false
|
|
177
|
+
reportUntypedNamedTuple = true
|
|
178
|
+
reportPrivateUsage = true
|
|
179
|
+
reportPrivateImportUsage = true
|
|
180
|
+
reportInconsistentConstructor = true
|
|
181
|
+
reportMissingSuperCall = false
|
|
182
|
+
reportUninitializedInstanceVariable = true
|
|
183
|
+
reportInvalidStringEscapeSequence = true
|
|
184
|
+
reportMissingParameterType = true
|
|
185
|
+
reportImplicitStringConcatenation = false
|
|
186
|
+
reportUndefinedVariable = true
|
|
187
|
+
reportUnboundVariable = true
|
|
188
|
+
reportInvalidStubStatement = true
|
|
189
|
+
reportIncompleteStub = true
|
|
190
|
+
reportUnsupportedDunderAll = true
|
|
191
|
+
reportUnusedCoroutine = true
|
|
192
|
+
reportUnnecessaryTypeIgnoreComment = true
|
|
193
|
+
reportMatchNotExhaustive = true
|
|
194
|
+
|
|
195
|
+
[tool.coverage.run]
|
|
196
|
+
branch = true
|
|
197
|
+
omit = ["files_keeper/tests/*"]
|
|
198
|
+
|
|
199
|
+
[tool.tox]
|
|
200
|
+
requires = ["tox>=4"]
|
|
201
|
+
env_list = ["3.13", "3.12", "3.11", "3.10", "3.9", "3.8"]
|
|
202
|
+
|
|
203
|
+
[tool.tox.env_run_base]
|
|
204
|
+
|
|
205
|
+
description = "run unit tests"
|
|
206
|
+
deps = [
|
|
207
|
+
"pytest>=8",
|
|
208
|
+
"pytest-sugar"
|
|
209
|
+
]
|
|
210
|
+
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
|
|
211
|
+
extras = ["test"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
__version__ = "0.0.1a0"
|
|
2
|
+
|
|
3
|
+
from .core import exceptions as exc
|
|
4
|
+
from .core.data import BaseData, FileData, MultipartData
|
|
5
|
+
from .core.registry import Registry
|
|
6
|
+
from .core.storage import (
|
|
7
|
+
Manager,
|
|
8
|
+
Reader,
|
|
9
|
+
Settings,
|
|
10
|
+
Storage,
|
|
11
|
+
Uploader,
|
|
12
|
+
adapters,
|
|
13
|
+
make_storage,
|
|
14
|
+
)
|
|
15
|
+
from .core.upload import Upload, make_upload
|
|
16
|
+
from .core.utils import (
|
|
17
|
+
Capability,
|
|
18
|
+
HashingReader,
|
|
19
|
+
IterableBytesReader,
|
|
20
|
+
humanize_filesize,
|
|
21
|
+
is_supported_type,
|
|
22
|
+
parse_filesize,
|
|
23
|
+
)
|
|
24
|
+
from .ext import hookimpl # must be the last line to avoid circular imports
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"adapters",
|
|
28
|
+
"FileData",
|
|
29
|
+
"BaseData",
|
|
30
|
+
"MultipartData",
|
|
31
|
+
"is_supported_type",
|
|
32
|
+
"Registry",
|
|
33
|
+
"Capability",
|
|
34
|
+
"parse_filesize",
|
|
35
|
+
"humanize_filesize",
|
|
36
|
+
"IterableBytesReader",
|
|
37
|
+
"HashingReader",
|
|
38
|
+
"make_upload",
|
|
39
|
+
"Upload",
|
|
40
|
+
"exc",
|
|
41
|
+
"make_storage",
|
|
42
|
+
"Storage",
|
|
43
|
+
"Reader",
|
|
44
|
+
"Uploader",
|
|
45
|
+
"Manager",
|
|
46
|
+
"Settings",
|
|
47
|
+
"hookimpl",
|
|
48
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Base abstract functionality of the extentsion.
|
|
2
|
+
|
|
3
|
+
All classes required for specific storage implementations are defined
|
|
4
|
+
here. Some utilities, like `make_storage` are also added to this module instead
|
|
5
|
+
of `utils` to avoid import cycles.
|
|
6
|
+
|
|
7
|
+
This module relies only on types, exceptions and utils to prevent import
|
|
8
|
+
cycles.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import copy
|
|
15
|
+
import dataclasses
|
|
16
|
+
from typing import Any, Generic
|
|
17
|
+
|
|
18
|
+
from typing_extensions import TypeVar
|
|
19
|
+
|
|
20
|
+
from . import types
|
|
21
|
+
|
|
22
|
+
TData = TypeVar("TData", bound=types.PData, default=Any)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass
|
|
26
|
+
class BaseData(Generic[TData]):
|
|
27
|
+
location: str
|
|
28
|
+
size: int = 0
|
|
29
|
+
content_type: str = ""
|
|
30
|
+
hash: str = ""
|
|
31
|
+
storage_data: dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
_plain_keys = ["location", "size", "content_type", "hash"]
|
|
34
|
+
_complex_keys = ["storage_data"]
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_dict(cls, record: dict[str, Any]):
|
|
38
|
+
return cls(
|
|
39
|
+
*[record[key] for key in cls._plain_keys],
|
|
40
|
+
*[copy.deepcopy(record[key]) for key in cls._complex_keys],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_object(cls, obj: TData):
|
|
45
|
+
return cls(
|
|
46
|
+
*[getattr(obj, key) for key in cls._plain_keys],
|
|
47
|
+
*[copy.deepcopy(getattr(obj, key)) for key in cls._complex_keys],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def into_object(self, obj: TData):
|
|
51
|
+
for key in self._plain_keys:
|
|
52
|
+
setattr(obj, key, getattr(self, key))
|
|
53
|
+
|
|
54
|
+
for key in self._complex_keys:
|
|
55
|
+
setattr(obj, key, copy.deepcopy(getattr(self, key)))
|
|
56
|
+
|
|
57
|
+
return obj
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclasses.dataclass
|
|
61
|
+
class FileData(BaseData[TData]):
|
|
62
|
+
"""Information required by storage to operate the file.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
location: filepath, filename or any other type of unique identifier
|
|
66
|
+
size: size of the file in bytes
|
|
67
|
+
content_type: MIMEtype of the file
|
|
68
|
+
hash: checksum of the file
|
|
69
|
+
storage_data: additional details set by storage adapter
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
```
|
|
73
|
+
FileData(
|
|
74
|
+
"local/path.txt",
|
|
75
|
+
123,
|
|
76
|
+
"text/plain",
|
|
77
|
+
md5_of_content,
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
content_type: str = "application/octet-stream"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclasses.dataclass
|
|
86
|
+
class MultipartData(BaseData[TData]):
|
|
87
|
+
"""Information required by storage to operate the incomplete upload.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
location: filepath, filename or any other type of unique identifier
|
|
91
|
+
size: expected size of the file in bytes
|
|
92
|
+
content_type: expected MIMEtype of the file
|
|
93
|
+
hash: expected checksum of the file
|
|
94
|
+
storage_data: additional details set by storage adapter
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
```
|
|
98
|
+
MultipartData(
|
|
99
|
+
"local/path.txt",
|
|
100
|
+
expected_size,
|
|
101
|
+
expected_content_type,
|
|
102
|
+
expected_hash,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
location: str = ""
|