Skip to content

Commit

Permalink
Merge pull request #11 from jowilf/dev
Browse files Browse the repository at this point in the history
Accept additional metadata from `File` object
  • Loading branch information
jowilf authored Aug 30, 2022
2 parents 5a4fb5a + f7a1dfc commit 56f91b4
Show file tree
Hide file tree
Showing 21 changed files with 250 additions and 1,557 deletions.
202 changes: 14 additions & 188 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,190 +1,16 @@

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
*.pyc
env*
.mypy_cache
.vscode
.idea
poetry.lock
dist
htmlcov
*.egg-info
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/
doc/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# Pipfile.lock

# poetry
# poetry.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
*.env*
.env/
.venv/
env/
venv/
ENV/
env.bak/
venv.bak/
pythonenv*

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/public
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# operating system-related files
# *.DS_Store
Thumbs.db

# profiling data
.prof

### VirtualEnv ###
# Virtualenv
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
pyvenv.cfg
.venv
pip-selfcheck.json

### VisualStudioCode ###
*.code-workspace
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
.history
.ionide

.idea/
site
*.db
.cache
__pycache__/
.pytest_cache/
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.4] - 2022-08-30

---

### Added

- Add `upload_storage` to the default information saved into the database. Before, `upload_storage` can be extracted
from `path` attribute. Now you can access directly with `file['upload_storage']` by
@jowilf https://github.com/jowilf/sqlalchemy-file/pull/11
- Accept additional metadata from `File` object by @jowilf https://github.com/jowilf/sqlalchemy-file/pull/11
- Add section [Upload File](https://jowilf.github.io/sqlalchemy-file/tutorial/using-files-in-models/#upload-file) to the
documentation

## [0.1.3] - 2022-08-23

---

### Added

- Add `thumbnail_size` property to ImageField by @jowilf https://github.com/jowilf/sqlalchemy-file/pull/9

## [0.1.2] - 2022-08-11

---

### Added

- Add CHANGELOG.md
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.4] - 2022-08-30

---

### Added

- Add `upload_storage` to the default information saved into the database. Before, `upload_storage` can be extracted
from `path` attribute. Now you can access directly with `file['upload_storage']` in [#11](https://github.com/jowilf/sqlalchemy-file/pull/11)
- Accept additional metadata from `File` object in [#11](https://github.com/jowilf/sqlalchemy-file/pull/11)
- Add section [Upload File](https://jowilf.github.io/sqlalchemy-file/tutorial/using-files-in-models/#upload-file) to the
documentation

## [0.1.3] - 2022-08-23

---
Expand Down
100 changes: 96 additions & 4 deletions docs/tutorial/using-files-in-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,104 @@ uploaded file is a valid image.
title = Column(String(100), unique=True)
cover = Column(ImageField(thumbnail_size=(128, 128)))
```
## Uploaded Files Information
## Upload File

Let's say you defined your model like this
```python
class Attachment(Base):
__tablename__ = "attachment"

id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(String(50), unique=True)
content = Column(FileField)
```
and configure your storage like this
```python
container = LocalStorageDriver("/tmp/storage").get_container("attachment")
StorageManager.add_storage("default", container)
```

### Save file object

Whenever a supported object is assigned to a [FileField][sqlalchemy_file.types.FileField] or [ImageField][sqlalchemy_file.types.ImageField]
it will be converted to a [File][sqlalchemy_file.file.File] object.
```python
with Session(engine) as session:
session.add(Attachment(name="attachment1", content=open("./example.txt", "rb")))
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")
session.add(Attachment(name="attachment4", content=file))
session.commit()
```
The file itself will be uploaded to your configured storage, and only the [File][sqlalchemy_file.file.File]
information will be stored on the database as JSON.

This is the same object you will get back when reloading the models from database and apart from the file itself which is accessible
through the `.file` property, it provides additional attributes described into the [File][sqlalchemy_file.file.File] documentation itself.
### Retrieve file object

This is the same [File][sqlalchemy_file.file.File] object you will get back when reloading the models from database and the file itself is accessible
through the `.file` property.

!!! note
Check the [File][sqlalchemy_file.file.File] documentation for all default attributes save into the database.

```python
with Session(engine) as session:
attachment = session.execute(
select(Attachment).where(Attachment.name == "attachment3")
).scalar_one()
assert attachment.content.saved # saved is True for saved file
assert attachment.content.file.read() == b"Hello world" # access file content
assert attachment.content["filename"] is not None # `unnamed` when no filename are provided
assert attachment.content["file_id"] is not None # uuid v4
assert attachment.content["upload_storage"] == "default"
assert attachment.content["content_type"] is not None
assert attachment.content["uploaded_at"] is not None
```

### Save additional information

It's important to note that [File][sqlalchemy_file.file.File] object inherit from python `dict`.
Therefore, you can add additional information to your file object like a dict object. Just make sure to not use
the default attributes used by [File][sqlalchemy_file.file.File] object internally.

!!! Example
```python
content = File(open("./example.txt", "rb"),custom_key1="custom_value1", custom_key2="custom_value2")
content["custom_key3"] = "custom_value3"
attachment = Attachment(name="Dummy", content=content)

session.add(attachment)
session.commit()
session.refresh(attachment)

assert attachment.custom_key1 == "custom_value1"
assert attachment.custom_key2 == "custom_value2"
assert attachment["custom_key3"] == "custom_value3"
```

!!! important
[File][sqlalchemy_file.file.File] provides also attribute style access.
You can access your keys as attributes.

### Metadata

*SQLAlchemy-file* store the uploaded file with some metadata. Only `filename` and `content_type` are sent by default,
. You can complete with `metadata` key inside your [File][sqlalchemy_file.file.File] object.

!!! Example
```py hl_lines="2"
with Session(engine) as session:
content = File(DummyFile(), metadata={"key1": "val1", "key2": "val2"})
attachment = Attachment(name="Additional metadata", content=content)
session.add(attachment)
session.commit()
attachment = session.execute(
select(Attachment).where(Attachment.name == "Additional metadata")
).scalar_one()
assert attachment.content.file.object.meta_data["key1"] == "val1"
assert attachment.content.file.object.meta_data["key2"] == "val2"
```

## Uploading on a Specific Storage

Expand Down Expand Up @@ -119,7 +211,7 @@ Validators can add additional properties to the file object. For example
the file object.

**SQLAlchemy-file** has built-in validators to get started, but you can create your own validator
by extending [ValidationError][sqlalchemy_file.exceptions.ValidationError] base class.
by extending [Validator][sqlalchemy_file.validators.Validator] base class.

Built-in validators:

Expand Down
Empty file added docs_src/__init__.py
Empty file.
Empty file added docs_src/tutorial/__init__.py
Empty file.
Empty file.
Empty file.
Loading

0 comments on commit 56f91b4

Please sign in to comment.