Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional Ruff rules & update dependencies #77

Merged
merged 6 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10","3.11" ]
python-version: [ "3.8", "3.9", "3.10","3.11" ]

services:
postgres:
Expand Down
9 changes: 4 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.9.0
hooks:
- id: pyupgrade
args:
- --py3-plus
- --keep-runtime-typing
- --py38-plus
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.254
rev: v0.0.277
hooks:
- id: ruff
args:
- --fix
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.7.0
hooks:
- id: black
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ engine = create_engine(
)
Base.metadata.create_all(engine)

with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
with Session(engine) as session, open("./example.txt", "rb") as local_file:
session.add(Attachment(name="attachment1", content=local_file))
session.add(Attachment(name="attachment2", content=b"Hello world"))
session.add(Attachment(name="attachment3", content="Hello world"))
file = File(content="Hello World", filename="hello.txt", content_type="text/plain")
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ engine = create_engine(
)
Base.metadata.create_all(engine)

with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
with Session(engine) as session, open("./example.txt", "rb") as local_file:
session.add(Attachment(name="attachment1", content=local_file))
session.add(Attachment(name="attachment2", content=b"Hello world"))
session.add(Attachment(name="attachment3", content="Hello world"))
file = File(content="Hello World", filename="hello.txt", content_type="text/plain")
Expand Down
4 changes: 2 additions & 2 deletions docs_src/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Attachment(Base):
)
Base.metadata.create_all(engine)

with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
with Session(engine) as session, open("./example.txt", "rb") as local_file:
session.add(Attachment(name="attachment1", content=local_file))
session.add(Attachment(name="attachment2", content=b"Hello world"))
session.add(Attachment(name="attachment3", content="Hello world"))
file = File(content="Hello World", filename="hello.txt", content_type="text/plain")
Expand Down
4 changes: 2 additions & 2 deletions docs_src/tutorial/quick-start/save_your_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Attachment(Base):
)
Base.metadata.create_all(engine)

with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
with Session(engine) as session, open("./example.txt", "rb") as local_file:
session.add(Attachment(name="attachment1", content=local_file))
session.add(Attachment(name="attachment2", content=b"Hello world"))
session.add(Attachment(name="attachment3", content="Hello world"))
# Use sqlalchemy_file.File object to provide custom filename and content_type
Expand Down
4 changes: 2 additions & 2 deletions docs_src/tutorial/using-files-in-models/007_multiple_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ class Attachment(Base):
)
Base.metadata.create_all(engine)

with Session(engine) as session:
with Session(engine) as session, open("./example.txt", "rb") as file:
session.add(
Attachment(
name="attachment1",
multiple_content=[
"from str",
b"from bytes",
open("./example.txt", "rb"),
file,
File(
content="Hello World",
filename="hello.txt",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Attachment(Base):
)
Base.metadata.create_all(engine)

with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
with Session(engine) as session, open("./example.txt", "rb") as local_file:
session.add(Attachment(name="attachment1", content=local_file))
session.add(Attachment(name="attachment2", content=b"Hello world"))
session.add(Attachment(name="attachment3", content="Hello world"))
file = File(content="Hello World", filename="hello.txt", content_type="text/plain")
Expand Down
10 changes: 4 additions & 6 deletions examples/fastapi/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import os
from typing import Generator, List, Optional, Union

Expand Down Expand Up @@ -31,13 +32,10 @@
os.makedirs("./upload_dir", 0o777, exist_ok=True)
driver = get_driver(Provider.LOCAL)("./upload_dir")

# cls = get_driver(Provider.MINIO)
# driver = cls("minioadmin", "minioadmin", secure=False, host="127.0.0.1", port=9000)

try:
with contextlib.suppress(ContainerAlreadyExistsError):
driver.create_container(container_name="category")
except ContainerAlreadyExistsError:
pass


container = driver.get_container(container_name="category")

Expand Down Expand Up @@ -135,7 +133,7 @@ async def serve_files(storage: str = Path(...), file_id: str = Path(...)):
return FileResponse(
file.get_cdn_url(), media_type=file.content_type, filename=file.filename
)
elif file.get_cdn_url() is not None:
elif file.get_cdn_url() is not None: # noqa: RET505
"""If file has public url, redirect to this url"""
return RedirectResponse(file.get_cdn_url())
else:
Expand Down
20 changes: 11 additions & 9 deletions examples/flask/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import os

from flask_sqlalchemy import SQLAlchemy
Expand Down Expand Up @@ -87,7 +88,7 @@ def serve_files(storage, file_id):
mimetype=file.content_type,
download_name=file.filename,
)
elif file.get_cdn_url() is not None:
elif file.get_cdn_url() is not None: # noqa: RET505
"""If file has public url, redirect to this url"""
return app.redirect(file.get_cdn_url())
else:
Expand All @@ -105,17 +106,18 @@ def serve_files(storage, file_id):
os.makedirs("./upload_dir", 0o777, exist_ok=True)
driver = get_driver(Provider.LOCAL)("./upload_dir")

# cls = get_driver(Provider.MINIO)
# driver = cls("minioadmin", "minioadmin", secure=False, host="127.0.0.1", port=9000)
"""
Or with MinIO:

try:
cls = get_driver(Provider.MINIO)
driver = cls("minioadmin", "minioadmin", secure=False, host="127.0.0.1", port=9000)
"""

with contextlib.suppress(ContainerAlreadyExistsError):
driver.create_container(container_name="images")
except ContainerAlreadyExistsError:
pass
try:

with contextlib.suppress(ContainerAlreadyExistsError):
driver.create_container(container_name="documents")
except ContainerAlreadyExistsError:
pass

StorageManager.add_storage("images", driver.get_container(container_name="images"))
StorageManager.add_storage(
Expand Down
37 changes: 29 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ Changelog = "https://jowilf.github.io/sqlalchemy-file/changelog/"
[project.optional-dependencies]
test = [
"pytest >=7.2.0, <7.3.0",
"mypy ==1.1.1",
"ruff ==0.0.253",
"black ==23.1.0",
"mypy ==1.4.1",
"ruff ==0.0.277",
"black ==23.7.0",
"coverage >=7.0.0, <7.3.0",
"fasteners ==0.18",
"PyMySQL[rsa] >=1.0.2, <1.1.0",
Expand All @@ -73,7 +73,7 @@ features = [
]
[tool.hatch.envs.default.scripts]
format = [
"ruff sqlalchemy_file tests docs_src --fix",
"ruff sqlalchemy_file tests --fix",
"black ."
]

Expand Down Expand Up @@ -135,18 +135,39 @@ source = ["sqlalchemy_file", "tests"]

[tool.ruff]
select = [
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"C90", # mccabe
"D", # pydocstyle
"E", # pycodestyle errors
"W", # pycodestyle warnings
"ERA", # eradicate
"F", # pyflakes
"I", # isort
"C", # flake8-comprehensions
"B", # flake8-bugbear
"N", # pep8-naming
"PIE", # flake8-pie,
"PLC", # pylint - convention
"PLE", # pylint - error
"PLW", # pylint - warning
"Q", # flake8-quotes
"RET", # flake8-return,
"RUF", # Ruff-specific rules
"SIM", # flake8-simplify
"UP", # pyupgrade
"W", # pycodestyle warnings
]
ignore = ["E501", "B904", "B008"]
ignore = ["E501", "B904", "B008", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D205"]
target-version = "py38"

[tool.ruff.pydocstyle]
convention = "google"

[tool.ruff.isort]
known-third-party = ["sqlalchemy_file"]

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]
"docs_src/**" = ["N999"]

[tool.mypy]
strict = true
warn_unused_ignores = false
Expand Down
5 changes: 2 additions & 3 deletions sqlalchemy_file/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
__version__ = "0.4.0"

from .file import File as File
from .types import FileField as FileField
from .types import ImageField as ImageField
from .file import File
from .types import FileField, ImageField
5 changes: 3 additions & 2 deletions sqlalchemy_file/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@


class BaseFile(typing.Dict[str, Any]):
"""
Base class for file object. It keeps information on a content related to a specific storage.
"""Base class for file object.

It keeps information on a content related to a specific storage.
It is a specialized dictionary that provides also attribute style access,
the dictionary parent permits easy encoding/decoding to JSON.

Expand Down
4 changes: 2 additions & 2 deletions sqlalchemy_file/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ class ValidationError(Exception):
"""Base class for ValidationError
Parameters:
key: Current Column key
msg: Validation error message
msg: Validation error message.
"""

def __init__(self, key: str, msg: str): # pragma: no cover
super().__init__("{}: {}".format(key, msg))
super().__init__(f"{key}: {msg}")
self.key = key
self.msg = msg

Expand Down
17 changes: 8 additions & 9 deletions sqlalchemy_file/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@


class File(BaseFile):
"""
Takes a file as content and uploads it to the appropriate storage
"""Takes a file as content and uploads it to the appropriate storage
according to the attached Column and file information into the
database as JSON.

Default attributes provided for all ``File`` include:

Attributes:

filename (str): This is the name of the uploaded file
file_id: This is the generated UUID for the uploaded file
upload_storage: Name of the storage used to save the uploaded file
Expand Down Expand Up @@ -69,7 +67,7 @@ def __init__(
self._thaw()

def apply_validators(self, validators: List[Validator], key: str = "") -> None:
"""Apply validators to current file"""
"""Apply validators to current file."""
for validator in validators:
validator.process(self, key)

Expand All @@ -78,13 +76,13 @@ def apply_processors(
processors: List[Processor],
upload_storage: Optional[str] = None,
) -> None:
"""Apply processors to current file"""
"""Apply processors to current file."""
for processor in processors:
processor.process(self, upload_storage)
self._freeze()

def save_to_storage(self, upload_storage: Optional[str] = None) -> None:
"""Save current file into provided `upload_storage`"""
"""Save current file into provided `upload_storage`."""
extra = self.get("extra", {})
extra.update({"content_type": self.content_type})

Expand All @@ -93,6 +91,7 @@ def save_to_storage(self, upload_storage: Optional[str] = None) -> None:
warnings.warn(
'metadata attribute is deprecated. Use extra={"meta_data": ...} instead',
DeprecationWarning,
stacklevel=1,
)
extra.update({"meta_data": metadata})

Expand All @@ -111,7 +110,7 @@ def save_to_storage(self, upload_storage: Optional[str] = None) -> None:
self["file_id"] = stored_file.name
self["upload_storage"] = upload_storage
self["uploaded_at"] = datetime.utcnow().isoformat()
self["path"] = "{}/{}".format(upload_storage, stored_file.name)
self["path"] = f"{upload_storage}/{stored_file.name}"
self["url"] = stored_file.get_cdn_url()
self["saved"] = True

Expand All @@ -125,7 +124,7 @@ def store_content(
headers: Optional[Dict[str, str]] = None,
) -> StoredFile:
"""Store content into provided `upload_storage`
with additional `metadata`. Can be use by processors
with additional `metadata`. Can be used by processors
to store additional files.
"""
name = name or str(uuid.uuid4())
Expand All @@ -137,7 +136,7 @@ def store_content(
extra=extra,
headers=headers,
)
self["files"].append("{}/{}".format(upload_storage, name))
self["files"].append(f"{upload_storage}/{name}")
return stored_file

def encode(self) -> Dict[str, Any]:
Expand Down
12 changes: 6 additions & 6 deletions sqlalchemy_file/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,24 @@ def get_metadata_file_obj(metadata: Dict[str, Any]) -> "SpooledTemporaryFile[byt


def get_content_from_file_obj(fileobj: Any) -> Any:
"""Provides a real file object from file content
"""Provides a real file object from file content.

Converts ``str`` and ``bytes`` to an actual file.
"""
if isinstance(fileobj, (str, bytes)):
f = SpooledTemporaryFile(INMEMORY_FILESIZE)
f.write(fileobj.encode() if isinstance(fileobj, str) else fileobj)
f.seek(0)
return f
elif getattr(fileobj, "file", None) is not None:
if getattr(fileobj, "file", None) is not None:
return fileobj.file
return fileobj


def get_filename_from_fileob(fileobj: Any) -> Any:
if getattr(fileobj, "filename", None) is not None:
return fileobj.filename
elif getattr(fileobj, "name", None) is not None:
if getattr(fileobj, "name", None) is not None:
return os.path.basename(fileobj.name)
return "unnamed"

Expand Down Expand Up @@ -64,13 +65,12 @@ def get_content_size_from_fileobj(file: Any) -> Any:

def convert_size(size: Union[str, int]) -> int:
# convert size to number of bytes ex: 1k -> 1000; 1Ki->1024
if isinstance(size, int):
return size
elif isinstance(size, str):
if isinstance(size, str):
pattern = re.compile(r"^(\d+)\s*(k|([KM]i?))$")
m = re.fullmatch(pattern, size)
if m is None:
raise ValueError("Invalid size %s" % size)
value, si, _ = m.groups()
si_map = {"k": 1000, "K": 1000, "M": 1000**2, "Ki": 1024, "Mi": 1024**2}
return int(value) * si_map[si]
return size
Loading