diff --git a/api-editor/gui/src/features/annotations/AnnotationImportDialog.tsx b/api-editor/gui/src/features/annotations/AnnotationImportDialog.tsx index 35ae08179..d2fe4f099 100644 --- a/api-editor/gui/src/features/annotations/AnnotationImportDialog.tsx +++ b/api-editor/gui/src/features/annotations/AnnotationImportDialog.tsx @@ -12,30 +12,64 @@ import { ModalHeader, ModalOverlay, Text as ChakraText, + useToast, } from '@chakra-ui/react'; import React, { useState } from 'react'; import { useAppDispatch } from '../../app/hooks'; import { StyledDropzone } from '../../common/StyledDropzone'; import { isValidJsonFile } from '../../common/util/validation'; -import { AnnotationStore, initialAnnotationStore, mergeAnnotationStore, setAnnotationStore } from './annotationSlice'; +import { + AnnotationStore, + EXPECTED_ANNOTATION_STORE_SCHEMA_VERSION, + initialAnnotationStore, + mergeAnnotationStore, + setAnnotationStore, + VersionedAnnotationStore, +} from './annotationSlice'; import { hideAnnotationImportDialog, toggleAnnotationImportDialog } from '../ui/uiSlice'; export const AnnotationImportDialog: React.FC = function () { + const toast = useToast(); const [fileName, setFileName] = useState(''); - const [newAnnotationStore, setNewAnnotationStore] = useState(initialAnnotationStore); + const [newAnnotationStore, setNewAnnotationStore] = useState(initialAnnotationStore); const dispatch = useAppDispatch(); + const validate = () => { + if (!fileName) { + toast({ + title: 'No File Selected', + description: 'Select a file to import or cancel this dialog.', + status: 'error', + duration: 4000, + }); + return false; + } + + if ((newAnnotationStore.schemaVersion ?? 1) !== EXPECTED_ANNOTATION_STORE_SCHEMA_VERSION) { + toast({ + title: 'Old Annotation File', + description: 'This file is not compatible with the current version of the API Editor.', + status: 'error', + duration: 4000, + }); + return false; + } + + return true; + }; const merge = () => { - if (fileName) { + if (validate()) { + delete newAnnotationStore.schemaVersion; dispatch(mergeAnnotationStore(newAnnotationStore)); + dispatch(hideAnnotationImportDialog()); } - dispatch(hideAnnotationImportDialog()); }; const replace = () => { - if (fileName) { + if (validate()) { + delete newAnnotationStore.schemaVersion; dispatch(setAnnotationStore(newAnnotationStore)); + dispatch(hideAnnotationImportDialog()); } - dispatch(hideAnnotationImportDialog()); }; const close = () => dispatch(toggleAnnotationImportDialog()); diff --git a/api-editor/gui/src/features/annotations/annotationSlice.ts b/api-editor/gui/src/features/annotations/annotationSlice.ts index 73dbe8149..2bf7ac442 100644 --- a/api-editor/gui/src/features/annotations/annotationSlice.ts +++ b/api-editor/gui/src/features/annotations/annotationSlice.ts @@ -3,6 +3,9 @@ import * as idb from 'idb-keyval'; import { RootState } from '../../app/store'; import { isValidUsername } from '../../common/util/validation'; +export const EXPECTED_ANNOTATION_STORE_SCHEMA_VERSION = 1; +export const EXPECTED_ANNOTATION_SLICE_SCHEMA_VERSION = 1; + /** * How many annotations can be applied to a class at once. */ @@ -11,16 +14,17 @@ export const maximumNumberOfClassAnnotations = 5; /** * How many annotations can be applied to a function at once. */ -export const maximumNumberOfFunctionAnnotations = 7; +export const maximumNumberOfFunctionAnnotations = 8; /** * How many annotations can be applied to a parameter at once. */ -export const maximumNumberOfParameterAnnotations = 8; +export const maximumNumberOfParameterAnnotations = 9; const maximumUndoHistoryLength = 10; export interface AnnotationSlice { + schemaVersion?: number; annotations: AnnotationStore; queue: AnnotationStore[]; queueIndex: number; @@ -75,6 +79,10 @@ export interface AnnotationStore { }; } +export interface VersionedAnnotationStore extends AnnotationStore { + schemaVersion?: number; +} + export interface Annotation { /** * ID of the annotated Python declaration. @@ -301,6 +309,10 @@ export const initialAnnotationSlice: AnnotationSlice = { export const initializeAnnotations = createAsyncThunk('annotations/initialize', async () => { try { const storedAnnotations = (await idb.get('annotations')) as AnnotationSlice; + if ((storedAnnotations.schemaVersion ?? 1) !== EXPECTED_ANNOTATION_SLICE_SCHEMA_VERSION) { + return initialAnnotationSlice; + } + return { ...initialAnnotationSlice, ...storedAnnotations, diff --git a/api-editor/gui/src/features/menuBar/HelpMenu.tsx b/api-editor/gui/src/features/menuBar/HelpMenu.tsx index 7bf6b75e6..58df3004a 100644 --- a/api-editor/gui/src/features/menuBar/HelpMenu.tsx +++ b/api-editor/gui/src/features/menuBar/HelpMenu.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Icon, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@chakra-ui/react'; +import { Box, Button, Icon, Menu, MenuButton, MenuDivider, MenuGroup, MenuItem, MenuList } from '@chakra-ui/react'; import React from 'react'; import { FaBug, FaChevronDown, FaLightbulb } from 'react-icons/fa'; import { bugReportURL, featureRequestURL, userGuideURL } from '../externalLinks/urlBuilder'; @@ -21,6 +21,7 @@ export const HelpMenu = function () { User Guide + (); + const [newPythonPackageString, setNewPythonPackageString] = useState(); const navigate = useNavigate(); const dispatch = useAppDispatch(); const submit = async () => { - if (newPythonPackage) { - const parsedPythonPackage = JSON.parse(newPythonPackage) as PythonPackageJson; + if (!fileName) { + toast({ + title: 'No File Selected', + description: 'Select a file to import or cancel this dialog.', + status: 'error', + duration: 4000, + }); + return; + } - dispatch(setPythonPackage(parsePythonPackageJson(parsedPythonPackage))); - dispatch(persistPythonPackage(parsedPythonPackage)); + if (newPythonPackageString) { + const pythonPackageJson = JSON.parse(newPythonPackageString) as PythonPackageJson; + const pythonPackage = parsePythonPackageJson(pythonPackageJson); + if (pythonPackage) { + dispatch(setPythonPackage(pythonPackage)); + dispatch(persistPythonPackage(pythonPackageJson)); - // Reset other slices - dispatch(resetAnnotationStore()); - dispatch(resetUsages()); - dispatch(resetUIAfterAPIImport()); - navigate('/'); + // Reset other slices + dispatch(resetAnnotationStore()); + dispatch(resetUsages()); + dispatch(resetUIAfterAPIImport()); + navigate('/'); + } else { + toast({ + title: 'Old API File', + description: 'This file is not compatible with the current version of the API Editor.', + status: 'error', + duration: 4000, + }); + } } }; const close = () => dispatch(toggleAPIImportDialog()); @@ -56,7 +77,7 @@ export const PackageDataImportDialog: React.FC = function () { const reader = new FileReader(); reader.onload = () => { if (typeof reader.result === 'string') { - setNewPythonPackage(reader.result); + setNewPythonPackageString(reader.result); dispatch(resetAnnotationStore()); } }; diff --git a/api-editor/gui/src/features/packageData/apiSlice.ts b/api-editor/gui/src/features/packageData/apiSlice.ts index 647261c9d..8f65f0414 100644 --- a/api-editor/gui/src/features/packageData/apiSlice.ts +++ b/api-editor/gui/src/features/packageData/apiSlice.ts @@ -8,14 +8,28 @@ import { selectUsages } from '../usages/usageSlice'; import { selectAnnotationStore } from '../annotations/annotationSlice'; import { PythonDeclaration } from './model/PythonDeclaration'; +export const EXPECTED_API_SCHEMA_VERSION = 1; + export interface APIState { pythonPackage: PythonPackage; } // Initial state ------------------------------------------------------------------------------------------------------- +const initialPythonPackageJson: PythonPackageJson = { + schemaVersion: EXPECTED_API_SCHEMA_VERSION, + distribution: 'empty', + package: 'empty', + version: '0.0.1', + modules: [], + classes: [], + functions: [], +}; + +const initialPythonPackage = new PythonPackage('empty', 'empty', '0.0.1'); + const initialState: APIState = { - pythonPackage: new PythonPackage('empty', 'empty', '0.0.1'), + pythonPackage: initialPythonPackage, }; // Thunks -------------------------------------------------------------------------------------------------------------- @@ -23,8 +37,14 @@ const initialState: APIState = { export const initializePythonPackage = createAsyncThunk('api/initialize', async () => { try { const storedPythonPackageJson = (await idb.get('api')) as PythonPackageJson; + const pythonPackage = parsePythonPackageJson(storedPythonPackageJson); + if (!pythonPackage) { + await idb.set('api', initialPythonPackageJson); + return initialState; + } + return { - pythonPackage: parsePythonPackageJson(storedPythonPackageJson), + pythonPackage, }; } catch { return initialState; diff --git a/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts b/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts index 2da973751..2d3b3c54d 100644 --- a/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts +++ b/api-editor/gui/src/features/packageData/model/PythonPackageBuilder.ts @@ -8,8 +8,10 @@ import { PythonPackage } from './PythonPackage'; import { PythonParameter, PythonParameterAssignment } from './PythonParameter'; import { PythonResult } from './PythonResult'; import { PythonDeclaration } from './PythonDeclaration'; +import { EXPECTED_API_SCHEMA_VERSION } from '../apiSlice'; export interface PythonPackageJson { + schemaVersion?: number; distribution: string; package: string; version: string; @@ -18,7 +20,11 @@ export interface PythonPackageJson { functions: PythonFunctionJson[]; } -export const parsePythonPackageJson = function (packageJson: PythonPackageJson): PythonPackage { +export const parsePythonPackageJson = function (packageJson: PythonPackageJson): PythonPackage | null { + if ((packageJson.schemaVersion ?? 1) !== EXPECTED_API_SCHEMA_VERSION) { + return null; + } + const idToDeclaration = new Map(); // Functions diff --git a/api-editor/gui/src/features/ui/uiSlice.ts b/api-editor/gui/src/features/ui/uiSlice.ts index 6a39418a5..5b7ccd480 100644 --- a/api-editor/gui/src/features/ui/uiSlice.ts +++ b/api-editor/gui/src/features/ui/uiSlice.ts @@ -8,12 +8,15 @@ import { PythonDeclaration } from '../packageData/model/PythonDeclaration'; import { UsageCountStore } from '../usages/model/UsageCountStore'; import { selectUsages } from '../usages/usageSlice'; +const EXPECTED_UI_SCHEMA_VERSION = 1; + export interface Filter { filter: string; name: string; } export interface UIState { + schemaVersion?: number; showAnnotationImportDialog: boolean; showAPIImportDialog: boolean; showUsageImportDialog: boolean; @@ -162,6 +165,10 @@ export const initialState: UIState = { export const initializeUI = createAsyncThunk('ui/initialize', async () => { try { const storedState = (await idb.get('ui')) as UIState; + if ((storedState.schemaVersion ?? 1) !== EXPECTED_UI_SCHEMA_VERSION) { + return initialState; + } + return { ...initialState, ...storedState, diff --git a/api-editor/gui/src/features/usages/UsageImportDialog.tsx b/api-editor/gui/src/features/usages/UsageImportDialog.tsx index 297a79aa9..2720d7c0c 100644 --- a/api-editor/gui/src/features/usages/UsageImportDialog.tsx +++ b/api-editor/gui/src/features/usages/UsageImportDialog.tsx @@ -12,6 +12,7 @@ import { ModalHeader, ModalOverlay, Text as ChakraText, + useToast, } from '@chakra-ui/react'; import React, { useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../app/hooks'; @@ -23,17 +24,38 @@ import { setUsages } from './usageSlice'; import { selectRawPythonPackage } from '../packageData/apiSlice'; export const UsageImportDialog: React.FC = function () { + const toast = useToast(); const [fileName, setFileName] = useState(''); const [newUsages, setNewUsages] = useState(); const dispatch = useAppDispatch(); const api = useAppSelector(selectRawPythonPackage); const submit = async () => { + if (!fileName) { + toast({ + title: 'No File Selected', + description: 'Select a file to import or cancel this dialog.', + status: 'error', + duration: 4000, + }); + return; + } + if (newUsages) { const parsedUsages = JSON.parse(newUsages) as UsageCountJson; - dispatch(setUsages(UsageCountStore.fromJson(parsedUsages, api))); + const usageCountStore = UsageCountStore.fromJson(parsedUsages, api); + if (usageCountStore) { + dispatch(setUsages(usageCountStore)); + close(); + } else { + toast({ + title: 'Old Usage Count File', + description: 'This file is not compatible with the current version of the API Editor.', + status: 'error', + duration: 4000, + }); + } } - close(); }; const close = () => dispatch(toggleUsageImportDialog()); diff --git a/api-editor/gui/src/features/usages/model/UsageCountStore.ts b/api-editor/gui/src/features/usages/model/UsageCountStore.ts index afddf5578..b2ef8390a 100644 --- a/api-editor/gui/src/features/usages/model/UsageCountStore.ts +++ b/api-editor/gui/src/features/usages/model/UsageCountStore.ts @@ -5,7 +5,10 @@ import { PythonModule } from '../../packageData/model/PythonModule'; import { PythonClass } from '../../packageData/model/PythonClass'; import { PythonFunction } from '../../packageData/model/PythonFunction'; +export const EXPECTED_USAGES_SCHEMA_VERSION = 1; + export interface UsageCountJson { + schemaVersion?: number; module_counts?: { [target: string]: number; }; @@ -26,7 +29,11 @@ export interface UsageCountJson { } export class UsageCountStore { - static fromJson(json: UsageCountJson, api?: PythonPackage): UsageCountStore { + static fromJson(json: UsageCountJson, api?: PythonPackage): UsageCountStore | null { + if ((json.schemaVersion ?? 1) !== EXPECTED_USAGES_SCHEMA_VERSION) { + return null; + } + return new UsageCountStore( new Map(Object.entries(json.module_counts ?? {})), new Map(Object.entries(json.class_counts)), @@ -86,6 +93,7 @@ export class UsageCountStore { toJson(): UsageCountJson { return { + schemaVersion: EXPECTED_USAGES_SCHEMA_VERSION, module_counts: Object.fromEntries(this.moduleUsages), class_counts: Object.fromEntries(this.classUsages), function_counts: Object.fromEntries(this.functionUsages), diff --git a/api-editor/gui/src/features/usages/usageSlice.ts b/api-editor/gui/src/features/usages/usageSlice.ts index 5183d442e..e1bdbd782 100644 --- a/api-editor/gui/src/features/usages/usageSlice.ts +++ b/api-editor/gui/src/features/usages/usageSlice.ts @@ -19,7 +19,7 @@ export const initializeUsages = createAsyncThunk('usages/initialize', async () = try { const storedUsageCountStoreJson = (await idb.get('usages')) as UsageCountJson; return { - usages: UsageCountStore.fromJson(storedUsageCountStoreJson), + usages: UsageCountStore.fromJson(storedUsageCountStoreJson) ?? new UsageCountStore(), }; } catch { return initialState; diff --git a/data/annotations/sklearn__annotations.json b/data/annotations/sklearn__annotations.json index 665d20025..e8d787689 100644 --- a/data/annotations/sklearn__annotations.json +++ b/data/annotations/sklearn__annotations.json @@ -1,4 +1,5 @@ { + "schemaVersion": 1, "constants": { "sklearn/sklearn._config/set_config/assume_finite": { "target": "sklearn/sklearn._config/set_config/assume_finite", diff --git a/data/api/sklearn__api.json b/data/api/sklearn__api.json index ca12f6ec2..751af0fa8 100644 --- a/data/api/sklearn__api.json +++ b/data/api/sklearn__api.json @@ -1,4 +1,5 @@ { + "schemaVersion": 1, "distribution": "scikit-learn", "package": "sklearn", "version": "1.1.1", diff --git a/data/usages/sklearn__usage_counts.json b/data/usages/sklearn__usage_counts.json index 6624d9470..dd2ec43ca 100644 --- a/data/usages/sklearn__usage_counts.json +++ b/data/usages/sklearn__usage_counts.json @@ -1,4 +1,5 @@ { + "schemaVersion": 1, "class_counts": { "sklearn/sklearn.preprocessing._label/LabelEncoder": 22465, "sklearn/sklearn.preprocessing._data/StandardScaler": 12598, diff --git a/package-parser/package_parser/model/annotations/__init__.py b/package-parser/package_parser/model/annotations/__init__.py index a24e67c70..2cdecbf96 100644 --- a/package-parser/package_parser/model/annotations/__init__.py +++ b/package-parser/package_parser/model/annotations/__init__.py @@ -1,4 +1,5 @@ from ._annotations import ( + ANNOTATION_SCHEMA_VERSION, AbstractAnnotation, AnnotationStore, BoundaryAnnotation, diff --git a/package-parser/package_parser/model/annotations/_annotations.py b/package-parser/package_parser/model/annotations/_annotations.py index cf76fcf6f..12058d330 100644 --- a/package-parser/package_parser/model/annotations/_annotations.py +++ b/package-parser/package_parser/model/annotations/_annotations.py @@ -2,6 +2,8 @@ from enum import Enum from typing import Any +ANNOTATION_SCHEMA_VERSION = 1 + @dataclass class AbstractAnnotation: @@ -86,6 +88,7 @@ def __init__(self): def to_json(self) -> dict: return { + "schemaVersion": ANNOTATION_SCHEMA_VERSION, "constants": { annotation.target: annotation.to_json() for annotation in self.constants }, diff --git a/package-parser/package_parser/model/api/__init__.py b/package-parser/package_parser/model/api/__init__.py index c119744f1..32007cdd3 100644 --- a/package-parser/package_parser/model/api/__init__.py +++ b/package-parser/package_parser/model/api/__init__.py @@ -1,5 +1,6 @@ from ._api import ( API, + API_SCHEMA_VERSION, Class, FromImport, Function, diff --git a/package-parser/package_parser/model/api/_api.py b/package-parser/package_parser/model/api/_api.py index 688a6b7e9..97f5fab35 100644 --- a/package-parser/package_parser/model/api/_api.py +++ b/package-parser/package_parser/model/api/_api.py @@ -14,6 +14,8 @@ ) from package_parser.utils import parent_id +API_SCHEMA_VERSION = 1 + class API: @staticmethod @@ -96,6 +98,7 @@ def get_default_value(self, parameter_id: str) -> Optional[str]: def to_json(self) -> Any: return { + "schemaVersion": API_SCHEMA_VERSION, "distribution": self.distribution, "package": self.package, "version": self.version, diff --git a/package-parser/package_parser/model/usages/__init__.py b/package-parser/package_parser/model/usages/__init__.py index b26ac2c33..24b0837a1 100644 --- a/package-parser/package_parser/model/usages/__init__.py +++ b/package-parser/package_parser/model/usages/__init__.py @@ -1 +1 @@ -from ._usages import UsageCountStore +from ._usages import USAGES_SCHEMA_VERSION, UsageCountStore diff --git a/package-parser/package_parser/model/usages/_usages.py b/package-parser/package_parser/model/usages/_usages.py index b421ea275..365171872 100644 --- a/package-parser/package_parser/model/usages/_usages.py +++ b/package-parser/package_parser/model/usages/_usages.py @@ -3,6 +3,8 @@ from collections import Counter from typing import Any +USAGES_SCHEMA_VERSION = 1 + ClassId = str FunctionId = str ParameterId = str @@ -184,6 +186,7 @@ def to_json(self) -> Any: """Converts this class to a dictionary, which can later be serialized as JSON.""" return { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": { class_id: usage_count for class_id, usage_count in self.class_usages.most_common() diff --git a/package-parser/tests/model/test_annotations.py b/package-parser/tests/model/test_annotations.py index 0723f13d9..b7011bd78 100644 --- a/package-parser/tests/model/test_annotations.py +++ b/package-parser/tests/model/test_annotations.py @@ -1,4 +1,5 @@ from package_parser.model.annotations import ( + ANNOTATION_SCHEMA_VERSION, AbstractAnnotation, AnnotationStore, BoundaryAnnotation, @@ -169,6 +170,7 @@ def test_annotation_store(): ) ) assert annotations.to_json() == { + "schemaVersion": ANNOTATION_SCHEMA_VERSION, "boundaries": { "test/boundary": { "target": "test/boundary", diff --git a/package-parser/tests/model/test_usages.py b/package-parser/tests/model/test_usages.py index bcefd7d02..be4079139 100644 --- a/package-parser/tests/model/test_usages.py +++ b/package-parser/tests/model/test_usages.py @@ -1,12 +1,13 @@ from typing import Any import pytest -from package_parser.model.usages import UsageCountStore +from package_parser.model.usages import USAGES_SCHEMA_VERSION, UsageCountStore @pytest.fixture def usage_counts_json() -> Any: return { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -31,6 +32,7 @@ def test_add_class_usage_for_new_class(usage_counts: UsageCountStore): usage_counts.add_class_usages("TestClass2") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": { "TestClass": 2, "TestClass2": 1, @@ -45,6 +47,7 @@ def test_add_class_usage_for_existing_class(usage_counts: UsageCountStore): usage_counts.add_class_usages("TestClass", 2) assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 4}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -65,6 +68,7 @@ def test_remove_class_for_existing_class(usage_counts: UsageCountStore): usage_counts.remove_class("TestClass") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {}, "function_counts": {}, "parameter_counts": {}, @@ -76,6 +80,7 @@ def test_add_function_usages_for_new_function(usage_counts: UsageCountStore): usage_counts.add_function_usages("TestClass.test_function_2") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": { "TestClass.test_function": 2, @@ -90,6 +95,7 @@ def test_add_function_usages_for_existing_function(usage_counts: UsageCountStore usage_counts.add_function_usages("TestClass.test_function", 2) assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 4}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -110,6 +116,7 @@ def test_remove_function_for_existing_function(usage_counts: UsageCountStore): usage_counts.remove_function("TestClass.test_function") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {}, "parameter_counts": {}, @@ -121,6 +128,7 @@ def test_add_parameter_usages_for_new_parameter(usage_counts: UsageCountStore): usage_counts.add_parameter_usages("TestClass.test_function.test_parameter_2") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": { @@ -135,6 +143,7 @@ def test_add_parameter_usages_for_existing_parameter(usage_counts: UsageCountSto usage_counts.add_parameter_usages("TestClass.test_function.test_parameter", 2) assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 4}, @@ -155,6 +164,7 @@ def test_remove_parameter_for_existing_parameter(usage_counts: UsageCountStore): usage_counts.remove_parameter("TestClass.test_function.test_parameter") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {}, @@ -166,6 +176,7 @@ def test_add_value_usages_for_new_parameter(usage_counts: UsageCountStore): usage_counts.add_value_usages("TestClass.test_function.test_parameter_2", "'test'") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -180,6 +191,7 @@ def test_add_value_usages_for_new_value(usage_counts: UsageCountStore): usage_counts.add_value_usages("TestClass.test_function.test_parameter", "'test2'") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -195,6 +207,7 @@ def test_add_value_usages_for_existing_parameter_and_value( usage_counts.add_value_usages("TestClass.test_function.test_parameter", "'test'", 2) assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -206,6 +219,7 @@ def test_init_value_for_new_parameter(usage_counts: UsageCountStore): usage_counts.init_value("TestClass.test_function.test_parameter_2") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -220,6 +234,7 @@ def test_init_value_for_existing_parameter(usage_counts: UsageCountStore): usage_counts.init_value("TestClass.test_function.test_parameter") assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": {"TestClass": 2}, "function_counts": {"TestClass.test_function": 2}, "parameter_counts": {"TestClass.test_function.test_parameter": 2}, @@ -329,6 +344,7 @@ def test_merge_other_into_self(usage_counts: UsageCountStore): usage_counts.merge_other_into_self(other) assert usage_counts.to_json() == { + "schemaVersion": USAGES_SCHEMA_VERSION, "class_counts": { "TestClass": 4, "TestClass2": 1,