mastodon-mock 0.0.1__tar.gz

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.
Files changed (107) hide show
  1. mastodon_mock-0.0.1/.gitignore +77 -0
  2. mastodon_mock-0.0.1/LICENSE +21 -0
  3. mastodon_mock-0.0.1/PKG-INFO +106 -0
  4. mastodon_mock-0.0.1/README.md +70 -0
  5. mastodon_mock-0.0.1/mastodon_mock/__about__.py +32 -0
  6. mastodon_mock-0.0.1/mastodon_mock/__init__.py +5 -0
  7. mastodon_mock-0.0.1/mastodon_mock/__main__.py +6 -0
  8. mastodon_mock-0.0.1/mastodon_mock/alembic/env.py +54 -0
  9. mastodon_mock-0.0.1/mastodon_mock/alembic/script.py.mako +27 -0
  10. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/.gitkeep +0 -0
  11. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/4876ba204456_add_followed_tags_and_status_quote_.py +60 -0
  12. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/6b88d352c69c_add_admin_moderation_tables_and_account_.py +194 -0
  13. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/7afb4f53fee4_initial_schema.py +622 -0
  14. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/7cb2c44ee202_add_quoted_status_id_to_statuses.py +32 -0
  15. mastodon_mock-0.0.1/mastodon_mock/alembic/versions/81d660ce78b7_add_featured_tags_table.py +51 -0
  16. mastodon_mock-0.0.1/mastodon_mock/app.py +84 -0
  17. mastodon_mock-0.0.1/mastodon_mock/cli.py +66 -0
  18. mastodon_mock-0.0.1/mastodon_mock/config.py +147 -0
  19. mastodon_mock-0.0.1/mastodon_mock/db/__init__.py +1 -0
  20. mastodon_mock-0.0.1/mastodon_mock/db/base.py +42 -0
  21. mastodon_mock-0.0.1/mastodon_mock/db/models.py +525 -0
  22. mastodon_mock-0.0.1/mastodon_mock/db/seed.py +95 -0
  23. mastodon_mock-0.0.1/mastodon_mock/deps.py +80 -0
  24. mastodon_mock-0.0.1/mastodon_mock/ids.py +23 -0
  25. mastodon_mock-0.0.1/mastodon_mock/middleware.py +130 -0
  26. mastodon_mock-0.0.1/mastodon_mock/pagination.py +97 -0
  27. mastodon_mock-0.0.1/mastodon_mock/py.typed +0 -0
  28. mastodon_mock-0.0.1/mastodon_mock/routers/__init__.py +1 -0
  29. mastodon_mock-0.0.1/mastodon_mock/routers/accounts.py +591 -0
  30. mastodon_mock-0.0.1/mastodon_mock/routers/admin.py +879 -0
  31. mastodon_mock-0.0.1/mastodon_mock/routers/conversations.py +74 -0
  32. mastodon_mock-0.0.1/mastodon_mock/routers/favourites_bookmarks.py +52 -0
  33. mastodon_mock-0.0.1/mastodon_mock/routers/filters.py +265 -0
  34. mastodon_mock-0.0.1/mastodon_mock/routers/helpers.py +84 -0
  35. mastodon_mock-0.0.1/mastodon_mock/routers/instance.py +320 -0
  36. mastodon_mock-0.0.1/mastodon_mock/routers/lists.py +155 -0
  37. mastodon_mock-0.0.1/mastodon_mock/routers/media.py +128 -0
  38. mastodon_mock-0.0.1/mastodon_mock/routers/notifications.py +293 -0
  39. mastodon_mock-0.0.1/mastodon_mock/routers/oauth.py +245 -0
  40. mastodon_mock-0.0.1/mastodon_mock/routers/polls.py +74 -0
  41. mastodon_mock-0.0.1/mastodon_mock/routers/preferences.py +90 -0
  42. mastodon_mock-0.0.1/mastodon_mock/routers/relationships.py +129 -0
  43. mastodon_mock-0.0.1/mastodon_mock/routers/search.py +84 -0
  44. mastodon_mock-0.0.1/mastodon_mock/routers/statuses.py +751 -0
  45. mastodon_mock-0.0.1/mastodon_mock/routers/tags.py +231 -0
  46. mastodon_mock-0.0.1/mastodon_mock/routers/timelines.py +129 -0
  47. mastodon_mock-0.0.1/mastodon_mock/serializers/__init__.py +1 -0
  48. mastodon_mock-0.0.1/mastodon_mock/serializers/accounts.py +130 -0
  49. mastodon_mock-0.0.1/mastodon_mock/serializers/admin.py +195 -0
  50. mastodon_mock-0.0.1/mastodon_mock/serializers/common.py +54 -0
  51. mastodon_mock-0.0.1/mastodon_mock/serializers/discovery.py +114 -0
  52. mastodon_mock-0.0.1/mastodon_mock/serializers/grouped_notifications.py +105 -0
  53. mastodon_mock-0.0.1/mastodon_mock/serializers/instance.py +118 -0
  54. mastodon_mock-0.0.1/mastodon_mock/serializers/media.py +23 -0
  55. mastodon_mock-0.0.1/mastodon_mock/serializers/misc.py +114 -0
  56. mastodon_mock-0.0.1/mastodon_mock/serializers/notifications.py +38 -0
  57. mastodon_mock-0.0.1/mastodon_mock/serializers/polls.py +53 -0
  58. mastodon_mock-0.0.1/mastodon_mock/serializers/relationships.py +78 -0
  59. mastodon_mock-0.0.1/mastodon_mock/serializers/statuses.py +188 -0
  60. mastodon_mock-0.0.1/mastodon_mock/services.py +144 -0
  61. mastodon_mock-0.0.1/mastodon_mock/testing/__init__.py +19 -0
  62. mastodon_mock-0.0.1/mastodon_mock/testing/plugin.py +117 -0
  63. mastodon_mock-0.0.1/mastodon_mock/testing/seed.py +23 -0
  64. mastodon_mock-0.0.1/mastodon_mock/testing/server.py +242 -0
  65. mastodon_mock-0.0.1/mastodon_mock/testing/sugar.py +124 -0
  66. mastodon_mock-0.0.1/mastodon_mock/versioning.py +50 -0
  67. mastodon_mock-0.0.1/pyproject.toml +269 -0
  68. mastodon_mock-0.0.1/tests/__init__.py +0 -0
  69. mastodon_mock-0.0.1/tests/conftest.py +96 -0
  70. mastodon_mock-0.0.1/tests/integration/__init__.py +0 -0
  71. mastodon_mock-0.0.1/tests/integration/conftest.py +152 -0
  72. mastodon_mock-0.0.1/tests/integration/test_integration_readonly.py +109 -0
  73. mastodon_mock-0.0.1/tests/integration/test_integration_write_mock_only.py +30 -0
  74. mastodon_mock-0.0.1/tests/mock_only/__init__.py +0 -0
  75. mastodon_mock-0.0.1/tests/mock_only/test_fast_server.py +39 -0
  76. mastodon_mock-0.0.1/tests/mock_only/test_mock_endpoints.py +49 -0
  77. mastodon_mock-0.0.1/tests/mock_only/test_scope_and_ratelimit.py +172 -0
  78. mastodon_mock-0.0.1/tests/test_alembic_drift.py +78 -0
  79. mastodon_mock-0.0.1/tests/test_bughunt_bulk_by_id.py +66 -0
  80. mastodon_mock-0.0.1/tests/test_bughunt_grouped_notifications.py +88 -0
  81. mastodon_mock-0.0.1/tests/test_bughunt_mastodon_py_contract.py +107 -0
  82. mastodon_mock-0.0.1/tests/test_bughunt_unit.py +260 -0
  83. mastodon_mock-0.0.1/tests/test_cli.py +14 -0
  84. mastodon_mock-0.0.1/tests/test_cli_main.py +97 -0
  85. mastodon_mock-0.0.1/tests/test_contract_admin.py +226 -0
  86. mastodon_mock-0.0.1/tests/test_contract_core.py +128 -0
  87. mastodon_mock-0.0.1/tests/test_contract_directory.py +36 -0
  88. mastodon_mock-0.0.1/tests/test_contract_discovery.py +156 -0
  89. mastodon_mock-0.0.1/tests/test_contract_extended.py +148 -0
  90. mastodon_mock-0.0.1/tests/test_contract_filters.py +63 -0
  91. mastodon_mock-0.0.1/tests/test_contract_gaps.py +125 -0
  92. mastodon_mock-0.0.1/tests/test_contract_grouped_notifications.py +94 -0
  93. mastodon_mock-0.0.1/tests/test_contract_lists.py +48 -0
  94. mastodon_mock-0.0.1/tests/test_contract_media.py +31 -0
  95. mastodon_mock-0.0.1/tests/test_contract_oauth.py +137 -0
  96. mastodon_mock-0.0.1/tests/test_contract_pagination.py +76 -0
  97. mastodon_mock-0.0.1/tests/test_contract_quotes.py +50 -0
  98. mastodon_mock-0.0.1/tests/test_contract_scheduled.py +52 -0
  99. mastodon_mock-0.0.1/tests/test_contract_status_validation.py +43 -0
  100. mastodon_mock-0.0.1/tests/test_contract_tags_quotes.py +161 -0
  101. mastodon_mock-0.0.1/tests/test_contract_timelines.py +40 -0
  102. mastodon_mock-0.0.1/tests/test_file_db.py +50 -0
  103. mastodon_mock-0.0.1/tests/test_pytest_fixtures_contract.py +159 -0
  104. mastodon_mock-0.0.1/tests/test_testing_plugin.py +172 -0
  105. mastodon_mock-0.0.1/tests/test_unit.py +64 -0
  106. mastodon_mock-0.0.1/tests/test_unit_media.py +36 -0
  107. mastodon_mock-0.0.1/tests/test_versions.py +55 -0
@@ -0,0 +1,77 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ eggs/
12
+ parts/
13
+ var/
14
+ sdist/
15
+ wheels/
16
+ pip-wheel-metadata/
17
+ share/python-wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ MANIFEST
21
+
22
+ # Virtual environments
23
+ .env
24
+ .venv
25
+ env/
26
+ venv/
27
+ ENV/
28
+ env.bak/
29
+ venv.bak/
30
+
31
+ # Testing
32
+ .tox/
33
+ .nox/
34
+ .coverage
35
+ .coverage.*
36
+ .cache
37
+ nosetests.xml
38
+ coverage.xml
39
+ *.cover
40
+ *.py,cover
41
+ .hypothesis/
42
+ .pytest_cache/
43
+ htmlcov/
44
+ junit.xml
45
+
46
+ # Type checking
47
+ .mypy_cache/
48
+ .dmypy.json
49
+ dmypy.json
50
+ .pyre/
51
+
52
+ # Linting
53
+ .ruff_cache/
54
+
55
+ # Docs
56
+ site/
57
+ docs/_build/
58
+
59
+ # Build history
60
+ .build_logs/
61
+ .build_history/
62
+ .uv/
63
+
64
+ # Editors
65
+ .idea/
66
+ .vscode/
67
+ *.swp
68
+ *.swo
69
+ *~
70
+ .DS_Store
71
+
72
+ # Local dev SQLite database (file-backed mode)
73
+ mastodon_mock.db
74
+ *.db
75
+
76
+ # Vendored reference copy of Mastodon.py (not a build dependency; installed from PyPI)
77
+ /Mastodon.py/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Martin
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.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: mastodon_mock
3
+ Version: 0.0.1
4
+ Summary: Stateful in-memory/SQLite mock of the Mastodon REST API for testing Mastodon clients
5
+ Project-URL: Repository, https://github.com/matthewdeanmartin/mastodon_mock
6
+ Project-URL: Documentation, https://mastodon_mock.readthedocs.io/en/latest/
7
+ Project-URL: Changelog, https://github.com/matthewdeanmartin/mastodon_mock/blob/main/CHANGELOG.md
8
+ Project-URL: homepage, https://github.com/matthewdeanmartin/mastodon_mock
9
+ Project-URL: issues, https://github.com/matthewdeanmartin/mastodon_mock/issues/
10
+ Author-email: Matthew Martin <matthewdeanmartin@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: fastapi,fediverse,mastodon,mastodon.py,mock,rest-api,testing
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: FastAPI
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
23
+ Classifier: Topic :: Software Development :: Testing :: Mocking
24
+ Requires-Python: >=3.13
25
+ Requires-Dist: alembic>=1.13.0
26
+ Requires-Dist: fastapi>=0.115.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: python-multipart>=0.0.9
29
+ Requires-Dist: sqlalchemy>=2.0.0
30
+ Requires-Dist: uvicorn>=0.30.0
31
+ Provides-Extra: test
32
+ Requires-Dist: httpx>=0.27.0; extra == 'test'
33
+ Requires-Dist: mastodon-py>=2.2.1; extra == 'test'
34
+ Requires-Dist: uvicorn>=0.30.0; extra == 'test'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # Mastodon Mock
38
+
39
+ [![PyPI version](https://badge.fury.io/py/mastodon_mock.svg)](https://badge.fury.io/py/mastodon_mock)
40
+ [![CI](https://github.com/matthewdeanmartin/mastodon_mock/actions/workflows/build.yml/badge.svg)](https://github.com/matthewdeanmartin/mastodon_mock/actions/workflows/build.yml)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/mastodon_mock.svg)](https://pypi.org/project/mastodon_mock/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/LICENSE)
43
+
44
+ `mastodon_mock` is a stateful, in-process mock of the Mastodon REST API. It runs a real
45
+ FastAPI server backed by a minimal in-memory (or on-disk) SQLite database, so client code
46
+ — including [Mastodon.py](https://github.com/halcy/Mastodon.py) — can post statuses, follow
47
+ accounts, build timelines, manage lists and filters, and exercise OAuth flows against a
48
+ fast, deterministic, side-effect-free target. It is intended for testing and local
49
+ development where talking to a live Mastodon instance is slow, flaky, or undesirable.
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pipx install mastodon_mock
55
+ ```
56
+
57
+ Or with pip:
58
+
59
+ ```bash
60
+ pip install mastodon_mock
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ Run the mock server:
66
+
67
+ ```bash
68
+ mastodon_mock serve --in-memory
69
+ ```
70
+
71
+ Useful flags:
72
+
73
+ - `serve --config PATH` — load configuration from a `.mastodon_mock.toml` file.
74
+ - `serve --host HOST --port PORT` — override the bind address.
75
+ - `serve --in-memory` — force an ephemeral in-memory SQLite database.
76
+ - `db upgrade` — run Alembic migrations to bring an on-disk database to head.
77
+
78
+ Point a client at it (for example, with Mastodon.py):
79
+
80
+ ```python
81
+ from mastodon import Mastodon
82
+
83
+ client = Mastodon(access_token="alice_token", api_base_url="http://127.0.0.1:8000")
84
+ client.status_post("hello from a mock!")
85
+ ```
86
+
87
+ See `mastodon_mock --help` for the full command reference.
88
+
89
+ ## Configuration
90
+
91
+ Configuration is resolved in this order: an explicit `--config` path (or
92
+ `./.mastodon_mock.toml`), then a `[tool.mastodon_mock]` table in `./pyproject.toml`,
93
+ then built-in defaults. See [https://github.com/matthewdeanmartin/mastodon_mock/blob/main/docs/overview/README.md](docs/overview/README.md) for details.
94
+
95
+ ## Contributing
96
+
97
+ See [CONTRIBUTING.md](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/docs/extending/CONTRIBUTING.md).
98
+
99
+ ## License
100
+
101
+ MIT — see [LICENSE](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/LICENSE).
102
+
103
+ ## Changelog
104
+
105
+ docs/overview/README.md
106
+ See [CHANGELOG.md](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/CHANGELOG.md).
@@ -0,0 +1,70 @@
1
+ # Mastodon Mock
2
+
3
+ [![PyPI version](https://badge.fury.io/py/mastodon_mock.svg)](https://badge.fury.io/py/mastodon_mock)
4
+ [![CI](https://github.com/matthewdeanmartin/mastodon_mock/actions/workflows/build.yml/badge.svg)](https://github.com/matthewdeanmartin/mastodon_mock/actions/workflows/build.yml)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/mastodon_mock.svg)](https://pypi.org/project/mastodon_mock/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/LICENSE)
7
+
8
+ `mastodon_mock` is a stateful, in-process mock of the Mastodon REST API. It runs a real
9
+ FastAPI server backed by a minimal in-memory (or on-disk) SQLite database, so client code
10
+ — including [Mastodon.py](https://github.com/halcy/Mastodon.py) — can post statuses, follow
11
+ accounts, build timelines, manage lists and filters, and exercise OAuth flows against a
12
+ fast, deterministic, side-effect-free target. It is intended for testing and local
13
+ development where talking to a live Mastodon instance is slow, flaky, or undesirable.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pipx install mastodon_mock
19
+ ```
20
+
21
+ Or with pip:
22
+
23
+ ```bash
24
+ pip install mastodon_mock
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Run the mock server:
30
+
31
+ ```bash
32
+ mastodon_mock serve --in-memory
33
+ ```
34
+
35
+ Useful flags:
36
+
37
+ - `serve --config PATH` — load configuration from a `.mastodon_mock.toml` file.
38
+ - `serve --host HOST --port PORT` — override the bind address.
39
+ - `serve --in-memory` — force an ephemeral in-memory SQLite database.
40
+ - `db upgrade` — run Alembic migrations to bring an on-disk database to head.
41
+
42
+ Point a client at it (for example, with Mastodon.py):
43
+
44
+ ```python
45
+ from mastodon import Mastodon
46
+
47
+ client = Mastodon(access_token="alice_token", api_base_url="http://127.0.0.1:8000")
48
+ client.status_post("hello from a mock!")
49
+ ```
50
+
51
+ See `mastodon_mock --help` for the full command reference.
52
+
53
+ ## Configuration
54
+
55
+ Configuration is resolved in this order: an explicit `--config` path (or
56
+ `./.mastodon_mock.toml`), then a `[tool.mastodon_mock]` table in `./pyproject.toml`,
57
+ then built-in defaults. See [https://github.com/matthewdeanmartin/mastodon_mock/blob/main/docs/overview/README.md](docs/overview/README.md) for details.
58
+
59
+ ## Contributing
60
+
61
+ See [CONTRIBUTING.md](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/docs/extending/CONTRIBUTING.md).
62
+
63
+ ## License
64
+
65
+ MIT — see [LICENSE](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/LICENSE).
66
+
67
+ ## Changelog
68
+
69
+ docs/overview/README.md
70
+ See [CHANGELOG.md](https://github.com/matthewdeanmartin/mastodon_mock/blob/main/CHANGELOG.md).
@@ -0,0 +1,32 @@
1
+ """Metadata for mastodon_mock."""
2
+
3
+ __all__ = [
4
+ "__credits__",
5
+ "__dependencies__",
6
+ "__description__",
7
+ "__keywords__",
8
+ "__license__",
9
+ "__readme__",
10
+ "__requires_python__",
11
+ "__status__",
12
+ "__title__",
13
+ "__version__",
14
+ ]
15
+
16
+ __title__ = "mastodon_mock"
17
+ __version__ = "0.0.1"
18
+ __description__ = "Stateful in-memory/SQLite mock of the Mastodon REST API for testing Mastodon clients"
19
+ __readme__ = "README.md"
20
+ __credits__ = [{"name": "Matthew Martin", "email": "matthewdeanmartin@gmail.com"}]
21
+ __keywords__ = ["mastodon", "mock", "fastapi", "testing", "fediverse", "rest-api", "mastodon.py"]
22
+ __license__ = "MIT"
23
+ __requires_python__ = ">=3.13"
24
+ __status__ = "4 - Beta"
25
+ __dependencies__ = [
26
+ "fastapi>=0.115.0",
27
+ "uvicorn>=0.30.0",
28
+ "sqlalchemy>=2.0.0",
29
+ "alembic>=1.13.0",
30
+ "pydantic>=2.0.0",
31
+ "python-multipart>=0.0.9",
32
+ ]
@@ -0,0 +1,5 @@
1
+ """Stateful Mastodon mock server that mocks the REST API with a minimal in-memory/sqlite database."""
2
+
3
+ from mastodon_mock.__about__ import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Allows `python -m mastodon_mock` invocation."""
2
+
3
+ from mastodon_mock.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,54 @@
1
+ """Alembic environment. Imports ``Base.metadata`` for autogeneration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from logging.config import fileConfig
6
+
7
+ from alembic import context
8
+ from sqlalchemy import engine_from_config, pool
9
+
10
+ from mastodon_mock.db import models # noqa: F401 (ensure models are registered on Base)
11
+ from mastodon_mock.db.base import Base
12
+
13
+ config = context.config
14
+ if config.config_file_name is not None:
15
+ fileConfig(config.config_file_name)
16
+
17
+ target_metadata = Base.metadata
18
+
19
+
20
+ def run_migrations_offline() -> None:
21
+ """Run migrations in 'offline' mode (emit SQL without a DB connection)."""
22
+ url = config.get_main_option("sqlalchemy.url")
23
+ context.configure(
24
+ url=url,
25
+ target_metadata=target_metadata,
26
+ literal_binds=True,
27
+ dialect_opts={"paramstyle": "named"},
28
+ render_as_batch=True,
29
+ )
30
+ with context.begin_transaction():
31
+ context.run_migrations()
32
+
33
+
34
+ def run_migrations_online() -> None:
35
+ """Run migrations in 'online' mode against a live connection."""
36
+ connectable = engine_from_config(
37
+ config.get_section(config.config_ini_section, {}),
38
+ prefix="sqlalchemy.",
39
+ poolclass=pool.NullPool,
40
+ )
41
+ with connectable.connect() as connection:
42
+ context.configure(
43
+ connection=connection,
44
+ target_metadata=target_metadata,
45
+ render_as_batch=True,
46
+ )
47
+ with context.begin_transaction():
48
+ context.run_migrations()
49
+
50
+
51
+ if context.is_offline_mode():
52
+ run_migrations_offline()
53
+ else:
54
+ run_migrations_online()
@@ -0,0 +1,27 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Sequence
10
+
11
+ from alembic import op
12
+ import sqlalchemy as sa
13
+ ${imports if imports else ""}
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = ${repr(up_revision)}
17
+ down_revision: str | None = ${repr(down_revision)}
18
+ branch_labels: str | Sequence[str] | None = ${repr(branch_labels)}
19
+ depends_on: str | Sequence[str] | None = ${repr(depends_on)}
20
+
21
+
22
+ def upgrade() -> None:
23
+ ${upgrades if upgrades else "pass"}
24
+
25
+
26
+ def downgrade() -> None:
27
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,60 @@
1
+ """add followed_tags and status quote state/policy
2
+
3
+ Revision ID: 4876ba204456
4
+ Revises: 6b88d352c69c
5
+ Create Date: 2026-06-15 11:41:06.516728
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Sequence
11
+
12
+ import sqlalchemy as sa
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "4876ba204456"
17
+ down_revision: str | None = "6b88d352c69c"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "followed_tags",
26
+ sa.Column("id", sa.BigInteger(), nullable=False),
27
+ sa.Column("account_id", sa.BigInteger(), nullable=False),
28
+ sa.Column("name", sa.String(), nullable=False),
29
+ sa.ForeignKeyConstraint(
30
+ ["account_id"],
31
+ ["accounts.id"],
32
+ ),
33
+ sa.PrimaryKeyConstraint("id"),
34
+ sa.UniqueConstraint("account_id", "name", name="uq_followed_tag"),
35
+ )
36
+ with op.batch_alter_table("followed_tags", schema=None) as batch_op:
37
+ batch_op.create_index(batch_op.f("ix_followed_tags_account_id"), ["account_id"], unique=False)
38
+ batch_op.create_index(batch_op.f("ix_followed_tags_name"), ["name"], unique=False)
39
+
40
+ # server_default lets the non-nullable columns apply to existing rows; the
41
+ # ORM supplies its own Python-side default for new rows.
42
+ with op.batch_alter_table("statuses", schema=None) as batch_op:
43
+ batch_op.add_column(sa.Column("quote_state", sa.String(), nullable=False, server_default="accepted"))
44
+ batch_op.add_column(sa.Column("quote_approval_policy", sa.String(), nullable=False, server_default="public"))
45
+
46
+ # ### end Alembic commands ###
47
+
48
+
49
+ def downgrade() -> None:
50
+ # ### commands auto generated by Alembic - please adjust! ###
51
+ with op.batch_alter_table("statuses", schema=None) as batch_op:
52
+ batch_op.drop_column("quote_approval_policy")
53
+ batch_op.drop_column("quote_state")
54
+
55
+ with op.batch_alter_table("followed_tags", schema=None) as batch_op:
56
+ batch_op.drop_index(batch_op.f("ix_followed_tags_name"))
57
+ batch_op.drop_index(batch_op.f("ix_followed_tags_account_id"))
58
+
59
+ op.drop_table("followed_tags")
60
+ # ### end Alembic commands ###
@@ -0,0 +1,194 @@
1
+ """add admin moderation tables and account fields
2
+
3
+ Revision ID: 6b88d352c69c
4
+ Revises: 7cb2c44ee202
5
+ Create Date: 2026-06-15 09:56:51.471891
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Sequence
11
+
12
+ import sqlalchemy as sa
13
+ from alembic import op
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "6b88d352c69c"
17
+ down_revision: str | None = "7cb2c44ee202"
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.create_table(
25
+ "admin_canonical_email_blocks",
26
+ sa.Column("id", sa.BigInteger(), nullable=False),
27
+ sa.Column("canonical_email_hash", sa.String(), nullable=False),
28
+ sa.Column("created_at", sa.DateTime(), nullable=False),
29
+ sa.PrimaryKeyConstraint("id"),
30
+ )
31
+ with op.batch_alter_table("admin_canonical_email_blocks", schema=None) as batch_op:
32
+ batch_op.create_index(
33
+ batch_op.f("ix_admin_canonical_email_blocks_canonical_email_hash"), ["canonical_email_hash"], unique=False
34
+ )
35
+ batch_op.create_index(batch_op.f("ix_admin_canonical_email_blocks_created_at"), ["created_at"], unique=False)
36
+
37
+ op.create_table(
38
+ "admin_domain_allows",
39
+ sa.Column("id", sa.BigInteger(), nullable=False),
40
+ sa.Column("domain", sa.String(), nullable=False),
41
+ sa.Column("created_at", sa.DateTime(), nullable=False),
42
+ sa.PrimaryKeyConstraint("id"),
43
+ )
44
+ with op.batch_alter_table("admin_domain_allows", schema=None) as batch_op:
45
+ batch_op.create_index(batch_op.f("ix_admin_domain_allows_created_at"), ["created_at"], unique=False)
46
+ batch_op.create_index(batch_op.f("ix_admin_domain_allows_domain"), ["domain"], unique=False)
47
+
48
+ op.create_table(
49
+ "admin_domain_blocks",
50
+ sa.Column("id", sa.BigInteger(), nullable=False),
51
+ sa.Column("domain", sa.String(), nullable=False),
52
+ sa.Column("severity", sa.String(), nullable=False),
53
+ sa.Column("reject_media", sa.Boolean(), nullable=False),
54
+ sa.Column("reject_reports", sa.Boolean(), nullable=False),
55
+ sa.Column("private_comment", sa.Text(), nullable=True),
56
+ sa.Column("public_comment", sa.Text(), nullable=True),
57
+ sa.Column("obfuscate", sa.Boolean(), nullable=False),
58
+ sa.Column("created_at", sa.DateTime(), nullable=False),
59
+ sa.PrimaryKeyConstraint("id"),
60
+ )
61
+ with op.batch_alter_table("admin_domain_blocks", schema=None) as batch_op:
62
+ batch_op.create_index(batch_op.f("ix_admin_domain_blocks_created_at"), ["created_at"], unique=False)
63
+ batch_op.create_index(batch_op.f("ix_admin_domain_blocks_domain"), ["domain"], unique=False)
64
+
65
+ op.create_table(
66
+ "admin_email_domain_blocks",
67
+ sa.Column("id", sa.BigInteger(), nullable=False),
68
+ sa.Column("domain", sa.String(), nullable=False),
69
+ sa.Column("created_at", sa.DateTime(), nullable=False),
70
+ sa.PrimaryKeyConstraint("id"),
71
+ )
72
+ with op.batch_alter_table("admin_email_domain_blocks", schema=None) as batch_op:
73
+ batch_op.create_index(batch_op.f("ix_admin_email_domain_blocks_created_at"), ["created_at"], unique=False)
74
+ batch_op.create_index(batch_op.f("ix_admin_email_domain_blocks_domain"), ["domain"], unique=False)
75
+
76
+ op.create_table(
77
+ "admin_ip_blocks",
78
+ sa.Column("id", sa.BigInteger(), nullable=False),
79
+ sa.Column("ip", sa.String(), nullable=False),
80
+ sa.Column("severity", sa.String(), nullable=False),
81
+ sa.Column("comment", sa.Text(), nullable=False),
82
+ sa.Column("created_at", sa.DateTime(), nullable=False),
83
+ sa.Column("expires_at", sa.DateTime(), nullable=True),
84
+ sa.PrimaryKeyConstraint("id"),
85
+ )
86
+ with op.batch_alter_table("admin_ip_blocks", schema=None) as batch_op:
87
+ batch_op.create_index(batch_op.f("ix_admin_ip_blocks_created_at"), ["created_at"], unique=False)
88
+ batch_op.create_index(batch_op.f("ix_admin_ip_blocks_ip"), ["ip"], unique=False)
89
+
90
+ op.create_table(
91
+ "reports",
92
+ sa.Column("id", sa.BigInteger(), nullable=False),
93
+ sa.Column("account_id", sa.BigInteger(), nullable=False),
94
+ sa.Column("target_account_id", sa.BigInteger(), nullable=False),
95
+ sa.Column("comment", sa.Text(), nullable=False),
96
+ sa.Column("category", sa.String(), nullable=False),
97
+ sa.Column("forwarded", sa.Boolean(), nullable=False),
98
+ sa.Column("status_ids", sa.JSON(), nullable=False),
99
+ sa.Column("rule_ids", sa.JSON(), nullable=False),
100
+ sa.Column("action_taken", sa.Boolean(), nullable=False),
101
+ sa.Column("action_taken_at", sa.DateTime(), nullable=True),
102
+ sa.Column("assigned_account_id", sa.BigInteger(), nullable=True),
103
+ sa.Column("action_taken_by_account_id", sa.BigInteger(), nullable=True),
104
+ sa.Column("created_at", sa.DateTime(), nullable=False),
105
+ sa.Column("updated_at", sa.DateTime(), nullable=False),
106
+ sa.ForeignKeyConstraint(
107
+ ["account_id"],
108
+ ["accounts.id"],
109
+ ),
110
+ sa.ForeignKeyConstraint(
111
+ ["action_taken_by_account_id"],
112
+ ["accounts.id"],
113
+ ),
114
+ sa.ForeignKeyConstraint(
115
+ ["assigned_account_id"],
116
+ ["accounts.id"],
117
+ ),
118
+ sa.ForeignKeyConstraint(
119
+ ["target_account_id"],
120
+ ["accounts.id"],
121
+ ),
122
+ sa.PrimaryKeyConstraint("id"),
123
+ )
124
+ with op.batch_alter_table("reports", schema=None) as batch_op:
125
+ batch_op.create_index(batch_op.f("ix_reports_account_id"), ["account_id"], unique=False)
126
+ batch_op.create_index(batch_op.f("ix_reports_created_at"), ["created_at"], unique=False)
127
+ batch_op.create_index(batch_op.f("ix_reports_target_account_id"), ["target_account_id"], unique=False)
128
+
129
+ # Non-nullable columns carry a server_default so the ALTER succeeds on
130
+ # tables with existing rows; the ORM applies its own Python-side defaults
131
+ # for new rows, so the schemas stay equivalent.
132
+ with op.batch_alter_table("accounts", schema=None) as batch_op:
133
+ batch_op.add_column(sa.Column("email", sa.String(), nullable=True))
134
+ batch_op.add_column(sa.Column("ip", sa.String(), nullable=True))
135
+ batch_op.add_column(sa.Column("role", sa.String(), nullable=False, server_default="user"))
136
+ batch_op.add_column(sa.Column("locale", sa.String(), nullable=False, server_default="en"))
137
+ batch_op.add_column(sa.Column("confirmed", sa.Boolean(), nullable=False, server_default=sa.true()))
138
+ batch_op.add_column(sa.Column("approved", sa.Boolean(), nullable=False, server_default=sa.true()))
139
+ batch_op.add_column(sa.Column("disabled", sa.Boolean(), nullable=False, server_default=sa.false()))
140
+ batch_op.add_column(sa.Column("silenced", sa.Boolean(), nullable=False, server_default=sa.false()))
141
+ batch_op.add_column(sa.Column("suspended", sa.Boolean(), nullable=False, server_default=sa.false()))
142
+ batch_op.add_column(sa.Column("sensitized", sa.Boolean(), nullable=False, server_default=sa.false()))
143
+ batch_op.add_column(sa.Column("invite_request", sa.String(), nullable=True))
144
+
145
+ # ### end Alembic commands ###
146
+
147
+
148
+ def downgrade() -> None:
149
+ # ### commands auto generated by Alembic - please adjust! ###
150
+ with op.batch_alter_table("accounts", schema=None) as batch_op:
151
+ batch_op.drop_column("invite_request")
152
+ batch_op.drop_column("sensitized")
153
+ batch_op.drop_column("suspended")
154
+ batch_op.drop_column("silenced")
155
+ batch_op.drop_column("disabled")
156
+ batch_op.drop_column("approved")
157
+ batch_op.drop_column("confirmed")
158
+ batch_op.drop_column("locale")
159
+ batch_op.drop_column("role")
160
+ batch_op.drop_column("ip")
161
+ batch_op.drop_column("email")
162
+
163
+ with op.batch_alter_table("reports", schema=None) as batch_op:
164
+ batch_op.drop_index(batch_op.f("ix_reports_target_account_id"))
165
+ batch_op.drop_index(batch_op.f("ix_reports_created_at"))
166
+ batch_op.drop_index(batch_op.f("ix_reports_account_id"))
167
+
168
+ op.drop_table("reports")
169
+ with op.batch_alter_table("admin_ip_blocks", schema=None) as batch_op:
170
+ batch_op.drop_index(batch_op.f("ix_admin_ip_blocks_ip"))
171
+ batch_op.drop_index(batch_op.f("ix_admin_ip_blocks_created_at"))
172
+
173
+ op.drop_table("admin_ip_blocks")
174
+ with op.batch_alter_table("admin_email_domain_blocks", schema=None) as batch_op:
175
+ batch_op.drop_index(batch_op.f("ix_admin_email_domain_blocks_domain"))
176
+ batch_op.drop_index(batch_op.f("ix_admin_email_domain_blocks_created_at"))
177
+
178
+ op.drop_table("admin_email_domain_blocks")
179
+ with op.batch_alter_table("admin_domain_blocks", schema=None) as batch_op:
180
+ batch_op.drop_index(batch_op.f("ix_admin_domain_blocks_domain"))
181
+ batch_op.drop_index(batch_op.f("ix_admin_domain_blocks_created_at"))
182
+
183
+ op.drop_table("admin_domain_blocks")
184
+ with op.batch_alter_table("admin_domain_allows", schema=None) as batch_op:
185
+ batch_op.drop_index(batch_op.f("ix_admin_domain_allows_domain"))
186
+ batch_op.drop_index(batch_op.f("ix_admin_domain_allows_created_at"))
187
+
188
+ op.drop_table("admin_domain_allows")
189
+ with op.batch_alter_table("admin_canonical_email_blocks", schema=None) as batch_op:
190
+ batch_op.drop_index(batch_op.f("ix_admin_canonical_email_blocks_created_at"))
191
+ batch_op.drop_index(batch_op.f("ix_admin_canonical_email_blocks_canonical_email_hash"))
192
+
193
+ op.drop_table("admin_canonical_email_blocks")
194
+ # ### end Alembic commands ###