sqlalchemy-deprecated-column 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .deprecate_field import configure, deprecated_column
2
+
3
+ __all__ = ["configure", "deprecated_column"]
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from sqlalchemy import null
8
+ from sqlalchemy.ext.hybrid import hybrid_property
9
+ from sqlalchemy.orm import mapped_column
10
+
11
+
12
+ @dataclass
13
+ class _Configuration:
14
+ alembic_mode: bool = False
15
+
16
+
17
+ _config = _Configuration()
18
+
19
+
20
+ def configure(alembic_mode: bool = False) -> None:
21
+ """Configure sqlalchemy-deprecate-fields behaviour.
22
+
23
+ Call with ``alembic_mode=True`` at the top of ``alembic/env.py``, before
24
+ any model imports, so that Alembic sees deprecated columns as real nullable
25
+ columns and does not generate DROP COLUMN migrations.
26
+ """
27
+ _config.alembic_mode = alembic_mode
28
+
29
+
30
+ class _DeprecatedColumn:
31
+ def __init__(self, *_args: Any, **_kwargs: Any) -> None:
32
+ pass
33
+
34
+ def __set_name__(self, owner: type, name: str) -> None:
35
+ @hybrid_property
36
+ def prop(instance: Any) -> None:
37
+ warnings.warn(
38
+ f"accessing deprecated field {type(instance).__name__}.{name}",
39
+ DeprecationWarning,
40
+ stacklevel=2,
41
+ )
42
+ return None
43
+
44
+ @prop.inplace.setter
45
+ def _(instance: Any, value: Any) -> None:
46
+ warnings.warn(
47
+ f"writing to deprecated field {type(instance).__name__}.{name}",
48
+ DeprecationWarning,
49
+ stacklevel=2,
50
+ )
51
+
52
+ @prop.inplace.expression
53
+ @classmethod
54
+ def _(cls: type) -> Any:
55
+ warnings.warn(
56
+ f"referencing deprecated class field {cls.__name__}.{name}",
57
+ DeprecationWarning,
58
+ stacklevel=2,
59
+ )
60
+ return null()
61
+
62
+ setattr(owner, name, prop)
63
+
64
+
65
+ def deprecated_column(*args: Any, **kwargs: Any) -> Any:
66
+ """Drop-in replacement for mapped_column() that marks the column as deprecated.
67
+
68
+ In normal mode the column is excluded from the ORM mapper entirely:
69
+ it will not appear in SELECT, INSERT, UPDATE, or RETURNING statements.
70
+ Instance reads return None and emit a DeprecationWarning; writes emit a
71
+ warning and are silently discarded.
72
+
73
+ In Alembic mode (after ``configure(alembic_mode=True)``) the call is
74
+ forwarded to ``mapped_column(*args, nullable=True, **kwargs)`` so that
75
+ Alembic sees the column as a regular nullable column and generates correct
76
+ migrations.
77
+
78
+ Usage::
79
+
80
+ class User(Base):
81
+ __tablename__ = "users"
82
+ id: Mapped[int] = mapped_column(primary_key=True)
83
+ old_email: Mapped[str] = deprecated_column(String(200))
84
+ """
85
+ if _config.alembic_mode:
86
+ kwargs["nullable"] = True
87
+ return mapped_column(*args, **kwargs)
88
+ return _DeprecatedColumn(*args, **kwargs)
File without changes
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlalchemy-deprecated-column
3
+ Version: 0.1.0
4
+ Summary: Utility to mark SQLAlchemy ORM columns as deprecated to allow removing them in a backwards compatible manner.
5
+ Author: Jakub Bacic
6
+ Author-email: Jakub Bacic <jakub.bacic@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Typing :: Typed
20
+ Classifier: Topic :: Database
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Dist: sqlalchemy>=2.0
23
+ Requires-Python: >=3.10
24
+ Project-URL: Repository, https://github.com/jakub-bacic/sqlalchemy-deprecate-fields
25
+ Project-URL: Issues, https://github.com/jakub-bacic/sqlalchemy-deprecate-fields/issues
26
+ Project-URL: Changelog, https://github.com/jakub-bacic/sqlalchemy-deprecate-fields/blob/main/CHANGELOG.md
27
+ Description-Content-Type: text/markdown
28
+
29
+ # sqlalchemy-deprecated-column
30
+
31
+ Safely remove SQLAlchemy ORM columns through a gradual deprecation process.
32
+ Inspired by [django-deprecate-fields](https://github.com/3YOURMIND/django-deprecate-fields).
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install sqlalchemy-deprecated-column
38
+ ```
39
+
40
+ ## How it works
41
+
42
+ Removing a column from a live database requires coordination between code and schema changes. Dropping the column in a single step would break any running application instances that still reference it. `deprecated_column()` lets you do this safely in three steps:
43
+
44
+ 1. **Deprecate**: replace `mapped_column()` with `deprecated_column()` and run an Alembic migration. The column stays in the database but becomes nullable if it wasn't already.
45
+ 2. **Deploy**: the column is hidden from the ORM — it no longer appears in any generated SQL. Any remaining code that reads or writes the column gets a `DeprecationWarning` at runtime, making stale references easy to find and remove.
46
+ 3. **Remove**: once all references are gone, delete the `deprecated_column()` definition from the model and run a final migration to drop the column from the database.
47
+
48
+ ## Usage
49
+
50
+ Replace `mapped_column()` with `deprecated_column()` for any column you want to deprecate:
51
+
52
+ ```python
53
+ from sqlalchemy_deprecated_column import deprecated_column
54
+
55
+ class User(Base):
56
+ __tablename__ = "users"
57
+
58
+ id: Mapped[int] = mapped_column(primary_key=True)
59
+ email: Mapped[str] = mapped_column(String)
60
+ old_username: Mapped[str] = deprecated_column(String) # was: mapped_column(String)
61
+ ```
62
+
63
+ While the column is deprecated the library:
64
+
65
+ - **Hides it from the ORM**: the column is excluded from all generated SQL queries — existing application code stays compatible even after the column is eventually dropped from the database.
66
+ - **Warns on instance read**: `instance.old_username` returns `None` and emits a `DeprecationWarning` naming the model and column, so the call site is easy to locate.
67
+ - **Warns on class-level reference**: `User.old_username` (e.g. in filter expressions) emits a `DeprecationWarning` and evaluates to SQL `NULL`.
68
+ - **Warns on write and discards the value**: `instance.old_username = "x"` emits a `DeprecationWarning` and silently drops the value, so no stale data is written to the database.
69
+
70
+ ## Alembic integration
71
+
72
+ Add the following **at the top of `alembic/env.py`, before any model imports**:
73
+
74
+ ```python
75
+ import sqlalchemy_deprecated_column
76
+ sqlalchemy_deprecated_column.configure(alembic_mode=True)
77
+
78
+ # model imports must come after configure()
79
+ from myapp import mymodel
80
+ target_metadata = mymodel.Base.metadata
81
+ ```
82
+
83
+ In Alembic mode, `deprecated_column()` acts as `mapped_column(nullable=True)`. Alembic will:
84
+
85
+ - **Not** generate `DROP COLUMN` for deprecated columns.
86
+ - **Generate `ALTER TABLE … DROP NOT NULL`** if the column was originally non-nullable. This is needed because once the column is deprecated the ORM stops including it in `INSERT` statements — a `NOT NULL` column without a value would cause those inserts to fail.
87
+
88
+ ## Requirements
89
+
90
+ - Python 3.10+
91
+ - SQLAlchemy 2.0+
92
+
93
+ ## License
94
+
95
+ The code in this project is licensed under MIT license. See [LICENSE](./LICENSE) for more information.
@@ -0,0 +1,7 @@
1
+ sqlalchemy_deprecated_column/__init__.py,sha256=zXxb7K26pIx6f36BbV0VBZVDEk9_wmwVQRhJZ9EKVc8,104
2
+ sqlalchemy_deprecated_column/deprecate_field.py,sha256=SirAUjWaluYb8eO8T4UJKVQI_eH7zGau-rgztyMmRWs,2758
3
+ sqlalchemy_deprecated_column/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ sqlalchemy_deprecated_column-0.1.0.dist-info/licenses/LICENSE,sha256=m77pqLLodhsaWr2RD7HxLjVDhci81XufaS719DWKrAI,1068
5
+ sqlalchemy_deprecated_column-0.1.0.dist-info/WHEEL,sha256=WvwXFgRajeoYkfRVmDhkP4Qlqo31Mk687zIO2QQoFmw,80
6
+ sqlalchemy_deprecated_column-0.1.0.dist-info/METADATA,sha256=rw8YdGSZcePNNKdz2O0TuqU7VlOk1fK3XdtgH7fO76s,4472
7
+ sqlalchemy_deprecated_column-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.7
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jakub Bacic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.