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.
@@ -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,3 @@
1
+ # file-keeper
2
+
3
+ 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -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 = ""