ethyca-fides 2.68.1b1__py2.py3-none-any.whl → 2.68.1b2__py2.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.

Potentially problematic release.


This version of ethyca-fides might be problematic. Click here for more details.

Files changed (116) hide show
  1. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/RECORD +113 -105
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/3baf42d251a6_add_generic_taxonomy_models.py +239 -0
  5. fides/api/api/v1/endpoints/generic_overrides.py +64 -167
  6. fides/api/db/base.py +6 -0
  7. fides/api/models/taxonomy.py +275 -0
  8. fides/api/service/deps.py +5 -0
  9. fides/api/service/privacy_request/request_service.py +6 -1
  10. fides/api/task/manual/manual_task_graph_task.py +11 -0
  11. fides/api/util/connection_type.py +68 -33
  12. fides/data/sample_project/docker-compose.yml +3 -3
  13. fides/service/taxonomy/__init__.py +0 -0
  14. fides/service/taxonomy/handlers/__init__.py +11 -0
  15. fides/service/taxonomy/handlers/base.py +42 -0
  16. fides/service/taxonomy/handlers/legacy_handler.py +95 -0
  17. fides/service/taxonomy/taxonomy_service.py +261 -0
  18. fides/service/taxonomy/utils.py +160 -0
  19. fides/ui-build/static/admin/404.html +1 -1
  20. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-65723cd4b8fc36ac.js → _app-2c10f6b217b7978b.js} +1 -1
  21. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-58827eb86516931f.js +1 -0
  22. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-766e57bcf38b5b1e.js → [id]-4e286a1e501a0c73.js} +1 -1
  23. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-709bcb0bc6a5382d.js +1 -0
  24. fides/ui-build/static/admin/_next/static/css/a72179b1754aadd3.css +1 -0
  25. fides/ui-build/static/admin/_next/static/{tzF4yti8NslASlGnxnZ8m → qvk5eMANVfwYkdURE7fgG}/_buildManifest.js +1 -1
  26. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  27. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  28. fides/ui-build/static/admin/add-systems.html +1 -1
  29. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  30. fides/ui-build/static/admin/consent/configure.html +1 -1
  31. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  32. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  33. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  34. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  35. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  36. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  37. fides/ui-build/static/admin/consent/properties.html +1 -1
  38. fides/ui-build/static/admin/consent/reporting.html +1 -1
  39. fides/ui-build/static/admin/consent.html +1 -1
  40. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  41. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  42. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  43. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  44. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  45. fides/ui-build/static/admin/data-catalog.html +1 -1
  46. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  47. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  48. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  49. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  50. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  51. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  52. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  53. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  54. fides/ui-build/static/admin/datamap.html +1 -1
  55. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  56. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  57. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  58. fides/ui-build/static/admin/dataset/new.html +1 -1
  59. fides/ui-build/static/admin/dataset.html +1 -1
  60. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  61. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  62. fides/ui-build/static/admin/datastore-connection.html +1 -1
  63. fides/ui-build/static/admin/index.html +1 -1
  64. fides/ui-build/static/admin/integrations/[id].html +1 -1
  65. fides/ui-build/static/admin/integrations.html +1 -1
  66. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  67. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  68. fides/ui-build/static/admin/lib/fides.js +1 -1
  69. fides/ui-build/static/admin/login/[provider].html +1 -1
  70. fides/ui-build/static/admin/login.html +1 -1
  71. fides/ui-build/static/admin/messaging/[id].html +1 -1
  72. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  73. fides/ui-build/static/admin/messaging.html +1 -1
  74. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  75. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  76. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  77. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  78. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  79. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  80. fides/ui-build/static/admin/poc/forms.html +1 -1
  81. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  82. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  83. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  84. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  85. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  86. fides/ui-build/static/admin/privacy-requests.html +1 -1
  87. fides/ui-build/static/admin/properties/[id].html +1 -1
  88. fides/ui-build/static/admin/properties/add-property.html +1 -1
  89. fides/ui-build/static/admin/properties.html +1 -1
  90. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  91. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  92. fides/ui-build/static/admin/settings/about.html +1 -1
  93. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  94. fides/ui-build/static/admin/settings/consent.html +1 -1
  95. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  96. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  97. fides/ui-build/static/admin/settings/domains.html +1 -1
  98. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  99. fides/ui-build/static/admin/settings/locations.html +1 -1
  100. fides/ui-build/static/admin/settings/organization.html +1 -1
  101. fides/ui-build/static/admin/settings/regulations.html +1 -1
  102. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  103. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  104. fides/ui-build/static/admin/systems.html +1 -1
  105. fides/ui-build/static/admin/taxonomy.html +1 -1
  106. fides/ui-build/static/admin/user-management/new.html +1 -1
  107. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  108. fides/ui-build/static/admin/user-management.html +1 -1
  109. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-53a763e49ce34a74.js +0 -1
  110. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-f43a988542813110.js +0 -1
  111. fides/ui-build/static/admin/_next/static/css/e1628f15dd5f019b.css +0 -1
  112. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/WHEEL +0 -0
  113. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/entry_points.txt +0 -0
  114. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/licenses/LICENSE +0 -0
  115. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/top_level.txt +0 -0
  116. /fides/ui-build/static/admin/_next/static/{tzF4yti8NslASlGnxnZ8m → qvk5eMANVfwYkdURE7fgG}/_ssgManifest.js +0 -0
@@ -0,0 +1,239 @@
1
+ """add generic taxonomy models
2
+
3
+ Revision ID: 3baf42d251a6
4
+ Revises: 2f3c1a2d6b10
5
+ Create Date: 2025-08-05 21:49:39.619569
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "3baf42d251a6"
15
+ down_revision = "2f3c1a2d6b10"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # Create taxonomy table
22
+ op.create_table(
23
+ "taxonomy",
24
+ sa.Column("id", sa.String(length=255), nullable=False),
25
+ sa.Column(
26
+ "created_at",
27
+ sa.DateTime(timezone=True),
28
+ server_default=sa.text("now()"),
29
+ nullable=True,
30
+ ),
31
+ sa.Column(
32
+ "updated_at",
33
+ sa.DateTime(timezone=True),
34
+ server_default=sa.text("now()"),
35
+ nullable=True,
36
+ ),
37
+ sa.Column("fides_key", sa.String(), nullable=False),
38
+ sa.Column("organization_fides_key", sa.Text(), nullable=True),
39
+ sa.Column("tags", postgresql.ARRAY(sa.String()), nullable=True),
40
+ sa.Column("name", sa.Text(), nullable=True),
41
+ sa.Column("description", sa.Text(), nullable=True),
42
+ sa.PrimaryKeyConstraint("fides_key"),
43
+ sa.UniqueConstraint("id", name="uq_taxonomy_id"),
44
+ )
45
+ op.create_index(
46
+ op.f("ix_taxonomy_fides_key"), "taxonomy", ["fides_key"], unique=True
47
+ )
48
+
49
+ # Create taxonomy_allowed_usage table
50
+ op.create_table(
51
+ "taxonomy_allowed_usage",
52
+ sa.Column("id", sa.String(length=255), nullable=False),
53
+ sa.Column(
54
+ "created_at",
55
+ sa.DateTime(timezone=True),
56
+ server_default=sa.text("now()"),
57
+ nullable=True,
58
+ ),
59
+ sa.Column(
60
+ "updated_at",
61
+ sa.DateTime(timezone=True),
62
+ server_default=sa.text("now()"),
63
+ nullable=True,
64
+ ),
65
+ sa.Column("source_taxonomy_key", sa.String(), nullable=False),
66
+ sa.Column(
67
+ "target_type", sa.String(), nullable=False
68
+ ), # Can be "system" OR "data_categories"
69
+ sa.ForeignKeyConstraint(
70
+ ["source_taxonomy_key"], ["taxonomy.fides_key"], ondelete="CASCADE"
71
+ ),
72
+ sa.PrimaryKeyConstraint("source_taxonomy_key", "target_type"),
73
+ sa.UniqueConstraint("id", name="uq_taxonomy_allowed_usage_id"),
74
+ )
75
+
76
+ # Create taxonomy_element table
77
+ op.create_table(
78
+ "taxonomy_element",
79
+ sa.Column("id", sa.String(length=255), nullable=False),
80
+ sa.Column(
81
+ "created_at",
82
+ sa.DateTime(timezone=True),
83
+ server_default=sa.text("now()"),
84
+ nullable=True,
85
+ ),
86
+ sa.Column(
87
+ "updated_at",
88
+ sa.DateTime(timezone=True),
89
+ server_default=sa.text("now()"),
90
+ nullable=True,
91
+ ),
92
+ sa.Column("fides_key", sa.String(), nullable=False),
93
+ sa.Column("organization_fides_key", sa.Text(), nullable=True),
94
+ sa.Column("tags", postgresql.ARRAY(sa.String()), nullable=True),
95
+ sa.Column("name", sa.Text(), nullable=True),
96
+ sa.Column("description", sa.Text(), nullable=True),
97
+ sa.Column("taxonomy_type", sa.String(), nullable=False),
98
+ sa.Column("parent_key", sa.Text(), nullable=True),
99
+ sa.Column("active", sa.BOOLEAN(), nullable=False),
100
+ sa.ForeignKeyConstraint(
101
+ ["parent_key"], ["taxonomy_element.fides_key"], ondelete="RESTRICT"
102
+ ),
103
+ sa.ForeignKeyConstraint(
104
+ ["taxonomy_type"], ["taxonomy.fides_key"], ondelete="CASCADE"
105
+ ),
106
+ sa.PrimaryKeyConstraint("fides_key"),
107
+ sa.UniqueConstraint("id", name="uq_taxonomy_element_id"),
108
+ )
109
+ op.create_index(
110
+ op.f("ix_taxonomy_element_fides_key"),
111
+ "taxonomy_element",
112
+ ["fides_key"],
113
+ unique=True,
114
+ )
115
+ op.create_index(
116
+ op.f("ix_taxonomy_element_active"), "taxonomy_element", ["active"], unique=False
117
+ )
118
+ op.create_index(
119
+ op.f("ix_taxonomy_element_parent_key"),
120
+ "taxonomy_element",
121
+ ["parent_key"],
122
+ unique=False,
123
+ )
124
+ op.create_index(
125
+ op.f("ix_taxonomy_element_taxonomy_type"),
126
+ "taxonomy_element",
127
+ ["taxonomy_type"],
128
+ unique=False,
129
+ )
130
+
131
+ # Create taxonomy_usage table
132
+ op.create_table(
133
+ "taxonomy_usage",
134
+ sa.Column("id", sa.String(length=255), nullable=False),
135
+ sa.Column(
136
+ "created_at",
137
+ sa.DateTime(timezone=True),
138
+ server_default=sa.text("now()"),
139
+ nullable=True,
140
+ ),
141
+ sa.Column(
142
+ "updated_at",
143
+ sa.DateTime(timezone=True),
144
+ server_default=sa.text("now()"),
145
+ nullable=True,
146
+ ),
147
+ sa.Column("source_element_key", sa.String(), nullable=False),
148
+ sa.Column("target_element_key", sa.String(), nullable=False),
149
+ sa.Column("source_taxonomy", sa.String(), nullable=False),
150
+ sa.Column("target_taxonomy", sa.String(), nullable=False),
151
+ sa.ForeignKeyConstraint(
152
+ ["source_taxonomy", "target_taxonomy"],
153
+ [
154
+ "taxonomy_allowed_usage.source_taxonomy_key",
155
+ "taxonomy_allowed_usage.target_type",
156
+ ],
157
+ name="fk_taxonomy_usage_allowed",
158
+ ondelete="RESTRICT",
159
+ ),
160
+ sa.PrimaryKeyConstraint("id"),
161
+ sa.UniqueConstraint(
162
+ "source_element_key",
163
+ "target_element_key",
164
+ name="uq_taxonomy_usage",
165
+ ),
166
+ )
167
+ op.create_index(
168
+ op.f("ix_taxonomy_usage_source_element_key"),
169
+ "taxonomy_usage",
170
+ ["source_element_key"],
171
+ unique=False,
172
+ )
173
+ op.create_index(
174
+ op.f("ix_taxonomy_usage_id"), "taxonomy_usage", ["id"], unique=False
175
+ )
176
+ op.create_index(
177
+ op.f("ix_taxonomy_usage_source_taxonomy"),
178
+ "taxonomy_usage",
179
+ ["source_taxonomy"],
180
+ unique=False,
181
+ )
182
+ op.create_index(
183
+ op.f("ix_taxonomy_usage_target_element_key"),
184
+ "taxonomy_usage",
185
+ ["target_element_key"],
186
+ unique=False,
187
+ )
188
+ op.create_index(
189
+ op.f("ix_taxonomy_usage_target_taxonomy"),
190
+ "taxonomy_usage",
191
+ ["target_taxonomy"],
192
+ unique=False,
193
+ )
194
+
195
+ # Seed legacy taxonomies that are managed by the system
196
+ op.execute(
197
+ """
198
+ INSERT INTO taxonomy (id, created_at, updated_at, fides_key, organization_fides_key, tags, name, description)
199
+ VALUES
200
+ ('data_category', now(), now(), 'data_category', NULL, NULL, 'Data categories', 'Taxonomy for data categories'),
201
+ ('data_use', now(), now(), 'data_use', NULL, NULL, 'Data uses', 'Taxonomy for data uses'),
202
+ ('data_subject', now(), now(), 'data_subject', NULL, NULL, 'Data subjects', 'Taxonomy for data subjects'),
203
+ ('system_group', now(), now(), 'system_group', NULL, NULL, 'System groups', 'Taxonomy for system groups')
204
+ """
205
+ )
206
+
207
+
208
+ def downgrade():
209
+ # Drop taxonomy_usage
210
+ op.drop_index(
211
+ op.f("ix_taxonomy_usage_source_element_key"), table_name="taxonomy_usage"
212
+ )
213
+ op.drop_index(
214
+ op.f("ix_taxonomy_usage_target_taxonomy"), table_name="taxonomy_usage"
215
+ )
216
+ op.drop_index(
217
+ op.f("ix_taxonomy_usage_target_element_key"), table_name="taxonomy_usage"
218
+ )
219
+ op.drop_index(
220
+ op.f("ix_taxonomy_usage_source_taxonomy"), table_name="taxonomy_usage"
221
+ )
222
+ op.drop_index(op.f("ix_taxonomy_usage_id"), table_name="taxonomy_usage")
223
+ op.drop_table("taxonomy_usage")
224
+
225
+ # Drop taxonomy_element
226
+ op.drop_index(
227
+ op.f("ix_taxonomy_element_taxonomy_type"), table_name="taxonomy_element"
228
+ )
229
+ op.drop_index(op.f("ix_taxonomy_element_parent_key"), table_name="taxonomy_element")
230
+ op.drop_index(op.f("ix_taxonomy_element_active"), table_name="taxonomy_element")
231
+ op.drop_index(op.f("ix_taxonomy_element_fides_key"), table_name="taxonomy_element")
232
+ op.drop_table("taxonomy_element")
233
+
234
+ # Drop taxonomy_allowed_usage
235
+ op.drop_table("taxonomy_allowed_usage")
236
+
237
+ # Drop taxonomy
238
+ op.drop_index(op.f("ix_taxonomy_fides_key"), table_name="taxonomy")
239
+ op.drop_table("taxonomy")
@@ -1,5 +1,5 @@
1
1
  from functools import partial
2
- from typing import Dict, List, Optional, Type, Union
2
+ from typing import Dict, List, Optional, Union
3
3
 
4
4
  from fastapi import Depends, HTTPException, Query, Response, Security
5
5
  from fastapi.concurrency import run_in_threadpool
@@ -11,17 +11,16 @@ from fideslang.models import Dataset as FideslangDataset
11
11
  from pydantic import ValidationError as PydanticValidationError
12
12
  from sqlalchemy import not_, select
13
13
  from sqlalchemy.ext.asyncio import AsyncSession
14
- from sqlalchemy.orm import Session, load_only
14
+ from sqlalchemy.orm import load_only
15
15
  from starlette import status
16
16
  from starlette.status import (
17
17
  HTTP_200_OK,
18
+ HTTP_400_BAD_REQUEST,
18
19
  HTTP_404_NOT_FOUND,
19
20
  HTTP_422_UNPROCESSABLE_ENTITY,
20
21
  )
21
22
 
22
- from fides.api.api.deps import get_db
23
23
  from fides.api.common_exceptions import KeyOrNameAlreadyExists, ValidationError
24
- from fides.api.db.base_class import get_key_from_data
25
24
  from fides.api.db.crud import list_resource_query
26
25
  from fides.api.db.ctl_session import get_async_db
27
26
  from fides.api.models.connectionconfig import ConnectionConfig, ConnectionType
@@ -37,9 +36,8 @@ from fides.api.schemas.taxonomy_extensions import (
37
36
  DataUse,
38
37
  DataUseCreateOrUpdate,
39
38
  )
40
- from fides.api.service.deps import get_dataset_service
39
+ from fides.api.service.deps import get_dataset_service, get_taxonomy_service
41
40
  from fides.api.util.api_router import APIRouter
42
- from fides.api.util.errors import FidesError, ForbiddenIsDefaultTaxonomyError
43
41
  from fides.api.util.filter_utils import apply_filters_to_query
44
42
  from fides.common.api.scope_registry import (
45
43
  CTL_DATASET_CREATE,
@@ -57,18 +55,12 @@ from fides.service.dataset.dataset_service import (
57
55
  DatasetNotFoundException,
58
56
  DatasetService,
59
57
  )
58
+ from fides.service.taxonomy.taxonomy_service import TaxonomyService
60
59
 
61
60
  from fides.api.models.sql_models import ( # type: ignore[attr-defined] # isort: skip
62
61
  Dataset as CtlDataset,
63
62
  )
64
63
 
65
- from fides.api.models.sql_models import ( # type: ignore[attr-defined] # isort: skip
66
- DataCategory as DataCategoryDbModel,
67
- DataSubject as DataSubjectDbModel,
68
- DataUse as DataUseDbModel,
69
- ModelWithDefaultField,
70
- )
71
-
72
64
  # We create routers to override specific methods in those defined in generic.py
73
65
  # when we need more custom implementations for only some of the methods in a router.
74
66
 
@@ -354,135 +346,6 @@ def clean_datasets(
354
346
  )
355
347
 
356
348
 
357
- def activate_taxonomy_parents(
358
- resource: Union[DataCategoryDbModel, DataUseDbModel, DataSubjectDbModel],
359
- db: Session,
360
- ) -> None:
361
- """
362
- Activates parents to match newly-active taxonomy node.
363
- """
364
- parent = resource.parent
365
- if parent:
366
- parent.active = True
367
- db.commit()
368
-
369
- activate_taxonomy_parents(parent, db)
370
-
371
-
372
- def deactivate_taxonomy_node_and_descendants(
373
- resource: Union[DataCategoryDbModel, DataUseDbModel, DataSubjectDbModel],
374
- db: Session,
375
- ) -> None:
376
- """
377
- Recursively de-activates all descendants of a given taxonomy node.
378
- """
379
- resource.active = False
380
- db.commit()
381
- children = resource.children
382
-
383
- for child in children:
384
- # Deactivate current child
385
- child.active = False
386
- db.commit()
387
-
388
- # Recursively deactivate all descendants of this child
389
- deactivate_taxonomy_node_and_descendants(child, db)
390
-
391
-
392
- def validate_and_create_taxonomy(
393
- db: Session,
394
- model: Union[
395
- Type[DataCategoryDbModel], Type[DataUseDbModel], Type[DataSubjectDbModel]
396
- ],
397
- validation_schema: type,
398
- data: Union[
399
- DataCategoryCreateOrUpdate, DataUseCreateOrUpdate, DataSubjectCreateOrUpdate
400
- ],
401
- ) -> Dict:
402
- """
403
- Validate and create a taxonomy element.
404
- """
405
- if not data.fides_key:
406
- raise FidesError(f"Fides key is required to create a {model.__name__} resource")
407
- if isinstance(data, ModelWithDefaultField) and data.is_default:
408
- raise ForbiddenIsDefaultTaxonomyError(
409
- model.__name__, data.fides_key, action="create"
410
- )
411
- validated_taxonomy = validation_schema(**data.model_dump(mode="json"))
412
- return model.create(db=db, data=validated_taxonomy.model_dump(mode="json"))
413
-
414
-
415
- def validate_and_update_taxonomy(
416
- db: Session,
417
- resource: Union[DataCategoryDbModel, DataUseDbModel, DataSubjectDbModel],
418
- validation_schema: type,
419
- data: Union[
420
- DataCategoryCreateOrUpdate, DataUseCreateOrUpdate, DataSubjectCreateOrUpdate
421
- ],
422
- ) -> Dict:
423
- """
424
- Validate and update a taxonomy element.
425
- """
426
- if (
427
- isinstance(data, ModelWithDefaultField)
428
- and data.is_default != resource.is_default
429
- ):
430
- raise ForbiddenIsDefaultTaxonomyError(
431
- "resource", data.fides_key, action="modify"
432
- )
433
-
434
- # If active field is being updated, cascade change either up or down
435
- if hasattr(data, "active"):
436
- if data.active:
437
- activate_taxonomy_parents(resource, db)
438
- else:
439
- # Cascade down - deactivate current node and children to match newly-deactivated taxonomy item
440
- deactivate_taxonomy_node_and_descendants(resource, db)
441
-
442
- validated_taxonomy = validation_schema(**data.model_dump(mode="json"))
443
- return resource.update(db=db, data=validated_taxonomy.model_dump(mode="json"))
444
-
445
-
446
- def create_or_update_taxonomy(
447
- db: Session,
448
- data: Union[
449
- DataCategoryCreateOrUpdate, DataUseCreateOrUpdate, DataSubjectCreateOrUpdate
450
- ],
451
- model: Union[
452
- Type[DataCategoryDbModel], Type[DataUseDbModel], Type[DataSubjectDbModel]
453
- ],
454
- validation_schema: type,
455
- ) -> Dict:
456
- """
457
- Create or update a taxonomy element.
458
- If the element is deactivated, it will be updated and re-activated, along with its parents.
459
- """
460
- if data.fides_key is None:
461
- disabled_resource_with_name = (
462
- db.query(model)
463
- .filter(
464
- model.active.is_(False),
465
- model.name == data.name,
466
- )
467
- .first()
468
- )
469
- data.fides_key = get_key_from_data(
470
- {"key": data.fides_key, "name": data.name}, validation_schema.__name__
471
- )
472
- if data.parent_key if hasattr(data, "parent_key") else None:
473
- # Updates fides_key if it is not the root level taxonomy node
474
- data.fides_key = f"{data.parent_key}.{data.fides_key}" # type: ignore[union-attr]
475
- if disabled_resource_with_name:
476
- data.active = True
477
- activate_taxonomy_parents(disabled_resource_with_name, db)
478
- return validate_and_update_taxonomy(
479
- db, disabled_resource_with_name, validation_schema, data
480
- )
481
- return validate_and_create_taxonomy(db, model, validation_schema, data)
482
-
483
- return validate_and_create_taxonomy(db, model, validation_schema, data)
484
-
485
-
486
349
  @data_use_router.post(
487
350
  "/data_use",
488
351
  dependencies=[Security(verify_oauth_client, scopes=[DATA_USE_CREATE])],
@@ -492,19 +355,21 @@ def create_or_update_taxonomy(
492
355
  )
493
356
  async def create_data_use(
494
357
  data_use: DataUseCreateOrUpdate,
495
- db: Session = Depends(get_db),
358
+ taxonomy_service: TaxonomyService = Depends(get_taxonomy_service),
496
359
  ) -> Dict:
497
360
  """
498
361
  Create a data use. Updates existing data use if data use with name already exists and is disabled.
499
362
  """
500
363
  try:
501
- return create_or_update_taxonomy(db, data_use, DataUseDbModel, DataUse)
364
+ return taxonomy_service.create_or_update_element(
365
+ "data_uses", data_use.model_dump()
366
+ )
502
367
  except KeyOrNameAlreadyExists:
503
368
  raise HTTPException(
504
369
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
505
370
  detail=f"Data use with key {data_use.fides_key} or name {data_use.name} already exists.",
506
371
  )
507
- except PydanticValidationError as e:
372
+ except (ValidationError, PydanticValidationError) as e:
508
373
  raise HTTPException(
509
374
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
510
375
  detail=str(e),
@@ -520,21 +385,26 @@ async def create_data_use(
520
385
  )
521
386
  async def create_data_category(
522
387
  data_category: DataCategoryCreateOrUpdate,
523
- db: Session = Depends(get_db),
388
+ taxonomy_service: TaxonomyService = Depends(get_taxonomy_service),
524
389
  ) -> Dict:
525
390
  """
526
391
  Create a data category
527
392
  """
528
393
 
529
394
  try:
530
- return create_or_update_taxonomy(
531
- db, data_category, DataCategoryDbModel, DataCategory
395
+ return taxonomy_service.create_or_update_element(
396
+ "data_categories", data_category.model_dump()
532
397
  )
533
398
  except KeyOrNameAlreadyExists:
534
399
  raise HTTPException(
535
400
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
536
401
  detail=f"Data category with key {data_category.fides_key} or name {data_category.name} already exists.",
537
402
  )
403
+ except (ValidationError, PydanticValidationError) as e:
404
+ raise HTTPException(
405
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
406
+ detail=str(e),
407
+ )
538
408
 
539
409
 
540
410
  @data_subject_router.post(
@@ -546,21 +416,26 @@ async def create_data_category(
546
416
  )
547
417
  async def create_data_subject(
548
418
  data_subject: DataSubjectCreateOrUpdate,
549
- db: Session = Depends(get_db),
419
+ taxonomy_service: TaxonomyService = Depends(get_taxonomy_service),
550
420
  ) -> Dict:
551
421
  """
552
422
  Create a data subject
553
423
  """
554
424
 
555
425
  try:
556
- return create_or_update_taxonomy(
557
- db, data_subject, DataSubjectDbModel, DataSubject
426
+ return taxonomy_service.create_or_update_element(
427
+ "data_subjects", data_subject.model_dump()
558
428
  )
559
429
  except KeyOrNameAlreadyExists:
560
430
  raise HTTPException(
561
431
  status_code=HTTP_422_UNPROCESSABLE_ENTITY,
562
432
  detail=f"Data subject with key {data_subject.fides_key} or name {data_subject.name} already exists.",
563
433
  )
434
+ except (ValidationError, PydanticValidationError) as e:
435
+ raise HTTPException(
436
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
437
+ detail=str(e),
438
+ )
564
439
 
565
440
 
566
441
  @data_use_router.put(
@@ -572,19 +447,31 @@ async def create_data_subject(
572
447
  )
573
448
  async def update_data_use(
574
449
  data_use: DataUseCreateOrUpdate,
575
- db: Session = Depends(get_db),
450
+ taxonomy_service: TaxonomyService = Depends(get_taxonomy_service),
576
451
  ) -> Dict:
577
452
  """
578
453
  Update a data use. Ensures updates to "active" are appropriately cascaded.
579
454
  """
580
-
581
- resource = DataUseDbModel.get_by(db, field="fides_key", value=data_use.fides_key)
582
- if not resource:
455
+ if not data_use.fides_key:
583
456
  raise HTTPException(
584
- status_code=HTTP_404_NOT_FOUND,
585
- detail=f"Data use not found with key: {data_use.fides_key}",
457
+ status_code=HTTP_400_BAD_REQUEST,
458
+ detail="fides_key is required for update operations.",
459
+ )
460
+ try:
461
+ result = taxonomy_service.update_element(
462
+ "data_uses", data_use.fides_key, data_use.model_dump()
463
+ )
464
+ if not result:
465
+ raise HTTPException(
466
+ status_code=HTTP_404_NOT_FOUND,
467
+ detail=f"Data use not found with key: {data_use.fides_key}",
468
+ )
469
+ return result
470
+ except (ValidationError, PydanticValidationError) as e:
471
+ raise HTTPException(
472
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
473
+ detail=str(e),
586
474
  )
587
- return validate_and_update_taxonomy(db, resource, DataUse, data_use)
588
475
 
589
476
 
590
477
  @data_category_router.put(
@@ -596,21 +483,31 @@ async def update_data_use(
596
483
  )
597
484
  async def update_data_category(
598
485
  data_category: DataCategoryCreateOrUpdate,
599
- db: Session = Depends(get_db),
486
+ taxonomy_service: TaxonomyService = Depends(get_taxonomy_service),
600
487
  ) -> Dict:
601
488
  """
602
489
  Update a data category. Ensures updates to "active" are appropriately cascaded.
603
490
  """
604
-
605
- resource = DataCategoryDbModel.get_by(
606
- db, field="fides_key", value=data_category.fides_key
607
- )
608
- if not resource:
491
+ if not data_category.fides_key:
609
492
  raise HTTPException(
610
- status_code=HTTP_404_NOT_FOUND,
611
- detail=f"Data category not found with key: {data_category.fides_key}",
493
+ status_code=HTTP_400_BAD_REQUEST,
494
+ detail="fides_key is required for update operations.",
495
+ )
496
+ try:
497
+ result = taxonomy_service.update_element(
498
+ "data_categories", data_category.fides_key, data_category.model_dump()
499
+ )
500
+ if not result:
501
+ raise HTTPException(
502
+ status_code=HTTP_404_NOT_FOUND,
503
+ detail=f"Data category not found with key: {data_category.fides_key}",
504
+ )
505
+ return result
506
+ except (ValidationError, PydanticValidationError) as e:
507
+ raise HTTPException(
508
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
509
+ detail=str(e),
612
510
  )
613
- return validate_and_update_taxonomy(db, resource, DataCategory, data_category)
614
511
 
615
512
 
616
513
  GENERIC_OVERRIDES_ROUTER = APIRouter()
fides/api/db/base.py CHANGED
@@ -76,6 +76,12 @@ from fides.api.models.storage import StorageConfig
76
76
  from fides.api.models.system_compass_sync import SystemCompassSync
77
77
  from fides.api.models.system_history import SystemHistory
78
78
  from fides.api.models.system_manager import SystemManager
79
+ from fides.api.models.taxonomy import (
80
+ Taxonomy,
81
+ TaxonomyAllowedUsage,
82
+ TaxonomyElement,
83
+ TaxonomyUsage,
84
+ )
79
85
  from fides.api.models.tcf_publisher_restrictions import (
80
86
  TCFConfiguration,
81
87
  TCFPublisherRestriction,