Spaces:
Running
Running
Initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .streamlit/config.toml +9 -0
- Makefile +26 -0
- __init__.py +0 -0
- app.py +29 -0
- components/__init__.py +0 -0
- components/tree/__init__.py +36 -0
- components/tree/frontend/.env +6 -0
- components/tree/frontend/.prettierrc +5 -0
- components/tree/frontend/build/asset-manifest.json +10 -0
- components/tree/frontend/build/index.html +1 -0
- components/tree/frontend/build/static/js/main.5a572f5d.js +0 -0
- components/tree/frontend/build/static/js/main.5a572f5d.js.LICENSE.txt +63 -0
- components/tree/frontend/build/static/js/main.5a572f5d.js.map +0 -0
- components/tree/frontend/package-lock.json +0 -0
- components/tree/frontend/package.json +47 -0
- components/tree/frontend/public/index.html +27 -0
- components/tree/frontend/src/Tree.tsx +215 -0
- components/tree/frontend/src/index.tsx +10 -0
- components/tree/frontend/src/react-app-env.d.ts +1 -0
- components/tree/frontend/tsconfig.json +25 -0
- core/__init__.py +0 -0
- core/constants.py +10 -0
- core/data_types.py +19 -0
- core/data_types_test.py +15 -0
- core/files.py +154 -0
- core/files_test.py +27 -0
- core/names.py +8 -0
- core/names_test.py +10 -0
- core/past_projects.py +34 -0
- core/record_sets.py +38 -0
- core/state.py +261 -0
- cypress.config.js +7 -0
- cypress/downloads/croissant-Titanic.json +1 -0
- cypress/downloads/croissant.json +1 -0
- cypress/e2e/createManually.cy.js +35 -0
- cypress/e2e/displayErrors.cy.js +30 -0
- cypress/e2e/loadCroissant.cy.js +61 -0
- cypress/e2e/renameDistribution.cy.js +36 -0
- cypress/e2e/uploadCsv.cy.js +59 -0
- cypress/fixtures/base.csv +4 -0
- cypress/fixtures/coco.json +409 -0
- cypress/fixtures/titanic.json +343 -0
- cypress/screenshots/uploadCsv.cy.js/Editor loads a local CSV as a resource -- should display the form Overview, Metadata, Resources, & Record Sets (failed).png +0 -0
- cypress/support/e2e.js +6 -0
- cypress/support/resize_observer.js +11 -0
- events/__init__.py +0 -0
- events/fields.py +147 -0
- events/metadata.py +28 -0
- events/record_sets.py +29 -0
- events/resources.py +41 -0
.streamlit/config.toml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[browser]
|
2 |
+
gatherUsageStats = false
|
3 |
+
|
4 |
+
[theme]
|
5 |
+
primaryColor = "#F29828"
|
6 |
+
backgroundColor = "#CCEBD4"
|
7 |
+
secondaryBackgroundColor = "#EEF2F9"
|
8 |
+
textColor = "#171D30"
|
9 |
+
font = "sans serif"
|
Makefile
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
black:
|
2 |
+
black \
|
3 |
+
--line-length 88 \
|
4 |
+
--preview \
|
5 |
+
.
|
6 |
+
|
7 |
+
isort:
|
8 |
+
isort \
|
9 |
+
--profile google \
|
10 |
+
--line-length 88 \
|
11 |
+
--use-parentheses \
|
12 |
+
--project mlcroissant \
|
13 |
+
--project components \
|
14 |
+
--project core \
|
15 |
+
--project events \
|
16 |
+
--project views \
|
17 |
+
--project state \
|
18 |
+
--project utils \
|
19 |
+
--multi-line 3 \
|
20 |
+
--thirdparty datasets \
|
21 |
+
.
|
22 |
+
|
23 |
+
format: black isort
|
24 |
+
|
25 |
+
pytest:
|
26 |
+
PYTHONPATH=. pytest
|
__init__.py
ADDED
File without changes
|
app.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
from core.state import CurrentStep
|
4 |
+
from utils import init_state
|
5 |
+
from views.splash import render_splash
|
6 |
+
from views.wizard import render_editor
|
7 |
+
|
8 |
+
init_state()
|
9 |
+
|
10 |
+
|
11 |
+
def _back_to_menu():
|
12 |
+
"""Sends the user back to the menu."""
|
13 |
+
init_state(force=True)
|
14 |
+
|
15 |
+
|
16 |
+
st.set_page_config(page_title="Croissant Editor", page_icon="🥐", layout="wide")
|
17 |
+
col1, col2 = st.columns([10, 1])
|
18 |
+
col1.header("Croissant Editor")
|
19 |
+
if st.session_state[CurrentStep] != CurrentStep.splash:
|
20 |
+
col2.write("\n") # Vertical box to shift the button menu
|
21 |
+
col2.button("Menu", on_click=_back_to_menu)
|
22 |
+
|
23 |
+
|
24 |
+
if st.session_state[CurrentStep] == CurrentStep.splash:
|
25 |
+
render_splash()
|
26 |
+
elif st.session_state[CurrentStep] == CurrentStep.editor:
|
27 |
+
render_editor()
|
28 |
+
else:
|
29 |
+
st.warning("invalid unhandled app state")
|
components/__init__.py
ADDED
File without changes
|
components/tree/__init__.py
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
import streamlit.components.v1 as components
|
4 |
+
|
5 |
+
# Create a _RELEASE constant. We'll set this to False while we're developing
|
6 |
+
# the component, and True when we're ready to package and distribute it.
|
7 |
+
_RELEASE = True
|
8 |
+
|
9 |
+
if not _RELEASE:
|
10 |
+
_component_func = components.declare_component(
|
11 |
+
"tree_component",
|
12 |
+
url="http://localhost:3001",
|
13 |
+
)
|
14 |
+
else:
|
15 |
+
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
16 |
+
build_dir = os.path.join(parent_dir, "frontend/build")
|
17 |
+
_component_func = components.declare_component("tree_component", path=build_dir)
|
18 |
+
|
19 |
+
|
20 |
+
def render_tree(nodes, key=None):
|
21 |
+
"""Create a new instance of "tree_component".
|
22 |
+
|
23 |
+
Args:
|
24 |
+
nodes: The nodes to render in the tree. Nodes are dictionaries with keys `name`
|
25 |
+
(unique identifier), `type` and `parent` (referencing another name).
|
26 |
+
key: An optional key that uniquely identifies this component. If this is
|
27 |
+
None, and the component's arguments are changed, the component will
|
28 |
+
be re-mounted in the Streamlit frontend and lose its current state.
|
29 |
+
|
30 |
+
Returns:
|
31 |
+
The number of times the component's "Click Me" button has been clicked.
|
32 |
+
(This is the value passed to `Streamlit.setComponentValue` on the
|
33 |
+
frontend.)
|
34 |
+
"""
|
35 |
+
component_value = _component_func(nodes=nodes, key=key, default=0)
|
36 |
+
return component_value
|
components/tree/frontend/.env
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Run the component's dev server on :3001
|
2 |
+
# (The Streamlit dev server already runs on :3000)
|
3 |
+
PORT=3001
|
4 |
+
|
5 |
+
# Don't automatically open the web browser on `npm run start`.
|
6 |
+
BROWSER=none
|
components/tree/frontend/.prettierrc
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"endOfLine": "lf",
|
3 |
+
"semi": false,
|
4 |
+
"trailingComma": "es5"
|
5 |
+
}
|
components/tree/frontend/build/asset-manifest.json
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"files": {
|
3 |
+
"main.js": "./static/js/main.5a572f5d.js",
|
4 |
+
"index.html": "./index.html",
|
5 |
+
"main.5a572f5d.js.map": "./static/js/main.5a572f5d.js.map"
|
6 |
+
},
|
7 |
+
"entrypoints": [
|
8 |
+
"static/js/main.5a572f5d.js"
|
9 |
+
]
|
10 |
+
}
|
components/tree/frontend/build/index.html
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
<!doctype html><html lang="en"><head><title>Streamlit Tree Component</title><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Streamlit Tree Component"/><script defer="defer" src="./static/js/main.5a572f5d.js"></script></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
components/tree/frontend/build/static/js/main.5a572f5d.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tree/frontend/build/static/js/main.5a572f5d.js.LICENSE.txt
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
object-assign
|
3 |
+
(c) Sindre Sorhus
|
4 |
+
@license MIT
|
5 |
+
*/
|
6 |
+
|
7 |
+
/**
|
8 |
+
* @license React
|
9 |
+
* react-dom.production.min.js
|
10 |
+
*
|
11 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
12 |
+
*
|
13 |
+
* This source code is licensed under the MIT license found in the
|
14 |
+
* LICENSE file in the root directory of this source tree.
|
15 |
+
*/
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @license React
|
19 |
+
* react-jsx-runtime.production.min.js
|
20 |
+
*
|
21 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
22 |
+
*
|
23 |
+
* This source code is licensed under the MIT license found in the
|
24 |
+
* LICENSE file in the root directory of this source tree.
|
25 |
+
*/
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @license React
|
29 |
+
* react.production.min.js
|
30 |
+
*
|
31 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
32 |
+
*
|
33 |
+
* This source code is licensed under the MIT license found in the
|
34 |
+
* LICENSE file in the root directory of this source tree.
|
35 |
+
*/
|
36 |
+
|
37 |
+
/**
|
38 |
+
* @license React
|
39 |
+
* scheduler.production.min.js
|
40 |
+
*
|
41 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
42 |
+
*
|
43 |
+
* This source code is licensed under the MIT license found in the
|
44 |
+
* LICENSE file in the root directory of this source tree.
|
45 |
+
*/
|
46 |
+
|
47 |
+
/** @license React v16.13.1
|
48 |
+
* react-is.production.min.js
|
49 |
+
*
|
50 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
51 |
+
*
|
52 |
+
* This source code is licensed under the MIT license found in the
|
53 |
+
* LICENSE file in the root directory of this source tree.
|
54 |
+
*/
|
55 |
+
|
56 |
+
/** @license React v16.14.0
|
57 |
+
* react.production.min.js
|
58 |
+
*
|
59 |
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
60 |
+
*
|
61 |
+
* This source code is licensed under the MIT license found in the
|
62 |
+
* LICENSE file in the root directory of this source tree.
|
63 |
+
*/
|
components/tree/frontend/build/static/js/main.5a572f5d.js.map
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tree/frontend/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
components/tree/frontend/package.json
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "tree_component",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@mui/icons-material": "^5.14.16",
|
7 |
+
"@mui/material": "^5.14.17",
|
8 |
+
"@mui/x-tree-view": "^6.17.0",
|
9 |
+
"react": "^18.2.0",
|
10 |
+
"react-dom": "^18.2.0",
|
11 |
+
"streamlit-component-lib": "^2.0.0"
|
12 |
+
},
|
13 |
+
"scripts": {
|
14 |
+
"start": "react-scripts start",
|
15 |
+
"build": "react-scripts build",
|
16 |
+
"test": "react-scripts test",
|
17 |
+
"eject": "react-scripts eject"
|
18 |
+
},
|
19 |
+
"eslintConfig": {
|
20 |
+
"extends": "react-app"
|
21 |
+
},
|
22 |
+
"browserslist": {
|
23 |
+
"production": [
|
24 |
+
">0.2%",
|
25 |
+
"not dead",
|
26 |
+
"not op_mini all"
|
27 |
+
],
|
28 |
+
"development": [
|
29 |
+
"last 1 chrome version",
|
30 |
+
"last 1 firefox version",
|
31 |
+
"last 1 safari version"
|
32 |
+
]
|
33 |
+
},
|
34 |
+
"homepage": ".",
|
35 |
+
"devDependencies": {
|
36 |
+
"@types/node": "^20.9.0",
|
37 |
+
"@types/react": "^18.2.37",
|
38 |
+
"@types/react-dom": "^18.2.15",
|
39 |
+
"react-scripts": "^5.0.1",
|
40 |
+
"typescript": "^5.2.2"
|
41 |
+
},
|
42 |
+
"overrides": {
|
43 |
+
"react-scripts": {
|
44 |
+
"typescript": "^5"
|
45 |
+
}
|
46 |
+
}
|
47 |
+
}
|
components/tree/frontend/public/index.html
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<title>Streamlit Tree Component</title>
|
6 |
+
<meta charset="UTF-8" />
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
8 |
+
<meta name="theme-color" content="#000000" />
|
9 |
+
<meta name="description" content="Streamlit Tree Component" />
|
10 |
+
</head>
|
11 |
+
|
12 |
+
<body>
|
13 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
14 |
+
<div id="root"></div>
|
15 |
+
<!--
|
16 |
+
This HTML file is a template.
|
17 |
+
If you open it directly in the browser, you will see an empty page.
|
18 |
+
|
19 |
+
You can add webfonts, meta tags, or analytics to this file.
|
20 |
+
The build step will place the bundled scripts into the <body> tag.
|
21 |
+
|
22 |
+
To begin the development, run `npm start` or `yarn start`.
|
23 |
+
To create a production bundle, use `npm run build` or `yarn build`.
|
24 |
+
-->
|
25 |
+
</body>
|
26 |
+
|
27 |
+
</html>
|
components/tree/frontend/src/Tree.tsx
ADDED
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Streamlit,
|
3 |
+
StreamlitComponentBase,
|
4 |
+
withStreamlitConnection,
|
5 |
+
} from "streamlit-component-lib"
|
6 |
+
import React, { ReactNode } from "react"
|
7 |
+
import { styled, useTheme } from "@mui/material/styles"
|
8 |
+
import Box from "@mui/material/Box"
|
9 |
+
import Typography from "@mui/material/Typography"
|
10 |
+
import FileCopyIcon from "@mui/icons-material/FileCopy"
|
11 |
+
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"
|
12 |
+
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"
|
13 |
+
import ArrowRightIcon from "@mui/icons-material/ArrowRight"
|
14 |
+
import { SvgIconProps } from "@mui/material/SvgIcon"
|
15 |
+
import { TreeView } from "@mui/x-tree-view/TreeView"
|
16 |
+
import {
|
17 |
+
TreeItem,
|
18 |
+
TreeItemProps,
|
19 |
+
treeItemClasses,
|
20 |
+
} from "@mui/x-tree-view/TreeItem"
|
21 |
+
|
22 |
+
// All code related to the MUI tree component is taken from https://mui.com/x/react-tree-view.
|
23 |
+
declare module "react" {
|
24 |
+
interface CSSProperties {
|
25 |
+
"--tree-view-color"?: string
|
26 |
+
"--tree-view-bg-color"?: string
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
type StyledTreeItemProps = TreeItemProps & {
|
31 |
+
bgColor?: string
|
32 |
+
bgColorForDarkMode?: string
|
33 |
+
color?: string
|
34 |
+
colorForDarkMode?: string
|
35 |
+
labelIcon: React.ElementType<SvgIconProps>
|
36 |
+
labelInfo?: string
|
37 |
+
labelText: string
|
38 |
+
}
|
39 |
+
|
40 |
+
const StyledTreeItemRoot = styled(TreeItem)(({ theme }) => ({
|
41 |
+
color: theme.palette.text.secondary,
|
42 |
+
[`& .${treeItemClasses.content}`]: {
|
43 |
+
color: theme.palette.text.secondary,
|
44 |
+
borderTopRightRadius: theme.spacing(2),
|
45 |
+
borderBottomRightRadius: theme.spacing(2),
|
46 |
+
paddingRight: theme.spacing(1),
|
47 |
+
fontWeight: theme.typography.fontWeightMedium,
|
48 |
+
"&.Mui-expanded": {
|
49 |
+
fontWeight: theme.typography.fontWeightRegular,
|
50 |
+
},
|
51 |
+
"&:hover": {
|
52 |
+
backgroundColor: theme.palette.action.hover,
|
53 |
+
},
|
54 |
+
"&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused": {
|
55 |
+
backgroundColor: `var(--tree-view-bg-color, ${theme.palette.action.selected})`,
|
56 |
+
color: "var(--tree-view-color)",
|
57 |
+
},
|
58 |
+
[`& .${treeItemClasses.label}`]: {
|
59 |
+
fontWeight: "inherit",
|
60 |
+
color: "inherit",
|
61 |
+
},
|
62 |
+
},
|
63 |
+
[`& .${treeItemClasses.group}`]: {
|
64 |
+
marginLeft: 0,
|
65 |
+
[`& .${treeItemClasses.content}`]: {
|
66 |
+
paddingLeft: theme.spacing(2),
|
67 |
+
},
|
68 |
+
},
|
69 |
+
})) as unknown as typeof TreeItem
|
70 |
+
|
71 |
+
const StyledTreeItem = React.forwardRef(function StyledTreeItem(
|
72 |
+
props: StyledTreeItemProps,
|
73 |
+
ref: React.Ref<HTMLLIElement>
|
74 |
+
) {
|
75 |
+
const theme = useTheme()
|
76 |
+
const {
|
77 |
+
bgColor,
|
78 |
+
color,
|
79 |
+
labelIcon: LabelIcon,
|
80 |
+
labelInfo,
|
81 |
+
labelText,
|
82 |
+
colorForDarkMode,
|
83 |
+
bgColorForDarkMode,
|
84 |
+
...other
|
85 |
+
} = props
|
86 |
+
|
87 |
+
const styleProps = {
|
88 |
+
"--tree-view-color":
|
89 |
+
theme.palette.mode !== "dark" ? color : colorForDarkMode,
|
90 |
+
"--tree-view-bg-color":
|
91 |
+
theme.palette.mode !== "dark" ? bgColor : bgColorForDarkMode,
|
92 |
+
}
|
93 |
+
|
94 |
+
return (
|
95 |
+
<StyledTreeItemRoot
|
96 |
+
label={
|
97 |
+
<Box
|
98 |
+
sx={{
|
99 |
+
display: "flex",
|
100 |
+
alignItems: "center",
|
101 |
+
p: 0.5,
|
102 |
+
pr: 0,
|
103 |
+
}}
|
104 |
+
>
|
105 |
+
<Box component={LabelIcon} color="inherit" sx={{ mr: 1 }} />
|
106 |
+
<Typography
|
107 |
+
data-testid="tree-element"
|
108 |
+
variant="body2"
|
109 |
+
sx={{
|
110 |
+
whiteSpace: "nowrap",
|
111 |
+
overflow: "hidden",
|
112 |
+
textOverflow: "ellipsis",
|
113 |
+
fontWeight: "inherit",
|
114 |
+
flexGrow: 1,
|
115 |
+
}}
|
116 |
+
>
|
117 |
+
{labelText}
|
118 |
+
</Typography>
|
119 |
+
<Typography variant="caption" color="inherit">
|
120 |
+
{labelInfo}
|
121 |
+
</Typography>
|
122 |
+
</Box>
|
123 |
+
}
|
124 |
+
style={styleProps}
|
125 |
+
{...other}
|
126 |
+
ref={ref}
|
127 |
+
/>
|
128 |
+
)
|
129 |
+
})
|
130 |
+
|
131 |
+
type Node = {
|
132 |
+
name: string
|
133 |
+
type: string
|
134 |
+
parents: string[]
|
135 |
+
}
|
136 |
+
|
137 |
+
type TreeNodes = { [key: string]: TreeNode }
|
138 |
+
|
139 |
+
type TreeNode = Node & {
|
140 |
+
children: string[]
|
141 |
+
}
|
142 |
+
|
143 |
+
const TreeNodeComponent = ({
|
144 |
+
treeNode,
|
145 |
+
treeNodes,
|
146 |
+
}: {
|
147 |
+
treeNode: TreeNode
|
148 |
+
treeNodes: TreeNodes
|
149 |
+
}) => {
|
150 |
+
const { children } = treeNode
|
151 |
+
const childrenNodes = children
|
152 |
+
.filter((child) => child in treeNodes)
|
153 |
+
.map((child) => treeNodes[child])
|
154 |
+
const labelIcon =
|
155 |
+
treeNode.type === "FileObject" ? InsertDriveFileIcon : FileCopyIcon
|
156 |
+
return (
|
157 |
+
<StyledTreeItem
|
158 |
+
onClick={() => Streamlit.setComponentValue(treeNode.name)}
|
159 |
+
nodeId={treeNode.name}
|
160 |
+
labelText={treeNode.name}
|
161 |
+
labelIcon={labelIcon}
|
162 |
+
>
|
163 |
+
{childrenNodes.map((childNode) => (
|
164 |
+
<TreeNodeComponent treeNode={childNode} treeNodes={treeNodes} />
|
165 |
+
))}
|
166 |
+
</StyledTreeItem>
|
167 |
+
)
|
168 |
+
}
|
169 |
+
|
170 |
+
const TreeViewWithNodes = ({ nodes }: { nodes: Node[] }) => {
|
171 |
+
const treeNodes: TreeNodes = {}
|
172 |
+
nodes.forEach((node) => {
|
173 |
+
treeNodes[node.name] = { ...node, children: [] }
|
174 |
+
})
|
175 |
+
nodes.forEach((node) => {
|
176 |
+
node.parents.forEach((parent) => {
|
177 |
+
if (parent in treeNodes) {
|
178 |
+
treeNodes[parent].children.push(node.name)
|
179 |
+
}
|
180 |
+
})
|
181 |
+
})
|
182 |
+
|
183 |
+
return (
|
184 |
+
<TreeView
|
185 |
+
defaultCollapseIcon={<ArrowDropDownIcon />}
|
186 |
+
defaultExpandIcon={<ArrowRightIcon />}
|
187 |
+
defaultEndIcon={<div style={{ width: 24 }} />}
|
188 |
+
expanded={Object.keys(treeNodes)}
|
189 |
+
sx={{
|
190 |
+
flexGrow: 1,
|
191 |
+
margin: -1,
|
192 |
+
padding: 1,
|
193 |
+
border: "1px solid rgba(23, 29, 48, 0.2)",
|
194 |
+
borderRadius: "0.5rem",
|
195 |
+
}}
|
196 |
+
>
|
197 |
+
{Object.values(treeNodes).map((treeNode) => {
|
198 |
+
return (
|
199 |
+
treeNode.parents.length === 0 && (
|
200 |
+
<TreeNodeComponent treeNode={treeNode} treeNodes={treeNodes} />
|
201 |
+
)
|
202 |
+
)
|
203 |
+
})}
|
204 |
+
</TreeView>
|
205 |
+
)
|
206 |
+
}
|
207 |
+
|
208 |
+
class Tree extends StreamlitComponentBase<{}> {
|
209 |
+
public render = (): ReactNode => {
|
210 |
+
const nodes = this.props.args["nodes"]
|
211 |
+
return <TreeViewWithNodes nodes={nodes} />
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
export default withStreamlitConnection(Tree)
|
components/tree/frontend/src/index.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react"
|
2 |
+
import ReactDOM from "react-dom"
|
3 |
+
import Tree from "./Tree"
|
4 |
+
|
5 |
+
ReactDOM.render(
|
6 |
+
<React.StrictMode>
|
7 |
+
<Tree />
|
8 |
+
</React.StrictMode>,
|
9 |
+
document.getElementById("root")
|
10 |
+
)
|
components/tree/frontend/src/react-app-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="react-scripts" />
|
components/tree/frontend/tsconfig.json
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "es5",
|
4 |
+
"lib": [
|
5 |
+
"dom",
|
6 |
+
"dom.iterable",
|
7 |
+
"esnext"
|
8 |
+
],
|
9 |
+
"allowJs": true,
|
10 |
+
"skipLibCheck": true,
|
11 |
+
"esModuleInterop": true,
|
12 |
+
"allowSyntheticDefaultImports": true,
|
13 |
+
"strict": true,
|
14 |
+
"forceConsistentCasingInFileNames": true,
|
15 |
+
"module": "esnext",
|
16 |
+
"moduleResolution": "node",
|
17 |
+
"resolveJsonModule": true,
|
18 |
+
"isolatedModules": true,
|
19 |
+
"noEmit": true,
|
20 |
+
"jsx": "react"
|
21 |
+
},
|
22 |
+
"include": [
|
23 |
+
"src"
|
24 |
+
]
|
25 |
+
}
|
core/__init__.py
ADDED
File without changes
|
core/constants.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from etils import epath
|
2 |
+
|
3 |
+
import mlcroissant as mlc
|
4 |
+
|
5 |
+
EDITOR_CACHE: epath.Path = mlc.constants.CROISSANT_CACHE / "editor"
|
6 |
+
PAST_PROJECTS_PATH: epath.Path = EDITOR_CACHE / "projects"
|
7 |
+
PROJECT_FOLDER_PATTERN = "%Y%m%d%H%M%S%f"
|
8 |
+
|
9 |
+
|
10 |
+
DF_HEIGHT = 150
|
core/data_types.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
import mlcroissant as mlc
|
6 |
+
|
7 |
+
|
8 |
+
def convert_dtype(dtype: Any):
|
9 |
+
"""Converts from NumPy/Pandas to Croissant data types."""
|
10 |
+
if dtype == np.int64:
|
11 |
+
return mlc.DataType.INTEGER
|
12 |
+
elif dtype == np.float64:
|
13 |
+
return mlc.DataType.FLOAT
|
14 |
+
elif dtype == np.bool_:
|
15 |
+
return mlc.DataType.BOOL
|
16 |
+
elif dtype == np.str_ or dtype == object:
|
17 |
+
return mlc.DataType.TEXT
|
18 |
+
else:
|
19 |
+
raise NotImplementedError(dtype)
|
core/data_types_test.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Tests for data_types."""
|
2 |
+
|
3 |
+
import numpy as np
|
4 |
+
import pytest
|
5 |
+
|
6 |
+
from .data_types import convert_dtype
|
7 |
+
|
8 |
+
|
9 |
+
def test_convert_dtype():
|
10 |
+
convert_dtype(np.int64) == "https://schema.org/Integer"
|
11 |
+
convert_dtype(np.float64) == "https://schema.org/Float"
|
12 |
+
convert_dtype(np.bool_) == "https://schema.org/Boolean"
|
13 |
+
convert_dtype(np.str_) == "https://schema.org/Text"
|
14 |
+
with pytest.raises(NotImplementedError):
|
15 |
+
convert_dtype(np.float32)
|
core/files.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import dataclasses
|
2 |
+
import hashlib
|
3 |
+
import io
|
4 |
+
import tempfile
|
5 |
+
|
6 |
+
from etils import epath
|
7 |
+
import pandas as pd
|
8 |
+
import requests
|
9 |
+
|
10 |
+
from .names import find_unique_name
|
11 |
+
from .state import FileObject
|
12 |
+
from .state import FileSet
|
13 |
+
|
14 |
+
FILE_OBJECT = "File object"
|
15 |
+
FILE_SET = "File set"
|
16 |
+
RESOURCE_TYPES = [FILE_OBJECT, FILE_SET]
|
17 |
+
|
18 |
+
|
19 |
+
@dataclasses.dataclass
|
20 |
+
class FileType:
|
21 |
+
name: str
|
22 |
+
encoding_format: str
|
23 |
+
extensions: list[str]
|
24 |
+
|
25 |
+
|
26 |
+
class FileTypes:
|
27 |
+
CSV = FileType(name="CSV", encoding_format="text/csv", extensions=["csv"])
|
28 |
+
EXCEL = FileType(
|
29 |
+
name="Excel",
|
30 |
+
encoding_format="application/vnd.ms-excel",
|
31 |
+
extensions=["xls", "xlsx", "xlsm"],
|
32 |
+
)
|
33 |
+
JSON = FileType(
|
34 |
+
name="JSON", encoding_format="application/json", extensions=["json"]
|
35 |
+
)
|
36 |
+
JSONL = FileType(
|
37 |
+
name="JSON-Lines",
|
38 |
+
encoding_format="application/jsonl+json",
|
39 |
+
extensions=["jsonl"],
|
40 |
+
)
|
41 |
+
PARQUET = FileType(
|
42 |
+
name="Parquet",
|
43 |
+
encoding_format="application/vnd.apache.parquet",
|
44 |
+
extensions=["parquet"],
|
45 |
+
)
|
46 |
+
|
47 |
+
|
48 |
+
FILE_TYPES: dict[str, FileType] = {
|
49 |
+
file_type.name: file_type
|
50 |
+
for file_type in [
|
51 |
+
FileTypes.CSV,
|
52 |
+
FileTypes.EXCEL,
|
53 |
+
FileTypes.JSON,
|
54 |
+
FileTypes.JSONL,
|
55 |
+
FileTypes.PARQUET,
|
56 |
+
]
|
57 |
+
}
|
58 |
+
|
59 |
+
|
60 |
+
def _sha256(content: bytes):
|
61 |
+
"""Computes the sha256 digest of the byte string."""
|
62 |
+
return hashlib.sha256(content).hexdigest()
|
63 |
+
|
64 |
+
|
65 |
+
def hash_file_path(url: str) -> epath.Path:
|
66 |
+
"""Reproducibly produces the file path."""
|
67 |
+
tempdir = epath.Path(tempfile.gettempdir())
|
68 |
+
hash = _sha256(url.encode())
|
69 |
+
return tempdir / f"croissant-editor-{hash}"
|
70 |
+
|
71 |
+
|
72 |
+
def download_file(url: str, file_path: epath.Path):
|
73 |
+
"""Downloads the file locally to `file_path`."""
|
74 |
+
with requests.get(url, stream=True) as request:
|
75 |
+
request.raise_for_status()
|
76 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
77 |
+
tmpdir = epath.Path(tmpdir) / "file"
|
78 |
+
with tmpdir.open("wb") as file:
|
79 |
+
for chunk in request.iter_content(chunk_size=8192):
|
80 |
+
file.write(chunk)
|
81 |
+
tmpdir.copy(file_path)
|
82 |
+
|
83 |
+
|
84 |
+
def get_dataframe(file_type: FileType, file: io.BytesIO | epath.Path) -> pd.DataFrame:
|
85 |
+
"""Gets the df associated to the file."""
|
86 |
+
if file_type == FileTypes.CSV:
|
87 |
+
return pd.read_csv(file)
|
88 |
+
elif file_type == FileTypes.EXCEL:
|
89 |
+
return pd.read_excel(file)
|
90 |
+
elif file_type == FileTypes.JSON:
|
91 |
+
return pd.read_json(file)
|
92 |
+
elif file_type == FileTypes.JSONL:
|
93 |
+
return pd.read_json(file, lines=True)
|
94 |
+
elif file_type == FileTypes.PARQUET:
|
95 |
+
return pd.read_parquet(file)
|
96 |
+
else:
|
97 |
+
raise NotImplementedError()
|
98 |
+
|
99 |
+
|
100 |
+
def file_from_url(file_type: FileType, url: str, names: set[str]) -> FileObject:
|
101 |
+
"""Downloads locally and extracts the file information."""
|
102 |
+
file_path = hash_file_path(url)
|
103 |
+
if not file_path.exists():
|
104 |
+
download_file(url, file_path)
|
105 |
+
with file_path.open("rb") as file:
|
106 |
+
sha256 = _sha256(file.read())
|
107 |
+
df = get_dataframe(file_type, file_path).infer_objects()
|
108 |
+
return FileObject(
|
109 |
+
name=find_unique_name(names, url.split("/")[-1]),
|
110 |
+
description="",
|
111 |
+
content_url=url,
|
112 |
+
encoding_format=file_type.encoding_format,
|
113 |
+
sha256=sha256,
|
114 |
+
df=df,
|
115 |
+
)
|
116 |
+
|
117 |
+
|
118 |
+
def file_from_upload(
|
119 |
+
file_type: FileType, file: io.BytesIO, names: set[str]
|
120 |
+
) -> FileObject:
|
121 |
+
"""Uploads locally and extracts the file information."""
|
122 |
+
sha256 = _sha256(file.getvalue())
|
123 |
+
df = get_dataframe(file_type, file).infer_objects()
|
124 |
+
return FileObject(
|
125 |
+
name=find_unique_name(names, file.name),
|
126 |
+
description="",
|
127 |
+
content_url=f"data/{file.name}",
|
128 |
+
encoding_format=file_type.encoding_format,
|
129 |
+
sha256=sha256,
|
130 |
+
df=df,
|
131 |
+
)
|
132 |
+
|
133 |
+
|
134 |
+
def file_from_form(
|
135 |
+
file_type: FileType, type: str, name, description, sha256: str, names: set[str]
|
136 |
+
) -> FileObject | FileSet:
|
137 |
+
"""Creates a file based on manually added fields."""
|
138 |
+
if type == FILE_OBJECT:
|
139 |
+
return FileObject(
|
140 |
+
name=find_unique_name(names, name),
|
141 |
+
description=description,
|
142 |
+
content_url="",
|
143 |
+
encoding_format=file_type.encoding_format,
|
144 |
+
sha256=sha256,
|
145 |
+
df=None,
|
146 |
+
)
|
147 |
+
elif type == FILE_SET:
|
148 |
+
return FileSet(
|
149 |
+
name=find_unique_name(names, name),
|
150 |
+
description=description,
|
151 |
+
encoding_format=file_type.encoding_format,
|
152 |
+
)
|
153 |
+
else:
|
154 |
+
raise ValueError("type has to be one of FILE_OBJECT, FILE_SET")
|
core/files_test.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from etils import epath
|
2 |
+
import pandas as pd
|
3 |
+
import pytest
|
4 |
+
|
5 |
+
from .files import file_from_url
|
6 |
+
from .files import FileTypes
|
7 |
+
|
8 |
+
|
9 |
+
def test_check_file_csv():
|
10 |
+
csv = epath.Path(
|
11 |
+
# This is the hash path for "https://my.url".
|
12 |
+
"/tmp/croissant-editor-f76b4732c82d83daf858fae2cc0e590d352a4bceb781351243a03daab11f76bc"
|
13 |
+
)
|
14 |
+
if csv.exists():
|
15 |
+
csv.unlink()
|
16 |
+
with csv.open("w") as f:
|
17 |
+
f.write("column1,column2\n")
|
18 |
+
f.write("a,1\n")
|
19 |
+
f.write("b,2\n")
|
20 |
+
f.write("c,3\n")
|
21 |
+
file = file_from_url(FileTypes.CSV, "https://my.url", set())
|
22 |
+
pd.testing.assert_frame_equal(
|
23 |
+
file.df, pd.DataFrame({"column1": ["a", "b", "c"], "column2": [1, 2, 3]})
|
24 |
+
)
|
25 |
+
# Fails with unknown encoding_format:
|
26 |
+
with pytest.raises(NotImplementedError):
|
27 |
+
file_from_url("unknown", "https://my.url", set())
|
core/names.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Module to handle naming of RecordSets and distribution."""
|
2 |
+
|
3 |
+
|
4 |
+
def find_unique_name(names: set[str], name: str):
|
5 |
+
"""Find a unique UID."""
|
6 |
+
while name in names:
|
7 |
+
name = f"{name}_0"
|
8 |
+
return name
|
core/names_test.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Tests for `names` module."""
|
2 |
+
|
3 |
+
from .names import find_unique_name
|
4 |
+
|
5 |
+
|
6 |
+
def test_find_unique_name():
|
7 |
+
names = set(["first", "second", "first_0"])
|
8 |
+
assert find_unique_name(names, "first") == "first_0_0"
|
9 |
+
assert find_unique_name(names, "second") == "second_0"
|
10 |
+
assert find_unique_name(names, "third") == "third"
|
core/past_projects.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import pickle
|
3 |
+
|
4 |
+
from etils import epath
|
5 |
+
import streamlit as st
|
6 |
+
|
7 |
+
from core.constants import PAST_PROJECTS_PATH
|
8 |
+
from core.state import CurrentProject
|
9 |
+
from core.state import Metadata
|
10 |
+
|
11 |
+
|
12 |
+
def load_past_projects_paths() -> list[epath.Path]:
|
13 |
+
PAST_PROJECTS_PATH.mkdir(parents=True, exist_ok=True)
|
14 |
+
return sorted(list(PAST_PROJECTS_PATH.iterdir()), reverse=True)
|
15 |
+
|
16 |
+
|
17 |
+
def _pickle_file(path: epath.Path) -> epath.Path:
|
18 |
+
return path / ".metadata.pkl"
|
19 |
+
|
20 |
+
|
21 |
+
def save_current_project():
|
22 |
+
metadata = st.session_state[Metadata]
|
23 |
+
project = st.session_state[CurrentProject]
|
24 |
+
project.path.mkdir(parents=True, exist_ok=True)
|
25 |
+
with _pickle_file(project.path).open("wb") as file:
|
26 |
+
try:
|
27 |
+
pickle.dump(metadata, file)
|
28 |
+
except pickle.PicklingError:
|
29 |
+
logging.error("Could not pickle metadata.")
|
30 |
+
|
31 |
+
|
32 |
+
def open_project(path: epath.Path) -> Metadata:
|
33 |
+
with _pickle_file(path).open("rb") as file:
|
34 |
+
return pickle.load(file)
|
core/record_sets.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from core.data_types import convert_dtype
|
2 |
+
from core.names import find_unique_name
|
3 |
+
from core.state import Field
|
4 |
+
from core.state import FileObject
|
5 |
+
from core.state import FileSet
|
6 |
+
from core.state import RecordSet
|
7 |
+
import mlcroissant as mlc
|
8 |
+
|
9 |
+
|
10 |
+
def infer_record_sets(file: FileObject | FileSet, names: set[str]) -> list[RecordSet]:
|
11 |
+
"""Infers one or several ml:RecordSets from a FileOject/FileSet."""
|
12 |
+
# For the moment, there is no inference support for FileSets.
|
13 |
+
if isinstance(file, FileSet):
|
14 |
+
return []
|
15 |
+
# We can infer only if the underlying `pd.DataFrame` could be built.
|
16 |
+
if file.df is None:
|
17 |
+
return []
|
18 |
+
fields = []
|
19 |
+
for column, value in file.df.dtypes.items():
|
20 |
+
source = mlc.Source(
|
21 |
+
uid=file.name,
|
22 |
+
node_type="distribution",
|
23 |
+
extract=mlc.Extract(column=column),
|
24 |
+
)
|
25 |
+
field = Field(
|
26 |
+
name=column,
|
27 |
+
data_types=[convert_dtype(value)],
|
28 |
+
source=source,
|
29 |
+
references=mlc.Source(),
|
30 |
+
)
|
31 |
+
fields.append(field)
|
32 |
+
return [
|
33 |
+
RecordSet(
|
34 |
+
fields=fields,
|
35 |
+
name=find_unique_name(names, file.name + "_record_set"),
|
36 |
+
description="",
|
37 |
+
)
|
38 |
+
]
|
core/state.py
ADDED
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Streamlit session state.
|
2 |
+
|
3 |
+
In the future, this could be the serialization format between front and back.
|
4 |
+
"""
|
5 |
+
|
6 |
+
from __future__ import annotations
|
7 |
+
|
8 |
+
import dataclasses
|
9 |
+
import datetime
|
10 |
+
from typing import Any
|
11 |
+
|
12 |
+
from etils import epath
|
13 |
+
import pandas as pd
|
14 |
+
|
15 |
+
from core.constants import PAST_PROJECTS_PATH
|
16 |
+
from core.constants import PROJECT_FOLDER_PATTERN
|
17 |
+
import mlcroissant as mlc
|
18 |
+
|
19 |
+
|
20 |
+
def create_class(mlc_class: type, instance: Any, **kwargs) -> Any:
|
21 |
+
"""Creates the mlcroissant class `mlc_class` from the editor `instance`."""
|
22 |
+
fields = dataclasses.fields(mlc_class)
|
23 |
+
params: dict[str, Any] = {}
|
24 |
+
for field in fields:
|
25 |
+
name = field.name
|
26 |
+
if hasattr(instance, name) and name not in kwargs:
|
27 |
+
params[name] = getattr(instance, name)
|
28 |
+
return mlc_class(**params, **kwargs)
|
29 |
+
|
30 |
+
|
31 |
+
class CurrentStep:
|
32 |
+
"""Holds all major state variables for the application."""
|
33 |
+
|
34 |
+
splash = "splash"
|
35 |
+
editor = "editor"
|
36 |
+
|
37 |
+
|
38 |
+
@dataclasses.dataclass
|
39 |
+
class CurrentProject:
|
40 |
+
"""The selected project."""
|
41 |
+
|
42 |
+
path: epath.Path
|
43 |
+
|
44 |
+
@classmethod
|
45 |
+
def create_new(cls) -> CurrentProject:
|
46 |
+
timestamp = datetime.datetime.now().strftime(PROJECT_FOLDER_PATTERN)
|
47 |
+
return CurrentProject(path=PAST_PROJECTS_PATH / timestamp)
|
48 |
+
|
49 |
+
|
50 |
+
class SelectedResource:
|
51 |
+
"""The selected FileSet or FileObject on the `Resources` page."""
|
52 |
+
|
53 |
+
pass
|
54 |
+
|
55 |
+
|
56 |
+
@dataclasses.dataclass
|
57 |
+
class SelectedRecordSet:
|
58 |
+
"""The selected RecordSet on the `RecordSets` page."""
|
59 |
+
|
60 |
+
record_set_key: int
|
61 |
+
record_set: RecordSet
|
62 |
+
|
63 |
+
|
64 |
+
@dataclasses.dataclass
|
65 |
+
class FileObject:
|
66 |
+
"""FileObject analogue for editor"""
|
67 |
+
|
68 |
+
name: str | None = None
|
69 |
+
description: str | None = None
|
70 |
+
contained_in: list[str] | None = dataclasses.field(default_factory=list)
|
71 |
+
content_size: str | None = None
|
72 |
+
content_url: str | None = None
|
73 |
+
encoding_format: str | None = None
|
74 |
+
sha256: str | None = None
|
75 |
+
df: pd.DataFrame | None = None
|
76 |
+
rdf: mlc.Rdf = dataclasses.field(default_factory=mlc.Rdf)
|
77 |
+
|
78 |
+
|
79 |
+
@dataclasses.dataclass
|
80 |
+
class FileSet:
|
81 |
+
"""FileSet analogue for editor"""
|
82 |
+
|
83 |
+
contained_in: list[str] = dataclasses.field(default_factory=list)
|
84 |
+
description: str | None = None
|
85 |
+
encoding_format: str | None = ""
|
86 |
+
includes: str | None = ""
|
87 |
+
name: str = ""
|
88 |
+
rdf: mlc.Rdf = dataclasses.field(default_factory=mlc.Rdf)
|
89 |
+
|
90 |
+
|
91 |
+
@dataclasses.dataclass
|
92 |
+
class Field:
|
93 |
+
"""Field analogue for editor"""
|
94 |
+
|
95 |
+
name: str | None = None
|
96 |
+
description: str | None = None
|
97 |
+
data_types: str | list[str] | None = None
|
98 |
+
source: mlc.Source | None = None
|
99 |
+
rdf: mlc.Rdf = dataclasses.field(default_factory=mlc.Rdf)
|
100 |
+
references: mlc.Source | None = None
|
101 |
+
|
102 |
+
|
103 |
+
@dataclasses.dataclass
|
104 |
+
class RecordSet:
|
105 |
+
"""Record Set analogue for editor"""
|
106 |
+
|
107 |
+
name: str = ""
|
108 |
+
data: Any = None
|
109 |
+
description: str | None = None
|
110 |
+
is_enumeration: bool | None = None
|
111 |
+
key: str | list[str] | None = None
|
112 |
+
fields: list[Field] = dataclasses.field(default_factory=list)
|
113 |
+
rdf: mlc.Rdf = dataclasses.field(default_factory=mlc.Rdf)
|
114 |
+
|
115 |
+
|
116 |
+
@dataclasses.dataclass
|
117 |
+
class Metadata:
|
118 |
+
"""main croissant data object, helper functions exist to load and unload this into the mlcroissant version"""
|
119 |
+
|
120 |
+
name: str = ""
|
121 |
+
description: str | None = None
|
122 |
+
citation: str | None = None
|
123 |
+
license: str | None = ""
|
124 |
+
url: str = ""
|
125 |
+
distribution: list[FileObject | FileSet] = dataclasses.field(default_factory=list)
|
126 |
+
record_sets: list[RecordSet] = dataclasses.field(default_factory=list)
|
127 |
+
rdf: mlc.Rdf = dataclasses.field(default_factory=mlc.Rdf)
|
128 |
+
|
129 |
+
def __bool__(self):
|
130 |
+
return self.name != "" and self.url != ""
|
131 |
+
|
132 |
+
def rename_distribution(self, old_name: str, new_name: str):
|
133 |
+
"""Renames a resource by changing all the references to this resource."""
|
134 |
+
# Update other resources:
|
135 |
+
for i, resource in enumerate(self.distribution):
|
136 |
+
contained_in = resource.contained_in
|
137 |
+
if contained_in and old_name in contained_in:
|
138 |
+
self.distribution[i].contained_in = [
|
139 |
+
new_name if name == old_name else name for name in contained_in
|
140 |
+
]
|
141 |
+
# Updating source/references works just as with RecordSets.
|
142 |
+
self.rename_record_set(old_name, new_name)
|
143 |
+
|
144 |
+
def rename_record_set(self, old_name: str, new_name: str):
|
145 |
+
"""Renames a RecordSet by changing all the references to this RecordSet."""
|
146 |
+
for i, record_set in enumerate(self.record_sets):
|
147 |
+
for j, field in enumerate(record_set.fields):
|
148 |
+
# Update source
|
149 |
+
source = field.source
|
150 |
+
if source and source.uid and source.uid.startswith(old_name):
|
151 |
+
new_uid = source.uid.replace(old_name, new_name, 1)
|
152 |
+
self.record_sets[i].fields[j].source.uid = new_uid
|
153 |
+
# Update references
|
154 |
+
references = field.references
|
155 |
+
if (
|
156 |
+
references
|
157 |
+
and references.uid
|
158 |
+
and references.uid.startswith(old_name)
|
159 |
+
):
|
160 |
+
new_uid = references.uid.replace(old_name, new_name, 1)
|
161 |
+
self.record_sets[i].fields[j].references.uid = new_uid
|
162 |
+
|
163 |
+
def rename_field(self, old_name: str, new_name: str):
|
164 |
+
"""Renames a field by changing all the references to this field."""
|
165 |
+
for i, record_set in enumerate(self.record_sets):
|
166 |
+
for j, field in enumerate(record_set.fields):
|
167 |
+
# Update source
|
168 |
+
source = field.source
|
169 |
+
# The difference with RecordSet is the `.endswith` here:
|
170 |
+
if (
|
171 |
+
source
|
172 |
+
and source.uid
|
173 |
+
and "/" in source.uid
|
174 |
+
and source.uid.endswith(old_name)
|
175 |
+
):
|
176 |
+
new_uid = source.uid.replace(old_name, new_name, 1)
|
177 |
+
self.record_sets[i].fields[j].source.uid = new_uid
|
178 |
+
# Update references
|
179 |
+
references = field.references
|
180 |
+
if (
|
181 |
+
references
|
182 |
+
and references.uid
|
183 |
+
and "/" in references.uid
|
184 |
+
and references.uid.endswith(old_name)
|
185 |
+
):
|
186 |
+
new_uid = references.uid.replace(old_name, new_name, 1)
|
187 |
+
self.record_sets[i].fields[j].references.uid = new_uid
|
188 |
+
|
189 |
+
def add_distribution(self, distribution: FileSet | FileObject) -> None:
|
190 |
+
self.distribution.append(distribution)
|
191 |
+
|
192 |
+
def remove_distribution(self, key: int) -> None:
|
193 |
+
del self.distribution[key]
|
194 |
+
|
195 |
+
def add_record_set(self, record_set: RecordSet) -> None:
|
196 |
+
self.record_sets.append(record_set)
|
197 |
+
|
198 |
+
def remove_record_set(self, key: int) -> None:
|
199 |
+
del self.record_sets[key]
|
200 |
+
|
201 |
+
def _find_record_set(self, record_set_key: int) -> RecordSet:
|
202 |
+
if record_set_key >= len(self.record_sets):
|
203 |
+
raise ValueError(f"Wrong index when finding a RecordSet: {record_set_key}")
|
204 |
+
return self.record_sets[record_set_key]
|
205 |
+
|
206 |
+
def add_field(self, record_set_key: int, field: Field) -> None:
|
207 |
+
record_set = self._find_record_set(record_set_key)
|
208 |
+
record_set.fields.append(field)
|
209 |
+
|
210 |
+
def remove_field(self, record_set_key: int, field_key: int) -> None:
|
211 |
+
record_set = self._find_record_set(record_set_key)
|
212 |
+
if field_key >= len(record_set.fields):
|
213 |
+
raise ValueError(f"Wrong index when removing field: {field_key}")
|
214 |
+
del record_set.fields[field_key]
|
215 |
+
|
216 |
+
def to_canonical(self) -> mlc.Metadata:
|
217 |
+
distribution = []
|
218 |
+
for file in self.distribution:
|
219 |
+
if isinstance(file, FileObject):
|
220 |
+
distribution.append(create_class(mlc.FileObject, file))
|
221 |
+
elif isinstance(file, FileSet):
|
222 |
+
distribution.append(create_class(mlc.FileSet, file))
|
223 |
+
record_sets = []
|
224 |
+
for record_set in self.record_sets:
|
225 |
+
fields = []
|
226 |
+
for field in record_set.fields:
|
227 |
+
fields.append(create_class(mlc.Field, field))
|
228 |
+
record_sets.append(create_class(mlc.RecordSet, record_set, fields=fields))
|
229 |
+
return create_class(
|
230 |
+
mlc.Metadata,
|
231 |
+
self,
|
232 |
+
distribution=distribution,
|
233 |
+
record_sets=record_sets,
|
234 |
+
)
|
235 |
+
|
236 |
+
@classmethod
|
237 |
+
def from_canonical(cls, canonical_metadata: mlc.Metadata) -> Metadata:
|
238 |
+
distribution = []
|
239 |
+
for file in canonical_metadata.distribution:
|
240 |
+
if isinstance(file, mlc.FileObject):
|
241 |
+
distribution.append(create_class(FileObject, file))
|
242 |
+
else:
|
243 |
+
distribution.append(create_class(FileSet, file))
|
244 |
+
record_sets = []
|
245 |
+
for record_set in canonical_metadata.record_sets:
|
246 |
+
fields = []
|
247 |
+
for field in record_set.fields:
|
248 |
+
fields.append(create_class(Field, field))
|
249 |
+
record_sets.append(
|
250 |
+
create_class(
|
251 |
+
RecordSet,
|
252 |
+
record_set,
|
253 |
+
fields=fields,
|
254 |
+
)
|
255 |
+
)
|
256 |
+
return create_class(
|
257 |
+
cls,
|
258 |
+
canonical_metadata,
|
259 |
+
distribution=distribution,
|
260 |
+
record_sets=record_sets,
|
261 |
+
)
|
cypress.config.js
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const { defineConfig } = require("cypress");
|
2 |
+
|
3 |
+
module.exports = defineConfig({
|
4 |
+
// To access content within Streamlit iframes for custom components:
|
5 |
+
chromeWebSecurity: false,
|
6 |
+
e2e: {},
|
7 |
+
});
|
cypress/downloads/croissant-Titanic.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"@context": {"@language": "en", "@vocab": "https://schema.org/", "column": "ml:column", "data": {"@id": "ml:data", "@type": "@json"}, "dataType": {"@id": "ml:dataType", "@type": "@vocab"}, "extract": "ml:extract", "field": "ml:field", "fileProperty": "ml:fileProperty", "format": "ml:format", "includes": "ml:includes", "isEnumeration": "ml:isEnumeration", "jsonPath": "ml:jsonPath", "ml": "http://mlcommons.org/schema/", "parentField": "ml:parentField", "path": "ml:path", "recordSet": "ml:recordSet", "references": "ml:references", "regex": "ml:regex", "repeated": "ml:repeated", "replace": "ml:replace", "sc": "https://schema.org/", "separator": "ml:separator", "source": "ml:source", "subField": "ml:subField", "transform": "ml:transform", "wd": "https://www.wikidata.org/wiki/"}, "@type": "sc:Dataset", "name": "Titanic", "description": "The original Titanic dataset, describing the status of individual passengers on the Titanic.\n\n The titanic data does not contain information from the crew, but it does contain actual ages of half of the passengers. \n\n For more information about how this dataset was constructed: \nhttps://web.archive.org/web/20200802155940/http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3info.txt\n\nOther useful information (useful for prices description for example):\nhttp://campus.lakeforest.edu/frank/FILES/MLFfiles/Bio150/Titanic/TitanicMETA.pdf\n\n Also see the following article describing shortcomings of the dataset data:\nhttps://emma-stiefel.medium.com/plugging-holes-in-kaggles-titanic-dataset-an-introduction-to-combining-datasets-with-fuzzywuzzy-60a686699da7\n", "citation": "The principal source for data about Titanic passengers is the Encyclopedia Titanica (http://www.encyclopedia-titanica.org/). The datasets used here were begun by a variety of researchers. One of the original sources is Eaton & Haas (1994) Titanic: Triumph and Tragedy, Patrick Stephens Ltd, which includes a passenger list created by many researchers and edited by Michael A. Findlay.\n\nThomas Cason of UVa has greatly updated and improved the titanic data frame using the Encyclopedia Titanica and created the dataset here. Some duplicate passengers have been dropped, many errors corrected, many missing ages filled in, and new variables created.\n", "license": "Public", "url": "https://www.openml.org/d/40945", "distribution": [{"@type": "sc:FileObject", "name": "passengers.csv", "contentSize": "117743 B", "contentUrl": "https://www.openml.org/data/get_csv/16826755/phpMYEkMl", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}, {"@type": "sc:FileObject", "name": "genders.csv", "description": "Maps gender values (\"male\", \"female\") to semantic URLs.", "contentSize": "117743 B", "contentUrl": "data/genders.csv", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}, {"@type": "sc:FileObject", "name": "embarkation_ports.csv", "description": "Maps Embarkation port initial to labeled values.", "contentSize": "117743 B", "contentUrl": "data/embarkation_ports.csv", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}], "recordSet": [{"@type": "ml:RecordSet", "name": "genders", "description": "Maps gender labels to semantic definitions.", "isEnumeration": true, "key": "label", "field": [{"@type": "ml:Field", "name": "label", "description": "One of {\"male\", \"female\"}", "dataType": ["sc:Text", "sc:name"], "source": {"distribution": "genders.csv", "extract": {"column": "label"}}}, {"@type": "ml:Field", "name": "url", "description": "Corresponding WikiData URL", "dataType": ["sc:URL", "wd:Q48277"], "source": {"distribution": "genders.csv", "extract": {"column": "url"}}}]}, {"@type": "ml:RecordSet", "name": "embarkation_ports", "description": "Maps Embarkation port initial to labeled values.", "isEnumeration": true, "key": "key", "field": [{"@type": "ml:Field", "name": "key", "description": "C, Q, S or ?", "dataType": "sc:Text", "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "key"}}}, {"@type": "ml:Field", "name": "label", "description": "Human-readable label", "dataType": ["sc:Text", "sc:name"], "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "label"}}}, {"@type": "ml:Field", "name": "url", "description": "Corresponding WikiData URL", "dataType": ["sc:URL", "wd:Q515"], "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "url"}}}]}, {"@type": "ml:RecordSet", "name": "passengers", "description": "The list of passengers. Does not include crew members.", "field": [{"@type": "ml:Field", "name": "name", "description": "Name of the passenger", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "name"}}}, {"@type": "ml:Field", "name": "gender", "description": "Gender of passenger (male or female)", "dataType": "sc:Text", "references": {"field": "genders/label"}, "source": {"distribution": "passengers.csv", "extract": {"column": "sex"}}}, {"@type": "ml:Field", "name": "age", "description": "Age of passenger at time of death. It's a string, because some values can be `?`.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "age"}}}, {"@type": "ml:Field", "name": "survived", "description": "Survival status of passenger (0: Lost, 1: Saved)", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "survived"}}}, {"@type": "ml:Field", "name": "pclass", "description": "Passenger Class (1st/2nd/3rd)", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "pclass"}}}, {"@type": "ml:Field", "name": "cabin", "description": "Passenger cabin.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "cabin"}}}, {"@type": "ml:Field", "name": "embarked", "description": "Port of Embarkation (C: Cherbourg, Q: Queenstown, S: Southampton, ?: Unknown).", "dataType": "sc:Text", "references": {"field": "embarkation_ports/key"}, "source": {"distribution": "passengers.csv", "extract": {"column": "embarked"}}}, {"@type": "ml:Field", "name": "fare", "description": "Passenger Fare (British pound). It's a string, because some values can be `?`.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "fare"}}}, {"@type": "ml:Field", "name": "home_destination", "description": "Home and destination", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "home.dest"}}}, {"@type": "ml:Field", "name": "ticket", "description": "Ticket Number, may include a letter.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "ticket"}}}, {"@type": "ml:Field", "name": "num_parents_children", "description": "Number of Parents/Children Aboard", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "parch"}}}, {"@type": "ml:Field", "name": "num_siblings_spouses", "description": "Number of Siblings/Spouses Aboard", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "sibsp"}}}, {"@type": "ml:Field", "name": "boat", "description": "Lifeboat used by passenger", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "boat"}}}, {"@type": "ml:Field", "name": "body", "description": "Body Identification Number", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "body"}}}]}]}
|
cypress/downloads/croissant.json
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
{"@context": {"@language": "en", "@vocab": "https://schema.org/", "column": "ml:column", "data": {"@id": "ml:data", "@type": "@json"}, "dataType": {"@id": "ml:dataType", "@type": "@vocab"}, "extract": "ml:extract", "field": "ml:field", "fileProperty": "ml:fileProperty", "format": "ml:format", "includes": "ml:includes", "isEnumeration": "ml:isEnumeration", "jsonPath": "ml:jsonPath", "ml": "http://mlcommons.org/schema/", "parentField": "ml:parentField", "path": "ml:path", "recordSet": "ml:recordSet", "references": "ml:references", "regex": "ml:regex", "repeated": "ml:repeated", "replace": "ml:replace", "sc": "https://schema.org/", "separator": "ml:separator", "source": "ml:source", "subField": "ml:subField", "transform": "ml:transform", "wd": "https://www.wikidata.org/wiki/"}, "@type": "sc:Dataset", "name": "Titanic", "description": "The original Titanic dataset, describing the status of individual passengers on the Titanic.\n\n The titanic data does not contain information from the crew, but it does contain actual ages of half of the passengers. \n\n For more information about how this dataset was constructed: \nhttps://web.archive.org/web/20200802155940/http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3info.txt\n\nOther useful information (useful for prices description for example):\nhttp://campus.lakeforest.edu/frank/FILES/MLFfiles/Bio150/Titanic/TitanicMETA.pdf\n\n Also see the following article describing shortcomings of the dataset data:\nhttps://emma-stiefel.medium.com/plugging-holes-in-kaggles-titanic-dataset-an-introduction-to-combining-datasets-with-fuzzywuzzy-60a686699da7\n", "citation": "The principal source for data about Titanic passengers is the Encyclopedia Titanica (http://www.encyclopedia-titanica.org/). The datasets used here were begun by a variety of researchers. One of the original sources is Eaton & Haas (1994) Titanic: Triumph and Tragedy, Patrick Stephens Ltd, which includes a passenger list created by many researchers and edited by Michael A. Findlay.\n\nThomas Cason of UVa has greatly updated and improved the titanic data frame using the Encyclopedia Titanica and created the dataset here. Some duplicate passengers have been dropped, many errors corrected, many missing ages filled in, and new variables created.\n", "license": "Public", "url": "https://www.openml.org/d/40945", "distribution": [{"@type": "sc:FileObject", "name": "passengers.csv", "contentSize": "117743 B", "contentUrl": "https://www.openml.org/data/get_csv/16826755/phpMYEkMl", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}, {"@type": "sc:FileObject", "name": "genders.csv", "description": "Maps gender values (\"male\", \"female\") to semantic URLs.", "contentSize": "117743 B", "contentUrl": "data/genders.csv", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}, {"@type": "sc:FileObject", "name": "embarkation_ports.csv", "description": "Maps Embarkation port initial to labeled values.", "contentSize": "117743 B", "contentUrl": "data/embarkation_ports.csv", "encodingFormat": "text/csv", "sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"}], "recordSet": [{"@type": "ml:RecordSet", "name": "genders", "description": "Maps gender labels to semantic definitions.", "isEnumeration": true, "key": "label", "field": [{"@type": "ml:Field", "name": "label", "description": "One of {\"male\", \"female\"}", "dataType": ["sc:Text", "sc:name"], "source": {"distribution": "genders.csv", "extract": {"column": "label"}}}, {"@type": "ml:Field", "name": "url", "description": "Corresponding WikiData URL", "dataType": ["sc:URL", "wd:Q48277"], "source": {"distribution": "genders.csv", "extract": {"column": "url"}}}]}, {"@type": "ml:RecordSet", "name": "embarkation_ports", "description": "Maps Embarkation port initial to labeled values.", "isEnumeration": true, "key": "key", "field": [{"@type": "ml:Field", "name": "key", "description": "C, Q, S or ?", "dataType": "sc:Text", "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "key"}}}, {"@type": "ml:Field", "name": "label", "description": "Human-readable label", "dataType": ["sc:Text", "sc:name"], "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "label"}}}, {"@type": "ml:Field", "name": "url", "description": "Corresponding WikiData URL", "dataType": ["sc:URL", "wd:Q515"], "source": {"distribution": "embarkation_ports.csv", "extract": {"column": "url"}}}]}, {"@type": "ml:RecordSet", "name": "passengers", "description": "The list of passengers. Does not include crew members.", "field": [{"@type": "ml:Field", "name": "name", "description": "Name of the passenger", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "name"}}}, {"@type": "ml:Field", "name": "gender", "description": "Gender of passenger (male or female)", "dataType": "sc:Text", "references": {"field": "genders/label"}, "source": {"distribution": "passengers.csv", "extract": {"column": "sex"}}}, {"@type": "ml:Field", "name": "age", "description": "Age of passenger at time of death. It's a string, because some values can be `?`.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "age"}}}, {"@type": "ml:Field", "name": "survived", "description": "Survival status of passenger (0: Lost, 1: Saved)", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "survived"}}}, {"@type": "ml:Field", "name": "pclass", "description": "Passenger Class (1st/2nd/3rd)", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "pclass"}}}, {"@type": "ml:Field", "name": "cabin", "description": "Passenger cabin.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "cabin"}}}, {"@type": "ml:Field", "name": "embarked", "description": "Port of Embarkation (C: Cherbourg, Q: Queenstown, S: Southampton, ?: Unknown).", "dataType": "sc:Text", "references": {"field": "embarkation_ports/key"}, "source": {"distribution": "passengers.csv", "extract": {"column": "embarked"}}}, {"@type": "ml:Field", "name": "fare", "description": "Passenger Fare (British pound). It's a string, because some values can be `?`.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "fare"}}}, {"@type": "ml:Field", "name": "home_destination", "description": "Home and destination", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "home.dest"}}}, {"@type": "ml:Field", "name": "ticket", "description": "Ticket Number, may include a letter.", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "ticket"}}}, {"@type": "ml:Field", "name": "num_parents_children", "description": "Number of Parents/Children Aboard", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "parch"}}}, {"@type": "ml:Field", "name": "num_siblings_spouses", "description": "Number of Siblings/Spouses Aboard", "dataType": "sc:Integer", "source": {"distribution": "passengers.csv", "extract": {"column": "sibsp"}}}, {"@type": "ml:Field", "name": "boat", "description": "Lifeboat used by passenger", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "boat"}}}, {"@type": "ml:Field", "name": "body", "description": "Body Identification Number", "dataType": "sc:Text", "source": {"distribution": "passengers.csv", "extract": {"column": "body"}}}]}]}
|
cypress/e2e/createManually.cy.js
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="cypress" />
|
2 |
+
|
3 |
+
import 'cypress-file-upload';
|
4 |
+
import 'cypress-iframe';
|
5 |
+
|
6 |
+
|
7 |
+
describe('Create a resource manually', () => {
|
8 |
+
it('should allow adding a FileObject resource', () => {
|
9 |
+
// Streamlit starts on :8501.
|
10 |
+
cy.visit('http://localhost:8501')
|
11 |
+
cy.get('button', {timeout: 10000}).contains('Create', {timeout: 10000}).click()
|
12 |
+
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
13 |
+
cy.get('[data-testid="stMarkdownContainer"]')
|
14 |
+
.contains('Metadata')
|
15 |
+
.click()
|
16 |
+
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
17 |
+
|
18 |
+
// Create a resource manually.
|
19 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('Resources').click()
|
20 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('Add manually').click()
|
21 |
+
|
22 |
+
cy.get('input[aria-label="File name:red[*]"]').type('test.csv').blur()
|
23 |
+
cy.get('input[aria-label="SHA256"]').type('abcdefgh1234567').blur()
|
24 |
+
cy.get('button').contains('Upload').click()
|
25 |
+
|
26 |
+
// The file is created, so we can click on it to see the details.
|
27 |
+
cy.enter('[title="components.tree.tree_component"]').then(getBody => {
|
28 |
+
getBody().contains('test.csv').click()
|
29 |
+
})
|
30 |
+
|
31 |
+
cy.get('input[aria-label="SHA256:red[*]"]')
|
32 |
+
.should('be.disabled')
|
33 |
+
.should('have.value', 'abcdefgh1234567')
|
34 |
+
})
|
35 |
+
})
|
cypress/e2e/displayErrors.cy.js
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="cypress" />
|
2 |
+
|
3 |
+
import 'cypress-file-upload';
|
4 |
+
|
5 |
+
describe('load existing errored croissant', () => {
|
6 |
+
it('should display errors', () => {
|
7 |
+
cy.visit('http://localhost:8501')
|
8 |
+
|
9 |
+
cy.fixture('coco.json').then((fileContent) => {
|
10 |
+
const file = {
|
11 |
+
fileContent,
|
12 |
+
fileName: 'coco.json', mimeType: 'text/json',
|
13 |
+
}
|
14 |
+
cy.get(
|
15 |
+
"[data-testid='stFileUploadDropzone']",
|
16 |
+
).attachFile(file, {
|
17 |
+
force: true,
|
18 |
+
subjectType: "drag-n-drop",
|
19 |
+
events: ["dragenter", "drop"],
|
20 |
+
})
|
21 |
+
})
|
22 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('not.exist')
|
23 |
+
// Empty the `name` field to create an error:
|
24 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('RecordSets').click()
|
25 |
+
cy.contains('split_enums (2 fields)').click()
|
26 |
+
cy.get('input[aria-label="Name:red[*]"][value="split_enums"]').should('be.visible').type('{selectall}{backspace}{enter}')
|
27 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('Overview').click()
|
28 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains("Errors").should('exist')
|
29 |
+
})
|
30 |
+
})
|
cypress/e2e/loadCroissant.cy.js
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="cypress" />
|
2 |
+
|
3 |
+
import 'cypress-file-upload';
|
4 |
+
import * as path from 'path';
|
5 |
+
|
6 |
+
describe('Editor loads Croissant without Error', () => {
|
7 |
+
it('should allow uploading existing croissant files', () => {
|
8 |
+
cy.visit('http://localhost:8501')
|
9 |
+
|
10 |
+
cy.fixture('titanic.json').then((fileContent) => {
|
11 |
+
const file = {
|
12 |
+
fileContent,
|
13 |
+
fileName: 'titanic.json', mimeType: 'text/json',
|
14 |
+
}
|
15 |
+
cy.get(
|
16 |
+
"[data-testid='stFileUploadDropzone']",
|
17 |
+
).attachFile(file, {
|
18 |
+
force: true,
|
19 |
+
subjectType: "drag-n-drop",
|
20 |
+
events: ["dragenter", "drop"],
|
21 |
+
})
|
22 |
+
})
|
23 |
+
cy.get('button').contains('Metadata').click()
|
24 |
+
|
25 |
+
cy
|
26 |
+
.get("[data-testid='element-container']")
|
27 |
+
.contains('Titanic')
|
28 |
+
.should('exist')
|
29 |
+
|
30 |
+
})
|
31 |
+
it('should download as json', () => {
|
32 |
+
cy.visit('http://localhost:8501')
|
33 |
+
|
34 |
+
cy.fixture('titanic.json').then((fileContent) => {
|
35 |
+
const file = {
|
36 |
+
fileContent,
|
37 |
+
fileName: 'titanic.json', mimeType: 'text/json',
|
38 |
+
}
|
39 |
+
cy.get(
|
40 |
+
"[data-testid='stFileUploadDropzone']",
|
41 |
+
).attachFile(file, {
|
42 |
+
force: true,
|
43 |
+
subjectType: "drag-n-drop",
|
44 |
+
events: ["dragenter", "drop"],
|
45 |
+
})
|
46 |
+
})
|
47 |
+
|
48 |
+
cy.get('[data-testid="stException"]').should('not.exist')
|
49 |
+
|
50 |
+
cy.get('button').contains('Export').should('exist').should('be.visible').click({force: true})
|
51 |
+
cy.fixture('titanic.json').then((fileContent) => {
|
52 |
+
const downloadsFolder = Cypress.config("downloadsFolder");
|
53 |
+
cy.readFile(path.join(downloadsFolder, "croissant-titanic.json"))
|
54 |
+
.then((downloadedFile) => {
|
55 |
+
downloadedFile = JSON.stringify(downloadedFile)
|
56 |
+
return downloadedFile
|
57 |
+
})
|
58 |
+
.should('deep.equal', JSON.stringify(fileContent))
|
59 |
+
})
|
60 |
+
})
|
61 |
+
})
|
cypress/e2e/renameDistribution.cy.js
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="cypress" />
|
2 |
+
|
3 |
+
import 'cypress-file-upload';
|
4 |
+
import 'cypress-iframe';
|
5 |
+
|
6 |
+
|
7 |
+
describe('Renaming of FileObjects/FileSets/RecordSets/Fields.', () => {
|
8 |
+
it('should rename the FileObject/FileSet everywhere', () => {
|
9 |
+
cy.visit('http://localhost:8501')
|
10 |
+
|
11 |
+
cy.fixture('titanic.json').then((fileContent) => {
|
12 |
+
const file = {
|
13 |
+
fileContent,
|
14 |
+
fileName: 'titanic.json', mimeType: 'text/json',
|
15 |
+
}
|
16 |
+
cy.get(
|
17 |
+
"[data-testid='stFileUploadDropzone']",
|
18 |
+
).attachFile(file, {
|
19 |
+
force: true,
|
20 |
+
subjectType: "drag-n-drop",
|
21 |
+
events: ["dragenter", "drop"],
|
22 |
+
})
|
23 |
+
})
|
24 |
+
cy.get('button').contains('Resources').click()
|
25 |
+
cy.enter('[title="components.tree.tree_component"]').then(getBody => {
|
26 |
+
// Click on genders.csv
|
27 |
+
getBody().contains('genders.csv').click()
|
28 |
+
})
|
29 |
+
cy.get('input[aria-label="Name:red[*]"][value="genders.csv"]').type('{selectall}{backspace}the-new-name{enter}')
|
30 |
+
|
31 |
+
cy.get('button').contains('RecordSets').click()
|
32 |
+
cy.contains('genders').click()
|
33 |
+
cy.contains('Edit fields details').click()
|
34 |
+
cy.contains('the-new-name')
|
35 |
+
})
|
36 |
+
})
|
cypress/e2e/uploadCsv.cy.js
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="cypress" />
|
2 |
+
|
3 |
+
import 'cypress-file-upload';
|
4 |
+
import 'cypress-iframe';
|
5 |
+
|
6 |
+
|
7 |
+
describe('Editor loads a local CSV as a resource', () => {
|
8 |
+
it('should display the form: Overview, Metadata, Resources, & Record Sets', () => {
|
9 |
+
// Streamlit starts on :8501.
|
10 |
+
cy.visit('http://localhost:8501')
|
11 |
+
cy.get('button', {timeout: 10000}).contains('Create', {timeout: 10000}).click()
|
12 |
+
|
13 |
+
cy.get('input[aria-label="Name:red[*]"]').type('MyDataset').blur()
|
14 |
+
cy.get('[data-testid="stMarkdownContainer"]')
|
15 |
+
.contains('Metadata')
|
16 |
+
.click()
|
17 |
+
cy.get('input[aria-label="URL:red[*]"]').type('https://mydataset.com', {force: true})
|
18 |
+
|
19 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('Resources').click()
|
20 |
+
// Drag and drop mimicking: streamlit/e2e/specs/st_file_uploader.spec.js.
|
21 |
+
cy.fixture('base.csv').then((fileContent) => {
|
22 |
+
const file = {
|
23 |
+
fileContent,
|
24 |
+
fileName: 'base.csv', mimeType: 'text/csv',
|
25 |
+
}
|
26 |
+
cy.get(
|
27 |
+
"[data-testid='stFileUploadDropzone']",
|
28 |
+
).attachFile(file, {
|
29 |
+
force: true,
|
30 |
+
subjectType: "drag-n-drop",
|
31 |
+
events: ["dragenter", "drop"],
|
32 |
+
})
|
33 |
+
})
|
34 |
+
cy.get('.uploadedFileData').contains('base.csv')
|
35 |
+
cy.get('button').contains('Upload').click()
|
36 |
+
// The file is uploaded, so we can click on it to see the details.
|
37 |
+
// Waiting a few seconds to wait for the resource to download.
|
38 |
+
cy.wait(2000)
|
39 |
+
cy.enter('[title="components.tree.tree_component"]').then(getBody => {
|
40 |
+
getBody().find('li').should('be.visible').click()
|
41 |
+
})
|
42 |
+
// For example, we see the first rows:
|
43 |
+
cy.contains('First rows of data:')
|
44 |
+
|
45 |
+
// On the record set page, we see the record set.
|
46 |
+
cy.get('[data-testid="stMarkdownContainer"]').contains('RecordSets').click()
|
47 |
+
cy.contains('base.csv_record_set (2 fields)').click()
|
48 |
+
// We also see the fields with the proper types.
|
49 |
+
cy.get('[data-testid="stDataFrameResizable"]').contains("column1")
|
50 |
+
cy.get('[data-testid="stDataFrameResizable"]').contains("https://schema.org/Text")
|
51 |
+
cy.get('[data-testid="stDataFrameResizable"]').contains("column2")
|
52 |
+
cy.get('[data-testid="stDataFrameResizable"]').contains("https://schema.org/Integer")
|
53 |
+
|
54 |
+
// I can edit the details of the fields.
|
55 |
+
cy.contains('Edit fields details').click()
|
56 |
+
cy.get('input[aria-label="Description"]').last().type('This is a nice custom description!{enter}')
|
57 |
+
cy.get('[data-testid="glide-cell-2-1"]').contains("This is a nice custom description!")
|
58 |
+
})
|
59 |
+
})
|
cypress/fixtures/base.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
column1,column2
|
2 |
+
A,1
|
3 |
+
B,2
|
4 |
+
C,3
|
cypress/fixtures/coco.json
ADDED
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"@context": {
|
3 |
+
"@language": "en",
|
4 |
+
"@vocab": "https://schema.org/",
|
5 |
+
"column": "ml:column",
|
6 |
+
"data": {
|
7 |
+
"@id": "ml:data",
|
8 |
+
"@type": "@json"
|
9 |
+
},
|
10 |
+
"dataType": {
|
11 |
+
"@id": "ml:dataType",
|
12 |
+
"@type": "@vocab"
|
13 |
+
},
|
14 |
+
"extract": "ml:extract",
|
15 |
+
"field": "ml:field",
|
16 |
+
"fileProperty": "ml:fileProperty",
|
17 |
+
"format": "ml:format",
|
18 |
+
"includes": "ml:includes",
|
19 |
+
"isEnumeration": "ml:isEnumeration",
|
20 |
+
"jsonPath": "ml:jsonPath",
|
21 |
+
"ml": "http://mlcommons.org/schema/",
|
22 |
+
"parentField": "ml:parentField",
|
23 |
+
"path": "ml:path",
|
24 |
+
"recordSet": "ml:recordSet",
|
25 |
+
"references": "ml:references",
|
26 |
+
"regex": "ml:regex",
|
27 |
+
"repeated": "ml:repeated",
|
28 |
+
"replace": "ml:replace",
|
29 |
+
"sc": "https://schema.org/",
|
30 |
+
"separator": "ml:separator",
|
31 |
+
"source": "ml:source",
|
32 |
+
"subField": "ml:subField",
|
33 |
+
"transform": "ml:transform",
|
34 |
+
"wd": "https://www.wikidata.org/wiki/"
|
35 |
+
},
|
36 |
+
"@type": "sc:Dataset",
|
37 |
+
"name": "COCO",
|
38 |
+
"description": "COCO is a large-scale object detection, segmentation, and captioning dataset. WARNING: `metadata.json` is incomplete and does not fully define the COCO2014 dataset. It lacks `recordSet` definitions that would enable automatic loading of all the annotations.",
|
39 |
+
"citation": "@article{DBLP:journals/corr/LinMBHPRDZ14,\n author = {Tsung{-}Yi Lin and\n Michael Maire and\n Serge J. Belongie and\n Lubomir D. Bourdev and\n Ross B. Girshick and\n James Hays and\n Pietro Perona and\n Deva Ramanan and\n Piotr Doll{'{a}}r and\n C. Lawrence Zitnick},\n title = {Microsoft {COCO:} Common Objects in Context},\n journal = {CoRR},\n volume = {abs/1405.0312},\n year = {2014},\n url = {http://arxiv.org/abs/1405.0312},\n archivePrefix = {arXiv},\n eprint = {1405.0312},\n timestamp = {Mon, 13 Aug 2018 16:48:13 +0200},\n biburl = {https://dblp.org/rec/bib/journals/corr/LinMBHPRDZ14},\n bibsource = {dblp computer science bibliography, https://dblp.org}\n}",
|
40 |
+
"license": [
|
41 |
+
"Creative Commons Attribution 4.0 License",
|
42 |
+
"https://www.flickr.com/creativecommons/"
|
43 |
+
],
|
44 |
+
"url": "https://cocodataset.org/",
|
45 |
+
"distribution": [
|
46 |
+
{
|
47 |
+
"@type": "sc:FileObject",
|
48 |
+
"name": "train2014.zip",
|
49 |
+
"contentSize": "13510573713 B",
|
50 |
+
"contentUrl": "http://images.cocodataset.org/zips/train2014.zip",
|
51 |
+
"encodingFormat": "application/zip",
|
52 |
+
"sha256": "sha256"
|
53 |
+
},
|
54 |
+
{
|
55 |
+
"@type": "sc:FileObject",
|
56 |
+
"name": "val2014.zip",
|
57 |
+
"contentSize": "6645013297 B",
|
58 |
+
"contentUrl": "http://images.cocodataset.org/zips/val2014.zip",
|
59 |
+
"encodingFormat": "application/zip",
|
60 |
+
"sha256": "sha256"
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"@type": "sc:FileObject",
|
64 |
+
"name": "test2014.zip",
|
65 |
+
"contentSize": "6660437059 B",
|
66 |
+
"contentUrl": "http://images.cocodataset.org/zips/test2014.zip",
|
67 |
+
"encodingFormat": "application/zip",
|
68 |
+
"sha256": "sha256"
|
69 |
+
},
|
70 |
+
{
|
71 |
+
"@type": "sc:FileSet",
|
72 |
+
"name": "image-files",
|
73 |
+
"containedIn": [
|
74 |
+
"train2014.zip",
|
75 |
+
"val2014.zip",
|
76 |
+
"test2014.zip"
|
77 |
+
],
|
78 |
+
"encodingFormat": "image/jpeg",
|
79 |
+
"includes": "*.jpg"
|
80 |
+
},
|
81 |
+
{
|
82 |
+
"@type": "sc:FileObject",
|
83 |
+
"name": "annotations_trainval2014.zip",
|
84 |
+
"contentSize": "252872794 B",
|
85 |
+
"contentUrl": "http://images.cocodataset.org/annotations/annotations_trainval2014.zip",
|
86 |
+
"encodingFormat": "application/zip",
|
87 |
+
"sha256": "sha256"
|
88 |
+
},
|
89 |
+
{
|
90 |
+
"@type": "sc:FileSet",
|
91 |
+
"name": "caption_annotations-files",
|
92 |
+
"containedIn": "annotations_trainval2014.zip",
|
93 |
+
"encodingFormat": "application/json",
|
94 |
+
"includes": "annotations/captions_(val|train)2014.json"
|
95 |
+
},
|
96 |
+
{
|
97 |
+
"@type": "sc:FileSet",
|
98 |
+
"name": "person_keypoints_annotations",
|
99 |
+
"containedIn": "annotations_trainval2014.zip",
|
100 |
+
"encodingFormat": "application/json",
|
101 |
+
"includes": "annotations/person_keypoints_(val|train)2014.json"
|
102 |
+
},
|
103 |
+
{
|
104 |
+
"@type": "sc:FileSet",
|
105 |
+
"name": "instancesperson_keypoints_annotations",
|
106 |
+
"containedIn": "annotations_trainval2014.zip",
|
107 |
+
"encodingFormat": "application/json",
|
108 |
+
"includes": "annotations/instances_(val|train)2014.json"
|
109 |
+
},
|
110 |
+
{
|
111 |
+
"@type": "sc:FileObject",
|
112 |
+
"name": "image_info_test2014.zip",
|
113 |
+
"contentSize": "763464 B",
|
114 |
+
"contentUrl": "http://images.cocodataset.org/annotations/image_info_test2014.zip",
|
115 |
+
"encodingFormat": "application/zip",
|
116 |
+
"sha256": "sha256"
|
117 |
+
},
|
118 |
+
{
|
119 |
+
"@type": "sc:FileSet",
|
120 |
+
"name": "imageinfo",
|
121 |
+
"containedIn": "image_info_test2014.zip",
|
122 |
+
"encodingFormat": "application/json",
|
123 |
+
"includes": "annotations/image_info_test.json"
|
124 |
+
}
|
125 |
+
],
|
126 |
+
"recordSet": [
|
127 |
+
{
|
128 |
+
"@type": "ml:RecordSet",
|
129 |
+
"name": "split_enums",
|
130 |
+
"description": "Maps split names to semantic values.",
|
131 |
+
"key": "name",
|
132 |
+
"field": [
|
133 |
+
{
|
134 |
+
"@type": "ml:Field",
|
135 |
+
"name": "name",
|
136 |
+
"description": "One of: train, val, test.",
|
137 |
+
"dataType": "sc:Text"
|
138 |
+
},
|
139 |
+
{
|
140 |
+
"@type": "ml:Field",
|
141 |
+
"name": "url",
|
142 |
+
"description": "Corresponding mlcommons.org definition URL",
|
143 |
+
"dataType": [
|
144 |
+
"sc:URL",
|
145 |
+
"wd:Q3985153"
|
146 |
+
]
|
147 |
+
}
|
148 |
+
],
|
149 |
+
"data": [
|
150 |
+
{
|
151 |
+
"name": "train",
|
152 |
+
"url": "https://mlcommons.org/definitions/training_split"
|
153 |
+
},
|
154 |
+
{
|
155 |
+
"name": "val",
|
156 |
+
"url": "https://mlcommons.org/definitions/validation_split"
|
157 |
+
},
|
158 |
+
{
|
159 |
+
"name": "test",
|
160 |
+
"url": "https://mlcommons.org/definitions/test_split"
|
161 |
+
}
|
162 |
+
]
|
163 |
+
},
|
164 |
+
{
|
165 |
+
"@type": "ml:RecordSet",
|
166 |
+
"name": "images",
|
167 |
+
"key": "image_id",
|
168 |
+
"field": [
|
169 |
+
{
|
170 |
+
"@type": "ml:Field",
|
171 |
+
"name": "image_id",
|
172 |
+
"description": "The filename of the image. eg: COCO_train2014_000000000003.jpg",
|
173 |
+
"dataType": "sc:Text",
|
174 |
+
"source": {
|
175 |
+
"distribution": "image-files",
|
176 |
+
"extract": {
|
177 |
+
"fileProperty": "filename"
|
178 |
+
},
|
179 |
+
"transform": {
|
180 |
+
"regex": "^COCO_[train|val|test]2014_(\\d+)\\.jpg$"
|
181 |
+
}
|
182 |
+
}
|
183 |
+
},
|
184 |
+
{
|
185 |
+
"@type": "ml:Field",
|
186 |
+
"name": "image_filename",
|
187 |
+
"description": "The filename of the image. eg: COCO_train2014_000000000003.jpg",
|
188 |
+
"dataType": "sc:Text",
|
189 |
+
"source": {
|
190 |
+
"distribution": "image-files",
|
191 |
+
"extract": {
|
192 |
+
"fileProperty": "filename"
|
193 |
+
}
|
194 |
+
}
|
195 |
+
},
|
196 |
+
{
|
197 |
+
"@type": "ml:Field",
|
198 |
+
"name": "image_content",
|
199 |
+
"description": "The content of the image.",
|
200 |
+
"dataType": "sc:ImageObject",
|
201 |
+
"source": {
|
202 |
+
"distribution": "image-files",
|
203 |
+
"extract": {
|
204 |
+
"fileProperty": "content"
|
205 |
+
}
|
206 |
+
}
|
207 |
+
},
|
208 |
+
{
|
209 |
+
"@type": "ml:Field",
|
210 |
+
"name": "split",
|
211 |
+
"dataType": [
|
212 |
+
"sc:Text",
|
213 |
+
"wd:Q3985153"
|
214 |
+
],
|
215 |
+
"references": {
|
216 |
+
"field": "split_enums/name"
|
217 |
+
},
|
218 |
+
"source": {
|
219 |
+
"distribution": "image-files",
|
220 |
+
"extract": {
|
221 |
+
"fileProperty": "fullpath"
|
222 |
+
},
|
223 |
+
"transform": {
|
224 |
+
"regex": "^(train|val|test)2014/.*\\.jpg$"
|
225 |
+
}
|
226 |
+
}
|
227 |
+
}
|
228 |
+
]
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"@type": "ml:RecordSet",
|
232 |
+
"name": "captions",
|
233 |
+
"key": "id",
|
234 |
+
"field": [
|
235 |
+
{
|
236 |
+
"@type": "ml:Field",
|
237 |
+
"name": "id",
|
238 |
+
"description": "The ID of the caption",
|
239 |
+
"dataType": "sc:Integer",
|
240 |
+
"source": {
|
241 |
+
"distribution": "caption_annotations-files",
|
242 |
+
"extract": {
|
243 |
+
"column": "id"
|
244 |
+
}
|
245 |
+
}
|
246 |
+
},
|
247 |
+
{
|
248 |
+
"@type": "ml:Field",
|
249 |
+
"name": "image_id",
|
250 |
+
"description": "The ID of the image",
|
251 |
+
"dataType": "sc:Integer",
|
252 |
+
"source": {
|
253 |
+
"distribution": "caption_annotations-files",
|
254 |
+
"extract": {
|
255 |
+
"column": "image_id"
|
256 |
+
}
|
257 |
+
}
|
258 |
+
},
|
259 |
+
{
|
260 |
+
"@type": "ml:Field",
|
261 |
+
"name": "caption",
|
262 |
+
"description": "The caption",
|
263 |
+
"dataType": [
|
264 |
+
"sc:Text",
|
265 |
+
"wd:Q18585177"
|
266 |
+
],
|
267 |
+
"source": {
|
268 |
+
"distribution": "caption_annotations-files",
|
269 |
+
"extract": {
|
270 |
+
"column": "caption"
|
271 |
+
}
|
272 |
+
}
|
273 |
+
},
|
274 |
+
{
|
275 |
+
"@type": "ml:Field",
|
276 |
+
"name": "split",
|
277 |
+
"dataType": [
|
278 |
+
"sc:Text",
|
279 |
+
"wd:Q3985153"
|
280 |
+
],
|
281 |
+
"references": {
|
282 |
+
"field": "split_enums/name"
|
283 |
+
},
|
284 |
+
"source": {
|
285 |
+
"distribution": "caption_annotations-files",
|
286 |
+
"extract": {
|
287 |
+
"fileProperty": "filename"
|
288 |
+
},
|
289 |
+
"transform": {
|
290 |
+
"regex": ".*_(val|train)2014\\.json$"
|
291 |
+
}
|
292 |
+
}
|
293 |
+
}
|
294 |
+
]
|
295 |
+
},
|
296 |
+
{
|
297 |
+
"@type": "ml:RecordSet",
|
298 |
+
"name": "categories",
|
299 |
+
"isEnumeration": true,
|
300 |
+
"key": "id",
|
301 |
+
"field": [
|
302 |
+
{
|
303 |
+
"@type": "ml:Field",
|
304 |
+
"name": "id",
|
305 |
+
"description": "The ID of the category",
|
306 |
+
"dataType": "sc:Integer",
|
307 |
+
"source": {
|
308 |
+
"distribution": "instancesperson_keypoints_annotations",
|
309 |
+
"extract": {
|
310 |
+
"column": "id"
|
311 |
+
}
|
312 |
+
}
|
313 |
+
},
|
314 |
+
{
|
315 |
+
"@type": "ml:Field",
|
316 |
+
"name": "name",
|
317 |
+
"description": "The name of the category.",
|
318 |
+
"dataType": [
|
319 |
+
"sc:Text",
|
320 |
+
"sc:name"
|
321 |
+
],
|
322 |
+
"source": {
|
323 |
+
"distribution": "instancesperson_keypoints_annotations",
|
324 |
+
"extract": {
|
325 |
+
"column": "name"
|
326 |
+
}
|
327 |
+
}
|
328 |
+
},
|
329 |
+
{
|
330 |
+
"@type": "ml:Field",
|
331 |
+
"name": "supercategory",
|
332 |
+
"description": "The name of the supercategory.",
|
333 |
+
"dataType": [
|
334 |
+
"sc:Text",
|
335 |
+
"sc:name"
|
336 |
+
],
|
337 |
+
"isEnumeration": true,
|
338 |
+
"source": {
|
339 |
+
"distribution": "instancesperson_keypoints_annotations",
|
340 |
+
"extract": {
|
341 |
+
"column": "supercategory"
|
342 |
+
}
|
343 |
+
}
|
344 |
+
}
|
345 |
+
]
|
346 |
+
},
|
347 |
+
{
|
348 |
+
"@type": "ml:RecordSet",
|
349 |
+
"name": "annotations",
|
350 |
+
"key": "id",
|
351 |
+
"field": [
|
352 |
+
{
|
353 |
+
"@type": "ml:Field",
|
354 |
+
"name": "id",
|
355 |
+
"description": "The ID of the annotation.",
|
356 |
+
"dataType": "sc:Integer",
|
357 |
+
"source": {
|
358 |
+
"distribution": "instancesperson_keypoints_annotations",
|
359 |
+
"extract": {
|
360 |
+
"column": "id"
|
361 |
+
}
|
362 |
+
}
|
363 |
+
},
|
364 |
+
{
|
365 |
+
"@type": "ml:Field",
|
366 |
+
"name": "category_id",
|
367 |
+
"description": "The ID of the category.",
|
368 |
+
"dataType": "sc:Integer",
|
369 |
+
"references": {
|
370 |
+
"field": "categories/id"
|
371 |
+
},
|
372 |
+
"source": {
|
373 |
+
"distribution": "instancesperson_keypoints_annotations",
|
374 |
+
"extract": {
|
375 |
+
"column": "category_id"
|
376 |
+
}
|
377 |
+
}
|
378 |
+
},
|
379 |
+
{
|
380 |
+
"@type": "ml:Field",
|
381 |
+
"name": "image_id",
|
382 |
+
"description": "The ID of the image.",
|
383 |
+
"dataType": "sc:Integer",
|
384 |
+
"references": {
|
385 |
+
"field": "images/image_id"
|
386 |
+
},
|
387 |
+
"source": {
|
388 |
+
"distribution": "instancesperson_keypoints_annotations",
|
389 |
+
"extract": {
|
390 |
+
"column": "image_id"
|
391 |
+
}
|
392 |
+
}
|
393 |
+
},
|
394 |
+
{
|
395 |
+
"@type": "ml:Field",
|
396 |
+
"name": "bbox",
|
397 |
+
"description": "The bounding box around annotated object[s].",
|
398 |
+
"dataType": "ml:BoundingBox",
|
399 |
+
"source": {
|
400 |
+
"distribution": "instancesperson_keypoints_annotations",
|
401 |
+
"extract": {
|
402 |
+
"column": "bbox"
|
403 |
+
}
|
404 |
+
}
|
405 |
+
}
|
406 |
+
]
|
407 |
+
}
|
408 |
+
]
|
409 |
+
}
|
cypress/fixtures/titanic.json
ADDED
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"@context": {
|
3 |
+
"@language": "en",
|
4 |
+
"@vocab": "https://schema.org/",
|
5 |
+
"column": "ml:column",
|
6 |
+
"data": {
|
7 |
+
"@id": "ml:data",
|
8 |
+
"@type": "@json"
|
9 |
+
},
|
10 |
+
"dataType": {
|
11 |
+
"@id": "ml:dataType",
|
12 |
+
"@type": "@vocab"
|
13 |
+
},
|
14 |
+
"extract": "ml:extract",
|
15 |
+
"field": "ml:field",
|
16 |
+
"fileProperty": "ml:fileProperty",
|
17 |
+
"format": "ml:format",
|
18 |
+
"includes": "ml:includes",
|
19 |
+
"isEnumeration": "ml:isEnumeration",
|
20 |
+
"jsonPath": "ml:jsonPath",
|
21 |
+
"ml": "http://mlcommons.org/schema/",
|
22 |
+
"parentField": "ml:parentField",
|
23 |
+
"path": "ml:path",
|
24 |
+
"recordSet": "ml:recordSet",
|
25 |
+
"references": "ml:references",
|
26 |
+
"regex": "ml:regex",
|
27 |
+
"repeated": "ml:repeated",
|
28 |
+
"replace": "ml:replace",
|
29 |
+
"sc": "https://schema.org/",
|
30 |
+
"separator": "ml:separator",
|
31 |
+
"source": "ml:source",
|
32 |
+
"subField": "ml:subField",
|
33 |
+
"transform": "ml:transform",
|
34 |
+
"wd": "https://www.wikidata.org/wiki/"
|
35 |
+
},
|
36 |
+
"@type": "sc:Dataset",
|
37 |
+
"name": "Titanic",
|
38 |
+
"description": "The original Titanic dataset, describing the status of individual passengers on the Titanic.\n\n The titanic data does not contain information from the crew, but it does contain actual ages of half of the passengers. \n\n For more information about how this dataset was constructed: \nhttps://web.archive.org/web/20200802155940/http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3info.txt\n\nOther useful information (useful for prices description for example):\nhttp://campus.lakeforest.edu/frank/FILES/MLFfiles/Bio150/Titanic/TitanicMETA.pdf\n\n Also see the following article describing shortcomings of the dataset data:\nhttps://emma-stiefel.medium.com/plugging-holes-in-kaggles-titanic-dataset-an-introduction-to-combining-datasets-with-fuzzywuzzy-60a686699da7\n",
|
39 |
+
"citation": "The principal source for data about Titanic passengers is the Encyclopedia Titanica (http://www.encyclopedia-titanica.org/). The datasets used here were begun by a variety of researchers. One of the original sources is Eaton & Haas (1994) Titanic: Triumph and Tragedy, Patrick Stephens Ltd, which includes a passenger list created by many researchers and edited by Michael A. Findlay.\n\nThomas Cason of UVa has greatly updated and improved the titanic data frame using the Encyclopedia Titanica and created the dataset here. Some duplicate passengers have been dropped, many errors corrected, many missing ages filled in, and new variables created.\n",
|
40 |
+
"license": "Public",
|
41 |
+
"url": "https://www.openml.org/d/40945",
|
42 |
+
"distribution": [
|
43 |
+
{
|
44 |
+
"@type": "sc:FileObject",
|
45 |
+
"name": "passengers.csv",
|
46 |
+
"contentSize": "117743 B",
|
47 |
+
"contentUrl": "https://www.openml.org/data/get_csv/16826755/phpMYEkMl",
|
48 |
+
"encodingFormat": "text/csv",
|
49 |
+
"sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"@type": "sc:FileObject",
|
53 |
+
"name": "genders.csv",
|
54 |
+
"description": "Maps gender values (\"male\", \"female\") to semantic URLs.",
|
55 |
+
"contentSize": "117743 B",
|
56 |
+
"contentUrl": "data/genders.csv",
|
57 |
+
"encodingFormat": "text/csv",
|
58 |
+
"sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"
|
59 |
+
},
|
60 |
+
{
|
61 |
+
"@type": "sc:FileObject",
|
62 |
+
"name": "embarkation_ports.csv",
|
63 |
+
"description": "Maps Embarkation port initial to labeled values.",
|
64 |
+
"contentSize": "117743 B",
|
65 |
+
"contentUrl": "data/embarkation_ports.csv",
|
66 |
+
"encodingFormat": "text/csv",
|
67 |
+
"sha256": "c617db2c7470716250f6f001be51304c76bcc8815527ab8bae734bdca0735737"
|
68 |
+
}
|
69 |
+
],
|
70 |
+
"recordSet": [
|
71 |
+
{
|
72 |
+
"@type": "ml:RecordSet",
|
73 |
+
"name": "genders",
|
74 |
+
"description": "Maps gender labels to semantic definitions.",
|
75 |
+
"isEnumeration": true,
|
76 |
+
"key": "label",
|
77 |
+
"field": [
|
78 |
+
{
|
79 |
+
"@type": "ml:Field",
|
80 |
+
"name": "label",
|
81 |
+
"description": "One of {\"male\", \"female\"}",
|
82 |
+
"dataType": [
|
83 |
+
"sc:Text",
|
84 |
+
"sc:name"
|
85 |
+
],
|
86 |
+
"source": {
|
87 |
+
"distribution": "genders.csv",
|
88 |
+
"extract": {
|
89 |
+
"column": "label"
|
90 |
+
}
|
91 |
+
}
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"@type": "ml:Field",
|
95 |
+
"name": "url",
|
96 |
+
"description": "Corresponding WikiData URL",
|
97 |
+
"dataType": [
|
98 |
+
"sc:URL",
|
99 |
+
"wd:Q48277"
|
100 |
+
],
|
101 |
+
"source": {
|
102 |
+
"distribution": "genders.csv",
|
103 |
+
"extract": {
|
104 |
+
"column": "url"
|
105 |
+
}
|
106 |
+
}
|
107 |
+
}
|
108 |
+
]
|
109 |
+
},
|
110 |
+
{
|
111 |
+
"@type": "ml:RecordSet",
|
112 |
+
"name": "embarkation_ports",
|
113 |
+
"description": "Maps Embarkation port initial to labeled values.",
|
114 |
+
"isEnumeration": true,
|
115 |
+
"key": "key",
|
116 |
+
"field": [
|
117 |
+
{
|
118 |
+
"@type": "ml:Field",
|
119 |
+
"name": "key",
|
120 |
+
"description": "C, Q, S or ?",
|
121 |
+
"dataType": "sc:Text",
|
122 |
+
"source": {
|
123 |
+
"distribution": "embarkation_ports.csv",
|
124 |
+
"extract": {
|
125 |
+
"column": "key"
|
126 |
+
}
|
127 |
+
}
|
128 |
+
},
|
129 |
+
{
|
130 |
+
"@type": "ml:Field",
|
131 |
+
"name": "label",
|
132 |
+
"description": "Human-readable label",
|
133 |
+
"dataType": [
|
134 |
+
"sc:Text",
|
135 |
+
"sc:name"
|
136 |
+
],
|
137 |
+
"source": {
|
138 |
+
"distribution": "embarkation_ports.csv",
|
139 |
+
"extract": {
|
140 |
+
"column": "label"
|
141 |
+
}
|
142 |
+
}
|
143 |
+
},
|
144 |
+
{
|
145 |
+
"@type": "ml:Field",
|
146 |
+
"name": "url",
|
147 |
+
"description": "Corresponding WikiData URL",
|
148 |
+
"dataType": [
|
149 |
+
"sc:URL",
|
150 |
+
"wd:Q515"
|
151 |
+
],
|
152 |
+
"source": {
|
153 |
+
"distribution": "embarkation_ports.csv",
|
154 |
+
"extract": {
|
155 |
+
"column": "url"
|
156 |
+
}
|
157 |
+
}
|
158 |
+
}
|
159 |
+
]
|
160 |
+
},
|
161 |
+
{
|
162 |
+
"@type": "ml:RecordSet",
|
163 |
+
"name": "passengers",
|
164 |
+
"description": "The list of passengers. Does not include crew members.",
|
165 |
+
"field": [
|
166 |
+
{
|
167 |
+
"@type": "ml:Field",
|
168 |
+
"name": "name",
|
169 |
+
"description": "Name of the passenger",
|
170 |
+
"dataType": "sc:Text",
|
171 |
+
"source": {
|
172 |
+
"distribution": "passengers.csv",
|
173 |
+
"extract": {
|
174 |
+
"column": "name"
|
175 |
+
}
|
176 |
+
}
|
177 |
+
},
|
178 |
+
{
|
179 |
+
"@type": "ml:Field",
|
180 |
+
"name": "gender",
|
181 |
+
"description": "Gender of passenger (male or female)",
|
182 |
+
"dataType": "sc:Text",
|
183 |
+
"references": {
|
184 |
+
"field": "genders/label"
|
185 |
+
},
|
186 |
+
"source": {
|
187 |
+
"distribution": "passengers.csv",
|
188 |
+
"extract": {
|
189 |
+
"column": "sex"
|
190 |
+
}
|
191 |
+
}
|
192 |
+
},
|
193 |
+
{
|
194 |
+
"@type": "ml:Field",
|
195 |
+
"name": "age",
|
196 |
+
"description": "Age of passenger at time of death. It's a string, because some values can be `?`.",
|
197 |
+
"dataType": "sc:Text",
|
198 |
+
"source": {
|
199 |
+
"distribution": "passengers.csv",
|
200 |
+
"extract": {
|
201 |
+
"column": "age"
|
202 |
+
}
|
203 |
+
}
|
204 |
+
},
|
205 |
+
{
|
206 |
+
"@type": "ml:Field",
|
207 |
+
"name": "survived",
|
208 |
+
"description": "Survival status of passenger (0: Lost, 1: Saved)",
|
209 |
+
"dataType": "sc:Integer",
|
210 |
+
"source": {
|
211 |
+
"distribution": "passengers.csv",
|
212 |
+
"extract": {
|
213 |
+
"column": "survived"
|
214 |
+
}
|
215 |
+
}
|
216 |
+
},
|
217 |
+
{
|
218 |
+
"@type": "ml:Field",
|
219 |
+
"name": "pclass",
|
220 |
+
"description": "Passenger Class (1st/2nd/3rd)",
|
221 |
+
"dataType": "sc:Integer",
|
222 |
+
"source": {
|
223 |
+
"distribution": "passengers.csv",
|
224 |
+
"extract": {
|
225 |
+
"column": "pclass"
|
226 |
+
}
|
227 |
+
}
|
228 |
+
},
|
229 |
+
{
|
230 |
+
"@type": "ml:Field",
|
231 |
+
"name": "cabin",
|
232 |
+
"description": "Passenger cabin.",
|
233 |
+
"dataType": "sc:Text",
|
234 |
+
"source": {
|
235 |
+
"distribution": "passengers.csv",
|
236 |
+
"extract": {
|
237 |
+
"column": "cabin"
|
238 |
+
}
|
239 |
+
}
|
240 |
+
},
|
241 |
+
{
|
242 |
+
"@type": "ml:Field",
|
243 |
+
"name": "embarked",
|
244 |
+
"description": "Port of Embarkation (C: Cherbourg, Q: Queenstown, S: Southampton, ?: Unknown).",
|
245 |
+
"dataType": "sc:Text",
|
246 |
+
"references": {
|
247 |
+
"field": "embarkation_ports/key"
|
248 |
+
},
|
249 |
+
"source": {
|
250 |
+
"distribution": "passengers.csv",
|
251 |
+
"extract": {
|
252 |
+
"column": "embarked"
|
253 |
+
}
|
254 |
+
}
|
255 |
+
},
|
256 |
+
{
|
257 |
+
"@type": "ml:Field",
|
258 |
+
"name": "fare",
|
259 |
+
"description": "Passenger Fare (British pound). It's a string, because some values can be `?`.",
|
260 |
+
"dataType": "sc:Text",
|
261 |
+
"source": {
|
262 |
+
"distribution": "passengers.csv",
|
263 |
+
"extract": {
|
264 |
+
"column": "fare"
|
265 |
+
}
|
266 |
+
}
|
267 |
+
},
|
268 |
+
{
|
269 |
+
"@type": "ml:Field",
|
270 |
+
"name": "home_destination",
|
271 |
+
"description": "Home and destination",
|
272 |
+
"dataType": "sc:Text",
|
273 |
+
"source": {
|
274 |
+
"distribution": "passengers.csv",
|
275 |
+
"extract": {
|
276 |
+
"column": "home.dest"
|
277 |
+
}
|
278 |
+
}
|
279 |
+
},
|
280 |
+
{
|
281 |
+
"@type": "ml:Field",
|
282 |
+
"name": "ticket",
|
283 |
+
"description": "Ticket Number, may include a letter.",
|
284 |
+
"dataType": "sc:Text",
|
285 |
+
"source": {
|
286 |
+
"distribution": "passengers.csv",
|
287 |
+
"extract": {
|
288 |
+
"column": "ticket"
|
289 |
+
}
|
290 |
+
}
|
291 |
+
},
|
292 |
+
{
|
293 |
+
"@type": "ml:Field",
|
294 |
+
"name": "num_parents_children",
|
295 |
+
"description": "Number of Parents/Children Aboard",
|
296 |
+
"dataType": "sc:Integer",
|
297 |
+
"source": {
|
298 |
+
"distribution": "passengers.csv",
|
299 |
+
"extract": {
|
300 |
+
"column": "parch"
|
301 |
+
}
|
302 |
+
}
|
303 |
+
},
|
304 |
+
{
|
305 |
+
"@type": "ml:Field",
|
306 |
+
"name": "num_siblings_spouses",
|
307 |
+
"description": "Number of Siblings/Spouses Aboard",
|
308 |
+
"dataType": "sc:Integer",
|
309 |
+
"source": {
|
310 |
+
"distribution": "passengers.csv",
|
311 |
+
"extract": {
|
312 |
+
"column": "sibsp"
|
313 |
+
}
|
314 |
+
}
|
315 |
+
},
|
316 |
+
{
|
317 |
+
"@type": "ml:Field",
|
318 |
+
"name": "boat",
|
319 |
+
"description": "Lifeboat used by passenger",
|
320 |
+
"dataType": "sc:Text",
|
321 |
+
"source": {
|
322 |
+
"distribution": "passengers.csv",
|
323 |
+
"extract": {
|
324 |
+
"column": "boat"
|
325 |
+
}
|
326 |
+
}
|
327 |
+
},
|
328 |
+
{
|
329 |
+
"@type": "ml:Field",
|
330 |
+
"name": "body",
|
331 |
+
"description": "Body Identification Number",
|
332 |
+
"dataType": "sc:Text",
|
333 |
+
"source": {
|
334 |
+
"distribution": "passengers.csv",
|
335 |
+
"extract": {
|
336 |
+
"column": "body"
|
337 |
+
}
|
338 |
+
}
|
339 |
+
}
|
340 |
+
]
|
341 |
+
}
|
342 |
+
]
|
343 |
+
}
|
cypress/screenshots/uploadCsv.cy.js/Editor loads a local CSV as a resource -- should display the form Overview, Metadata, Resources, & Record Sets (failed).png
ADDED
cypress/support/e2e.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import "./resize_observer"
|
3 |
+
|
4 |
+
beforeEach(() => {
|
5 |
+
cy.ignore_resize_observer();
|
6 |
+
})
|
cypress/support/resize_observer.js
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Cypress.Commands.add("ignore_resize_observer", () => {
|
2 |
+
const resizeObserverLoopErrRe = /ResizeObserver loop limit exceeded/
|
3 |
+
|
4 |
+
// consensus was that this exception didn't matter mostly, and was intermittent when running tests.
|
5 |
+
// https://stackoverflow.com/questions/63653605/resizeobserver-loop-limit-exceeded-api-is-never-used
|
6 |
+
Cypress.on('uncaught:exception', err => {
|
7 |
+
if (resizeObserverLoopErrRe.test(err.message)) {
|
8 |
+
return false
|
9 |
+
}
|
10 |
+
})
|
11 |
+
})
|
events/__init__.py
ADDED
File without changes
|
events/fields.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import enum
|
2 |
+
from typing import Any
|
3 |
+
|
4 |
+
import streamlit as st
|
5 |
+
|
6 |
+
from core.state import Field
|
7 |
+
from core.state import Metadata
|
8 |
+
import mlcroissant as mlc
|
9 |
+
|
10 |
+
|
11 |
+
class ExtractType:
|
12 |
+
"""The type of extraction to perform."""
|
13 |
+
|
14 |
+
COLUMN = "Column"
|
15 |
+
JSON_PATH = "JSON path"
|
16 |
+
FILE_CONTENT = "File content"
|
17 |
+
FILE_NAME = "File name"
|
18 |
+
FILE_PATH = "File path"
|
19 |
+
FILE_FULLPATH = "Full path"
|
20 |
+
FILE_LINES = "Lines in file"
|
21 |
+
FILE_LINE_NUMBERS = "Line numbers in file"
|
22 |
+
|
23 |
+
|
24 |
+
class TransformType:
|
25 |
+
"""The type of transformation to perform."""
|
26 |
+
|
27 |
+
FORMAT = "Apply format"
|
28 |
+
JSON_PATH = "Apply JSON path"
|
29 |
+
REGEX = "Apply regular expression"
|
30 |
+
REPLACE = "Replace"
|
31 |
+
SEPARATOR = "Separator"
|
32 |
+
|
33 |
+
|
34 |
+
def _get_source(source: mlc.Source | None, value: Any) -> mlc.Source:
|
35 |
+
if not source:
|
36 |
+
source = mlc.Source(extract=mlc.Extract())
|
37 |
+
if value == ExtractType.COLUMN:
|
38 |
+
source.extract = mlc.Extract(column="")
|
39 |
+
elif value == ExtractType.FILE_CONTENT:
|
40 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.content)
|
41 |
+
elif value == ExtractType.FILE_NAME:
|
42 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.filename)
|
43 |
+
elif value == ExtractType.FILE_PATH:
|
44 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.filepath)
|
45 |
+
elif value == ExtractType.FILE_FULLPATH:
|
46 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.fullpath)
|
47 |
+
elif value == ExtractType.FILE_LINES:
|
48 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.lines)
|
49 |
+
elif value == ExtractType.FILE_LINE_NUMBERS:
|
50 |
+
source.extract = mlc.Extract(file_property=mlc.FileProperty.lineNumbers)
|
51 |
+
elif value == ExtractType.JSON_PATH:
|
52 |
+
source.extract = mlc.Extract(json_path="")
|
53 |
+
return source
|
54 |
+
|
55 |
+
|
56 |
+
class FieldEvent(enum.Enum):
|
57 |
+
"""Event that triggers a field change."""
|
58 |
+
|
59 |
+
NAME = "NAME"
|
60 |
+
DESCRIPTION = "DESCRIPTION"
|
61 |
+
DATA_TYPE = "DATA_TYPE"
|
62 |
+
SOURCE = "SOURCE"
|
63 |
+
SOURCE_EXTRACT = "SOURCE_EXTRACT"
|
64 |
+
SOURCE_EXTRACT_COLUMN = "SOURCE_EXTRACT_COLUMN"
|
65 |
+
SOURCE_EXTRACT_JSON_PATH = "SOURCE_EXTRACT_JSON_PATH"
|
66 |
+
TRANSFORM = "TRANSFORM"
|
67 |
+
TRANSFORM_FORMAT = "TRANSFORM_FORMAT"
|
68 |
+
REFERENCE = "REFERENCE"
|
69 |
+
REFERENCE_EXTRACT = "REFERENCE_EXTRACT"
|
70 |
+
REFERENCE_EXTRACT_COLUMN = "REFERENCE_EXTRACT_COLUMN"
|
71 |
+
REFERENCE_EXTRACT_JSON_PATH = "REFERENCE_EXTRACT_JSON_PATH"
|
72 |
+
|
73 |
+
|
74 |
+
def handle_field_change(
|
75 |
+
change: FieldEvent,
|
76 |
+
field: Field,
|
77 |
+
key: str,
|
78 |
+
**kwargs,
|
79 |
+
):
|
80 |
+
value = st.session_state[key]
|
81 |
+
if change == FieldEvent.NAME:
|
82 |
+
old_name = field.name
|
83 |
+
new_name = value
|
84 |
+
if old_name != new_name:
|
85 |
+
metadata: Metadata = st.session_state[Metadata]
|
86 |
+
metadata.rename_field(old_name=old_name, new_name=new_name)
|
87 |
+
field.name = value
|
88 |
+
elif change == FieldEvent.DESCRIPTION:
|
89 |
+
field.description = value
|
90 |
+
elif change == FieldEvent.DATA_TYPE:
|
91 |
+
field.data_types = [value]
|
92 |
+
elif change == FieldEvent.SOURCE:
|
93 |
+
node_type = "field" if "/" in value else "distribution"
|
94 |
+
source = mlc.Source(uid=value, node_type=node_type)
|
95 |
+
field.source = source
|
96 |
+
elif change == FieldEvent.SOURCE_EXTRACT:
|
97 |
+
source = field.source
|
98 |
+
source = _get_source(source, value)
|
99 |
+
field.source = source
|
100 |
+
elif change == FieldEvent.SOURCE_EXTRACT_COLUMN:
|
101 |
+
if not field.source:
|
102 |
+
field.source = mlc.Source(extract=mlc.Extract())
|
103 |
+
field.source.extract = mlc.Extract(column=value)
|
104 |
+
elif change == FieldEvent.SOURCE_EXTRACT_JSON_PATH:
|
105 |
+
if not field.source:
|
106 |
+
field.source = mlc.Source(extract=mlc.Extract())
|
107 |
+
field.source.extract = mlc.Extract(json_path=value)
|
108 |
+
elif change == FieldEvent.TRANSFORM:
|
109 |
+
number = kwargs.get("number")
|
110 |
+
if number is not None and number < len(field.source.transforms):
|
111 |
+
field.source.transforms[number] = mlc.Transform()
|
112 |
+
elif change == TransformType.FORMAT:
|
113 |
+
number = kwargs.get("number")
|
114 |
+
if number is not None and number < len(field.source.transforms):
|
115 |
+
field.source.transforms[number] = mlc.Transform(format=value)
|
116 |
+
elif change == TransformType.JSON_PATH:
|
117 |
+
number = kwargs.get("number")
|
118 |
+
if number is not None and number < len(field.source.transforms):
|
119 |
+
field.source.transforms[number] = mlc.Transform(json_path=value)
|
120 |
+
elif change == TransformType.REGEX:
|
121 |
+
number = kwargs.get("number")
|
122 |
+
if number is not None and number < len(field.source.transforms):
|
123 |
+
field.source.transforms[number] = mlc.Transform(regex=value)
|
124 |
+
elif change == TransformType.REPLACE:
|
125 |
+
number = kwargs.get("number")
|
126 |
+
if number is not None and number < len(field.source.transforms):
|
127 |
+
field.source.transforms[number] = mlc.Transform(replace=value)
|
128 |
+
elif change == TransformType.SEPARATOR:
|
129 |
+
number = kwargs.get("number")
|
130 |
+
if number is not None and number < len(field.source.transforms):
|
131 |
+
field.source.transforms[number] = mlc.Transform(separator=value)
|
132 |
+
elif change == FieldEvent.REFERENCE:
|
133 |
+
node_type = "field" if "/" in value else "distribution"
|
134 |
+
source = mlc.Source(uid=value, node_type=node_type)
|
135 |
+
field.references = source
|
136 |
+
elif change == FieldEvent.REFERENCE_EXTRACT:
|
137 |
+
source = field.references
|
138 |
+
source = _get_source(source, value)
|
139 |
+
field.references = source
|
140 |
+
elif change == FieldEvent.REFERENCE_EXTRACT_COLUMN:
|
141 |
+
if not field.references:
|
142 |
+
field.references = mlc.Source(extract=mlc.Extract())
|
143 |
+
field.references.extract = mlc.Extract(column=value)
|
144 |
+
elif change == FieldEvent.REFERENCE_EXTRACT_JSON_PATH:
|
145 |
+
if not field.references:
|
146 |
+
field.references = mlc.Source(extract=mlc.Extract())
|
147 |
+
field.references.extract = mlc.Extract(json_path=value)
|
events/metadata.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import enum
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
from core.state import Metadata
|
6 |
+
|
7 |
+
|
8 |
+
class MetadataEvent(enum.Enum):
|
9 |
+
"""Event that triggers a metadata change."""
|
10 |
+
|
11 |
+
NAME = "NAME"
|
12 |
+
DESCRIPTION = "DESCRIPTION"
|
13 |
+
URL = "URL"
|
14 |
+
LICENSE = "LICENSE"
|
15 |
+
CITATION = "CITATION"
|
16 |
+
|
17 |
+
|
18 |
+
def handle_metadata_change(event: MetadataEvent, metadata: Metadata, key: str):
|
19 |
+
if event == MetadataEvent.NAME:
|
20 |
+
metadata.name = st.session_state[key]
|
21 |
+
elif event == MetadataEvent.DESCRIPTION:
|
22 |
+
metadata.description = st.session_state[key]
|
23 |
+
elif event == MetadataEvent.LICENSE:
|
24 |
+
metadata.license = st.session_state[key]
|
25 |
+
elif event == MetadataEvent.CITATION:
|
26 |
+
metadata.citation = st.session_state[key]
|
27 |
+
elif event == MetadataEvent.URL:
|
28 |
+
metadata.url = st.session_state[key]
|
events/record_sets.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import enum
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
from core.state import Metadata
|
6 |
+
from core.state import RecordSet
|
7 |
+
|
8 |
+
|
9 |
+
class RecordSetEvent(enum.Enum):
|
10 |
+
"""Event that triggers a RecordSet change."""
|
11 |
+
|
12 |
+
NAME = "NAME"
|
13 |
+
DESCRIPTION = "DESCRIPTION"
|
14 |
+
IS_ENUMERATION = "IS_ENUMERATION"
|
15 |
+
|
16 |
+
|
17 |
+
def handle_record_set_change(event: RecordSetEvent, record_set: RecordSet, key: str):
|
18 |
+
value = st.session_state[key]
|
19 |
+
if event == RecordSetEvent.NAME:
|
20 |
+
old_name = record_set.name
|
21 |
+
new_name = value
|
22 |
+
if old_name != new_name:
|
23 |
+
metadata: Metadata = st.session_state[Metadata]
|
24 |
+
metadata.rename_record_set(old_name=old_name, new_name=new_name)
|
25 |
+
record_set.name = value
|
26 |
+
elif event == RecordSetEvent.DESCRIPTION:
|
27 |
+
record_set.description = value
|
28 |
+
elif event == RecordSetEvent.IS_ENUMERATION:
|
29 |
+
record_set.is_enumeration = value
|
events/resources.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import enum
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
from core.state import FileObject
|
6 |
+
from core.state import FileSet
|
7 |
+
from core.state import Metadata
|
8 |
+
|
9 |
+
Resource = FileObject | FileSet
|
10 |
+
|
11 |
+
|
12 |
+
class ResourceEvent(enum.Enum):
|
13 |
+
"""Event that triggers a resource change."""
|
14 |
+
|
15 |
+
NAME = "NAME"
|
16 |
+
DESCRIPTION = "DESCRIPTION"
|
17 |
+
ENCODING_FORMAT = "ENCODING_FORMAT"
|
18 |
+
SHA256 = "SHA256"
|
19 |
+
CONTENT_SIZE = "CONTENT_SIZE"
|
20 |
+
CONTENT_URL = "CONTENT_URL"
|
21 |
+
|
22 |
+
|
23 |
+
def handle_resource_change(event: ResourceEvent, resource: Resource, key: str):
|
24 |
+
value = st.session_state[key]
|
25 |
+
if event == ResourceEvent.NAME:
|
26 |
+
old_name = resource.name
|
27 |
+
new_name = value
|
28 |
+
if old_name != new_name:
|
29 |
+
metadata: Metadata = st.session_state[Metadata]
|
30 |
+
metadata.rename_distribution(old_name=old_name, new_name=new_name)
|
31 |
+
resource.name = value
|
32 |
+
elif event == ResourceEvent.DESCRIPTION:
|
33 |
+
resource.description = value
|
34 |
+
elif event == ResourceEvent.ENCODING_FORMAT:
|
35 |
+
resource.encoding_format = value
|
36 |
+
elif event == ResourceEvent.SHA256:
|
37 |
+
resource.sha256 = value
|
38 |
+
elif event == ResourceEvent.CONTENT_SIZE:
|
39 |
+
resource.content_size = value
|
40 |
+
elif event == ResourceEvent.CONTENT_URL:
|
41 |
+
resource.content_url = value
|