This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push:
new 279ca4a Add a distribution table to the database
279ca4a is described below
commit 279ca4a9dd6cb8b901a990e14e1214b948b4944c
Author: Sean B. Palmer <[email protected]>
AuthorDate: Thu Aug 7 15:45:11 2025 +0100
Add a distribution table to the database
---
atr/db/__init__.py | 2 -
atr/models/sql.py | 104 ++++++++++++++++++----
atr/routes/distribute.py | 74 ++++------------
atr/routes/projects.py | 5 --
atr/storage/__init__.py | 20 ++---
atr/storage/writers/__init__.py | 3 +-
atr/storage/writers/distributions.py | 113 ++++++++++++++++++++++++
atr/validate.py | 9 --
migrations/versions/0018_2025.08.07_41ccdd9a.py | 65 ++++++++++++++
pyproject.toml | 1 -
10 files changed, 294 insertions(+), 102 deletions(-)
diff --git a/atr/db/__init__.py b/atr/db/__init__.py
index 39ba3e1..530a7ce 100644
--- a/atr/db/__init__.py
+++ b/atr/db/__init__.py
@@ -370,8 +370,6 @@ class Session(sqlalchemy.ext.asyncio.AsyncSession):
if _releases:
query = query.options(select_in_load(sql.Project.releases))
- if _distribution_channels:
- query =
query.options(select_in_load(sql.Project.distribution_channels))
if _super_project:
query = query.options(joined_load(sql.Project.super_project))
if _release_policy:
diff --git a/atr/models/sql.py b/atr/models/sql.py
index ebe226e..8431679 100644
--- a/atr/models/sql.py
+++ b/atr/models/sql.py
@@ -22,6 +22,7 @@
# https://github.com/fastapi/sqlmodel/pull/778/files
# from __future__ import annotations
+import dataclasses
import datetime
import enum
from typing import Any, Final, Literal, Optional, TypeVar
@@ -47,6 +48,16 @@ sqlmodel.SQLModel.metadata = sqlalchemy.MetaData(
}
)
+# Data classes
+
+
[email protected](frozen=True)
+class DistributionPlatformValue:
+ name: str
+ template_url: str
+ requires_owner_namespace: bool = False
+ default_owner_namespace: str | None = None
+
# Enumerations
@@ -81,6 +92,42 @@ class CheckResultStatusIgnore(str, enum.Enum):
return f"CheckResultStatusIgnore.{self.value.upper()}"
+class DistributionPlatform(enum.Enum):
+ ARTIFACTHUB = DistributionPlatformValue(
+ name="ArtifactHub (Helm)",
+
template_url="https://artifacthub.io/api/v1/packages/helm/{owner_namespace}/{package}/{version}",
+ requires_owner_namespace=True,
+ )
+ DOCKER = DistributionPlatformValue(
+ name="Docker",
+
template_url="https://hub.docker.com/v2/namespaces/{owner_namespace}/repositories/{package}/tags/{version}",
+ default_owner_namespace="library",
+ )
+ GITHUB = DistributionPlatformValue(
+ name="GitHub",
+
template_url="https://api.github.com/repos/{owner_namespace}/{package}/releases/tags/v{version}",
+ requires_owner_namespace=True,
+ )
+ MAVEN = DistributionPlatformValue(
+ name="Maven Central",
+
template_url="https://search.maven.org/solrsearch/select?q=g:{owner_namespace}+AND+a:{package}+AND+v:{version}&core=gav&rows=20&wt=json",
+ requires_owner_namespace=True,
+ )
+ NPM = DistributionPlatformValue(
+ name="npm",
+ template_url="https://registry.npmjs.org/{package}",
+ )
+ NPM_SCOPED = DistributionPlatformValue(
+ name="npm (scoped)",
+ template_url="https://registry.npmjs.org/@{owner_namespace}/{package}",
+ requires_owner_namespace=True,
+ )
+ PYPI = DistributionPlatformValue(
+ name="PyPI",
+ template_url="https://pypi.org/pypi/{package}/{version}/json",
+ )
+
+
class ProjectStatus(str, enum.Enum):
ACTIVE = "active"
DORMANT = "dormant"
@@ -426,9 +473,9 @@ class Project(sqlmodel.SQLModel, table=True):
# see_also(Release.project)
releases: list["Release"] = sqlmodel.Relationship(back_populates="project")
- # 1-M: Project -> [DistributionChannel]
- # M-1: DistributionChannel -> Project
- distribution_channels: list["DistributionChannel"] =
sqlmodel.Relationship(back_populates="project")
+ # # 1-M: Project -> [DistributionChannel]
+ # # M-1: DistributionChannel -> Project
+ # distribution_channels: list["DistributionChannel"] =
sqlmodel.Relationship(back_populates="project")
# 1-1: Project -C-> ReleasePolicy
# 1-1: ReleasePolicy -> Project
@@ -652,6 +699,12 @@ class Release(sqlmodel.SQLModel, table=True):
back_populates="release", sa_relationship_kwargs={"cascade": "all,
delete-orphan"}
)
+ # 1-M: Release -> [Distribution]
+ # M-1: Distribution -> Release
+ distributions: list["Distribution"] = sqlmodel.Relationship(
+ back_populates="release", sa_relationship_kwargs={"cascade": "all,
delete-orphan"}
+ )
+
# The combination of project_name and version must be unique
__table_args__ = (sqlmodel.UniqueConstraint("project_name", "version",
name="unique_project_version"),)
@@ -764,21 +817,38 @@ class CheckResultIgnore(sqlmodel.SQLModel, table=True):
self.created =
datetime.datetime.fromisoformat(self.created.rstrip("Z"))
-# DistributionChannel: Project
-class DistributionChannel(sqlmodel.SQLModel, table=True):
+# Distribution: Release
+class Distribution(sqlmodel.SQLModel, table=True):
id: int = sqlmodel.Field(default=None, primary_key=True)
- name: str = sqlmodel.Field(index=True, unique=True)
- url: str
- credentials: str
- is_test: bool = sqlmodel.Field(default=False)
- automation_endpoint: str
-
- project_name: str = sqlmodel.Field(foreign_key="project.name")
-
- # M-1: DistributionChannel -> Project
- # 1-M: Project -> [DistributionChannel]
- project: Project =
sqlmodel.Relationship(back_populates="distribution_channels")
- see_also(Project.distribution_channels)
+ release_name: str = sqlmodel.Field(foreign_key="release.name")
+ release: Release = sqlmodel.Relationship(back_populates="distributions")
+ platform: DistributionPlatform =
sqlmodel.Field(default=DistributionPlatform.ARTIFACTHUB)
+ owner_namespace: str | None = sqlmodel.Field(default=None)
+ package: str
+ version: str
+ staging: bool = sqlmodel.Field(default=False)
+ upload_date: datetime.datetime | None = sqlmodel.Field(default=None)
+ api_url: str
+ # The API response can be huge, e.g. from npm
+ # So we do not store it in the database
+ # api_response: Any =
sqlmodel.Field(sa_column=sqlalchemy.Column(sqlalchemy.JSON))
+
+
+# # DistributionChannel: Project
+# class DistributionChannel(sqlmodel.SQLModel, table=True):
+# id: int = sqlmodel.Field(default=None, primary_key=True)
+# name: str = sqlmodel.Field(index=True, unique=True)
+# url: str
+# credentials: str
+# is_test: bool = sqlmodel.Field(default=False)
+# automation_endpoint: str
+#
+# project_name: str = sqlmodel.Field(foreign_key="project.name")
+#
+# # M-1: DistributionChannel -> Project
+# # 1-M: Project -> [DistributionChannel]
+# project: Project =
sqlmodel.Relationship(back_populates="distribution_channels")
+# see_also(Project.distribution_channels)
# PublicSigningKey: Committee
diff --git a/atr/routes/distribute.py b/atr/routes/distribute.py
index 1463cd8..00ad36b 100644
--- a/atr/routes/distribute.py
+++ b/atr/routes/distribute.py
@@ -17,9 +17,7 @@
from __future__ import annotations
-import dataclasses
import datetime
-import enum
import json
import aiohttp
@@ -74,52 +72,8 @@ class PyPIResponse(schema.Lax):
urls: list[PyPIUrl] = pydantic.Field(default_factory=list)
[email protected](frozen=True)
-class PlatformValue:
- name: str
- template_url: str
- requires_owner_namespace: bool = False
- default_owner_namespace: str | None = None
-
-
-class Platform(enum.Enum):
- ARTIFACTHUB = PlatformValue(
- name="ArtifactHub (Helm)",
-
template_url="https://artifacthub.io/api/v1/packages/helm/{owner_namespace}/{package}/{version}",
- requires_owner_namespace=True,
- )
- DOCKER = PlatformValue(
- name="Docker",
-
template_url="https://hub.docker.com/v2/namespaces/{owner_namespace}/repositories/{package}/tags/{version}",
- default_owner_namespace="library",
- )
- GITHUB = PlatformValue(
- name="GitHub",
-
template_url="https://api.github.com/repos/{owner_namespace}/{package}/releases/tags/v{version}",
- requires_owner_namespace=True,
- )
- MAVEN = PlatformValue(
- name="Maven Central",
-
template_url="https://search.maven.org/solrsearch/select?q=g:{owner_namespace}+AND+a:{package}+AND+v:{version}&core=gav&rows=20&wt=json",
- requires_owner_namespace=True,
- )
- NPM = PlatformValue(
- name="npm",
- template_url="https://registry.npmjs.org/{package}",
- )
- NPM_SCOPED = PlatformValue(
- name="npm (scoped)",
- template_url="https://registry.npmjs.org/@{owner_namespace}/{package}",
- requires_owner_namespace=True,
- )
- PYPI = PlatformValue(
- name="PyPI",
- template_url="https://pypi.org/pypi/{package}/{version}/json",
- )
-
-
class DistributeForm(forms.Typed):
- platform = forms.select("Platform", choices=Platform)
+ platform = forms.select("Platform", choices=sql.DistributionPlatform)
owner_namespace = forms.optional(
"Owner or Namespace",
placeholder="E.g. com.example or scope or library",
@@ -158,7 +112,7 @@ class DistributeForm(forms.Typed):
# And this way we also get nice JSON from the Pydantic model dump
# Including all of the enum properties
class DistributeData(schema.Lax):
- platform: Platform
+ platform: sql.DistributionPlatform
owner_namespace: str | None = None
package: str
version: str
@@ -225,7 +179,9 @@ async def _distribute_page(
return await template.blank("Distribute", content=block.collect())
-async def _distribute_post_api(api_url: str, platform: Platform, version: str)
-> outcome.Outcome[basic.JSON]:
+async def _distribute_post_api(
+ api_url: str, platform: sql.DistributionPlatform, version: str
+) -> outcome.Outcome[basic.JSON]:
try:
async with aiohttp.ClientSession() as session:
async with session.get(api_url) as response:
@@ -235,7 +191,7 @@ async def _distribute_post_api(api_url: str, platform:
Platform, version: str) -
except aiohttp.ClientError as e:
return outcome.Error(e)
match platform:
- case Platform.NPM | Platform.NPM_SCOPED:
+ case sql.DistributionPlatform.NPM |
sql.DistributionPlatform.NPM_SCOPED:
if version not in NpmResponse.model_validate(result).time:
e = RuntimeError(f"Version '{version}' not found")
return outcome.Error(e)
@@ -321,34 +277,38 @@ def _distribute_post_table(block: htm.Block, dd:
DistributeData) -> None:
block.table(".table.table-striped.table-bordered")[tbody]
-def _platform_upload_date(platform: Platform, data: basic.JSON, version: str)
-> datetime.datetime | None: # noqa: C901
+def _platform_upload_date( # noqa: C901
+ platform: sql.DistributionPlatform,
+ data: basic.JSON,
+ version: str,
+) -> datetime.datetime | None:
match platform:
- case Platform.ARTIFACTHUB:
+ case sql.DistributionPlatform.ARTIFACTHUB:
if not (versions :=
ArtifactHubResponse.model_validate(data).available_versions):
return None
return datetime.datetime.fromtimestamp(versions[0].ts,
tz=datetime.UTC)
- case Platform.DOCKER:
+ case sql.DistributionPlatform.DOCKER:
if not (pushed_at :=
DockerResponse.model_validate(data).tag_last_pushed):
return None
return datetime.datetime.fromisoformat(pushed_at.rstrip("Z"))
- case Platform.GITHUB:
+ case sql.DistributionPlatform.GITHUB:
if not (published_at :=
GitHubResponse.model_validate(data).published_at):
return None
return datetime.datetime.fromisoformat(published_at.rstrip("Z"))
- case Platform.MAVEN:
+ case sql.DistributionPlatform.MAVEN:
if not (docs :=
MavenResponse.model_validate(data).response.get("docs")):
return None
if not (timestamp := docs[0].timestamp):
return None
return datetime.datetime.fromtimestamp(timestamp / 1000,
tz=datetime.UTC)
- case Platform.NPM | Platform.NPM_SCOPED:
+ case sql.DistributionPlatform.NPM |
sql.DistributionPlatform.NPM_SCOPED:
if not (times := NpmResponse.model_validate(data).time):
return None
# Versions can be in the form "1.2.3" or "v1.2.3", so we check for
both
if not (upload_time := times.get(version) or
times.get(f"v{version}")):
return None
return datetime.datetime.fromisoformat(upload_time.rstrip("Z"))
- case Platform.PYPI:
+ case sql.DistributionPlatform.PYPI:
if not (urls := PyPIResponse.model_validate(data).urls):
return None
if not (upload_time := urls[0].upload_time_iso_8601):
diff --git a/atr/routes/projects.py b/atr/routes/projects.py
index c3255ab..1428c60 100644
--- a/atr/routes/projects.py
+++ b/atr/routes/projects.py
@@ -206,11 +206,6 @@ async def delete(session: routes.CommitterSession) ->
response.Response:
return await session.redirect(
projects, error=f"Cannot delete project '{project_name}'
because it has associated releases."
)
- if project.distribution_channels:
- return await session.redirect(
- projects,
- error=f"Cannot delete project '{project_name}' because it has
associated distribution channels.",
- )
await data.delete(project)
await data.commit()
diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py
index ccc2d14..4f27492 100644
--- a/atr/storage/__init__.py
+++ b/atr/storage/__init__.py
@@ -163,6 +163,7 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
def __init__(self, write: Write, data: db.Session, committee_name: str):
self.__committee_name = committee_name
self.checks = writers.checks.CommitteeMember(write, self, data,
committee_name)
+ self.distributions = writers.distributions.CommitteeMember(write,
self, data, committee_name)
self.keys = writers.keys.CommitteeMember(write, self, data,
committee_name)
self.tokens = writers.tokens.CommitteeMember(write, self, data,
committee_name)
@@ -171,7 +172,6 @@ class WriteAsCommitteeMember(WriteAsCommitteeParticipant):
return self.__committee_name
-# TODO: Or WriteAsCommitteeAdmin
class WriteAsFoundationAdmin(WriteAsCommitteeMember):
def __init__(self, write: Write, data: db.Session, committee_name: str):
self.__committee_name = committee_name
@@ -196,15 +196,6 @@ class Write:
def authorisation(self) -> principal.Authorisation:
return self.__authorisation
- # def as_committee_admin(self, committee_name: str) ->
types.Outcome[WriteAsCommitteeMember]:
- # if self.__asf_uid is None:
- # return types.OutcomeException(AccessError("No ASF UID"))
- # try:
- # wacm = WriteAsCommitteeMember(self, self.__data, self.__asf_uid,
committee_name)
- # except Exception as e:
- # return types.OutcomeException(e)
- # return types.OutcomeResult(wacm)
-
def as_committee_member(self, committee_name: str) ->
WriteAsCommitteeMember:
return
self.as_committee_member_outcome(committee_name).result_or_raise()
@@ -366,3 +357,12 @@ async def write(asf_uid: str | None | ArgumentNoneType =
ArgumentNone) -> AsyncG
async with db.session() as data:
# TODO: Replace data with a DatabaseWriter instance
yield Write(authorisation, data)
+
+
[email protected]
+async def write_as_committee_member(
+ committee_name: str,
+ asf_uid: str | None | ArgumentNoneType = ArgumentNone,
+) -> AsyncGenerator[WriteAsCommitteeMember]:
+ async with write(asf_uid) as w:
+ yield w.as_committee_member(committee_name)
diff --git a/atr/storage/writers/__init__.py b/atr/storage/writers/__init__.py
index ddb82d7..f3613b0 100644
--- a/atr/storage/writers/__init__.py
+++ b/atr/storage/writers/__init__.py
@@ -16,7 +16,8 @@
# under the License.
import atr.storage.writers.checks as checks
+import atr.storage.writers.distributions as distributions
import atr.storage.writers.keys as keys
import atr.storage.writers.tokens as tokens
-__all__ = ["checks", "keys", "tokens"]
+__all__ = ["checks", "distributions", "keys", "tokens"]
diff --git a/atr/storage/writers/distributions.py
b/atr/storage/writers/distributions.py
new file mode 100644
index 0000000..8786f6f
--- /dev/null
+++ b/atr/storage/writers/distributions.py
@@ -0,0 +1,113 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Removing this will cause circular imports
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import atr.db as db
+import atr.models.sql as sql
+import atr.storage as storage
+
+if TYPE_CHECKING:
+ import datetime
+
+
+class GeneralPublic:
+ def __init__(
+ self,
+ write: storage.Write,
+ write_as: storage.WriteAsGeneralPublic,
+ data: db.Session,
+ ):
+ self.__write = write
+ self.__write_as = write_as
+ self.__data = data
+ self.__asf_uid = write.authorisation.asf_uid
+
+
+class FoundationCommitter(GeneralPublic):
+ def __init__(self, write: storage.Write, write_as:
storage.WriteAsFoundationCommitter, data: db.Session):
+ super().__init__(write, write_as, data)
+ self.__write = write
+ self.__write_as = write_as
+ self.__data = data
+ asf_uid = write.authorisation.asf_uid
+ if asf_uid is None:
+ raise storage.AccessError("No ASF UID")
+ self.__asf_uid = asf_uid
+
+
+class CommitteeParticipant(FoundationCommitter):
+ def __init__(
+ self,
+ write: storage.Write,
+ write_as: storage.WriteAsCommitteeParticipant,
+ data: db.Session,
+ committee_name: str,
+ ):
+ super().__init__(write, write_as, data)
+ self.__write = write
+ self.__write_as = write_as
+ self.__data = data
+ self.__asf_uid = write.authorisation.asf_uid
+ self.__committee_name = committee_name
+
+
+class CommitteeMember(CommitteeParticipant):
+ def __init__(
+ self,
+ write: storage.Write,
+ write_as: storage.WriteAsCommitteeMember,
+ data: db.Session,
+ committee_name: str,
+ ):
+ super().__init__(write, write_as, data, committee_name)
+ self.__write = write
+ self.__write_as = write_as
+ self.__data = data
+ asf_uid = write.authorisation.asf_uid
+ if asf_uid is None:
+ raise storage.AccessError("No ASF UID")
+ self.__asf_uid = asf_uid
+ self.__committee_name = committee_name
+
+ async def add_distribution(
+ self,
+ release_name: str,
+ platform: sql.DistributionPlatform,
+ owner_namespace: str | None,
+ package: str,
+ version: str,
+ staging: bool,
+ upload_date: datetime.datetime | None,
+ api_url: str,
+ ) -> sql.Distribution:
+ distribution = sql.Distribution(
+ platform=platform,
+ release_name=release_name,
+ owner_namespace=owner_namespace,
+ package=package,
+ version=version,
+ staging=staging,
+ upload_date=upload_date,
+ api_url=api_url,
+ )
+ self.__data.add(distribution)
+ await self.__data.commit()
+ return distribution
diff --git a/atr/validate.py b/atr/validate.py
index ad55c23..0d29952 100644
--- a/atr/validate.py
+++ b/atr/validate.py
@@ -171,7 +171,6 @@ def project(p: sql.Project) -> AnnotatedDivergences:
yield from project_category(p)
yield from project_committee(p)
yield from project_created(p)
- yield from project_distribution_channels(p)
yield from project_full_name(p)
yield from project_programming_languages(p)
yield from project_release_policy(p)
@@ -235,14 +234,6 @@ def project_created(p: sql.Project) -> Divergences:
yield from divergences_predicate(predicate, expected, p.created)
-@project_components("Project.distribution_channels")
-def project_distribution_channels(p: sql.Project) -> Divergences:
- """Check that distribution_channels is empty."""
- expected: list[object] = []
- actual = p.distribution_channels
- yield from divergences(expected, actual)
-
-
@project_components("Project.full_name")
def project_full_name(p: sql.Project) -> Divergences:
"""Check that the project full_name is present and starts with 'Apache
'."""
diff --git a/migrations/versions/0018_2025.08.07_41ccdd9a.py
b/migrations/versions/0018_2025.08.07_41ccdd9a.py
new file mode 100644
index 0000000..4094fc5
--- /dev/null
+++ b/migrations/versions/0018_2025.08.07_41ccdd9a.py
@@ -0,0 +1,65 @@
+"""Add Distribution, and remove DistributionChannel
+
+Revision ID: 0018_2025.08.07_41ccdd9a
+Revises: 0017_2025.07.29_a880eb40
+Create Date: 2025-08-07 14:32:07.523311+00:00
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+# Revision identifiers, used by Alembic
+revision: str = "0018_2025.08.07_41ccdd9a"
+down_revision: str | None = "0017_2025.07.29_a880eb40"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ op.create_table(
+ "distribution",
+ sa.Column("id", sa.Integer(), nullable=False),
+ sa.Column("release_name", sa.String(), nullable=False),
+ sa.Column(
+ "platform",
+ sa.Enum(
+ "ARTIFACTHUB", "DOCKER", "GITHUB", "MAVEN", "NPM",
"NPM_SCOPED", "PYPI", name="distributionplatform"
+ ),
+ nullable=False,
+ ),
+ sa.Column("owner_namespace", sa.String(), nullable=True),
+ sa.Column("package", sa.String(), nullable=False),
+ sa.Column("version", sa.String(), nullable=False),
+ sa.Column("staging", sa.Boolean(), nullable=False,
server_default=sa.false()),
+ sa.Column("upload_date", sa.DateTime(), nullable=True),
+ sa.Column("api_url", sa.String(), nullable=False),
+ sa.ForeignKeyConstraint(["release_name"], ["release.name"],
name=op.f("fk_distribution_release_name_release")),
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_distribution")),
+ )
+ with op.batch_alter_table("distributionchannel", schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f("ix_distributionchannel_name"))
+
+ op.drop_table("distributionchannel")
+
+
+def downgrade() -> None:
+ op.create_table(
+ "distributionchannel",
+ sa.Column("id", sa.INTEGER(), nullable=False),
+ sa.Column("name", sa.VARCHAR(), nullable=False),
+ sa.Column("url", sa.VARCHAR(), nullable=False),
+ sa.Column("credentials", sa.VARCHAR(), nullable=False),
+ sa.Column("is_test", sa.BOOLEAN(), nullable=False),
+ sa.Column("automation_endpoint", sa.VARCHAR(), nullable=False),
+ sa.Column("project_name", sa.VARCHAR(), nullable=False),
+ sa.ForeignKeyConstraint(
+ ["project_name"], ["project.name"],
name=op.f("fk_distributionchannel_project_name_project")
+ ),
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_distributionchannel")),
+ )
+ with op.batch_alter_table("distributionchannel", schema=None) as batch_op:
+ batch_op.create_index(batch_op.f("ix_distributionchannel_name"),
["name"], unique=1)
+
+ op.drop_table("distribution")
diff --git a/pyproject.toml b/pyproject.toml
index db3a264..8dfd794 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -129,4 +129,3 @@ select = [
"atr/db/__init__.py" = ["C901"]
"atr/routes/modules.py" = ["F401"]
"migrations/env.py" = ["E402"]
-"scripts/release_path_parse.py" = ["C901", "RUF001"]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]