Skip to content

Commit c9e8150

Browse files
authored
Merge pull request #499 from Police-Data-Accessibility-Project/mc_494_suggest_data_source
Begin draft
2 parents 8c8e792 + 08c0c4a commit c9e8150

File tree

92 files changed

+1527
-150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1527
-150
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Enable data source/agency submission
2+
3+
Revision ID: 6adf9d894180
4+
Revises: 9d57b3b79d35
5+
Create Date: 2025-10-20 16:20:44.081736
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects.postgresql import ENUM, ARRAY
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = '6adf9d894180'
17+
down_revision: Union[str, None] = '9d57b3b79d35'
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
def upgrade() -> None:
22+
_add_autogenerated_agency_id()
23+
_add_new_columns_to_optional_ds_metadata()
24+
25+
def _add_new_columns_to_optional_ds_metadata():
26+
table_name: str = "url_optional_data_source_metadata"
27+
28+
agency_aggregation_enum = ENUM(
29+
'federal',
30+
'state',
31+
'county',
32+
'local',
33+
name='agency_aggregation_enum',
34+
create_type=True,
35+
)
36+
agency_aggregation_enum.create(op.get_bind())
37+
38+
update_method_enum = ENUM(
39+
'Overwrite',
40+
'Insert',
41+
'No updates',
42+
name='update_method_enum',
43+
create_type=True
44+
)
45+
update_method_enum.create(op.get_bind())
46+
47+
retention_schedule_enum = ENUM(
48+
'Future only',
49+
'1 month',
50+
'1 day',
51+
'1 week',
52+
'1-10 years',
53+
'< 1 day',
54+
'< 1 week',
55+
'< 1 year',
56+
'> 10 years',
57+
name='retention_schedule_enum',
58+
create_type=True
59+
)
60+
retention_schedule_enum.create(op.get_bind())
61+
62+
access_type_enum = ENUM(
63+
'Webpage',
64+
'Download',
65+
'API',
66+
name='access_type_enum',
67+
create_type=True,
68+
)
69+
access_type_enum.create(op.get_bind())
70+
71+
for column in [
72+
sa.Column('coverage_start', sa.Date(), nullable=True),
73+
sa.Column('coverage_end', sa.Date(), nullable=True),
74+
sa.Column("agency_supplied", sa.Boolean(), nullable=True),
75+
sa.Column('agency_originated', sa.Boolean(), nullable=True),
76+
sa.Column('agency_aggregation', agency_aggregation_enum),
77+
sa.Column('agency_described_not_in_database', sa.Text(), nullable=True),
78+
sa.Column('update_method', update_method_enum, nullable=True),
79+
sa.Column('readme_url', sa.Text(), nullable=True),
80+
sa.Column('originating_entity', sa.Text(), nullable=True),
81+
sa.Column('retention_schedule', retention_schedule_enum, nullable=True),
82+
sa.Column('scraper_url', sa.Text(), nullable=True),
83+
sa.Column('submission_notes', sa.Text(), nullable=True),
84+
sa.Column('access_notes', sa.Text(), nullable=True),
85+
sa.Column('access_types', ARRAY(
86+
access_type_enum
87+
), nullable=True),
88+
]:
89+
op.add_column(
90+
table_name,
91+
column,
92+
)
93+
94+
def _add_autogenerated_agency_id():
95+
op.execute(
96+
"""
97+
CREATE SEQUENCE agencies_agency_id START WITH 23191;
98+
"""
99+
)
100+
101+
op.execute(
102+
"""
103+
ALTER TABLE agencies
104+
ALTER COLUMN agency_id SET DEFAULT nextval('agencies_agency_id');
105+
"""
106+
)
107+
108+
def downgrade() -> None:
109+
pass

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ timeout = 300
33
asyncio_default_fixture_loop_scope=function
44
markers =
55
manual: mark test as manual-only (excluded from default test runs)
6+
asyncio_mode = auto

src/api/endpoints/agencies/by_id/delete/__init__.py

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from sqlalchemy import delete
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from src.db.models.impl.agency.sqlalchemy import Agency
5+
from src.db.queries.base.builder import QueryBuilderBase
6+
7+
8+
class DeleteAgencyQueryBuilder(QueryBuilderBase):
9+
10+
def __init__(
11+
self,
12+
agency_id: int,
13+
):
14+
super().__init__()
15+
self.agency_id = agency_id
16+
17+
async def run(self, session: AsyncSession) -> None:
18+
statement = (
19+
delete(Agency)
20+
.where(Agency.agency_id == self.agency_id)
21+
)
22+
await session.execute(statement)

src/api/endpoints/agencies/by_id/delete/request.py

Whitespace-only changes.

src/api/endpoints/agencies/by_id/locations/__init__.py

Whitespace-only changes.

src/api/endpoints/agencies/by_id/locations/delete/__init__.py

Whitespace-only changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from sqlalchemy import delete
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from src.db.models.impl.link.agency_location.sqlalchemy import LinkAgencyLocation
5+
from src.db.queries.base.builder import QueryBuilderBase
6+
7+
8+
class DeleteAgencyLocationQueryBuilder(QueryBuilderBase):
9+
10+
def __init__(
11+
self,
12+
agency_id: int,
13+
location_id: int,
14+
):
15+
super().__init__()
16+
self.agency_id = agency_id
17+
self.location_id = location_id
18+
19+
async def run(self, session: AsyncSession) -> None:
20+
statement = (
21+
delete(LinkAgencyLocation)
22+
.where(
23+
(LinkAgencyLocation.agency_id == self.agency_id)
24+
& (LinkAgencyLocation.location_id == self.location_id)
25+
)
26+
)
27+
28+
await session.execute(statement)
29+

0 commit comments

Comments
 (0)