{"id":13825373,"url":"https://github.com/pylover/sqlalchemy-media","last_synced_at":"2026-03-16T06:38:00.429Z","repository":{"id":43665330,"uuid":"68097204","full_name":"pylover/sqlalchemy-media","owner":"pylover","description":"Another attachment extension for SqlAlchemy to manage assets which are associated with database models but you don't want to store them into the database","archived":false,"fork":false,"pushed_at":"2023-05-25T07:09:53.000Z","size":9011,"stargazers_count":89,"open_issues_count":21,"forks_count":27,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-09T15:03:36.902Z","etag":null,"topics":["assets-management","orm","python-3-5","python-3-6","sqlalchemy"],"latest_commit_sha":null,"homepage":"http://sqlalchemy-media.dobisel.com/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pylover.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-13T10:06:57.000Z","updated_at":"2025-02-26T10:41:39.000Z","dependencies_parsed_at":"2024-05-28T19:27:12.976Z","dependency_job_id":"5d2c7b43-cd12-44cf-bd9c-7d5aa3ec4ba1","html_url":"https://github.com/pylover/sqlalchemy-media","commit_stats":null,"previous_names":[],"tags_count":65,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pylover%2Fsqlalchemy-media","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pylover%2Fsqlalchemy-media/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pylover%2Fsqlalchemy-media/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pylover%2Fsqlalchemy-media/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pylover","download_url":"https://codeload.github.com/pylover/sqlalchemy-media/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055276,"owners_count":21040156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["assets-management","orm","python-3-5","python-3-6","sqlalchemy"],"created_at":"2024-08-04T09:01:19.778Z","updated_at":"2026-03-16T06:37:55.398Z","avatar_url":"https://github.com/pylover.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"sqlalchemy-media\n================\n\n**This project is not maintained anymore, But any new maintainer and or PR will be appreciated.**\n\n.. image:: http://img.shields.io/pypi/v/sqlalchemy-media.svg\n     :target: https://pypi.python.org/pypi/sqlalchemy-media\n\n.. image:: https://travis-ci.org/pylover/sqlalchemy-media.svg?branch=master\n     :target: https://travis-ci.org/pylover/sqlalchemy-media\n\n.. image:: https://coveralls.io/repos/github/pylover/sqlalchemy-media/badge.svg?branch=master\n     :target: https://coveralls.io/github/pylover/sqlalchemy-media?branch=master\n\n.. image:: https://img.shields.io/badge/license-MIT-brightgreen.svg\n     :target: https://github.com/pylover/sqlalchemy-media/blob/master/LICENSE\n\n.. image:: https://img.shields.io/gitter/room/pylover/sqlalchemy-media.svg\n     :target: https://gitter.im/pylover/sqlalchemy-media\n\n.. image:: https://pepy.tech/badge/sqlalchemy-media\n     :target: https://pepy.tech/project/sqlalchemy-media\n\n.. image:: https://pepy.tech/badge/sqlalchemy-media/month\n     :target: https://pepy.tech/project/sqlalchemy-media\n\n.. image:: https://pepy.tech/badge/sqlalchemy-media/week\n     :target: https://pepy.tech/project/sqlalchemy-media\n\n\n\nDocumentation\n-------------\n\nSee the documentation_ for full description.\n\n\nWhy ?\n-----\nNowadays, most of the database applications are used to allow users to upload \nand attach files of various types to ORM models.\n\nHandling those jobs is not simple if you have to care about Security, \nHigh-Availability, Scalability, CDN and more things you may have already \nbeen concerned. Accepting a file from public space, analysing, validating,\nprocessing(Normalizing) and making it available to public space again is \nthe main goal of this project.\n\nSql-Alchemy is the best platform for implementing this stuff. It has \nthe SqlAlchemyMutable_ types facility to manipulate the objects with any \ntype in-place. why not ?\n\n.. note:: The main idea comes from dahlia's SQLAlchemyImageAttach_.\n\nOverview\n--------\n\n- Storing and locating any file, tracking it by sqlalchemy models.\n- Storage layer is completely separated from data model, with a simple api: (put, delete, open, locate)\n- Using any SqlAlchemy data type which interfaces Python dictionary. This is achieved by using the\n  SqlAlchemyTypeDecorators_ and SqlAlchemyMutable_.\n- Offering ``delete_orphan`` flag to automatically delete files which orphaned via attribute set or delete from\n  collections, or objects leaved in memory alone! by setting it's last pointer to None.\n- Attaching files from Url, LocalFileSystem and Streams.\n- Extracting the file's mimetype from the backend stream if possible, using ``magic`` module.\n- Limiting file size(min, max), to prevent DOS attacks.\n- Adding timestamp in url to help caching.\n- Auto generating thumbnails, using ``width``, ``height`` and or ``ratio``.\n- Analyzing files \u0026 images using ``magic`` and ``wand``.\n- Validating ``mimetype``, ``width``, ``height`` and image ``ratio``.\n- Automatically resize \u0026 reformat images before store.\n- Thanks to `@YukSeungChan \u003chttps://github.com/YukSeungChan\u003e`_, for implementing the ``S3Store`` and ``OS2Store``.\n- Thanks to `@ribrea \u003chttps://github.com/ribrea\u003e`_, for Google cloud storage support.\n- SSH/SFTP Store\n\n\nQuick Start\n-----------\n\nHere is a simple example to see how to use this library:\n\n.. code-block:: python\n\n  import functools\n  import json\n  from os.path import exists, join\n\n  from sqlalchemy import create_engine, TypeDecorator, Unicode, Column, Integer\n  from sqlalchemy.orm import sessionmaker\n  from sqlalchemy.ext.declarative import declarative_base\n\n  from sqlalchemy_media import StoreManager, FileSystemStore, Image, ImageAnalyzer, ImageValidator, ImageProcessor\n\n\n  TEMP_PATH = '/tmp/sqlalchemy-media'\n  Base = declarative_base()\n  engine = create_engine('sqlite:///:memory:', echo=False)\n  session_factory = sessionmaker(bind=engine)\n\n\n  StoreManager.register(\n      'fs',\n      functools.partial(FileSystemStore, TEMP_PATH, 'http://static.example.org/'),\n      default=True\n  )\n\n\n  class Json(TypeDecorator):\n      impl = Unicode\n\n      def process_bind_param(self, value, engine):\n          return json.dumps(value)\n\n      def process_result_value(self, value, engine):\n          if value is None:\n              return None\n\n          return json.loads(value)\n\n\n  class ProfileImage(Image):\n      __pre_processors__ = [\n          ImageAnalyzer(),\n          ImageValidator(\n              minimum=(80, 80),\n              maximum=(800, 600),\n              min_aspect_ratio=1.2,\n              content_types=['image/jpeg', 'image/png']\n          ),\n          ImageProcessor(\n              fmt='jpeg',\n              width=120,\n              crop=dict(\n                  left='10%',\n                  top='10%',\n                  width='80%',\n                  height='80%',\n              )\n          )\n      ]\n\n\n  class Person(Base):\n      __tablename__ = 'person'\n\n      id = Column(Integer, primary_key=True)\n      name = Column(Unicode(100))\n      image = Column(ProfileImage.as_mutable(Json))\n\n      def __repr__(self):\n          return \"\u003c%s id=%s\u003e\" % (self.name, self.id)\n\n\n  Base.metadata.create_all(engine, checkfirst=True)\n\n  if __name__ == '__main__':\n      session = session_factory()\n\n      with StoreManager(session):\n          person1 = Person()\n          person1.image = ProfileImage.create_from('https://www.python.org/static/img/python-logo@2x.png')\n          session.add(person1)\n          session.commit()\n\n          print('Content type:', person1.image.content_type)\n          print('Extension:', person1.image.extension)\n          print('Length:', person1.image.length)\n          print('Original filename:', person1.image.original_filename)\n\n          thumbnail = person1.image.get_thumbnail(width=32, auto_generate=True)\n          print(thumbnail.height)\n          assert exists(join(TEMP_PATH, thumbnail.path))\n\n          thumbnail = person1.image.get_thumbnail(ratio=.3, auto_generate=True)\n          print(thumbnail.width, thumbnail.height)\n          assert exists(join(TEMP_PATH, thumbnail.path))\n\n          person1.image.attach('https://www.python.org/static/img/python-logo.png')\n          session.commit()\n\n          print('Content type:', person1.image.content_type)\n          print('Extension:', person1.image.extension)\n          print('Length:', person1.image.length)\n          print('Original filename:', person1.image.original_filename)\n\n      with StoreManager(session, delete_orphan=True):\n          deleted_filename = join(TEMP_PATH, person1.image.path)\n          person1.image = None\n          session.commit()\n\n          assert not exists(deleted_filename)\n\n          person1.image = ProfileImage.create_from('https://www.python.org/static/img/python-logo.png')\n          session.commit()\n\n          print('Content type:', person1.image.content_type)\n          print('Extension:', person1.image.extension)\n          print('Length:', person1.image.length)\n          print('Original filename:', person1.image.original_filename)\n\n\nWill produce::\n\n    Content type: image/jpeg\n    Extension: .jpg\n    Length: 2020\n    Original filename: https://www.python.org/static/img/python-logo@2x.png\n    8\n    28 7\n    Content type: image/jpeg\n    Extension: .jpg\n    Length: 2080\n    Original filename: https://www.python.org/static/img/python-logo.png\n    Content type: image/jpeg\n    Extension: .jpg\n    Length: 2080\n    Original filename: https://www.python.org/static/img/python-logo.png\n\n\nExamples\n--------\n\nCheckout the `examples` directory in the root of the repo.\n\nBranching\n---------\n\nA new branching model is applied to this repository, which consists of a \nmaster branch and release branches.\n\n\nContribution\n------------\n\n- Remember the zen of Python(``import this``) before doing anything.\n- Maximum line width is: `79`.\n\n\n.. _SqlAlchemyMutable: http://docs.sqlalchemy.org/en/latest/orm/extensions/mutable.html\n.. _SqlAlchemyTypeDecorators: http://docs.sqlalchemy.org/en/latest/core/custom_types.html#typedecorator-recipes\n.. _SQLAlchemyImageAttach: https://github.com/dahlia/sqlalchemy-imageattach\n.. _documentation: http://sqlalchemy-media.dobisel.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpylover%2Fsqlalchemy-media","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpylover%2Fsqlalchemy-media","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpylover%2Fsqlalchemy-media/lists"}