plain.models 0.47.0__tar.gz → 0.48.0__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 (136) hide show
  1. {plain_models-0.47.0 → plain_models-0.48.0}/PKG-INFO +1 -1
  2. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/CHANGELOG.md +11 -0
  3. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/creation.py +1 -0
  4. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/cli.py +66 -0
  5. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/executor.py +29 -27
  6. {plain_models-0.47.0 → plain_models-0.48.0}/pyproject.toml +1 -1
  7. {plain_models-0.47.0 → plain_models-0.48.0}/.gitignore +0 -0
  8. {plain_models-0.47.0 → plain_models-0.48.0}/LICENSE +0 -0
  9. {plain_models-0.47.0 → plain_models-0.48.0}/README.md +0 -0
  10. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/AGENTS.md +0 -0
  11. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/README.md +0 -0
  12. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/__init__.py +0 -0
  13. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/aggregates.py +0 -0
  14. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/__init__.py +0 -0
  15. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/__init__.py +0 -0
  16. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/base.py +0 -0
  17. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/client.py +0 -0
  18. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/features.py +0 -0
  19. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/introspection.py +0 -0
  20. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/operations.py +0 -0
  21. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/schema.py +0 -0
  22. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/base/validation.py +0 -0
  23. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/ddl_references.py +0 -0
  24. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/__init__.py +0 -0
  25. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/base.py +0 -0
  26. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/client.py +0 -0
  27. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/compiler.py +0 -0
  28. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/creation.py +0 -0
  29. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/features.py +0 -0
  30. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/introspection.py +0 -0
  31. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/operations.py +0 -0
  32. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/schema.py +0 -0
  33. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/mysql/validation.py +0 -0
  34. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/__init__.py +0 -0
  35. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/base.py +0 -0
  36. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/client.py +0 -0
  37. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/creation.py +0 -0
  38. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/features.py +0 -0
  39. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/introspection.py +0 -0
  40. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/operations.py +0 -0
  41. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/postgresql/schema.py +0 -0
  42. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/__init__.py +0 -0
  43. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/_functions.py +0 -0
  44. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/base.py +0 -0
  45. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/client.py +0 -0
  46. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/creation.py +0 -0
  47. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/features.py +0 -0
  48. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/introspection.py +0 -0
  49. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/operations.py +0 -0
  50. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/sqlite3/schema.py +0 -0
  51. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backends/utils.py +0 -0
  52. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backups/__init__.py +0 -0
  53. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backups/cli.py +0 -0
  54. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backups/clients.py +0 -0
  55. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/backups/core.py +0 -0
  56. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/base.py +0 -0
  57. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/config.py +0 -0
  58. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/connections.py +0 -0
  59. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/constants.py +0 -0
  60. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/constraints.py +0 -0
  61. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/database_url.py +0 -0
  62. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/db.py +0 -0
  63. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/default_settings.py +0 -0
  64. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/deletion.py +0 -0
  65. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/entrypoints.py +0 -0
  66. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/enums.py +0 -0
  67. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/exceptions.py +0 -0
  68. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/expressions.py +0 -0
  69. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/__init__.py +0 -0
  70. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/json.py +0 -0
  71. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/mixins.py +0 -0
  72. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/related.py +0 -0
  73. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/related_descriptors.py +0 -0
  74. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/related_lookups.py +0 -0
  75. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/related_managers.py +0 -0
  76. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/fields/reverse_related.py +0 -0
  77. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/forms.py +0 -0
  78. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/__init__.py +0 -0
  79. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/comparison.py +0 -0
  80. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/datetime.py +0 -0
  81. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/math.py +0 -0
  82. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/mixins.py +0 -0
  83. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/text.py +0 -0
  84. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/functions/window.py +0 -0
  85. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/indexes.py +0 -0
  86. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/lookups.py +0 -0
  87. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/__init__.py +0 -0
  88. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/autodetector.py +0 -0
  89. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/exceptions.py +0 -0
  90. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/graph.py +0 -0
  91. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/loader.py +0 -0
  92. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/migration.py +0 -0
  93. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/operations/__init__.py +0 -0
  94. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/operations/base.py +0 -0
  95. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/operations/fields.py +0 -0
  96. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/operations/models.py +0 -0
  97. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/operations/special.py +0 -0
  98. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/optimizer.py +0 -0
  99. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/questioner.py +0 -0
  100. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/recorder.py +0 -0
  101. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/serializer.py +0 -0
  102. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/state.py +0 -0
  103. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/utils.py +0 -0
  104. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/migrations/writer.py +0 -0
  105. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/options.py +0 -0
  106. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/otel.py +0 -0
  107. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/preflight.py +0 -0
  108. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/query.py +0 -0
  109. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/query_utils.py +0 -0
  110. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/registry.py +0 -0
  111. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/__init__.py +0 -0
  112. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/compiler.py +0 -0
  113. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/constants.py +0 -0
  114. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/datastructures.py +0 -0
  115. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/query.py +0 -0
  116. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/subqueries.py +0 -0
  117. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/sql/where.py +0 -0
  118. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/test/__init__.py +0 -0
  119. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/test/pytest.py +0 -0
  120. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/test/utils.py +0 -0
  121. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/transaction.py +0 -0
  122. {plain_models-0.47.0 → plain_models-0.48.0}/plain/models/utils.py +0 -0
  123. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/examples/migrations/0001_initial.py +0 -0
  124. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  125. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  126. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/examples/migrations/__init__.py +0 -0
  127. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/examples/models.py +0 -0
  128. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/settings.py +0 -0
  129. {plain_models-0.47.0 → plain_models-0.48.0}/tests/app/urls.py +0 -0
  130. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_database_url.py +0 -0
  131. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_delete_behaviors.py +0 -0
  132. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_exceptions.py +0 -0
  133. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_manager_assignment.py +0 -0
  134. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_models.py +0 -0
  135. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_related_descriptors.py +0 -0
  136. {plain_models-0.47.0 → plain_models-0.48.0}/tests/test_related_manager_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.models
3
- Version: 0.47.0
3
+ Version: 0.48.0
4
4
  Summary: Model your data and store it in a database.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,5 +1,16 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.48.0](https://github.com/dropseed/plain/releases/plain-models@0.48.0) (2025-09-26)
4
+
5
+ ### What's changed
6
+
7
+ - Migrations now run in a single transaction by default for databases that support transactional DDL, providing all-or-nothing migration batches for better safety and consistency ([6d0c105](https://github.com/dropseed/plain/commit/6d0c105fa9))
8
+ - Added `--atomic-batch/--no-atomic-batch` options to `plain migrate` to explicitly control whether migrations are run in a single transaction ([6d0c105](https://github.com/dropseed/plain/commit/6d0c105fa9))
9
+
10
+ ### Upgrade instructions
11
+
12
+ - No changes required
13
+
3
14
  ## [0.47.0](https://github.com/dropseed/plain/releases/plain-models@0.47.0) (2025-09-25)
4
15
 
5
16
  ### What's changed
@@ -59,6 +59,7 @@ class BaseDatabaseCreation:
59
59
  prune=False,
60
60
  no_input=True,
61
61
  verbosity=max(verbosity - 1, 0),
62
+ atomic_batch=False, # No need for atomic batch when creating test database
62
63
  )
63
64
 
64
65
  # Ensure a connection for the side effect of initializing the test database.
@@ -362,6 +362,11 @@ def makemigrations(package_labels, dry_run, empty, no_input, name, check, verbos
362
362
  default=1,
363
363
  help="Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output",
364
364
  )
365
+ @click.option(
366
+ "--atomic-batch/--no-atomic-batch",
367
+ default=None,
368
+ help="Run migrations in a single transaction (auto-detected by default)",
369
+ )
365
370
  def migrate(
366
371
  package_label,
367
372
  migration_name,
@@ -372,6 +377,7 @@ def migrate(
372
377
  prune,
373
378
  no_input,
374
379
  verbosity,
380
+ atomic_batch,
375
381
  ):
376
382
  """Updates database schema. Manages both packages with migrations and those without."""
377
383
 
@@ -575,6 +581,65 @@ def migrate(
575
581
  # pprint(sql)
576
582
 
577
583
  if migration_plan:
584
+ # Determine whether to use atomic batch
585
+ use_atomic_batch = False
586
+ if len(migration_plan) > 1:
587
+ # Check database capabilities
588
+ can_rollback_ddl = db_connection.features.can_rollback_ddl
589
+
590
+ # Check if all migrations support atomic
591
+ non_atomic_migrations = [m for m in migration_plan if not m.atomic]
592
+
593
+ if atomic_batch is True:
594
+ # User explicitly requested atomic batch
595
+ if not can_rollback_ddl:
596
+ raise click.UsageError(
597
+ f"--atomic-batch not supported on {db_connection.vendor}. "
598
+ "Remove the flag or use a database that supports transactional DDL."
599
+ )
600
+ if non_atomic_migrations:
601
+ names = ", ".join(
602
+ f"{m.package_label}.{m.name}" for m in non_atomic_migrations[:3]
603
+ )
604
+ if len(non_atomic_migrations) > 3:
605
+ names += f", and {len(non_atomic_migrations) - 3} more"
606
+ raise click.UsageError(
607
+ f"--atomic-batch requested but these migrations have atomic=False: {names}"
608
+ )
609
+ use_atomic_batch = True
610
+ if verbosity >= 1:
611
+ click.echo(
612
+ f" Running {len(migration_plan)} migrations in atomic batch (all-or-nothing)"
613
+ )
614
+ elif atomic_batch is False:
615
+ # User explicitly disabled atomic batch
616
+ use_atomic_batch = False
617
+ if verbosity >= 1:
618
+ click.echo(f" Running {len(migration_plan)} migrations separately")
619
+ else:
620
+ # Auto-detect (atomic_batch is None)
621
+ if can_rollback_ddl and not non_atomic_migrations:
622
+ use_atomic_batch = True
623
+ if verbosity >= 1:
624
+ click.echo(
625
+ f" Running {len(migration_plan)} migrations in atomic batch (all-or-nothing)"
626
+ )
627
+ else:
628
+ use_atomic_batch = False
629
+ if verbosity >= 1:
630
+ if not can_rollback_ddl:
631
+ click.echo(
632
+ f" Running {len(migration_plan)} migrations separately ({db_connection.vendor} doesn't support batch transactions)"
633
+ )
634
+ elif non_atomic_migrations:
635
+ click.echo(
636
+ f" Running {len(migration_plan)} migrations separately (some migrations have atomic=False)"
637
+ )
638
+ else:
639
+ click.echo(
640
+ f" Running {len(migration_plan)} migrations separately"
641
+ )
642
+
578
643
  if backup or (backup is None and settings.DEBUG):
579
644
  backup_name = f"migrate_{time.strftime('%Y%m%d_%H%M%S')}"
580
645
  click.secho(
@@ -599,6 +664,7 @@ def migrate(
599
664
  plan=migration_plan,
600
665
  state=pre_migrate_state.clone(),
601
666
  fake=fake,
667
+ atomic_batch=use_atomic_batch,
602
668
  )
603
669
  # post_migrate signals have access to all models. Ensure that all models
604
670
  # are reloaded in case any are delayed.
@@ -1,3 +1,6 @@
1
+ from contextlib import nullcontext
2
+
3
+ from ..transaction import atomic
1
4
  from .loader import MigrationLoader
2
5
  from .recorder import MigrationRecorder
3
6
  from .state import ProjectState
@@ -52,12 +55,14 @@ class MigrationExecutor:
52
55
  migration.mutate_state(state, preserve=False)
53
56
  return state
54
57
 
55
- def migrate(self, targets, plan=None, state=None, fake=False):
58
+ def migrate(self, targets, plan=None, state=None, fake=False, atomic_batch=False):
56
59
  """
57
60
  Migrate the database up to the given targets.
58
61
 
59
62
  Plain first needs to create all project states before a migration is
60
63
  (un)applied and in a second step run all the database operations.
64
+
65
+ atomic_batch: Whether to run all migrations in a single transaction.
61
66
  """
62
67
  # The plain_migrations table must be present to record applied
63
68
  # migrations, but don't create it if there are no migrations to apply.
@@ -82,34 +87,31 @@ class MigrationExecutor:
82
87
  if state is None:
83
88
  # The resulting state should still include applied migrations.
84
89
  state = self._create_project_state(with_applied_migrations=True)
85
- state = self._migrate_all_forwards(state, plan, full_plan, fake=fake)
86
-
87
- self.check_replacements()
88
90
 
89
- return state
91
+ migrations_to_run = set(plan)
92
+
93
+ # Choose context manager based on atomic_batch
94
+ batch_context = atomic if (atomic_batch and len(plan) > 1) else nullcontext
95
+
96
+ with batch_context():
97
+ for migration in full_plan:
98
+ if not migrations_to_run:
99
+ # We remove every migration that we applied from these sets so
100
+ # that we can bail out once the last migration has been applied
101
+ # and don't always run until the very end of the migration
102
+ # process.
103
+ break
104
+ if migration in migrations_to_run:
105
+ if "models_registry" not in state.__dict__:
106
+ if self.progress_callback:
107
+ self.progress_callback("render_start")
108
+ state.models_registry # Render all -- performance critical
109
+ if self.progress_callback:
110
+ self.progress_callback("render_success")
111
+ state = self.apply_migration(state, migration, fake=fake)
112
+ migrations_to_run.remove(migration)
90
113
 
91
- def _migrate_all_forwards(self, state, plan, full_plan, fake):
92
- """
93
- Take a list of 2-tuples of the form (migration instance, False) and
94
- apply them in the order they occur in the full_plan.
95
- """
96
- migrations_to_run = set(plan)
97
- for migration in full_plan:
98
- if not migrations_to_run:
99
- # We remove every migration that we applied from these sets so
100
- # that we can bail out once the last migration has been applied
101
- # and don't always run until the very end of the migration
102
- # process.
103
- break
104
- if migration in migrations_to_run:
105
- if "models_registry" not in state.__dict__:
106
- if self.progress_callback:
107
- self.progress_callback("render_start")
108
- state.models_registry # Render all -- performance critical
109
- if self.progress_callback:
110
- self.progress_callback("render_success")
111
- state = self.apply_migration(state, migration, fake=fake)
112
- migrations_to_run.remove(migration)
114
+ self.check_replacements()
113
115
 
114
116
  return state
115
117
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.models"
3
- version = "0.47.0"
3
+ version = "0.48.0"
4
4
  description = "Model your data and store it in a database."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes