Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - edelooff/sqlalchemy-json: Full-featured JSON type with mutation tracking for SQLAlchemy · GitHub
Skip to content

Latest commit

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sqlalchemy-json

SQLAlchemy-JSON provides mutation-tracked JSON types to SQLAlchemy:

  • MutableJson is a straightforward implementation for keeping track of top-level changes to JSON objects;
  • NestedMutableJson is an extension of this which tracks changes even when these happen in nested objects or arrays (Python dicts and lists).

Examples

Basic change tracking

This is essentially the SQLAlchemy mutable JSON recipe. We define a simple author model which list the author's name and a property handles for various social media handles used:

classAuthor(Base):
name=Column(Text)
handles=Column(MutableJson)

Or, using the declarative mapping style:

classCategory(Base):
__tablename__="categories"id=mapped_column(Integer, primary_key=True)
created_at: Mapped[DateTime] =mapped_column(DateTime, default=datetime.now)
updated_at: Mapped[DateTime] =mapped_column(
DateTime, default=datetime.now, onupdate=datetime.now
)
keywords: Mapped[list[str]] =mapped_column(MutableJson)

The example below loads one of the existing authors and retrieves the mapping of social media handles. The error in the twitter handle is then corrected and committed. The change is detected by SQLAlchemy and the appropriate UPDATE statement is generated.

>>>author=session.query(Author).first()
>>>author.handles
{'twitter': '@JohnDoe', 'facebook': 'JohnDoe'}
>>>author.handles['twitter'] ='@JDoe'>>>session.commit()
>>>author.handles
{'twitter': '@JDoe', 'facebook': 'JohnDoe'}

Nested change tracking

The example below defines a simple model for articles. One of the properties on this model is a mutable JSON structure called references which includes a count of links that the article contains, grouped by domain:

classArticle(Base):
author=Column(ForeignKey('author.name'))
content=Column(Text)
references=Column(NestedMutableJson)

With this in place, an existing article is loaded and its current references inspected. Following that, the count for one of these is increased by ten, and the session is committed:

>>>article=session.query(Article).first()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 4, 'zzzeek/sqlalchemy': 7}}
>>>article.references['github.com']['edelooff/sqlalchemy-json'] +=10>>>session.commit()
>>>article.references
{'github.com': {'edelooff/sqlalchemy-json': 14, 'zzzeek/sqlalchemy': 7}}

Had the articles model used MutableJson like in the previous example this code would have failed. This is because the top level dictionary is never altered directly. The nested mutable ensures the change happening at the lower level bubbles up to the outermost container.

Non-native JSON / other serialization types

By default, sqlalchemy-json uses the JSON column type provided by SQLAlchemy (specifically sqlalchemy.types.JSON.) If you wish to use another type (e.g. PostgreSQL's JSONB), your database does not natively support JSON (e.g. versions of SQLite before 3.37.2/), or you wish to serialize to a format other than JSON, you'll need to provide a different backing type.

This is done by using the utility function mutable_json_type. This type creator function accepts two parameters:

  • dbtype controls the database type used. This can be an existing type provided by SQLAlchemy or SQLALchemy-utils, or an augmented type to provide serialization to any other format;
  • nested controls whether the created type is made mutable based on MutableDict or NestedMutable (defaults to False for MutableDict).
importjsonfromsqlalchemyimportJSON, String, TypeDecoratorfromsqlalchemy.dialects.postgresqlimportJSONBfromsqlalchemy_jsonimportmutable_json_typeclassJsonString(TypeDecorator):
"""Enables JSON storage by encoding and decoding on the fly."""impl=Stringdefprocess_bind_param(self, value, dialect):
returnjson.dumps(value)
defprocess_result_value(self, value, dialect):
returnjson.loads(value)
postgres_jsonb_mutable=mutable_json_type(dbtype=JSONB)
string_backed_nested_mutable=mutable_json_type(dbtype=JsonString, nested=True)

Dependencies

  • sqlalchemy

Development

Here's how to setup your development environment:

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"# run tests
pytest

Changelog

0.7.0

  • Adds support for top-level list for MutableJson, rather than having that support only be available in the nested variant (#51)
  • Adds pytest as development dependency

0.6.0

  • Fixes pickling support (#36)
  • Drops python 2.x support (previously claimed, but already broken for some time)
  • Removes test runners for CPython 3.6 since Github actions support has been dropped

0.5.0

  • Fixes a lingering Python 3 compatibility issue (cmp parameter for TrackedList.sort)
  • Adds pickling and unpickling support (#28)
  • Adds tracking for dictionary in-place updates (#33)

0.4.0

  • Adds a type creation function to allow for custom or alternate serialization types. This allows for a way around the regression in SQLite compatibility introduced by v0.3.0.

0.3.0

  • Switches JSON base type to sqlalchemy.types.JSON from deprecated JSON type provided by SQLAlchemy-utils.

0.2.2

  • Fixes a bug where assigning None to the column resulted in an error (#10)

0.2.1

  • Fixes a typo in the README found after uploading 0.2.0 to PyPI.

0.2.0 (unreleased)

  • Now uses JSONType provided by SQLAlchemy-utils to handle backend storage;
  • Backwards incompatible: Changed class name JsonObject to MutableJson and NestedJsonObject to NestedMutableJson
  • Outermost container for NestedMutableJson can now be an array (Python list)

0.1.0 (unreleased)

Initial version. This initially carried a 1.0.0 version number but has never been released on PyPI.

About

Full-featured JSON type with mutation tracking for SQLAlchemy

Resources

Stars

195 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages